diff --git a/backend/api/controllers/ape-keys.ts b/backend/api/controllers/ape-keys.ts index 2784bd9ce..bc56a4007 100644 --- a/backend/api/controllers/ape-keys.ts +++ b/backend/api/controllers/ape-keys.ts @@ -7,7 +7,7 @@ import { MonkeyResponse } from "../../utils/monkey-response"; import { base64UrlEncode } from "../../utils/misc"; function cleanApeKey(apeKey: MonkeyTypes.ApeKey): Partial { - return _.omit(apeKey, "hash"); + return _.omit(apeKey, "hash", "_id", "uid", "useCount"); } class ApeKeysController { @@ -15,7 +15,7 @@ class ApeKeysController { const { uid } = req.ctx.decodedToken; const apeKeys = await ApeKeysDAO.getApeKeys(uid); - const hashlessKeys = _.mapValues(apeKeys, cleanApeKey); + const hashlessKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value(); return new MonkeyResponse("ApeKeys retrieved", hashlessKeys); } @@ -43,13 +43,15 @@ class ApeKeysController { const apeKey: MonkeyTypes.ApeKey = { name, enabled, + uid, hash: saltyHash, createdOn: Date.now(), modifiedOn: Date.now(), lastUsedOn: -1, + useCount: 0, }; - const apeKeyId = await ApeKeysDAO.addApeKey(uid, apeKey); + const apeKeyId = await ApeKeysDAO.addApeKey(apeKey); return new MonkeyResponse("ApeKey generated", { apeKey: base64UrlEncode(`${uid}.${apeKeyId}.${apiKey}`), @@ -58,12 +60,12 @@ class ApeKeysController { }); } - static async updateApeKey(req: MonkeyTypes.Request): Promise { + static async editApeKey(req: MonkeyTypes.Request): Promise { const { apeKeyId } = req.params; const { name, enabled } = req.body; const { uid } = req.ctx.decodedToken; - await ApeKeysDAO.updateApeKey(uid, apeKeyId, name, enabled); + await ApeKeysDAO.editApeKey(uid, apeKeyId, name, enabled); return new MonkeyResponse("ApeKey updated"); } diff --git a/backend/api/controllers/user.ts b/backend/api/controllers/user.ts index c35804065..e96f7c2df 100644 --- a/backend/api/controllers/user.ts +++ b/backend/api/controllers/user.ts @@ -1,4 +1,3 @@ -import _ from "lodash"; import UsersDAO from "../../dao/user"; import BotDAO from "../../dao/bot"; import MonkeyError from "../../utils/error"; @@ -7,10 +6,6 @@ import { MonkeyResponse } from "../../utils/monkey-response"; import { linkAccount } from "../../utils/discord"; import { buildAgentLog } from "../../utils/misc"; -function cleanUser(user: MonkeyTypes.User): Omit { - return _.omit(user, "apeKeys"); -} - class UserController { static async createNewUser( req: MonkeyTypes.Request @@ -106,7 +101,7 @@ class UserController { const agentLog = buildAgentLog(req); Logger.log("user_data_requested", agentLog, uid); - return new MonkeyResponse("User data retrieved", cleanUser(userInfo)); + return new MonkeyResponse("User data retrieved", userInfo); } static async linkDiscord(req: MonkeyTypes.Request): Promise { diff --git a/backend/api/routes/ape-keys.ts b/backend/api/routes/ape-keys.ts index df0ec37db..96ee19bf1 100644 --- a/backend/api/routes/ape-keys.ts +++ b/backend/api/routes/ape-keys.ts @@ -62,7 +62,7 @@ router.patch( enabled: joi.boolean(), }, }), - asyncHandler(ApeKeysController.updateApeKey) + asyncHandler(ApeKeysController.editApeKey) ); router.delete( diff --git a/backend/dao/ape-keys.ts b/backend/dao/ape-keys.ts index 827205641..8a607eba7 100644 --- a/backend/dao/ape-keys.ts +++ b/backend/dao/ape-keys.ts @@ -1,86 +1,92 @@ import _ from "lodash"; -import UsersDAO from "./user"; -import { ObjectId } from "mongodb"; -import MonkeyError from "../utils/error"; +import db from "../init/db"; +import { Filter, ObjectId, MatchKeysAndValues } from "mongodb"; -function checkIfKeyExists( - apeKeys: MonkeyTypes.User["apeKeys"], +const COLLECTION_NAME = "ape-keys"; + +function getApeKeyFilter( + uid: string, keyId: string -): void { - if (!_.has(apeKeys, keyId)) { - throw new MonkeyError(404, "Could not find ApeKey"); - } +): Filter { + return { + _id: new ObjectId(keyId), + uid, + }; } class ApeKeysDAO { - static async getApeKeys(uid: string): Promise { - const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; - const userApeKeys = user.apeKeys ?? {}; - return userApeKeys; + static async getApeKeys(uid: string): Promise { + return await db + .collection(COLLECTION_NAME) + .find({ uid }) + .toArray(); + } + + static async getApeKey( + uid: string, + keyId: string + ): Promise { + return await db + .collection(COLLECTION_NAME) + .findOne(getApeKeyFilter(uid, keyId)); } static async countApeKeysForUser(uid: string): Promise { - const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; - return _.size(user.apeKeys); + const apeKeys = await this.getApeKeys(uid); + return _.size(apeKeys); } - static async addApeKey( - uid: string, - apeKey: MonkeyTypes.ApeKey - ): Promise { - const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; + static async addApeKey(apeKey: MonkeyTypes.ApeKey): Promise { + const apeKeyId = new ObjectId(); - const apeKeyId = new ObjectId().toHexString(); + await db.collection(COLLECTION_NAME).insertOne({ + _id: apeKeyId, + ...apeKey, + }); - const apeKeys = { - ...user.apeKeys, - [apeKeyId]: apeKey, - }; - - await UsersDAO.setApeKeys(uid, apeKeys); - - return apeKeyId; + return apeKeyId.toHexString(); } - static async updateApeKey( + private static async updateApeKey( uid: string, keyId: string, - name?: string, - enabled?: boolean + updates: MatchKeysAndValues ): Promise { - const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; - checkIfKeyExists(user.apeKeys, keyId); + await db + .collection(COLLECTION_NAME) + .updateOne(getApeKeyFilter(uid, keyId), { + $inc: { useCount: _.has(updates, "lastUsedOn") ? 1 : 0 }, + $set: _.pickBy(updates, (value) => !_.isNil(value)), + }); + } - const apeKey = user.apeKeys![keyId]; - - const updatedApeKey: MonkeyTypes.ApeKey = { - ...apeKey, + static async editApeKey( + uid: string, + keyId: string, + name: string, + enabled: boolean + ): Promise { + const apeKeyUpdates = { + name, + enabled, modifiedOn: Date.now(), - name: name ?? apeKey.name, - enabled: _.isNil(enabled) ? apeKey.enabled : enabled, }; - user.apeKeys![keyId] = updatedApeKey; + await this.updateApeKey(uid, keyId, apeKeyUpdates); + } - await UsersDAO.setApeKeys(uid, user.apeKeys); + static async updateLastUsedOn(uid: string, keyId: string): Promise { + const apeKeyUpdates = { + lastUsedOn: Date.now(), + }; + + await this.updateApeKey(uid, keyId, apeKeyUpdates); } static async deleteApeKey(uid: string, keyId: string): Promise { - const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User; - checkIfKeyExists(user.apeKeys, keyId); - - const apeKeys = _.omit(user.apeKeys, keyId); - await UsersDAO.setApeKeys(uid, apeKeys); - } - - static async updateLastUsedOn( - user: MonkeyTypes.User, - keyId: string - ): Promise { - checkIfKeyExists(user.apeKeys, keyId); - - user.apeKeys![keyId].lastUsedOn = Date.now(); - await UsersDAO.setApeKeys(user.uid, user.apeKeys); + await db + .collection(COLLECTION_NAME) + .deleteOne(getApeKeyFilter(uid, keyId)); } } diff --git a/backend/dao/user.js b/backend/dao/user.js index 63369823a..898beada0 100644 --- a/backend/dao/user.js +++ b/backend/dao/user.js @@ -298,10 +298,6 @@ class UsersDAO { } } - static async setApeKeys(uid, apeKeys) { - await db.collection("users").updateOne({ uid }, { $set: { apeKeys } }); - } - static async getPersonalBests(uid, mode, mode2) { const user = await db.collection("users").findOne({ uid }); if (mode2) { diff --git a/backend/middlewares/auth.ts b/backend/middlewares/auth.ts index c863af56d..7a4aa81d5 100644 --- a/backend/middlewares/auth.ts +++ b/backend/middlewares/auth.ts @@ -1,6 +1,4 @@ -import _ from "lodash"; import { compare } from "bcrypt"; -import UsersDAO from "../dao/user"; import ApeKeysDAO from "../dao/ape-keys"; import MonkeyError from "../utils/error"; import { verifyIdToken } from "../utils/auth"; @@ -156,10 +154,13 @@ async function authenticateWithApeKey( 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 targetApeKey = await ApeKeysDAO.getApeKey(uid, keyId); - if (!targetApeKey?.enabled) { + if (!targetApeKey) { + throw new MonkeyError(404, "ApeKey not found"); + } + + if (!targetApeKey.enabled) { const { code, message } = statuses.APE_KEY_INACTIVE; throw new MonkeyError(code, message); } @@ -171,12 +172,12 @@ async function authenticateWithApeKey( throw new MonkeyError(code, message); } - await ApeKeysDAO.updateLastUsedOn(keyOwner, keyId); + await ApeKeysDAO.updateLastUsedOn(uid, keyId); return { type: "ApeKey", uid, - email: keyOwner.email, + email: "", }; } diff --git a/backend/types/types.d.ts b/backend/types/types.d.ts index 0a1a55006..a45c87b12 100644 --- a/backend/types/types.d.ts +++ b/backend/types/types.d.ts @@ -62,16 +62,17 @@ declare namespace MonkeyTypes { uid: string; quoteMod?: boolean; cannotReport?: boolean; - apeKeys?: Record; banned?: boolean; } interface ApeKey { + uid: string; name: string; hash: string; createdOn: number; modifiedOn: number; lastUsedOn: number; + useCount: number; enabled: boolean; }