mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-03-10 05:35:05 +08:00
Update ape keys data model (#2655) by bruce
* Update ape keys data model * Add use count * Add safer check
This commit is contained in:
parent
61ebf2cc39
commit
9dfb352a40
7 changed files with 82 additions and 81 deletions
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -62,7 +62,7 @@ router.patch(
|
|||
enabled: joi.boolean(),
|
||||
},
|
||||
}),
|
||||
asyncHandler(ApeKeysController.updateApeKey)
|
||||
asyncHandler(ApeKeysController.editApeKey)
|
||||
);
|
||||
|
||||
router.delete(
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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: "",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
3
backend/types/types.d.ts
vendored
3
backend/types/types.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue