diff --git a/app/scenes/Settings/components/WebhookSubscriptionForm.tsx b/app/scenes/Settings/components/WebhookSubscriptionForm.tsx index 03e43410f..30f46c29b 100644 --- a/app/scenes/Settings/components/WebhookSubscriptionForm.tsx +++ b/app/scenes/Settings/components/WebhookSubscriptionForm.tsx @@ -150,7 +150,7 @@ interface FormData { } function generateSigningSecret() { - return `olws_${randomstring.generate(32)}`; + return `ol_whs_${randomstring.generate(32)}`; } function WebhookSubscriptionForm({ handleSubmit, webhookSubscription }: Props) { diff --git a/server/middlewares/authentication.ts b/server/middlewares/authentication.ts index 299e47d16..002ee80b6 100644 --- a/server/middlewares/authentication.ts +++ b/server/middlewares/authentication.ts @@ -61,7 +61,7 @@ export default function auth(options: AuthenticationOptions = {}) { let user: User | null | undefined; if (token) { - if (String(token).match(/^[\w]{38}$/)) { + if (ApiKey.match(String(token))) { ctx.state.authType = AuthenticationType.API; let apiKey; diff --git a/server/models/ApiKey.test.ts b/server/models/ApiKey.test.ts new file mode 100644 index 000000000..cd033ee77 --- /dev/null +++ b/server/models/ApiKey.test.ts @@ -0,0 +1,20 @@ +import randomstring from "randomstring"; +import { buildApiKey } from "@server/test/factories"; +import ApiKey from "./ApiKey"; + +describe("#ApiKey", () => { + describe("match", () => { + test("should match an API secret", async () => { + const apiKey = await buildApiKey({ + name: "Dev", + }); + expect(ApiKey.match(apiKey?.secret)).toBe(true); + expect(ApiKey.match(`${randomstring.generate(38)}`)).toBe(true); + }); + + test("should not match non secrets", async () => { + expect(ApiKey.match("123")).toBe(false); + expect(ApiKey.match("1234567890")).toBe(false); + }); + }); +}); diff --git a/server/models/ApiKey.ts b/server/models/ApiKey.ts index 82708a3da..8c5a49e31 100644 --- a/server/models/ApiKey.ts +++ b/server/models/ApiKey.ts @@ -15,6 +15,8 @@ import Length from "./validators/Length"; @Table({ tableName: "apiKeys", modelName: "apiKey" }) @Fix class ApiKey extends ParanoidModel { + static prefix = "ol_api_"; + @Length({ min: 3, max: 255, @@ -32,10 +34,21 @@ class ApiKey extends ParanoidModel { @BeforeValidate static async generateSecret(model: ApiKey) { if (!model.secret) { - model.secret = randomstring.generate(38); + model.secret = `${ApiKey.prefix}${randomstring.generate(38)}`; } } + /** + * Validates that the input touch could be an API key, this does not check + * that the key exists in the database. + * + * @param text The text to validate + * @returns True if likely an API key + */ + static match(text: string) { + return !!text.replace(ApiKey.prefix, "").match(/^[\w]{38}$/); + } + // associations @BelongsTo(() => User, "userId")