Compare commits
No commits in common. "11827c30c154287834305af65f7bdb979ffedf41" and "231ea8149d8c265d91ac56ec99b99d65f881ed60" have entirely different histories.
11827c30c1
...
231ea8149d
@ -20,7 +20,7 @@ export const AuthRequestSchema = z.object({
|
|||||||
door: z.string(),
|
door: z.string(),
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
ip: z.string().optional(),
|
ip: z.string().optional(),
|
||||||
timeout: z.coerce.number().gt(0, "Timeout cannot be 0").optional(),
|
timeout: z.number().gt(0, "Timeout cannot be 0").optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AuthRequest = z.infer<typeof AuthRequestSchema>;
|
export type AuthRequest = z.infer<typeof AuthRequestSchema>;
|
||||||
|
|||||||
@ -6,40 +6,38 @@ import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs
|
|||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { createDynaBridgeClient } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorStatus } from "../../../types/DoorStatus";
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
import { DoorConfigSchema, getDoorConfigID } from "../../../schema/DoorConfig";
|
import { getDoorConfigID } from "../../../schema/DoorConfig";
|
||||||
import { getDoorAliasID } from "../../../schema/DoorAlias";
|
import { getDoorAliasID } from "../../../schema/DoorAlias";
|
||||||
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
import { withMetrics } from "../../../common/DoormanHandler";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
|
||||||
import { setResponseJson } from "../../../utils/responseUtils";
|
|
||||||
|
|
||||||
export const InfoRequestSchema = z.object({
|
export interface InfoRequest extends ServerlessEventObject {
|
||||||
door: z.string().optional(),
|
door?: string;
|
||||||
buzzer: z.string().optional(),
|
buzzer?: string;
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
.refine(data => data.buzzer || data.door, 'Buzzer or door must be provided');
|
|
||||||
|
|
||||||
export type InfoRequest = z.infer<typeof InfoRequestSchema>;
|
// TODO: change these to be multiple
|
||||||
export interface InfoRequestTwilio extends ServerlessEventObject<InfoRequest, UserAgentHeader> { };
|
discordUser: string;
|
||||||
|
msg: string;
|
||||||
|
json: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const InfoResponseClientSchema = DoorConfigSchema
|
export interface InfoResponseClient {
|
||||||
.omit({ PK: true, SK: true, pin: true })
|
buzzer: string;
|
||||||
.extend({ door: z.string() });
|
door: string;
|
||||||
|
pressKey: string;
|
||||||
|
fallbackNumbers: string[];
|
||||||
|
discordUsers: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export type InfoResponseClient = z.infer<typeof InfoResponseClientSchema>;
|
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> = async function(context, event, callback) {
|
||||||
|
|
||||||
export const InfoResponseUISchema = InfoResponseClientSchema.extend({ id: z.string(), status: z.nativeEnum(DoorStatus) });
|
|
||||||
|
|
||||||
export type InfoResponseUI = z.infer<typeof InfoResponseUISchema>;
|
|
||||||
|
|
||||||
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwilio> = withMetrics("info", async (context, event, callback, metricsRegistry) => {
|
|
||||||
const response = new Twilio.Response();
|
const response = new Twilio.Response();
|
||||||
const req = InfoRequestSchema.parse(event);
|
|
||||||
|
|
||||||
let door = req.door;
|
let door = event.door;
|
||||||
const buzzer = req.buzzer?.slice(-10);
|
const buzzer = event.buzzer?.slice(-10);
|
||||||
|
|
||||||
|
if (!door && !buzzer) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
const db = createDynaBridgeClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
|
||||||
@ -47,9 +45,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwil
|
|||||||
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
|
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
|
||||||
.then(async (alias) => {
|
.then(async (alias) => {
|
||||||
if (!alias) {
|
if (!alias) {
|
||||||
setResponseJson(response, 404, {
|
response
|
||||||
err: "This buzzer is not registered",
|
.setStatusCode(404)
|
||||||
});
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ err: "This buzzer is not registered" });
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return alias.name;
|
return alias.name;
|
||||||
@ -60,32 +59,42 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwil
|
|||||||
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
setResponseJson(response, 404, {
|
response
|
||||||
err: "This buzzer is not registered",
|
.setStatusCode(404)
|
||||||
});
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ err: "This buzzer is not registered properly" });
|
||||||
} else {
|
} else {
|
||||||
if (buzzer) {
|
if (buzzer) {
|
||||||
// respond to twilio CLIENT
|
// respond to twilio CLIENT
|
||||||
const body: InfoResponseClient = InfoResponseClientSchema.parse({
|
response
|
||||||
...config,
|
.setStatusCode(200)
|
||||||
buzzer,
|
.appendHeader('Content-Type', 'application/json')
|
||||||
door,
|
.setBody({
|
||||||
});
|
buzzer,
|
||||||
|
door,
|
||||||
setResponseJson(response, 200, body);
|
fallbackNumbers: config.fallbackNumbers,
|
||||||
|
pressKey: config.pressKey,
|
||||||
|
discordUsers: config.discordUsers,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
||||||
|
|
||||||
const body: InfoResponseUI = InfoResponseUISchema.parse({
|
|
||||||
...config,
|
|
||||||
id: door,
|
|
||||||
door,
|
|
||||||
status,
|
|
||||||
})
|
|
||||||
|
|
||||||
// respond to UI
|
// respond to UI
|
||||||
setResponseJson(response, 200, body);
|
response
|
||||||
|
.setStatusCode(200)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({
|
||||||
|
id: door,
|
||||||
|
timeout: config.timeout,
|
||||||
|
buzzer: config.buzzer,
|
||||||
|
status,
|
||||||
|
buzzerCode: config.buzzerCode,
|
||||||
|
fallbackNumbers: config.fallbackNumbers,
|
||||||
|
pressKey: config.pressKey,
|
||||||
|
discordUsers: config.discordUsers,
|
||||||
|
greeting: config.greeting || "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,4 +104,4 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwil
|
|||||||
db.ddbClient.destroy();
|
db.ddbClient.destroy();
|
||||||
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
});
|
};
|
||||||
@ -4,46 +4,49 @@ import { jsonMsgSuffix, sendMessageToUser } from "../../../utils/discord";
|
|||||||
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
|
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
|
||||||
import { NotifyMetrics, registerMetrics } from "../../../metrics/NotifyMetrics";
|
import { NotifyMetrics, registerMetrics } from "../../../metrics/NotifyMetrics";
|
||||||
import { Counter, Summary } from "prom-client";
|
import { Counter, Summary } from "prom-client";
|
||||||
import { z } from "zod";
|
|
||||||
import zu from "zod_utilz";
|
|
||||||
import { setResponseJson } from "../../../utils/responseUtils";
|
|
||||||
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
|
||||||
|
|
||||||
export const NotifyRequestSchema = z.object({
|
|
||||||
key: z.string(),
|
|
||||||
|
|
||||||
// these must be arrays
|
export interface NotifyRequest extends ServerlessEventObject {
|
||||||
discordUser: zu.stringToJSON().refine(arg => arg instanceof Array),
|
door: string;
|
||||||
msg: zu.stringToJSON().refine(arg => arg instanceof Array),
|
|
||||||
json: zu.stringToJSON().refine(arg => arg instanceof Array)
|
|
||||||
});
|
|
||||||
|
|
||||||
export type NotifyRequest = z.infer<typeof NotifyRequestSchema>;
|
key: string;
|
||||||
|
|
||||||
export interface NotifyRequestTwilio extends ServerlessEventObject<NotifyRequest, UserAgentHeader> { }
|
// these are arrays in the request
|
||||||
|
discordUser: string;
|
||||||
|
msg: string;
|
||||||
|
json: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequestTwilio> = withMetrics('notify', async (context, event, callback, metricsRegistry) => {
|
export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequest> = withMetrics('notify', async (context, event, callback, metricsRegistry) => {
|
||||||
const response = new Twilio.Response();
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
registerMetrics(metricsRegistry);
|
registerMetrics(metricsRegistry);
|
||||||
|
|
||||||
const req = NotifyRequestSchema.parse(event);
|
|
||||||
|
|
||||||
// secure notify endpoint
|
// secure notify endpoint
|
||||||
if (req.key !== context.NOTIFY_SECRET_KEY) {
|
if (event.key !== context.NOTIFY_SECRET_KEY) {
|
||||||
getMetricFromRegistry<Counter>(metricsRegistry, NotifyMetrics.UNAUTHENTICATED_CALL).inc(1);
|
getMetricFromRegistry<Counter>(metricsRegistry, NotifyMetrics.UNAUTHENTICATED_CALL).inc(1);
|
||||||
setResponseJson(response, 401, {
|
response
|
||||||
err: "Unauthenticated call", event
|
.setStatusCode(401)
|
||||||
});
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ err: "Unauthenticated call", event });
|
||||||
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
let users: string[] = req.discordUser as string[];
|
let users: string[];
|
||||||
let msgs: string[] = req.msg as string[];
|
let msgs: string[];
|
||||||
let jsons: string[] = req.json as string[];
|
let jsons: string[];
|
||||||
let promises = [];
|
let promises = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
users = JSON.parse(event.discordUser);
|
||||||
|
console.log(users);
|
||||||
|
msgs = JSON.parse(event.msg);
|
||||||
|
console.log("before parsing", event.json);
|
||||||
|
jsons = JSON.parse(event.json);
|
||||||
|
console.log("after parsing", event.json);
|
||||||
|
|
||||||
const recordNotifyLatency = getMetricFromRegistry<Summary>(metricsRegistry, NotifyMetrics.DISCORD_LATENCY)
|
const recordNotifyLatency = getMetricFromRegistry<Summary>(metricsRegistry, NotifyMetrics.DISCORD_LATENCY)
|
||||||
.startTimer();
|
.startTimer();
|
||||||
|
|
||||||
@ -63,7 +66,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequestTw
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setResponseJson(response, 500, { err: e, event });
|
response
|
||||||
|
.setStatusCode(500)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ err: e, event });
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,36 +8,28 @@ 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 const OnboardRequestSchema = z.object({
|
export interface OnboardRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||||
newConfig: zu.stringToJSON().optional(),
|
newConfig?: string;
|
||||||
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');
|
|
||||||
|
|
||||||
export type OnboardRequest = z.infer<typeof OnboardRequestSchema>;
|
// for oauth redirect
|
||||||
|
code?: string;
|
||||||
|
state?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OnboardRequestTwilio extends ServerlessEventObject<OnboardRequest, UserAgentHeader> { };
|
export interface DiscordOnboardingState {
|
||||||
|
name: string;
|
||||||
export const DiscordOnboardingStateSchema = z.object({
|
id: string;
|
||||||
name: z.string(),
|
apiRedirect: 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, OnboardRequestTwilio> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => {
|
export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequest> = withMetrics('onboard', async (context, event, callback, metricsRegistry) => {
|
||||||
const response = new Twilio.Response();
|
const response = new Twilio.Response();
|
||||||
registerMetrics(metricsRegistry);
|
response.appendHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
const req = OnboardRequestSchema.parse(event);
|
registerMetrics(metricsRegistry);
|
||||||
|
|
||||||
const db = createDynaBridgeClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
|
||||||
@ -49,13 +41,23 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
});
|
});
|
||||||
|
|
||||||
// create door config route
|
// create door config route
|
||||||
if (req.code && req.state) {
|
if (event.code && event.state) {
|
||||||
const code = req.code;
|
const code = event.code;
|
||||||
let discordState: DiscordOnboardingState = DiscordOnboardingStateSchema.parse(req.state);
|
let discordState: DiscordOnboardingState;
|
||||||
|
|
||||||
|
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) {
|
||||||
setResponseJson(response, 404, { err: "approval not found" });
|
response.setStatusCode(404)
|
||||||
|
.setBody({ err: "approval not found" });
|
||||||
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("something went wrong with discord authorization");
|
console.log("something went wrong with discord authorization");
|
||||||
setResponseJson(response, 401, { err });
|
response.setStatusCode(401);
|
||||||
|
response.setBody({ err });
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +84,9 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
console.log(profileId);
|
console.log(profileId);
|
||||||
|
|
||||||
if (!profileId) {
|
if (!profileId) {
|
||||||
setResponseJson(response, 404, { err: "profile not found" });
|
response.setStatusCode(404);
|
||||||
|
response.setBody({ err: "profile not found" });
|
||||||
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,26 +144,37 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
// 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]);
|
||||||
setResponseJson(response, 200, { redirect: context.DOORMAN_URL + `?door=${config.name}` });
|
response.setStatusCode(200);
|
||||||
|
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);
|
||||||
|
|
||||||
setResponseJson(response, 409, { err: "something went wrong during onboarding" });
|
response.setStatusCode(409);
|
||||||
|
response.setBody({ err: "something went wrong during onboarding" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
let newConfig = req.newConfig;
|
let newConfig = event.newConfig;
|
||||||
|
|
||||||
if (!newConfig) {
|
if (!newConfig) {
|
||||||
setResponseJson(response, 400, { err: "missing newConfig" });
|
response.setStatusCode(400);
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let newConfigParsed: OnboardDoorReq;
|
||||||
|
|
||||||
console.log("parsing config");
|
console.log("parsing config");
|
||||||
let newConfigParsed: OnboardDoorReq = OnboardDoorReqSchema.parse(newConfig);
|
try {
|
||||||
|
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?
|
||||||
@ -166,7 +182,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
|
|
||||||
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);
|
||||||
setResponseJson(response, 409, { err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
|
response.setStatusCode(409);
|
||||||
|
response.setBody({ err: `A buzzer is already registered with the number ${newConfigObj.buzzer}` });
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +191,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
|
|
||||||
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);
|
||||||
setResponseJson(response, 409, { err: `A buzzer is already registered with the name ${newConfigObj.name}` });
|
response.setStatusCode(409);
|
||||||
|
response.setBody({ err: `A buzzer is already registered with the name ${newConfigObj.name}` });
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,11 +202,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
// 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 = DiscordOnboardingStateSchema.parse({
|
const discordState: DiscordOnboardingState = {
|
||||||
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,
|
||||||
@ -196,6 +214,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, OnboardRequestT
|
|||||||
state: JSON.stringify(discordState),
|
state: JSON.stringify(discordState),
|
||||||
});
|
});
|
||||||
|
|
||||||
setResponseJson(response, 200, { redirect });
|
response.setStatusCode(200);
|
||||||
|
response.setBody({ redirect });
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,35 +4,34 @@
|
|||||||
|
|
||||||
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
import { shouldBlockRequest } from "../../../utils/blockUserAgent";
|
||||||
import { createDynaBridgeClient } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorStatus } from "../../../types/DoorStatus";
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
import { withMetrics } from "../../../common/DoormanHandler";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { setResponseJson } from "../../../utils/responseUtils";
|
|
||||||
import zu from "zod_utilz";
|
|
||||||
|
|
||||||
export const StatusRequestSchema = z.object({
|
export interface StatusRequest extends ServerlessEventObject {
|
||||||
door: z.string(),
|
door: string;
|
||||||
});
|
}
|
||||||
|
|
||||||
export type StatusRequest = z.infer<typeof StatusRequestSchema>;
|
export interface StatusResponse {
|
||||||
|
status: DoorStatus,
|
||||||
|
fingerprint: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StatusRequestTwilio extends ServerlessEventObject<StatusRequest, UserAgentHeader> { }
|
export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest> = async function(context, event, callback) {
|
||||||
|
|
||||||
export const StatusResponseSchema = z.object({
|
|
||||||
status: z.nativeEnum(DoorStatus),
|
|
||||||
fingerprint: zu.json().optional(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type StatusResponse = z.infer<typeof StatusResponseSchema>;
|
|
||||||
|
|
||||||
export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequestTwilio> = withMetrics('status', async (context, event, callback) => {
|
|
||||||
const response = new Twilio.Response();
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
const req = StatusRequestSchema.parse(event);
|
if (shouldBlockRequest(event)) {
|
||||||
const door = req.door;
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const door = event.door;
|
||||||
|
|
||||||
|
if (!door) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
const db = createDynaBridgeClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
@ -42,24 +41,26 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequestTw
|
|||||||
if (isOpen && lock) {
|
if (isOpen && lock) {
|
||||||
const fingerprint = JSON.parse(lock.fingerprint);
|
const fingerprint = JSON.parse(lock.fingerprint);
|
||||||
|
|
||||||
const body = StatusResponseSchema.parse({
|
response
|
||||||
status: DoorStatus.OPEN,
|
.setStatusCode(200)
|
||||||
fingerprint,
|
.appendHeader('Content-Type', 'application/json')
|
||||||
});
|
.setBody({
|
||||||
|
status: DoorStatus.OPEN,
|
||||||
setResponseJson(response, 200, body);
|
fingerprint,
|
||||||
|
});
|
||||||
|
|
||||||
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
||||||
} else {
|
} else {
|
||||||
const body = StatusResponseSchema.parse({
|
response
|
||||||
|
.setStatusCode(200)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({
|
||||||
status: DoorStatus.CLOSED,
|
status: DoorStatus.CLOSED,
|
||||||
});
|
});
|
||||||
|
|
||||||
setResponseJson(response, 200, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy the internal client after
|
// destroy the internal client after
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
db.ddbClient.destroy();
|
db.ddbClient.destroy();
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
});
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export async function getConfig(context: TwilioContext, buzzer: string): Promise
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
return undefined;
|
return undefined;
|
||||||
}) as InfoResponseClient;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function notifyDiscord(context: TwilioContext, msg: string[], u: string[], optionalJsonStr: string[], metricsRegistry: Registry){
|
export async function notifyDiscord(context: TwilioContext, msg: string[], u: string[], optionalJsonStr: string[], metricsRegistry: Registry){
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { AuthComponent, IAuthMode } from "../components/AuthComponent";
|
|||||||
import OtpInput from 'react-otp-input';
|
import OtpInput from 'react-otp-input';
|
||||||
import { CountdownBar } from "../components/CountdownBar";
|
import { CountdownBar } from "../components/CountdownBar";
|
||||||
import { DoorResponse } from "../types/DoorResponse";
|
import { DoorResponse } from "../types/DoorResponse";
|
||||||
import { fetchUrlEncoded } from "../helpers/FetchHelper";
|
|
||||||
|
|
||||||
export async function loader({ params, request }: any) {
|
export async function loader({ params, request }: any) {
|
||||||
const door = new URL(request.url).searchParams.get('door');
|
const door = new URL(request.url).searchParams.get('door');
|
||||||
@ -14,9 +13,7 @@ export async function loader({ params, request }: any) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetchUrlEncoded('/api/door/info', {
|
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json());
|
||||||
door,
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
|
||||||
@ -73,9 +70,7 @@ export function DoorPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const timer = setInterval(async () => {
|
const timer = setInterval(async () => {
|
||||||
const response = await fetchUrlEncoded('/api/door/info', {
|
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json());
|
||||||
door,
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
// polling assumes that the door was opened and whatever closed it was the buzzer system...
|
// polling assumes that the door was opened and whatever closed it was the buzzer system...
|
||||||
// ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door
|
// ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { InputTokenGroup } from "../components/InputTokenGroup";
|
|||||||
import { ReactNode, useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
import CInput from "react-hook-form-cloudscape/components/input";
|
import CInput from "react-hook-form-cloudscape/components/input";
|
||||||
import CTextArea from "react-hook-form-cloudscape/components/textarea";
|
import CTextArea from "react-hook-form-cloudscape/components/textarea";
|
||||||
import { fetchUrlEncoded } from "../helpers/FetchHelper";
|
|
||||||
|
|
||||||
export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, isConfirmed: boolean };
|
export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, isConfirmed: boolean };
|
||||||
|
|
||||||
@ -108,30 +107,25 @@ export const EditPage = ({ isOnboarding }: EditPageProps) => {
|
|||||||
timeout: parseInt("" + getValues("timeout"))
|
timeout: parseInt("" + getValues("timeout"))
|
||||||
};
|
};
|
||||||
|
|
||||||
const body = {
|
fetch(apiRoute + `?door=${door}&newConfig=${encodeURIComponent(JSON.stringify(form))}`)
|
||||||
door,
|
.then(res => res.json())
|
||||||
newConfig: JSON.stringify(form),
|
.then(res => {
|
||||||
};
|
if (res.err) {
|
||||||
|
addAlert("error", res.err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isOnboarding) {
|
||||||
|
addAlert("success", `Created approval, check Discord notifcation from Doorman to confirm and approve the changes`);
|
||||||
|
} else if (res.redirect) {
|
||||||
|
// redirect for discord login
|
||||||
|
addAlert("in-progress", `Created Door, you will now be redirected to Discord login to complete the onboarding`);
|
||||||
|
|
||||||
fetchUrlEncoded(apiRoute, body)
|
// redirect in 2 seconds
|
||||||
.then(res => res.json())
|
setTimeout(() => {
|
||||||
.then(res => {
|
window.location = res.redirect;
|
||||||
if (res.err) {
|
}, 2_000);
|
||||||
addAlert("error", res.err);
|
}
|
||||||
return;
|
})
|
||||||
}
|
|
||||||
if (!isOnboarding) {
|
|
||||||
addAlert("success", `Created approval, check Discord notifcation from Doorman to confirm and approve the changes`);
|
|
||||||
} else if (res.redirect) {
|
|
||||||
// redirect for discord login
|
|
||||||
addAlert("in-progress", `Created Door, you will now be redirected to Discord login to complete the onboarding`);
|
|
||||||
|
|
||||||
// redirect in 2 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location = res.redirect;
|
|
||||||
}, 2_000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user