-
-
Save designly1/cf2475dd533f88ab3d08cdaf9c025f89 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// /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