Update ape keys data model (#2655) by bruce

* Update ape keys data model

* Add use count

* Add safer check
This commit is contained in:
Bruce Berrios 2022-03-07 18:41:04 -05:00 committed by GitHub
parent 61ebf2cc39
commit 9dfb352a40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 82 additions and 81 deletions

View file

@ -7,7 +7,7 @@ import { MonkeyResponse } from "../../utils/monkey-response";
import { base64UrlEncode } from "../../utils/misc";
function cleanApeKey(apeKey: MonkeyTypes.ApeKey): Partial<MonkeyTypes.ApeKey> {
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<MonkeyResponse> {
static async editApeKey(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
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");
}

View file

@ -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<MonkeyTypes.User, "apeKeys"> {
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<MonkeyResponse> {

View file

@ -62,7 +62,7 @@ router.patch(
enabled: joi.boolean(),
},
}),
asyncHandler(ApeKeysController.updateApeKey)
asyncHandler(ApeKeysController.editApeKey)
);
router.delete(

View file

@ -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<MonkeyTypes.ApeKey> {
return {
_id: new ObjectId(keyId),
uid,
};
}
class ApeKeysDAO {
static async getApeKeys(uid: string): Promise<MonkeyTypes.User["apeKeys"]> {
const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User;
const userApeKeys = user.apeKeys ?? {};
return userApeKeys;
static async getApeKeys(uid: string): Promise<MonkeyTypes.ApeKey[]> {
return await db
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
.find({ uid })
.toArray();
}
static async getApeKey(
uid: string,
keyId: string
): Promise<MonkeyTypes.ApeKey | null> {
return await db
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
.findOne(getApeKeyFilter(uid, keyId));
}
static async countApeKeysForUser(uid: string): Promise<number> {
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<string> {
const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User;
static async addApeKey(apeKey: MonkeyTypes.ApeKey): Promise<string> {
const apeKeyId = new ObjectId();
const apeKeyId = new ObjectId().toHexString();
await db.collection<MonkeyTypes.ApeKey>(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<MonkeyTypes.ApeKey>
): Promise<void> {
const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User;
checkIfKeyExists(user.apeKeys, keyId);
await db
.collection<MonkeyTypes.ApeKey>(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<void> {
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<void> {
const apeKeyUpdates = {
lastUsedOn: Date.now(),
};
await this.updateApeKey(uid, keyId, apeKeyUpdates);
}
static async deleteApeKey(uid: string, keyId: string): Promise<void> {
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<void> {
checkIfKeyExists(user.apeKeys, keyId);
user.apeKeys![keyId].lastUsedOn = Date.now();
await UsersDAO.setApeKeys(user.uid, user.apeKeys);
await db
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
.deleteOne(getApeKeyFilter(uid, keyId));
}
}

View file

@ -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) {

View file

@ -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: "",
};
}

View file

@ -62,16 +62,17 @@ declare namespace MonkeyTypes {
uid: string;
quoteMod?: boolean;
cannotReport?: boolean;
apeKeys?: Record<string, ApeKey>;
banned?: boolean;
}
interface ApeKey {
uid: string;
name: string;
hash: string;
createdOn: number;
modifiedOn: number;
lastUsedOn: number;
useCount: number;
enabled: boolean;
}