import { orm } from '../orm/orm'; import jwt from 'jsonwebtoken'; import { UnwrappedRequest } from '../utilities/guard'; import { ErrorResponse, OkResponse, UnauthorizedResponse } from '../utilities/responseHelper'; import { Claims } from '../orm/claims'; import { ChangePasswordRequest, LoginRequest, SecureId } from '../utilities/requestModels'; async function login(request: UnwrappedRequest): Promise { try { const verify: { userId: SecureId; refreshCount: string; } | null = await orm.users.verifyCredentials(request.body.email, request.body.password); if (!verify) { return new UnauthorizedResponse('Invalid credentials'); } // Build refresh token that expires in 30 days, return as secure HTTP only cookie. const tokenLifeSpanInDays = 30; const token = jwt.sign( { u: verify.userId.raw, r: verify.refreshCount, }, process.env.JWT_SECRET_KEY as string, { expiresIn: `${tokenLifeSpanInDays * 24}h` }, ); const cookies = request?.request?.cookies; cookies?.set({ name: 'refresh', value: token, httpOnly: true, secure: true, maxAge: tokenLifeSpanInDays * 24 * 60 * 60, path: '/api/auth/token' }); return new OkResponse(); } catch (error: any) { return new ErrorResponse(error as Error); } } async function token(request: UnwrappedRequest): Promise { try { const cookies = request.request.cookies; const refreshCookie = cookies.get('refresh'); if (!refreshCookie) { return new UnauthorizedResponse('No refresh token found'); } const refreshToken: { u: string; r: string; } = jwt.verify(refreshCookie, process.env.JWT_SECRET_KEY as string) as { u: string; r: string }; if (!(await orm.users.verifyRefreshCount(SecureId.fromID(refreshToken.u), refreshToken.r))) { const response = new UnauthorizedResponse('Invalid refresh token'); response.headers.set('Clear-Site-Data', '"cookies","cache","storage","executionContexts"'); return response; } const claims: Claims | null = await orm.claims.getByUserId(refreshToken.u); const token = jwt.sign({ ...claims }, process.env.JWT_SECRET_KEY as string, { expiresIn: process.env.JWT_LIFESPAN as any, }); return new OkResponse({ token }); } catch (error: any) { return new ErrorResponse(error as Error); } } async function logout(): Promise { try { const response = new OkResponse(); response.headers.set('Clear-Site-Data', '"cookies","cache","storage","executionContexts"'); return response; } catch (error: any) { return new ErrorResponse(error as Error); } } async function changePassword(request: UnwrappedRequest): Promise { try { return new OkResponse( await orm.users.changePassword( SecureId.fromHash(request.params.id), request.body.oldPassword, request.body.newPassword, request.claims, ), ); } catch (error: any) { return new ErrorResponse(error as Error); } } export default { login, token, logout, changePassword, };