diff --git a/bun.lockb b/bun.lockb index 3e694fd..a42e586 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/doorman-api/.env.local b/packages/doorman-api/.env.local index 86f563c..065c26d 100644 --- a/packages/doorman-api/.env.local +++ b/packages/doorman-api/.env.local @@ -34,3 +34,5 @@ DOORMAN_URL=http://localhost:3005 # this isn't really a secret, its just to prevent spam on /notify in prod NOTIFY_SECRET_KEY=discordnotifyme + +COOKIE_PASSWORD=testtesttesttesttesttesttesttest diff --git a/packages/doorman-api/.env.twiliotemplate b/packages/doorman-api/.env.twiliotemplate index 39c73b5..b1e6c37 100644 --- a/packages/doorman-api/.env.twiliotemplate +++ b/packages/doorman-api/.env.twiliotemplate @@ -28,3 +28,5 @@ LOKI_PW=doormanlogs NOTIFY_SECRET_KEY=discordnotifyme DOORMAN_URL= + +COOKIE_PASSWORD=testtesttesttesttesttesttesttest diff --git a/packages/doorman-api/package.json b/packages/doorman-api/package.json index 028981e..f973e93 100644 --- a/packages/doorman-api/package.json +++ b/packages/doorman-api/package.json @@ -21,6 +21,7 @@ "discord-oauth2": "^2.12.1", "discord.js": "^14.19.3", "dynabridge": "^0.3.8", + "iron-session": "^8.0.4", "is-deep-subset": "^0.1.1", "prom-client": "^15.1.3", "promise.timeout": "^1.2.0", diff --git a/packages/doorman-api/src/common/DoormanHandler.ts b/packages/doorman-api/src/common/DoormanHandler.ts index ab6bf7c..4653b72 100644 --- a/packages/doorman-api/src/common/DoormanHandler.ts +++ b/packages/doorman-api/src/common/DoormanHandler.ts @@ -1,4 +1,4 @@ -import { ServerlessCallback, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types"; +import { ServerlessCallback, ServerlessFunctionSignature, TwilioResponse } from "@twilio-labs/serverless-runtime-types/types"; import { PrometheusContentType, Registry, Pushgateway, Summary, Counter, register } from "prom-client"; import { DoormanLambdaContext } from "./DoormanHandlerContext"; import { shouldBlockRequest } from "../utils/blockUserAgent"; @@ -13,8 +13,7 @@ import pTimeout, { TimeoutError } from "promise.timeout"; import { ZodError } from "zod"; import { fromError } from "zod-validation-error"; import { setResponseJson } from "../utils/responseUtils"; - -export type BaseEvent = { request: { cookies: {}; headers: {}; }; } +import { BaseEvent, getSessionFromRequest, SessionType, setSessionOnResponse } from "./SessionHandler"; export type CallbackResult = Parameters; export type FailFastCallback = () => CallbackResult; @@ -73,7 +72,8 @@ const INNER_HANDLER_MAXIMUM_DURATION_MS: number = 8_750; */ export function withMetrics( functionName: string, - handler: DoormanLambda + handler: DoormanLambda, + parseSessionCookie?: boolean, ): ServerlessFunctionSignature { return async (context, event, callback) => { console.log("[CommonHandler] created loki logger"); @@ -208,6 +208,11 @@ export function withMetrics const startTime = Date.now(); console.log(`[CommonHandler] started handler at ${startTime}`); + if (parseSessionCookie) { + console.log(`[CommonHandler] unwrapping session cookie`); + event.request.session = await getSessionFromRequest(event, context); + } + const handlerResponsePromise: Promise> = new Promise(async (resolve, reject) => { // intercept the callbackResult let callbackResult: Parameters | undefined; diff --git a/packages/doorman-api/src/common/DoormanHandlerContext.ts b/packages/doorman-api/src/common/DoormanHandlerContext.ts index 42a26da..42efcac 100644 --- a/packages/doorman-api/src/common/DoormanHandlerContext.ts +++ b/packages/doorman-api/src/common/DoormanHandlerContext.ts @@ -15,4 +15,5 @@ export interface DoormanLambdaContext extends EnvironmentVariables { LOKI_PW: string; NOTIFY_SECRET_KEY: string; DOORMAN_URL: string; + COOKIE_PASSWORD: string; }; diff --git a/packages/doorman-api/src/common/SessionHandler.ts b/packages/doorman-api/src/common/SessionHandler.ts new file mode 100644 index 0000000..30d38ae --- /dev/null +++ b/packages/doorman-api/src/common/SessionHandler.ts @@ -0,0 +1,50 @@ +import { TwilioResponse } from "@twilio-labs/serverless-runtime-types/types"; +import { TwilioContext } from "../types/TwilioContext"; +import { sealData, unsealData } from "iron-session"; +import { DoormanLambdaContext } from "./DoormanHandlerContext"; + +export type BaseEvent = { request: { cookies: { session?: string; }; headers: {}; session?: SessionType }; } + +export type SessionType = any; +export const IRON_SESSION_COOKIE_NAME = "session"; + +export async function getSessionFromRequest(event: BaseEvent, context: DoormanLambdaContext): Promise { + if (!event.request.cookies[IRON_SESSION_COOKIE_NAME]) { + return undefined + } + + let session: SessionType | undefined; + try { + session = await unsealData(event.request.cookies[IRON_SESSION_COOKIE_NAME], { + password: context.COOKIE_PASSWORD, + }); + } catch (e: any) { + console.error("error unsealing cookie"); + } + + return session; +} + +export async function setSessionOnResponse(res: TwilioResponse, context: TwilioContext, session: any): Promise { + if (!session) { + return res; + } + + try { + const cookie = await sealData(session, { + password: context.COOKIE_PASSWORD, + }); + console.log(cookie); + res.setCookie(IRON_SESSION_COOKIE_NAME, cookie); + } catch (e: any) { + console.log("failed to seal cookie"); + console.log(e.message); + } + + return res; +} + +export async function destroySession(res: TwilioResponse): Promise { + res.removeCookie(IRON_SESSION_COOKIE_NAME); + return res; +} diff --git a/packages/doorman-api/src/functions/api/door/info.ts b/packages/doorman-api/src/functions/api/door/info.ts index 8297386..f17e7d5 100644 --- a/packages/doorman-api/src/functions/api/door/info.ts +++ b/packages/doorman-api/src/functions/api/door/info.ts @@ -14,6 +14,7 @@ import { z } from "zod"; import { UserAgentHeader } from "../../../utils/blockUserAgent"; import { setResponseJson } from "../../../utils/responseUtils"; import { sanitizePhoneNumber } from "../../../utils/phoneUtils"; +import { BaseEvent, destroySession, setSessionOnResponse } from "../../../common/SessionHandler"; export const InfoRequestSchema = z.object({ door: z.string().optional(), @@ -23,7 +24,7 @@ export const InfoRequestSchema = z.object({ .refine(data => data.buzzer || data.door, 'Buzzer or door must be provided'); export type InfoRequest = z.infer; -export interface InfoRequestTwilio extends ServerlessEventObject { }; +export type InfoRequestTwilio = ServerlessEventObject & BaseEvent; export const InfoResponseClientSchema = DoorConfigSchema .omit({ PK: true, SK: true, pin: true }) @@ -95,5 +96,13 @@ export const handler: ServerlessFunctionSignature