From d7928444735a80bb1f54c45ee5c088ef1afa5176 Mon Sep 17 00:00:00 2001 From: Bruce Berrios <58147810+Bruception@users.noreply.github.com> Date: Tue, 1 Mar 2022 17:43:22 -0500 Subject: [PATCH] Add ape key authentication (#2610) * Add ape key authentication * Move ape key config to server config * Remove full stops * Fix --- backend/api/controllers/ape-keys.ts | 12 ++++----- backend/constants/base-configuration.ts | 2 ++ backend/middlewares/auth.ts | 35 ++++++++++++++++++++----- backend/types/types.d.ts | 2 ++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/backend/api/controllers/ape-keys.ts b/backend/api/controllers/ape-keys.ts index 57f8666e4..2e4a14df5 100644 --- a/backend/api/controllers/ape-keys.ts +++ b/backend/api/controllers/ape-keys.ts @@ -6,9 +6,6 @@ import MonkeyError from "../../handlers/error"; import { MonkeyResponse } from "../../handlers/monkey-response"; import { base64UrlEncode } from "../../handlers/misc"; -const APE_KEY_BYTES = 48; -const SALT_ROUNDS = parseInt(process.env.APE_KEY_SALT_ROUNDS, 10) || 5; - function cleanApeKey(apeKey: MonkeyTypes.ApeKey): Partial { return _.omit(apeKey, "hash"); } @@ -28,7 +25,8 @@ class ApeKeysController { ): Promise { const { name, enabled } = req.body; const { uid } = req.ctx.decodedToken; - const { maxKeysPerUser } = req.ctx.configuration.apeKeys; + const { maxKeysPerUser, apeKeyBytes, apeKeySaltRounds } = + req.ctx.configuration.apeKeys; const currentNumberOfApeKeys = await ApeKeysDAO.countApeKeysForUser(uid); @@ -39,8 +37,8 @@ class ApeKeysController { ); } - const apiKey = randomBytes(APE_KEY_BYTES).toString("base64url"); - const saltyHash = await hash(apiKey, SALT_ROUNDS); + const apiKey = randomBytes(apeKeyBytes).toString("base64url"); + const saltyHash = await hash(apiKey, apeKeySaltRounds); const apeKey: MonkeyTypes.ApeKey = { name, @@ -53,7 +51,7 @@ class ApeKeysController { const apeKeyId = await ApeKeysDAO.addApeKey(uid, apeKey); return new MonkeyResponse("ApeKey generated", { - apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`), + apeKey: base64UrlEncode(`${uid}.${apeKeyId}.${apiKey}`), apeKeyId, apeKeyDetails: cleanApeKey(apeKey), }); diff --git a/backend/constants/base-configuration.ts b/backend/constants/base-configuration.ts index ee3a52008..c3f99d68d 100644 --- a/backend/constants/base-configuration.ts +++ b/backend/constants/base-configuration.ts @@ -20,6 +20,8 @@ const BASE_CONFIGURATION: MonkeyTypes.Configuration = { endpointsEnabled: false, acceptKeys: false, maxKeysPerUser: 0, + apeKeyBytes: 24, + apeKeySaltRounds: 5, }, enableSavingResults: { enabled: false, diff --git a/backend/middlewares/auth.ts b/backend/middlewares/auth.ts index f278e7d08..d23ca0d8e 100644 --- a/backend/middlewares/auth.ts +++ b/backend/middlewares/auth.ts @@ -1,5 +1,9 @@ +import _ from "lodash"; +import { compare } from "bcrypt"; +import UsersDAO from "../dao/user"; import MonkeyError from "../handlers/error"; import { verifyIdToken } from "../handlers/auth"; +import { base64UrlDecode } from "../handlers/misc"; import { NextFunction, Response, Handler } from "express"; interface RequestAuthenticationOptions { @@ -94,7 +98,7 @@ async function authenticateWithAuthHeader( throw new MonkeyError( 401, "Unknown authentication scheme", - `The authentication scheme "${authScheme}" is not implemented.` + `The authentication scheme "${authScheme}" is not implemented` ); } @@ -137,13 +141,32 @@ async function authenticateWithApeKey( options: RequestAuthenticationOptions ): Promise { if (!configuration.apeKeys.acceptKeys) { - throw new MonkeyError(403, "ApeKeys are not being accepted at this time."); - } - if (!options.acceptApeKeys) { - throw new MonkeyError(401, "This endpoint does not accept ApeKeys."); + throw new MonkeyError(403, "ApeKeys are not being accepted at this time"); } - throw new MonkeyError(401, "ApeKeys are not implemented."); + if (!options.acceptApeKeys) { + throw new MonkeyError(401, "This endpoint does not accept ApeKeys"); + } + + try { + const decodedKey = base64UrlDecode(key); + const [uid, keyId, apeKey] = decodedKey.split("."); + + const keyOwner = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; + const targetApeKey = _.get(keyOwner.apeKeys, keyId); + const isKeyValid = await compare(apeKey, targetApeKey?.hash); + + if (!isKeyValid) { + throw new MonkeyError(400); + } + + return { + uid, + email: keyOwner.email, + }; + } catch (error) { + throw new MonkeyError(400, "Invalid ApeKey"); + } } export { authenticateRequest }; diff --git a/backend/types/types.d.ts b/backend/types/types.d.ts index 7a7e78254..5cbf4ace7 100644 --- a/backend/types/types.d.ts +++ b/backend/types/types.d.ts @@ -18,6 +18,8 @@ declare namespace MonkeyTypes { endpointsEnabled: boolean; acceptKeys: boolean; maxKeysPerUser: number; + apeKeyBytes: number; + apeKeySaltRounds: number; }; enableSavingResults: { enabled: boolean;