Skip to content

Instantly share code, notes, and snippets.

@designly1
Created October 30, 2023 13:57
Show Gist options
  • Save designly1/cf2475dd533f88ab3d08cdaf9c025f89 to your computer and use it in GitHub Desktop.
Save designly1/cf2475dd533f88ab3d08cdaf9c025f89 to your computer and use it in GitHub Desktop.
// auth.ts helper functions
import { jwtVerify, JWTPayload, decodeJwt } from 'jose';
export function getJwtSecretKey() {
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT Secret key is not set');
}
const enc: Uint8Array = new TextEncoder().encode(secret);
return enc;
}
export async function verifyJwtToken(token: string): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(token, getJwtSecretKey());
return payload;
} catch (error) {
return null;
}
}
export function decodeJwtToken(token: string): JWTPayload | null {
try {
const { payload } = decodeJwt(token) as { payload: JWTPayload };
return payload;
} catch (error) {
return null;
}
}
// middleware.ts
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
import { verifyJwtToken } from './lib/common/auth';
const authRoutes = ['/app/*'];
function matchesWildcard(path: string, pattern: string): boolean {
if (pattern.endsWith('/*')) {
const basePattern = pattern.slice(0, -2);
return path.startsWith(basePattern);
}
return path === pattern;
}
export async function middleware(request: NextRequest) {
const LOGIN = `${process.env.NEXT_PUBLIC_BASE_URL}/login?redirect=${
request.nextUrl.pathname + request.nextUrl.search
}`;
if (authRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) {
const token = request.cookies.get('token');
if (!token) {
return NextResponse.redirect(LOGIN);
}
try {
const payload = await verifyJwtToken(token.value);
if (!payload) {
// Delete token
request.cookies.delete('token');
return NextResponse.redirect(LOGIN);
}
} catch (error) {
// Delete token
request.cookies.delete('token');
return NextResponse.redirect(LOGIN);
}
}
let redirectToApp = false;
// Redirect login to app if already logged in
if (request.nextUrl.pathname === '/login') {
const token = request.cookies.get('token');
if (token) {
try {
const payload = await verifyJwtToken(token.value);
if (payload) {
redirectToApp = true;
} else {
// Delete token
request.cookies.delete('token');
}
} catch (error) {
// Delete token
request.cookies.delete('token');
}
}
}
if (redirectToApp) {
return NextResponse.redirect(`${process.env.NEXT_PUBLIC_BASE_URL}/app`);
} else {
return NextResponse.next();
}
}
// /api/login route
import { NextResponse } from 'next/server';
import { User } from '@/models/associations';
import { SignJWT } from 'jose';
import * as log from '@/lib/common/logger';
import { getJwtSecretKey } from '@/lib/common/auth';
import checkTurnstileToken from '@/lib/api/checkTurnstileToken';
export interface I_ApiUserLoginRequest {
login: string;
password: string;
tsToken: string;
}
export interface I_ApiUserLoginResponse extends ApiResponse {}
export const dynamic = 'force-dynamic';
export async function POST(request: Request) {
const body = (await request.json()) as I_ApiUserLoginRequest;
// trimm all values
const { login, password, tsToken } = Object.fromEntries(
Object.entries(body).map(([key, value]) => [key, value.trim()]),
) as I_ApiUserLoginRequest;
if (!login || !password) {
const res: I_ApiUserLoginResponse = {
success: false,
message: 'Either login or password is missing',
};
return NextResponse.json(res, { status: 400 });
}
if (process.env.NODE_ENV !== 'development') {
if (!tsToken) {
const res: I_ApiUserLoginResponse = {
success: false,
message: 'Missing Turnstile token',
};
return NextResponse.json(res, { status: 400 });
}
const isTurnstileTokenValid = await checkTurnstileToken(tsToken);
if (!isTurnstileTokenValid) {
const res: I_ApiUserLoginResponse = {
success: false,
message: 'Invalid Turnstile token',
};
return NextResponse.json(res, { status: 400 });
}
}
try {
const user = await User.login(login, password);
const token = await new SignJWT({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
phone: user.phone,
role: user.role,
})
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime(`${User.jwtExpires}s`)
.sign(getJwtSecretKey());
const res: I_ApiUserLoginResponse = {
success: true,
};
const response = NextResponse.json(res);
response.cookies.set({
name: 'token',
value: token,
path: '/',
});
return response;
} catch (error: any) {
log.error(error);
const res: I_ApiUserLoginResponse = {
success: false,
message: error.message || 'Something went wrong',
};
return NextResponse.json(res, { status: 500 });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment