Files
bgApp/src/endpoints/auth.ts

105 lines
3.5 KiB
TypeScript

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 } from '../utilities/requestModels';
import { UserId } from '../utilities/secureIds';
async function login(request: UnwrappedRequest<LoginRequest>): Promise<Response> {
try {
const verify: {
userId: UserId;
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_REFRESH_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<Response> {
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_REFRESH_KEY as string) as { u: string; r: string };
if (!(await orm.users.verifyRefreshCount(UserId.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<Response> {
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<ChangePasswordRequest>): Promise<Response> {
try {
return new OkResponse(
await orm.users.changePassword(
UserId.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,
};