This commit is contained in:
Mythie
2025-01-06 14:44:20 +11:00
parent 866b036484
commit 7009995204
12 changed files with 235 additions and 0 deletions

View File

@ -0,0 +1,27 @@
import type { ContentfulStatusCode } from 'hono/utils/http-status';
export const AuthenticationErrorCode = {
Unauthorized: 'UNAUTHORIZED',
InvalidCredentials: 'INVALID_CREDENTIALS',
SessionNotFound: 'SESSION_NOT_FOUND',
SessionExpired: 'SESSION_EXPIRED',
InvalidToken: 'INVALID_TOKEN',
MissingToken: 'MISSING_TOKEN',
} as const;
export type AuthenticationErrorCode =
// eslint-disable-next-line @typescript-eslint/ban-types
(typeof AuthenticationErrorCode)[keyof typeof AuthenticationErrorCode] | (string & {});
export const ErrorStatusMap: Record<AuthenticationErrorCode, ContentfulStatusCode> = {
[AuthenticationErrorCode.Unauthorized]: 401,
[AuthenticationErrorCode.InvalidCredentials]: 401,
[AuthenticationErrorCode.SessionNotFound]: 401,
[AuthenticationErrorCode.SessionExpired]: 401,
[AuthenticationErrorCode.InvalidToken]: 401,
[AuthenticationErrorCode.MissingToken]: 400,
};
export function getErrorStatus(code: AuthenticationErrorCode) {
return ErrorStatusMap[code] ?? 400;
}

View File

@ -0,0 +1,42 @@
import type { Context } from 'hono';
import type { ContentfulStatusCode } from 'hono/utils/http-status';
import type { AuthenticationErrorCode } from './error-codes';
import { getErrorStatus } from './error-codes';
interface ErrorResponse {
error: string;
message: string;
stack?: string;
}
export class AuthenticationError extends Error {
code: AuthenticationErrorCode;
statusCode: ContentfulStatusCode;
constructor(code: AuthenticationErrorCode, message?: string, statusCode?: ContentfulStatusCode) {
super(message);
this.code = code;
this.name = 'AuthenticationError';
// Use provided status code or look it up from the map
this.statusCode = statusCode ?? getErrorStatus(code);
}
toJSON(): ErrorResponse {
return {
error: this.code,
message: this.message,
...(process.env.NODE_ENV === 'development' && { stack: this.stack }),
};
}
toHonoResponse(c: Context) {
return c.json(
{
success: false,
...this.toJSON(),
},
this.statusCode,
);
}
}

View File

@ -0,0 +1,30 @@
import { prisma } from '@documenso/prisma';
import { AuthenticationErrorCode } from '../error-codes';
import { AuthenticationError } from '../errors';
export const getSession = async (token: string) => {
const result = await prisma.session.findUnique({
where: {
sessionToken: token,
},
include: {
user: true,
},
});
if (!result) {
throw new AuthenticationError(AuthenticationErrorCode.SessionNotFound);
}
if (result.expires < new Date()) {
throw new AuthenticationError(AuthenticationErrorCode.SessionExpired);
}
const { user, ...session } = result;
return {
session,
user,
};
};

View File

@ -0,0 +1,5 @@
import { customAlphabet } from 'nanoid';
const sessionTokenId = customAlphabet('abcdefhiklmnorstuvwxz', 10);
export const createSessionToken = (length = 10) => `session_${sessionTokenId(length)}` as const;