add rotating keys
All checks were successful
Build and push image for doorman / docker (push) Successful in 1m49s
Build and push image for doorman / deploy-portainer (push) Successful in 26s

This commit is contained in:
Martin Dimitrov 2024-02-27 22:48:39 -08:00
parent f0ab000415
commit d92305e504
8 changed files with 59 additions and 4 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -10,6 +10,8 @@
"typescript": "^5.0.0" "typescript": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"express-fingerprint": "^1.2.2" "crypto": "^1.0.1",
"express-fingerprint": "^1.2.2",
"node-fetch": "^3.3.2"
} }
} }

View File

@ -1,8 +1,11 @@
import { Request, RequestHandler } from "express"; import { Request, RequestHandler } from "express";
import { getRedisClient } from "../clients/db/RedisDbProvider"; 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 { IAuthMode } from "../types/IAuthMode";
import { IDoorConfig } from "../types/IDoorConfig"; import { IDoorConfig } from "../types/IDoorConfig";
import { doorRotatingKey } from "../types/RedisKeys";
import crypto from "crypto";
import fetch from "node-fetch";
const client = await getRedisClient(); const client = await getRedisClient();
@ -14,12 +17,15 @@ export const HandleAuthMode: RequestHandler = async (req, res, next) => {
return; return;
} }
const isAuthorized = authModes.some((mode) => { const checkAuth = async (mode: IAuthMode): Promise<boolean> => {
switch(mode) { switch(mode) {
case IAuthMode.FIXED_PIN: return handleFixedPinAuth(req) case IAuthMode.FIXED_PIN: return handleFixedPinAuth(req)
case IAuthMode.RANDOM_ROTATING_KEY: return await handleRandomRotatingKeyAuth(req)
default: return false; default: return false;
} }
}); }
const isAuthorized = (await Promise.all(authModes.map((mode) => checkAuth(mode)))).some(b => b);
if (!isAuthorized) { if (!isAuthorized) {
res.status(401).json({ msg: 'Unauthorized' }); res.status(401).json({ msg: 'Unauthorized' });
@ -33,3 +39,27 @@ const handleFixedPinAuth = (req: Request): boolean => {
const fixedPin = getDoorSettingString(req.params.id, IDoorConfig.FIXED_PIN); const fixedPin = getDoorSettingString(req.params.id, IDoorConfig.FIXED_PIN);
return fixedPin !== undefined && req.query['key'] === fixedPin; return fixedPin !== undefined && req.query['key'] === fixedPin;
} }
const handleRandomRotatingKeyAuth = async (req: Request): Promise<boolean> => {
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 });
}

View File

@ -1,7 +1,11 @@
import express from "express"; import express from "express";
import DoorRouter from "./routers/DoorRouter"; 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 Fingerprint = require('express-fingerprint');
const client = getRedisClient();
const app = express(); const app = express();
@ -25,4 +29,5 @@ app.use('/api/door', DoorRouter);
app.listen(5000, async () => { app.listen(5000, async () => {
console.log("listening on port 5000"); console.log("listening on port 5000");
initializeRandomDoorPins();
}); });

View File

@ -5,5 +5,6 @@ declare module "bun" {
REDIS_CONNECT_URL: string; // `redis[s]://[[username][:password]@][host][:port][/db-number]` REDIS_CONNECT_URL: string; // `redis[s]://[[username][:password]@][host][:port][/db-number]`
DOOR_OPEN_TIMEOUT: number; DOOR_OPEN_TIMEOUT: number;
DOOR_FIXED_PIN: string; DOOR_FIXED_PIN: string;
ROTATING_KEY_NTFY: string;
} }
} }

View File

@ -1,3 +1,4 @@
export enum IAuthMode { export enum IAuthMode {
FIXED_PIN = "FIXED_PIN", FIXED_PIN = "FIXED_PIN",
RANDOM_ROTATING_KEY = "RANDOM_ROTATING_KEY",
} }

View File

@ -7,6 +7,10 @@ export function doorStatusKey(id: string) {
return concatKeys(RedisKeys.DOORS, id, 'open'); return concatKeys(RedisKeys.DOORS, id, 'open');
} }
export function doorRotatingKey(id: string) {
return concatKeys(RedisKeys.DOORS, id, 'rotatingKey');
}
export function concatKeys(...keys: String[]) { export function concatKeys(...keys: String[]) {
return keys.join(':'); return keys.join(':');
} }

View File

@ -37,3 +37,15 @@ export const getDoorSettingTimeLock = (door: string): number[] => {
// never locked (always -1 < hr < 25) // never locked (always -1 < hr < 25)
return [25, -1]; 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;
}