update onboard route

This commit is contained in:
Martin Dimitrov 2025-06-07 16:55:29 -07:00
parent 1c87e93d2f
commit 8ca379b6db

View File

@ -8,29 +8,37 @@ import { createDynaBridgeClient } from "../../../utils/ddb";
import DiscordOauth2 from "discord-oauth2"; import DiscordOauth2 from "discord-oauth2";
import { DoorAliasSchema, getDoorAliasID } from "../../../schema/DoorAlias"; import { DoorAliasSchema, getDoorAliasID } from "../../../schema/DoorAlias";
import { Counter } from "prom-client"; import { Counter } from "prom-client";
import { z } from "zod";
import zu from "zod_utilz";
import { setResponseJson } from "../../../utils/responseUtils";
export interface OnboardRequest extends ServerlessEventObject<{}, UserAgentHeader> { export const OnboardRequestSchema = z.object({
newConfig?: string; newConfig: zu.stringToJSON().optional(),
code: z.string().optional(),
state: zu.stringToJSON().optional(),
}).partial()
.refine(data => data.newConfig || (data.code && data.state), 'newConfig or (code and state) must be specified');
// for oauth redirect export type OnboardRequest = z.infer<typeof OnboardRequestSchema>;
code?: string;
state?: string;
}
export interface DiscordOnboardingState { export interface OnboardRequestTwilio extends ServerlessEventObject<OnboardRequest, UserAgentHeader> { };
name: string;
id: string; export const DiscordOnboardingStateSchema = z.object({
apiRedirect: string; name: z.string(),
} id: z.string(),
apiRedirect: z.string(),
});
export type DiscordOnboardingState = z.infer<typeof DiscordOnboardingStateSchema>;
const ONBOARDING_SCOPE = ['identify', 'email', 'guilds.join']; const ONBOARDING_SCOPE = ['identify', 'email', 'guilds.join'];
export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => { export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestTwilio> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
response.appendHeader('Content-Type', 'application/json');
registerMetrics(metricsRegistry); registerMetrics(metricsRegistry);
const req = OnboardRequestSchema.parse(event);
const db = createDynaBridgeClient(context); const db = createDynaBridgeClient(context);
// return oauth link // return oauth link
@ -41,23 +49,13 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
}); });
// create door config route // create door config route
if (event.code && event.state) { if (req.code && req.state) {
const code = event.code; const code = req.code;
let discordState: DiscordOnboardingState; let discordState: DiscordOnboardingState = DiscordOnboardingStateSchema.parse(req.state);
try {
discordState = JSON.parse(event.state);
} catch (e) {
response.setStatusCode(400);
response.setBody({ err: "invalid state" });
return callback(null, response);
}
const config = await db.entities.onboardDoorConfig.findById(getOnboardDoorId(discordState.name)); const config = await db.entities.onboardDoorConfig.findById(getOnboardDoorId(discordState.name));
if (!config || config.nonce !== discordState.id) { if (!config || config.nonce !== discordState.id) {
response.setStatusCode(404) setResponseJson(response, 404, { err: "approval not found" });
.setBody({ err: "approval not found" });
return callback(null, response); return callback(null, response);
} }
@ -73,8 +71,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
}); });
} catch (err) { } catch (err) {
console.log("something went wrong with discord authorization"); console.log("something went wrong with discord authorization");
response.setStatusCode(401); setResponseJson(response, 401, { err });
response.setBody({ err });
return callback(null, response); return callback(null, response);
} }
@ -84,9 +81,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
console.log(profileId); console.log(profileId);
if (!profileId) { if (!profileId) {
response.setStatusCode(404); setResponseJson(response, 404, { err: "profile not found" });
response.setBody({ err: "profile not found" });
return callback(null, response); return callback(null, response);
} }
@ -144,37 +139,26 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
// and all must succeed or none succeed // and all must succeed or none succeed
try { try {
await db.transaction([createDoorAlias, createDoorConfig, deleteOnboardingConfig]); await db.transaction([createDoorAlias, createDoorConfig, deleteOnboardingConfig]);
response.setStatusCode(200); setResponseJson(response, 200, { redirect: context.DOORMAN_URL + `?door=${config.name}` });
response.setBody({ redirect: context.DOORMAN_URL + `?door=${config.name}` });
} catch (e) { } catch (e) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.TRANSACTION_CONFLICT).inc(1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.TRANSACTION_CONFLICT).inc(1);
console.error(e); console.error(e);
response.setStatusCode(409); setResponseJson(response, 409, { err: "something went wrong during onboarding" });
response.setBody({ err: "something went wrong during onboarding" });
} }
return callback(null, response); return callback(null, response);
} }
let newConfig = event.newConfig; let newConfig = req.newConfig;
if (!newConfig) { if (!newConfig) {
response.setStatusCode(400); setResponseJson(response, 400, { err: "missing newConfig" });
return callback(null, response); return callback(null, response);
} }
let newConfigParsed: OnboardDoorReq;
console.log("parsing config"); console.log("parsing config");
try { let newConfigParsed: OnboardDoorReq = OnboardDoorReqSchema.parse(newConfig);
newConfigParsed = OnboardDoorReqSchema.parse(JSON.parse(newConfig));
} catch (err) {
response.setStatusCode(400);
response.setBody({ err });
return callback(null, response);
}
const newConfigObj = createOnboardDoorConfig(newConfigParsed); const newConfigObj = createOnboardDoorConfig(newConfigParsed);
// check if this name or buzzer is already registered? // check if this name or buzzer is already registered?
@ -182,8 +166,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
if (existingAlias) { if (existingAlias) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.BUZZER_CONFLICT).inc({ buzzer: newConfigObj.buzzer }, 1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.BUZZER_CONFLICT).inc({ buzzer: newConfigObj.buzzer }, 1);
response.setStatusCode(409); setResponseJson(response, 409, { err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
response.setBody({ err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
return callback(null, response); return callback(null, response);
} }
@ -191,8 +174,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
if (existingConfig) { if (existingConfig) {
getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.NAME_CONFLICT).inc({ name: newConfigObj.name }, 1); getMetricFromRegistry<Counter>(metricsRegistry, OnboardMetrics.NAME_CONFLICT).inc({ name: newConfigObj.name }, 1);
response.setStatusCode(409); setResponseJson(response, 409, { err: `A buzzer is already registered with the name ${newConfigObj.name}` });
response.setBody({ err: `A buzzer is already registered with the name ${newConfigObj.name}` });
return callback(null, response); return callback(null, response);
} }
@ -202,11 +184,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
// save to DB, this is a middle step until discord auth comes in // save to DB, this is a middle step until discord auth comes in
await db.entities.onboardDoorConfig.save(newConfigObj); await db.entities.onboardDoorConfig.save(newConfigObj);
const discordState: DiscordOnboardingState = { const discordState: DiscordOnboardingState = DiscordOnboardingStateSchema.parse({
name: newConfigObj.name, name: newConfigObj.name,
id: newConfigObj.nonce, id: newConfigObj.nonce,
apiRedirect: '/api/door/onboard', apiRedirect: '/api/door/onboard',
} });
const redirect = oauth.generateAuthUrl({ const redirect = oauth.generateAuthUrl({
scope: ONBOARDING_SCOPE, scope: ONBOARDING_SCOPE,
@ -214,7 +196,6 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest>
state: JSON.stringify(discordState), state: JSON.stringify(discordState),
}); });
response.setStatusCode(200); setResponseJson(response, 200, { redirect });
response.setBody({ redirect });
return callback(null, response); return callback(null, response);
}); });