From 00e89f44e8bd353fba8d7067ca74b30f0ca83861 Mon Sep 17 00:00:00 2001 From: Dan Gowans Date: Thu, 15 Sep 2022 11:09:03 -0400 Subject: [PATCH] api key --- .gitignore | 4 ++ helpers/functions.api.d.ts | 4 ++ helpers/functions.api.js | 43 ++++++++++++++++++ helpers/functions.api.ts | 56 ++++++++++++++++++++++++ helpers/initializer.database.cemetery.js | 3 +- helpers/initializer.database.cemetery.ts | 3 +- routes/login.js | 5 ++- routes/login.ts | 7 ++- temp/legacy.importFromCSV.js | 3 +- temp/legacy.importFromCSV.ts | 3 +- types/globalTypes.js | 3 +- types/recordTypes.d.ts | 1 + types/recordTypes.ts | 1 + 13 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 helpers/functions.api.d.ts create mode 100644 helpers/functions.api.js create mode 100644 helpers/functions.api.ts diff --git a/.gitignore b/.gitignore index f2addd77..e35f65bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,18 @@ .nyc_output/ bin/daemon/ coverage/ + cypress/downloads/ cypress/screenshots/ cypress/videos/ + data/sessions/ + node_modules/ data/*.db data/*.db-journal +data/apiKeys.json data/config.d.ts data/config.js diff --git a/helpers/functions.api.d.ts b/helpers/functions.api.d.ts new file mode 100644 index 00000000..5b4c4e9b --- /dev/null +++ b/helpers/functions.api.d.ts @@ -0,0 +1,4 @@ +import * as recordTypes from "../types/recordTypes"; +export declare const regenerateApiKey: (userName: string) => Promise; +export declare const getApiKey: (userName: string) => Promise; +export declare const getApiKeyFromSession: (session: recordTypes.PartialSession) => Promise; diff --git a/helpers/functions.api.js b/helpers/functions.api.js new file mode 100644 index 00000000..b24d1385 --- /dev/null +++ b/helpers/functions.api.js @@ -0,0 +1,43 @@ +import fs from "node:fs/promises"; +import crypto from "node:crypto"; +import Debug from "debug"; +const debug = Debug("lot-occupancy-system:functions.api"); +const apiKeyPath = "data/apiKeys.json"; +let apiKeys; +const loadApiKeys = async () => { + try { + const fileData = await fs.readFile(apiKeyPath, "utf8"); + apiKeys = JSON.parse(fileData); + } + catch (error) { + debug(error); + apiKeys = {}; + } +}; +const saveApiKeys = async () => { + try { + await fs.writeFile(apiKeyPath, JSON.stringify(apiKeys), "utf8"); + } + catch (error) { + debug(error); + } +}; +const generateApiKey = (apiKeyPrefix) => { + return apiKeyPrefix + "-" + crypto.randomUUID() + "-" + Date.now(); +}; +export const regenerateApiKey = async (userName) => { + apiKeys[userName] = generateApiKey(userName); + await saveApiKeys(); +}; +export const getApiKey = async (userName) => { + if (!apiKeys) { + await loadApiKeys(); + } + if (!apiKeys[userName]) { + await regenerateApiKey(userName); + } + return apiKeys[userName]; +}; +export const getApiKeyFromSession = async (session) => { + return await getApiKey(session.user.userName); +}; diff --git a/helpers/functions.api.ts b/helpers/functions.api.ts new file mode 100644 index 00000000..c10d5346 --- /dev/null +++ b/helpers/functions.api.ts @@ -0,0 +1,56 @@ +import fs from "node:fs/promises"; +import crypto from "node:crypto"; + +import Debug from "debug"; + +import * as recordTypes from "../types/recordTypes"; + +const debug = Debug("lot-occupancy-system:functions.api"); + +const apiKeyPath = "data/apiKeys.json"; +let apiKeys: { [userName: string]: string }; + +const loadApiKeys = async () => { + try { + const fileData = await fs.readFile(apiKeyPath, "utf8"); + apiKeys = JSON.parse(fileData); + } catch (error) { + debug(error); + apiKeys = {}; + } +}; + +const saveApiKeys = async () => { + try { + await fs.writeFile(apiKeyPath, JSON.stringify(apiKeys), "utf8"); + } catch (error) { + debug(error); + } +}; + +const generateApiKey = (apiKeyPrefix: string) => { + return apiKeyPrefix + "-" + crypto.randomUUID() + "-" + Date.now(); +}; + +export const regenerateApiKey = async (userName: string) => { + apiKeys[userName] = generateApiKey(userName); + await saveApiKeys(); +}; + +export const getApiKey = async (userName: string) => { + if (!apiKeys) { + await loadApiKeys(); + } + + if (!apiKeys[userName]) { + await regenerateApiKey(userName); + } + + return apiKeys[userName]; +}; + +export const getApiKeyFromSession = async ( + session: recordTypes.PartialSession +) => { + return await getApiKey(session.user.userName); +}; diff --git a/helpers/initializer.database.cemetery.js b/helpers/initializer.database.cemetery.js index 80ab0870..c1313213 100644 --- a/helpers/initializer.database.cemetery.js +++ b/helpers/initializer.database.cemetery.js @@ -13,7 +13,8 @@ const session = { userName: "init.cemetery", userProperties: { canUpdate: true, - isAdmin: true + isAdmin: true, + apiKey: "" } } }; diff --git a/helpers/initializer.database.cemetery.ts b/helpers/initializer.database.cemetery.ts index 26325edd..2416c78b 100644 --- a/helpers/initializer.database.cemetery.ts +++ b/helpers/initializer.database.cemetery.ts @@ -24,7 +24,8 @@ const session: PartialSession = { userName: "init.cemetery", userProperties: { canUpdate: true, - isAdmin: true + isAdmin: true, + apiKey: "" } } }; diff --git a/routes/login.js b/routes/login.js index 79502c77..9d8eabcf 100644 --- a/routes/login.js +++ b/routes/login.js @@ -2,6 +2,7 @@ import { Router } from "express"; import * as configFunctions from "../helpers/functions.config.js"; import * as authenticationFunctions from "../helpers/functions.authentication.js"; import { useTestDatabases } from "../data/databasePaths.js"; +import { getApiKey } from "../helpers/functions.api.js"; export const router = Router(); const safeRedirects = new Set([ "/admin/fees", @@ -75,11 +76,13 @@ router .some((currentUserName) => { return (userNameLowerCase === currentUserName.toLowerCase()); }); + const apiKey = await getApiKey(userNameLowerCase); userObject = { userName: userNameLowerCase, userProperties: { canUpdate, - isAdmin + isAdmin, + apiKey } }; } diff --git a/routes/login.ts b/routes/login.ts index 58361c90..e87e6a17 100644 --- a/routes/login.ts +++ b/routes/login.ts @@ -6,6 +6,8 @@ import * as authenticationFunctions from "../helpers/functions.authentication.js import { useTestDatabases } from "../data/databasePaths.js"; +import { getApiKey } from "../helpers/functions.api.js"; + import type * as recordTypes from "../types/recordTypes"; export const router = Router(); @@ -112,11 +114,14 @@ router ); }); + const apiKey = await getApiKey(userNameLowerCase); + userObject = { userName: userNameLowerCase, userProperties: { canUpdate, - isAdmin + isAdmin, + apiKey } }; } diff --git a/temp/legacy.importFromCSV.js b/temp/legacy.importFromCSV.js index 4944da6d..1b4d5a2b 100644 --- a/temp/legacy.importFromCSV.js +++ b/temp/legacy.importFromCSV.js @@ -28,7 +28,8 @@ const user = { userName: "import.unix", userProperties: { canUpdate: true, - isAdmin: false + isAdmin: false, + apiKey: "" } } }; diff --git a/temp/legacy.importFromCSV.ts b/temp/legacy.importFromCSV.ts index 66a21062..843ac78c 100644 --- a/temp/legacy.importFromCSV.ts +++ b/temp/legacy.importFromCSV.ts @@ -203,7 +203,8 @@ const user: recordTypes.PartialSession = { userName: "import.unix", userProperties: { canUpdate: true, - isAdmin: false + isAdmin: false, + apiKey: "" } } }; diff --git a/types/globalTypes.js b/types/globalTypes.js index c8ad2e54..cb0ff5c3 100644 --- a/types/globalTypes.js +++ b/types/globalTypes.js @@ -1,2 +1 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +export {}; diff --git a/types/recordTypes.d.ts b/types/recordTypes.d.ts index 4bf0d1a9..18f04941 100644 --- a/types/recordTypes.d.ts +++ b/types/recordTypes.d.ts @@ -246,6 +246,7 @@ export interface User { export interface UserProperties { canUpdate: boolean; isAdmin: boolean; + apiKey: string; } declare module "express-session" { interface Session { diff --git a/types/recordTypes.ts b/types/recordTypes.ts index fc0f817f..b9e848e5 100644 --- a/types/recordTypes.ts +++ b/types/recordTypes.ts @@ -337,6 +337,7 @@ export interface User { export interface UserProperties { canUpdate: boolean; isAdmin: boolean; + apiKey: string; } declare module "express-session" {