105 lines
3.5 KiB
TypeScript
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,
|
|
};
|