so I want to unprotect the login page and here is my folder structure:
here is my middleware:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if (req.pathname === "login") {
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
so how do I make it so the middleware does not apply to login.js.
Edit: it now returns [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Unable to snapshot resolve dependencies
code for this project is here
so I solved the error I was not getting the pathname from req.nextUrl and here is the correct code:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = req.cookies.token;
if (pathname == "/login" && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
You can check if the request is for the /login page itself, and bail early.
Checkout all properties on NextRequest that are available.
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if( req.pathname === 'login'){
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
The Accepted answer helped me a lot. I'm adding my solution because I had to make some modifications.
I used [...nextauth].js file with jwt and session callbacks and when I used the solution from accepted answer, I was getting ERR_TOO_MANY_REQUESTS cycle. My solution was to add a few more URLs:
import { NextResponse } from "next/server";
import { getToken } from 'next-auth/jwt';
export default async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = await getToken({ req, secret: process.env.JWT_SECRET });
if ((pathname == "/api/auth/signin" || pathname == "/favicon.ico" || pathname == "/api/auth/callback/credentials") && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.rewrite(new URL('/api/auth/signin', req.url));
}
return NextResponse.next();
}
Related
I have a problem with my nextjs pizzashop application. When i run the app on my computer i get this error :
The error goes away after the page refreshes.
This is my index.js
import axios from "axios";
import Head from "next/head";
import { useState } from "react";
import Add from "../components/Add";
import AddButton from "../components/AddButton";
import Featured from "../components/Featured";
import PizzaList from "../components/PizzaList";
import styles from "../styles/Home.module.css";
export default function Home({ pizzaList }) {
const [close, setClose] = useState(true);
return (
<div className={styles.container}>
<Head>
<title>Pizza Restaurant in New york</title>
<meta name="description" content="Best pizza shop in town" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Featured />
{<AddButton setClose={setClose} style={{ display: "none" }} />}
<PizzaList pizzaList={pizzaList} />
{!close && <Add setClose={setClose} />}
</div>
);
}
export const getServerSideProps = async (ctx) => {
const myCookie = ctx.req?.cookies || "";
let admin = false;
if (myCookie.token === process.env.TOKEN) {
admin = true;
}
const res = await axios.get("http://localhost:3000/api/products");
return {
props: {
pizzaList: res.data,
admin,
},
};
};
When i upload the application to vercel i get this error :
This is the error I'm getting in vercel server runtime
This is my /api/products/[id].js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const {
method,
query: { id },
cookies,
} = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const product = await Product.findById(id);
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "PUT") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.findByIdAndUpdate(id, req.body, {
new: true,
});
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "DELETE") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
await Product.findByIdAndDelete(id);
res.status(200).json("The product has been deleted!");
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
This is my /api/products/index.js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const { method, cookies } = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "POST") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
I suspect the error is coming from my get request but i cannot diagnose why. Anyone have any idea?
I am assuming the server side application can be set up as separate deployment. In that case, you can simply follow something similar to what is given here: https://vercel.com/guides/handling-node-request-body
Instead of sending a request to http://localhost:3000/api/products, you can use the deployment url for the server side code like http://some-deployment-url/api/products to access the data you require.
Here is my function:
import axios from "axios";
import { redirect } from "react-router-dom";
let fetch = async (data, method, url, responseType, header) => {
let requestObj = { "method": method, "url": url }
if (method.toLowerCase() === "get") {
requestObj.params = data;
} else {
requestObj.data = data;
}
if (responseType) {
requestObj["responseType"] = responseType;
}
if (header) {
requestObj["headers"] = header;
}
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
console.log("Unauthorized");
return redirect("/login");
}else {
return error;
}
}
);
let result = await axios(requestObj);
return result;
}
I want to make this function be called by other components,
Therefore, the useNavigate() solution does not fit my situation.
Is it mean I need to check the response status in each component and forward the browser to the /login when the access is not authorized?
PS: I am using "react-router-dom": "^6.4.2".
import axios from "axios";
import { useNavigate } from "react-router-dom";
let navigate = useNavigate();
let fetch = async (data, method, url, responseType, header) => {
let requestObj = { "method": method, "url": url }
if (method.toLowerCase() === "get") {
requestObj.params = data;
} else {
requestObj.data = data;
}
if (responseType) {
requestObj["responseType"] = responseType;
}
if (header) {
requestObj["headers"] = header;
}
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
console.log("Unauthorized");
return navigate("/login");
}else {
return error;
}
}
);
let result = await axios(requestObj);
return result;
}
try to use useNavigate() instead of redirect this should work
You can use browserHistory to redirect to another component
import { browserHistory } from 'react-router-dom';
if (error.response.status === 401)
{
console.log("Unauthorized");
browserHistory.push('/login');
}
else
{
return error;
}
I want to redirect a user to a page that informs the user to check his email for verification immediately after registration. I am trying to use Navigate from react-router-dom but the return statement was not being executed. Below is my code
API call
import { ToastNotification } from "../components/ToastNotification";
import axiosInstance from "../utils/axiosInstance";
import { Navigate } from "react-router-dom";
export const userRegister = async (email, password, regd_as) => {
const body = JSON.stringify({ email, password, regd_as });
try {
const response = await axiosInstance.post(`/account/signup/`, body);
if (response.status === 201) {
ToastNotification("Account has been created successfully.", "success");
return <Navigate to="/verify-email" />; //this is not being executed
}
} catch (error) {
if (error.response) {
if (
error.response.status === 400 &&
error.response.data.message === "User Exists"
) {
ToastNotification("Account already exists.", "error");
} else {
ToastNotification(
"We are facing some problems. Please try registering later.",
"error"
);
}
} else if (error.request) {
ToastNotification(
"We are facing some problems. Please try registering some time later."
);
}
}
};
component calling the API
const formDataSubmit = (e) => {
e.preventDefault();
if (password !== conPassword) {
ToastNotification(
"Password and Confirm Password did not match.",
"error"
);
} else {
if (regd_as === "") {
ToastNotification(`"Registered As" cannot be blank`, "info");
} else {
userRegister(email, password, regd_as);
}
}
};
what should I do to redirect the user immediately after registering?
You can't return JSX from a callback like this and expect it to be rendered and affect any change. In other words, the Navigate component isn't actually rendered into the DOM to do anything.
Use the useNavigate hook to access a navigate function as part of a resolved Promise from the userRegister function. Pass the navigate function to the handler.
Example:
import { useNavigate } from 'react-router-dom';
...
const navigate = useNavigate();
...
const formDataSubmit = (e) => {
e.preventDefault();
if (password !== conPassword) {
ToastNotification(
"Password and Confirm Password did not match.",
"error"
);
} else {
if (regd_as === "") {
ToastNotification(`"Registered As" cannot be blank`, "info");
} else {
userRegister(email, password, regd_as, navigate); // <-- pass navigate function
}
}
};
...
import { ToastNotification } from "../components/ToastNotification";
import axiosInstance from "../utils/axiosInstance";
export const userRegister = async (email, password, regd_as, navigate) => {
const body = JSON.stringify({ email, password, regd_as });
try {
const response = await axiosInstance.post(`/account/signup/`, body);
if (response.status === 201) {
ToastNotification("Account has been created successfully.", "success");
return navigate("/verify-email" { replace: true }); // <-- issue imperative redirect
}
} catch (error) {
if (error.response) {
if (
error.response.status === 400 &&
error.response.data.message === "User Exists"
) {
ToastNotification("Account already exists.", "error");
} else {
ToastNotification(
"We are facing some problems. Please try registering later.",
"error"
);
}
} else if (error.request) {
ToastNotification(
"We are facing some problems. Please try registering some time later."
);
}
}
};
I think the return should be wrapped with brackets like this
return(
<>
<Navigate to="/verify-email" />
</>
)
Here's more info https://www.pluralsight.com/guides/return-variable-in-render-function-in-react
In my project, I have a namespace that exports some functions that use Axios, in the same file I add an interceptor to axios instance like that :
axios.interceptors.response.use(
(res) => res,
(error) => {
if (
error.response &&
(error.response.status?.toString() === "400" ||
error.response.status?.toString() === "403" ||
error.response.status?.toString() === "404")
) {
return Promise.reject(
Error(JSON.stringify(error.response.data?.status?.errors[0]))
);
} else if (error.response) {
return Promise.reject(
Error(
`server responsed with the following code: ${error.response?.status} and the following message: ${error.response?.statusText}`
)
);
} else if (error.request) {
return Promise.reject(
Error(
"The request was made but no response was received, check your network connection"
)
);
} else Promise.reject(error);
}
);
I want to test that this interceptor works as expected, I search the forms here and googled a lot but all the answers are basically mocking the interceptor not testing it.
I have tried:
mocking the response of an axios post request and checking the AxiosPromise that gets returned but it only contained the result I mocked. it seems that it ignores the interceptor when I mock using mockResolvedValue.
I have tried adding an interceptor to the mocked axios instance but that did not work too.
Thanks
What about pulling the function out and testing it without axios?
import axios, { AxiosError, AxiosResponse } from 'axios'
export const onFullfilled = (response: AxiosResponse) => {
// Your interceptor handling a successful response
}
export const onRejected = (error: AxiosError) => {
// Your interceptor handling a failed response
}
axios.interceptors.response.use(onFullfilled, onRejected)
Now you can test the functions onFullfilled and onRejected with less dependencies to axios.
You have to mock the interceptor and run the callbacks.
Here is an example on how to do it:
httpService.ts
import axios from "axios";
import { toast } from "react-toastify";
axios.interceptors.request.use((config) => {
config.baseURL = process.env.API_URL || "http://localhost:5000";
return config;
});
axios.interceptors.response.use(null, (error) => {
const expectedError =
error.response &&
error.response.status >= 400 &&
error.response.status < 500;
if (!expectedError) {
toast.error("An unexpected error occured");
}
return Promise.reject(error);
});
export default {
get: axios.get,
post: axios.post,
put: axios.put,
delete: axios.delete,
};
httpService.test.ts
import axios from "axios";
import { toast } from "react-toastify";
import "./httpService";
jest.mock("axios", () => ({
__esModule: true,
default: {
interceptors: {
request: { use: jest.fn(() => {}) },
response: { use: jest.fn(() => {}) },
},
},
}));
const fakeError = {
response: {
status: undefined,
},
};
const mockRequestCallback = (axios.interceptors.request.use as jest.Mock).mock
.calls[0][0];
const mockResponseErrorCallback = (axios.interceptors.response.use as jest.Mock)
.mock.calls[0][1];
const toastErrorSpy = jest.spyOn(toast, "error");
beforeEach(() => {
toastErrorSpy.mockClear();
});
test("request error interceptor", () => {
expect(mockRequestCallback({})).toStrictEqual({
baseURL: "http://localhost:5000",
});
});
test("unexpected error on response interceptor", () => {
fakeError.response.status = 500;
mockResponseErrorCallback(fakeError).catch(() => {});
expect(toastErrorSpy).toHaveBeenCalled();
});
test("expected error on response interceptor", () => {
fakeError.response.status = 400;
mockResponseErrorCallback(fakeError).catch(() => {});
expect(toastErrorSpy).not.toHaveBeenCalled();
});
Use this mock functionality
jest.mock('axios', () => {
return {
interceptors: {
request: {
use: jest.fn(),
eject: jest.fn()
},
response: {
use: jest.fn(),
eject: jest.fn()
},
},
};
});
In my vue.js application I've a login system. My main.js looks like this:
import Vue from 'vue';
import NProgress from 'nprogress';
import Resource from 'vue-resource';
import Router from 'vue-router';
import App from './App.vue';
import Login from './components/Authentication/Login.vue';
import auth from './Services/Authentication/Auth';
Vue.use(Router);
Vue.use(Resource);
auth.checkAuth();
export var router = new Router({
history: true
});
router.map({
'/': {
name: 'Login',
component: Login,
guest: true
}
});
router.beforeEach((transition) => {
if (transition.to.auth && !auth.user.authenticated) {
transition.redirect('/login');
} else if (transition.to.guest && auth.user.authenticated) {
transition.redirect('/');
} else {
transition.next();
}
});
Vue.http.interceptors.push((request, next) => {
NProgress.start();
const token = auth.getToken();
request.headers['Authorization'] = 'Bearer ' + token;
request.headers['X-CSRF-TOKEN'] = document.querySelector('meta[name="token"]').content;
request.respondWith(request.body);
next((response) => {
NProgress.done();
if (response.status == 404) {
router.go('/');
} else if (response.status == 401 && response.data.refreshed_token) {
// If you received 401 "Unauthorized" response
// with a refreshed_token in the payload,
// this means you've got to refresh your token
auth.setToken(response.data.refreshed_token);
}
return response;
});
});
So on every request I check the user auth.checkAuth(); that function looks like this (Auth.js):
checkAuth () {
var token || localStorage.getItem('jwt-token');
if (!token) {
return false;
}
Vue.http.get('/api/me')
.then(({data}) => {
this.user.id = data.id;
this.user.name = data.name;
this.user.email = data.email;
this.user.role = data.role;
this.user.authenticated = true;
}, ({data}) => {
router.go('/');
});
}
So my problem is that in my router.beforeEach -> auth.user.authenticated I check if the user is authenticated. But because the promise from auth.checkAuth(); is not returned so auth.user.authenticated is always false!
How can I fix this problem?
Would be very helpful!
For future users having the same problem
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next((response) => {
if(response.status == 401 ) {//or add any error code you want here
//Do another request for some endpoint here to get a fresh token and override your token variable
}
});
});