mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-03-10 13:44:27 +08:00
Final touches/tuning for ApeKeys (#2663)
* Final touches/tuning to ApeKeys * Add rate limiting note * Add malformed status
This commit is contained in:
parent
67e7c44e89
commit
7fa2827a60
5 changed files with 57 additions and 43 deletions
|
@ -54,7 +54,7 @@ class ApeKeysController {
|
|||
const apeKeyId = await ApeKeysDAO.addApeKey(apeKey);
|
||||
|
||||
return new MonkeyResponse("ApeKey generated", {
|
||||
apeKey: base64UrlEncode(`${uid}.${apeKeyId}.${apiKey}`),
|
||||
apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`),
|
||||
apeKeyId,
|
||||
apeKeyDetails: cleanApeKey(apeKey),
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ type Statuses = {
|
|||
GIT_GUD: Status;
|
||||
APE_KEY_INVALID: Status;
|
||||
APE_KEY_INACTIVE: Status;
|
||||
APE_KEY_MALFORMED: Status;
|
||||
};
|
||||
|
||||
const statuses: Statuses = {
|
||||
|
@ -52,7 +53,11 @@ const statuses: Statuses = {
|
|||
},
|
||||
APE_KEY_INACTIVE: {
|
||||
code: 471,
|
||||
message: "Ape is inactive",
|
||||
message: "ApeKey is inactive",
|
||||
},
|
||||
APE_KEY_MALFORMED: {
|
||||
code: 472,
|
||||
message: "ApeKey is malformed",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import _ from "lodash";
|
||||
import db from "../init/db";
|
||||
import { Filter, ObjectId, MatchKeysAndValues } from "mongodb";
|
||||
import MonkeyError from "../utils/error";
|
||||
|
||||
const COLLECTION_NAME = "ape-keys";
|
||||
|
||||
|
@ -22,13 +23,10 @@ class ApeKeysDAO {
|
|||
.toArray();
|
||||
}
|
||||
|
||||
static async getApeKey(
|
||||
uid: string,
|
||||
keyId: string
|
||||
): Promise<MonkeyTypes.ApeKey | null> {
|
||||
static async getApeKey(keyId: string): Promise<MonkeyTypes.ApeKey | null> {
|
||||
return await db
|
||||
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
|
||||
.findOne(getApeKeyFilter(uid, keyId));
|
||||
.findOne({ _id: new ObjectId(keyId) });
|
||||
}
|
||||
|
||||
static async countApeKeysForUser(uid: string): Promise<number> {
|
||||
|
@ -37,14 +35,10 @@ class ApeKeysDAO {
|
|||
}
|
||||
|
||||
static async addApeKey(apeKey: MonkeyTypes.ApeKey): Promise<string> {
|
||||
const apeKeyId = new ObjectId();
|
||||
|
||||
await db.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME).insertOne({
|
||||
_id: apeKeyId,
|
||||
...apeKey,
|
||||
});
|
||||
|
||||
return apeKeyId.toHexString();
|
||||
const insertionResult = await db
|
||||
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
|
||||
.insertOne(apeKey);
|
||||
return insertionResult.insertedId.toHexString();
|
||||
}
|
||||
|
||||
private static async updateApeKey(
|
||||
|
@ -52,12 +46,16 @@ class ApeKeysDAO {
|
|||
keyId: string,
|
||||
updates: MatchKeysAndValues<MonkeyTypes.ApeKey>
|
||||
): Promise<void> {
|
||||
await db
|
||||
const updateResult = await db
|
||||
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
|
||||
.updateOne(getApeKeyFilter(uid, keyId), {
|
||||
$inc: { useCount: _.has(updates, "lastUsedOn") ? 1 : 0 },
|
||||
$set: _.pickBy(updates, (value) => !_.isNil(value)),
|
||||
});
|
||||
|
||||
if (updateResult.modifiedCount === 0) {
|
||||
throw new MonkeyError(404, "ApeKey not found");
|
||||
}
|
||||
}
|
||||
|
||||
static async editApeKey(
|
||||
|
@ -84,9 +82,13 @@ class ApeKeysDAO {
|
|||
}
|
||||
|
||||
static async deleteApeKey(uid: string, keyId: string): Promise<void> {
|
||||
await db
|
||||
const deletionResult = await db
|
||||
.collection<MonkeyTypes.ApeKey>(COLLECTION_NAME)
|
||||
.deleteOne(getApeKeyFilter(uid, keyId));
|
||||
|
||||
if (deletionResult.deletedCount === 0) {
|
||||
throw new MonkeyError(404, "ApeKey not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "Documentation for the public endpoints provided by the Monketype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY` ",
|
||||
"description": "Documentation for the public endpoints provided by the Monketype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY`\n\nThere is a rate limit of `30 requests per minute` across all endpoints. Rate limit rates are shared across all ape keys.",
|
||||
"version": "1.0.0",
|
||||
"title": "Monkeytype API",
|
||||
"termsOfService": "https://monkeytype.com/terms-of-service",
|
||||
|
|
|
@ -151,34 +151,41 @@ async function authenticateWithApeKey(
|
|||
throw new MonkeyError(401, "This endpoint does not accept ApeKeys");
|
||||
}
|
||||
|
||||
const decodedKey = base64UrlDecode(key);
|
||||
const [uid, keyId, apeKey] = decodedKey.split(".");
|
||||
try {
|
||||
const decodedKey = base64UrlDecode(key);
|
||||
const [keyId, apeKey] = decodedKey.split(".");
|
||||
|
||||
const targetApeKey = await ApeKeysDAO.getApeKey(uid, keyId);
|
||||
const targetApeKey = await ApeKeysDAO.getApeKey(keyId);
|
||||
if (!targetApeKey) {
|
||||
throw new MonkeyError(404, "ApeKey not found");
|
||||
}
|
||||
|
||||
if (!targetApeKey) {
|
||||
throw new MonkeyError(404, "ApeKey not found");
|
||||
if (!targetApeKey.enabled) {
|
||||
const { code, message } = statuses.APE_KEY_INACTIVE;
|
||||
throw new MonkeyError(code, message);
|
||||
}
|
||||
|
||||
const isKeyValid = await compare(apeKey, targetApeKey.hash);
|
||||
if (!isKeyValid) {
|
||||
const { code, message } = statuses.APE_KEY_INVALID;
|
||||
throw new MonkeyError(code, message);
|
||||
}
|
||||
|
||||
await ApeKeysDAO.updateLastUsedOn(targetApeKey.uid, keyId);
|
||||
|
||||
return {
|
||||
type: "ApeKey",
|
||||
uid: targetApeKey.uid,
|
||||
email: "",
|
||||
};
|
||||
} catch (error) {
|
||||
if (!(error instanceof MonkeyError)) {
|
||||
const { code, message } = statuses.APE_KEY_MALFORMED;
|
||||
throw new MonkeyError(code, message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!targetApeKey.enabled) {
|
||||
const { code, message } = statuses.APE_KEY_INACTIVE;
|
||||
throw new MonkeyError(code, message);
|
||||
}
|
||||
|
||||
const isKeyValid = await compare(apeKey, targetApeKey?.hash ?? "");
|
||||
|
||||
if (!isKeyValid) {
|
||||
const { code, message } = statuses.APE_KEY_INVALID;
|
||||
throw new MonkeyError(code, message);
|
||||
}
|
||||
|
||||
await ApeKeysDAO.updateLastUsedOn(uid, keyId);
|
||||
|
||||
return {
|
||||
type: "ApeKey",
|
||||
uid,
|
||||
email: "",
|
||||
};
|
||||
}
|
||||
|
||||
export { authenticateRequest };
|
||||
|
|
Loading…
Reference in a new issue