setup ts for building
This commit is contained in:
parent
1a2e128c47
commit
309c70f42e
@ -8,6 +8,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prepare-client-serverless": "bun --filter 'doorman-client' build && cp -fr packages/client/dist/* packages/serverless/assets/ && cp -f packages/serverless/assets/index.html packages/serverless/assets/assets/index.html",
|
||||
"prepare-server-serverless": "bun --filter 'doorman-server' build && cp -fr packages/server/build/* packages/serverless/functions/ && mv packages/serverless/functions/server.js packages/serverless/functions/api.js",
|
||||
"deploy-serverless": "bun run prepare-client-serverless && bun --filter 'serverless' deploy"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "bun --hot run src/server.ts"
|
||||
"start": "bun --hot run src/server.ts",
|
||||
"build": "bun tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
import { IAccessControl } from "../../types/IAccessControl";
|
||||
import type { IAccessControl } from "../../types/IAccessControl";
|
||||
|
||||
export abstract class AbstractDbClient {
|
||||
// set operations for challenges
|
||||
/**
|
||||
* Add a challenge in progress. It will expire from the DB after a configured amount of time
|
||||
* @param challenge - 32 random bytes in hex
|
||||
* @returns true if the operation completed successfully
|
||||
*/
|
||||
public abstract putChallenge(challenge: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Checks if the given challenge already exists in the db
|
||||
* @param challenge - challenge to check against db
|
||||
@ -16,13 +8,6 @@ export abstract class AbstractDbClient {
|
||||
*/
|
||||
public abstract doesChallengeExist(challenge: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Remove the challenge from the DB manually
|
||||
* @param challenge
|
||||
* @returns true if removed successfully, false if not in DB.
|
||||
*/
|
||||
public abstract removeChallenge(challenge: string): Promise<boolean>;
|
||||
|
||||
// access control methods
|
||||
/**
|
||||
* Set an entry in the DB to mark that a particular challenge was completed by a certain key.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { RedisClientOptions, RedisClientType, RedisFunctions, RedisModules, RedisScripts, createClient } from "redis";
|
||||
import { IAccessControl } from "../../types/IAccessControl";
|
||||
import type { RedisClientOptions, RedisClientType, RedisFunctions, RedisModules, RedisScripts } from "redis";
|
||||
import { createClient } from "redis";
|
||||
import type { IAccessControl } from "../../types/IAccessControl";
|
||||
import { AbstractDbClient } from "./AbstractDbClient";
|
||||
import { RedisKeys } from "../../types/RedisKeys";
|
||||
|
||||
@ -18,24 +19,6 @@ export class RedisDbClient<A extends RedisModules, B extends RedisFunctions, C e
|
||||
return this.client.connect();
|
||||
}
|
||||
|
||||
private scheduleRemoval(challenge: string): void {
|
||||
this.timers[challenge] = setTimeout(async () => {
|
||||
console.log("expiring challenge", challenge);
|
||||
await this.removeChallenge(challenge);
|
||||
}, Bun.env.CHALLENGE_EXPIRE_MS);
|
||||
}
|
||||
|
||||
public async putChallenge(challenge: string): Promise<boolean> {
|
||||
let res: number = await this.client.sAdd(RedisKeys.CHALLENGES, challenge);
|
||||
|
||||
// if the sAdd succeeded
|
||||
if (res > 0) {
|
||||
this.scheduleRemoval(challenge);
|
||||
}
|
||||
|
||||
return res > 0;
|
||||
}
|
||||
|
||||
public doesChallengeExist(challenge: string): Promise<boolean> {
|
||||
return this.client.sIsMember(RedisKeys.CHALLENGES, challenge);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { getEnv } from "../../util/EnvConfigUtil";
|
||||
import { RedisDbClient } from "./RedisDbClient";
|
||||
|
||||
|
||||
@ -6,7 +7,7 @@ let client: RedisDbClient<any, any, any>;
|
||||
|
||||
export async function getRedisClient(): Promise<RedisDbClient<any, any, any>> {
|
||||
if (!client) {
|
||||
client = new RedisDbClient((err) => console.error(err), { url: Bun.env.REDIS_CONNECT_URL });
|
||||
client = new RedisDbClient((err) => console.error(err), { url: getEnv("REDIS_CONNECT_URL") });
|
||||
await client.connect()
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Request, RequestHandler } from "express";
|
||||
import type { Request, RequestHandler } from "express";
|
||||
import { getRedisClient } from "../clients/db/RedisDbProvider";
|
||||
import { getAllDoorNames, getAuthModes, getDoorSettingString } from "../util/EnvConfigUtil";
|
||||
import { getAllDoorNames, getAuthModes, getDoorSettingString, getEnv } from "../util/EnvConfigUtil";
|
||||
import { IAuthMode } from "../types/IAuthMode";
|
||||
import { IDoorConfig } from "../types/IDoorConfig";
|
||||
import { doorRotatingKey } from "../types/RedisKeys";
|
||||
@ -60,7 +60,7 @@ export const replaceDoorRandomKey = async (door: string) => {
|
||||
|
||||
await client.put(doorRotatingKey(door), newKey);
|
||||
|
||||
const message = `New key for door ${door}! Unlock link: ${Bun.env.BASE_DOMAIN}/door/${door}?rotatingKey=${newKey}`;
|
||||
const message = `New key for door ${door}! Unlock link: ${getEnv("BASE_DOMAIN")}/door/${door}?rotatingKey=${newKey}`;
|
||||
console.log(message);
|
||||
await fetch(Bun.env.ROTATING_KEY_NTFY, { method: "POST", body: message });
|
||||
await fetch(getEnv("ROTATING_KEY_NTFY"), { method: "POST", body: message });
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { RequestHandler } from "express";
|
||||
import type { RequestHandler } from "express";
|
||||
import { getDoorSettingTimeLock } from "../util/EnvConfigUtil";
|
||||
import { IDoorStatus } from "../types/IDoorStatus";
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import express from "express";
|
||||
import { getRedisClient } from "../clients/db/RedisDbProvider";
|
||||
import { doorStatusKey } from "../types/RedisKeys";
|
||||
import { HandleAuthMode } from "../middlewares/DoorAuthModes";
|
||||
import { getAllDoorNames, getAuthModes, getDoorSettingNumber, getDoorSettingString } from "../util/EnvConfigUtil";
|
||||
import { getAllDoorNames, getAuthModes, getDoorSettingNumber, getDoorSettingString, getEnv } from "../util/EnvConfigUtil";
|
||||
import { IDoorConfig } from "../types/IDoorConfig";
|
||||
import { TimeLockVerify } from "../middlewares/TimeLockMiddleware";
|
||||
import { IDoorStatus } from "../types/IDoorStatus";
|
||||
@ -11,7 +11,7 @@ const router = express.Router();
|
||||
const client = await getRedisClient();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
res.redirect(`/api/door/${Bun.env.DEFAULT_DOOR || getAllDoorNames()[0]}`);
|
||||
res.redirect(`/api/door/${getEnv("DEFAULT_DOOR") || getAllDoorNames()[0]}`);
|
||||
});
|
||||
|
||||
router.get('/:id', async(req, res) => {
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import express from "express";
|
||||
import { getRedisClient } from "../clients/db/RedisDbProvider";
|
||||
import crypto from "crypto";
|
||||
|
||||
// @ts-ignore
|
||||
import lnurl, { verifyAuthorizationSignature } from "lnurl";
|
||||
|
||||
const router = express.Router();
|
||||
const client = await getRedisClient();
|
||||
|
||||
router.get("/login", async (req, res) => {
|
||||
let challenge: string = "";
|
||||
|
||||
// challenge must be unique
|
||||
do {
|
||||
challenge = crypto.randomBytes(32).toString('hex');
|
||||
} while (await client.doesChallengeExist(challenge));
|
||||
|
||||
const { subdomain } = req.query;
|
||||
|
||||
if (!subdomain) {
|
||||
res.status(400).json({ message: "Missing subdomain" });
|
||||
return;
|
||||
}
|
||||
|
||||
// MUST BE https for clearnet
|
||||
const url = `https://${subdomain}.${Bun.env.BASE_DOMAIN}/api/lnurl/validate?action=auth&tag=login&k1=${challenge}`;
|
||||
const encoded = lnurl.encode(url);
|
||||
|
||||
// add the challenge to db
|
||||
await client.putChallenge(challenge);
|
||||
|
||||
res.json({ challenge, lnurl: encoded });
|
||||
});
|
||||
|
||||
router.get('/validate', async (req, res) => {
|
||||
// @ts-ignore
|
||||
const { k1, key, sig } = req.query as IAuthCallbackQuery;
|
||||
|
||||
if (!(await client.doesChallengeExist(k1))) {
|
||||
console.log("challenge doesn't exist");
|
||||
res.status(404).json({ status: "ERROR", reason: "challenge not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const signatureValid: boolean = verifyAuthorizationSignature(sig, k1, key);
|
||||
|
||||
if (signatureValid) {
|
||||
console.log("signature is valid");
|
||||
|
||||
await client.removeChallenge(k1);
|
||||
await client.markChallengeSuccess(k1, key);
|
||||
|
||||
res.status(200).json({ status: "ok" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Signature mismatch");
|
||||
res.status(401).json({ status: "ERROR", reason: "Signature mismatch" });
|
||||
});
|
||||
|
||||
export default router;
|
||||
@ -1,11 +0,0 @@
|
||||
declare module "bun" {
|
||||
interface Env {
|
||||
CHALLENGE_EXPIRE_MS: number;
|
||||
BASE_DOMAIN: string;
|
||||
REDIS_CONNECT_URL: string; // `redis[s]://[[username][:password]@][host][:port][/db-number]`
|
||||
DOOR_OPEN_TIMEOUT: number;
|
||||
DOOR_FIXED_PIN: string;
|
||||
ROTATING_KEY_NTFY: string;
|
||||
DEFAULT_DOOR: string;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
import { IAuthMode } from "../types/IAuthMode";
|
||||
import { IDoorConfig } from "../types/IDoorConfig";
|
||||
|
||||
export const getEnv = (key: string): string => {
|
||||
return process.env[key] || "";
|
||||
}
|
||||
|
||||
const doorToEnv = (door: string): string => {
|
||||
return door.toUpperCase().replaceAll(' ', '_').replaceAll('-', '_');
|
||||
};
|
||||
@ -16,7 +20,7 @@ export const getAuthModes = (door: string): IAuthMode[] => {
|
||||
};
|
||||
|
||||
export const getDoorSettingString = (door: string, setting: IDoorConfig): string | undefined => {
|
||||
return Bun.env[`${setting}_${doorToEnv(door)}`];
|
||||
return getEnv(`${setting}_${doorToEnv(door)}`);
|
||||
};
|
||||
|
||||
export const getDoorSettingNumber = (door: string, setting: IDoorConfig): number => {
|
||||
@ -41,7 +45,7 @@ export const getDoorSettingTimeLock = (door: string): number[] => {
|
||||
export const getAllDoorNames = (): string[] => {
|
||||
const names: string[] = [];
|
||||
|
||||
Object.keys(Bun.env).forEach(key => {
|
||||
Object.keys(process.env).forEach(key => {
|
||||
if (key.startsWith(IDoorConfig.AUTH_MODES)) {
|
||||
names.push(key.replace(IDoorConfig.AUTH_MODES + "_", "").toLowerCase());
|
||||
}
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import { RedisClientType } from "redis";
|
||||
import { RedisKeys } from "../types/RedisKeys";
|
||||
|
||||
export function expireChallenges(client: RedisClientType): Promise<any> {
|
||||
return client.zRemRangeByScore(RedisKeys.CHALLENGES, '-inf', Date.now());
|
||||
}
|
||||
28
packages/server/tsconfig.json
Normal file
28
packages/server/tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"outDir": "build",
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
// "allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": false,
|
||||
|
||||
// Best practices
|
||||
"strict": false,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// // Some stricter flags
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
// "noPropertyAccessFromIndexSignature": true
|
||||
}
|
||||
}
|
||||
1
packages/serverless/.gitignore
vendored
1
packages/serverless/.gitignore
vendored
@ -11,6 +11,7 @@ lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
assets
|
||||
functions
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
@ -5,11 +5,18 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "twilio-run",
|
||||
"deploy": "twilio-run deploy --service-name react-twilio-serverless"
|
||||
"deploy": "twilio-run deploy --service-name react-twilio-serverless",
|
||||
"clean": "rm -rf assets/* functions/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"twilio": "^3.56",
|
||||
"@twilio/runtime-handler": "1.3.0"
|
||||
"@twilio/runtime-handler": "1.3.0",
|
||||
"@codegenie/serverless-express": "^4.14.1",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"express-rate-limit": "^6.10.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"redis": "^4.6.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"twilio-run": "^3.5.4"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user