import { Response as Response } from "./throwables" import { Token, User } from "@prisma/client" import { database } from "../prismaClient" /** * Object containing multiple functions related to API authentication. * * @see {@link festaAPI} and its parameters {@link FestaAPI}. */ export type FestaAuthenticator = { /** * The value that the `WWW-Authenticate` header should be set to in a handler using this authenticator. */ header: string, /** * Async function which uses the value of the `Authorization` HTTP header to authenticate the user agent performing the API request. * * Any thrown {@link Error} should be caught by the handler, and its {@link Error.message} returned by the API. * * @param config - The current configuration of the request handler. * @param header - The value of the `Authorization` HTTP header. * @throws {ThrowableResponse} */ perform: (params: { config: Config, header: string | undefined }) => Promise, } /** * {@link FestaAuthenticator} which does not require any authentication. * * Never throws. */ export const festaNoAuth: FestaAuthenticator = { header: "", perform: async ({ }) => { return {} }, } /** * Structure of the authentication data returned by the Festa API. */ export type FestaToken = Token & { user: User } /** * {@link FestaAuthenticator} which authenticates the user agent based on the value of its Bearer token. * * Returns the {@link FestaToken} if successful, or `undefined` if the Authorization header is missing. * * Throws an HTTP 401 error if the header is malformed or the token is invalid or expired. */ export const festaBearerAuthOptional: FestaAuthenticator = { header: "Bearer", perform: async ({ header }) => { if (!header || header === "false") { return undefined } const token = header.match(/^Bearer (\S+)$/)?.[1] if (!token) { throw Response.error({ status: 401, message: "Malformed Authorization header" }) } const dbToken = await database.token.findUnique({ where: { token }, include: { user: true } }) if (!dbToken) { throw Response.error({ status: 401, message: "Invalid Authorization token" }) } const now = new Date() if (dbToken.expiresAt.getTime() < now.getTime()) { throw Response.error({ status: 401, message: "Expired Authorization token" }) } return dbToken } } /** * {@link FestaAuthenticator} which authenticates the user agent based on the value of its Bearer token. * * Returns the {@link FestaToken} if successful. * * Throws an HTTP 401 error if the header is missing or malformed, or the token is invalid or expired. */ export const festaBearerAuthRequired: FestaAuthenticator = { header: "Bearer", perform: async (params) => { if (!params.header) { throw Response.error({ status: 401, message: "Missing Authorization header" }) } return await festaBearerAuthOptional.perform(params) as FestaToken } }