diff --git a/bun.lockb b/bun.lockb index 2c8eb1f..45ed243 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 7fd2300..b628df4 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "typescript": "^5.0.0" }, "dependencies": { - "express-fingerprint": "^1.2.2" + "crypto": "^1.0.1", + "express-fingerprint": "^1.2.2", + "node-fetch": "^3.3.2" } } diff --git a/packages/server/src/middlewares/DoorAuthModes.ts b/packages/server/src/middlewares/DoorAuthModes.ts index b66dd96..d8755a4 100644 --- a/packages/server/src/middlewares/DoorAuthModes.ts +++ b/packages/server/src/middlewares/DoorAuthModes.ts @@ -1,8 +1,11 @@ import { Request, RequestHandler } from "express"; import { getRedisClient } from "../clients/db/RedisDbProvider"; -import { getAuthModes, getDoorSettingString } from "../util/EnvConfigUtil"; +import { getAllDoorNames, getAuthModes, getDoorSettingString } from "../util/EnvConfigUtil"; import { IAuthMode } from "../types/IAuthMode"; import { IDoorConfig } from "../types/IDoorConfig"; +import { doorRotatingKey } from "../types/RedisKeys"; +import crypto from "crypto"; +import fetch from "node-fetch"; const client = await getRedisClient(); @@ -14,12 +17,15 @@ export const HandleAuthMode: RequestHandler = async (req, res, next) => { return; } - const isAuthorized = authModes.some((mode) => { + const checkAuth = async (mode: IAuthMode): Promise => { switch(mode) { case IAuthMode.FIXED_PIN: return handleFixedPinAuth(req) + case IAuthMode.RANDOM_ROTATING_KEY: return await handleRandomRotatingKeyAuth(req) default: return false; } - }); + } + + const isAuthorized = (await Promise.all(authModes.map((mode) => checkAuth(mode)))).some(b => b); if (!isAuthorized) { res.status(401).json({ msg: 'Unauthorized' }); @@ -32,4 +38,28 @@ export const HandleAuthMode: RequestHandler = async (req, res, next) => { const handleFixedPinAuth = (req: Request): boolean => { const fixedPin = getDoorSettingString(req.params.id, IDoorConfig.FIXED_PIN); return fixedPin !== undefined && req.query['key'] === fixedPin; +} + +const handleRandomRotatingKeyAuth = async (req: Request): Promise => { + const currentPin = await client.get(doorRotatingKey(req.params.id)); + + if (currentPin === req.query['rotatingKey']) { + await replaceDoorRandomKey(req.params.id); + return true; + } + return false; +} + +export const initializeRandomDoorPins = () => { + const doors = getAllDoorNames(); + doors.forEach(replaceDoorRandomKey); +} + +export const replaceDoorRandomKey = async (door: string) => { + const newKey = crypto.randomBytes(20).toString('hex'); + + await client.put(doorRotatingKey(door), newKey); + + const message = `New key for door ${door}! Unlock link: ${Bun.env.BASE_DOMAIN}/api/door/${door}/auth?rotatingKey=${newKey}`; + await fetch(Bun.env.ROTATING_KEY_NTFY, { method: "POST", body: message }); } \ No newline at end of file diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 6885363..e2e709d 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,7 +1,11 @@ import express from "express"; import DoorRouter from "./routers/DoorRouter"; +import { getAllDoorNames } from "./util/EnvConfigUtil"; +import { getRedisClient } from "./clients/db/RedisDbProvider"; +import { initializeRandomDoorPins } from "./middlewares/DoorAuthModes"; const Fingerprint = require('express-fingerprint'); +const client = getRedisClient(); const app = express(); @@ -25,4 +29,5 @@ app.use('/api/door', DoorRouter); app.listen(5000, async () => { console.log("listening on port 5000"); + initializeRandomDoorPins(); }); \ No newline at end of file diff --git a/packages/server/src/types/Environment.ts b/packages/server/src/types/Environment.ts index ea330f4..63a8fc4 100644 --- a/packages/server/src/types/Environment.ts +++ b/packages/server/src/types/Environment.ts @@ -5,5 +5,6 @@ declare module "bun" { REDIS_CONNECT_URL: string; // `redis[s]://[[username][:password]@][host][:port][/db-number]` DOOR_OPEN_TIMEOUT: number; DOOR_FIXED_PIN: string; + ROTATING_KEY_NTFY: string; } } \ No newline at end of file diff --git a/packages/server/src/types/IAuthMode.ts b/packages/server/src/types/IAuthMode.ts index 35612a8..4d1ad2a 100644 --- a/packages/server/src/types/IAuthMode.ts +++ b/packages/server/src/types/IAuthMode.ts @@ -1,3 +1,4 @@ export enum IAuthMode { FIXED_PIN = "FIXED_PIN", + RANDOM_ROTATING_KEY = "RANDOM_ROTATING_KEY", } \ No newline at end of file diff --git a/packages/server/src/types/RedisKeys.ts b/packages/server/src/types/RedisKeys.ts index 15be8e7..8da098d 100644 --- a/packages/server/src/types/RedisKeys.ts +++ b/packages/server/src/types/RedisKeys.ts @@ -7,6 +7,10 @@ export function doorStatusKey(id: string) { return concatKeys(RedisKeys.DOORS, id, 'open'); } +export function doorRotatingKey(id: string) { + return concatKeys(RedisKeys.DOORS, id, 'rotatingKey'); +} + export function concatKeys(...keys: String[]) { return keys.join(':'); } \ No newline at end of file diff --git a/packages/server/src/util/EnvConfigUtil.ts b/packages/server/src/util/EnvConfigUtil.ts index 4868151..fc36460 100644 --- a/packages/server/src/util/EnvConfigUtil.ts +++ b/packages/server/src/util/EnvConfigUtil.ts @@ -36,4 +36,16 @@ export const getDoorSettingTimeLock = (door: string): number[] => { // never locked (always -1 < hr < 25) return [25, -1]; +} + +export const getAllDoorNames = (): string[] => { + const names: string[] = []; + + Object.keys(Bun.env).forEach(key => { + if (key.startsWith(IDoorConfig.AUTH_MODES)) { + names.push(key.replace(IDoorConfig.AUTH_MODES + "_", "").toLowerCase()); + } + }); + + return names; } \ No newline at end of file