diff --git a/server/src/server/router/api/v1/oauth/clients/router.test.ts b/server/src/server/router/api/v1/oauth/clients/router.test.ts index d0c91f6f7..bca1cc705 100644 --- a/server/src/server/router/api/v1/oauth/clients/router.test.ts +++ b/server/src/server/router/api/v1/oauth/clients/router.test.ts @@ -14,6 +14,7 @@ const clientDataset: OAuth2ApplicationDocument[] = [ name: "foo", redirectUri: "example.com", requestedPermissions: ["customise_profile"], + webhookUri: null, }, { author: 1, @@ -22,6 +23,7 @@ const clientDataset: OAuth2ApplicationDocument[] = [ name: "bar", redirectUri: "example.com", requestedPermissions: ["customise_profile"], + webhookUri: null, }, { author: 2, @@ -30,6 +32,7 @@ const clientDataset: OAuth2ApplicationDocument[] = [ name: "baz", redirectUri: "example.com", requestedPermissions: ["customise_profile"], + webhookUri: null, }, ]; @@ -59,6 +62,7 @@ t.test("GET /api/v1/oauth/clients", async (t) => { name: "foo", redirectUri: "example.com", requestedPermissions: ["customise_profile"], + webhookUri: null, }, { author: 1, @@ -67,6 +71,7 @@ t.test("GET /api/v1/oauth/clients", async (t) => { name: "bar", redirectUri: "example.com", requestedPermissions: ["customise_profile"], + webhookUri: null, }, ] ); @@ -220,6 +225,7 @@ t.test("GET /api/v1/oauth/clients/:clientID", (t) => { author: 1, requestedPermissions: ["customise_profile"], redirectUri: "https://example.com/callback", + webhookUri: null, }); t.end(); @@ -264,6 +270,25 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => { t.end(); }); + t.test("Should be able to modify a clients webhookUri.", async (t) => { + const res = await mockApi + .patch("/api/v1/oauth/clients/CLIENT_1") + .send({ webhookUri: "https://example.com" }) + .set("Cookie", cookie); + + t.equal(res.statusCode, 200); + + t.equal(res.body.body.webhookUri, "https://example.com"); + + const dbRes = await db["oauth2-clients"].findOne({ + clientID: "CLIENT_1", + }); + + t.equal(dbRes?.webhookUri, "https://example.com"); + + t.end(); + }); + t.test("Must validate name to be between 3 and 80 characters.", async (t) => { const res = await mockApi .patch("/api/v1/oauth/clients/CLIENT_1") diff --git a/server/src/server/router/api/v1/oauth/clients/router.ts b/server/src/server/router/api/v1/oauth/clients/router.ts index 45a73c80f..245a7deed 100644 --- a/server/src/server/router/api/v1/oauth/clients/router.ts +++ b/server/src/server/router/api/v1/oauth/clients/router.ts @@ -4,12 +4,13 @@ import prValidate from "server/middleware/prudence-validate"; import p from "prudence"; import db from "external/mongo/db"; import { GetClientFromID, RequireOwnershipOfClient } from "./middleware"; -import { DedupeArr, IsValidURL, Random20Hex } from "utils/misc"; +import { DedupeArr, DeleteUndefinedProps, IsValidURL, Random20Hex } from "utils/misc"; import CreateLogCtx from "lib/logger/logger"; import { APIPermissions } from "tachi-common"; import { AllPermissions } from "server/middleware/auth"; import { ServerConfig } from "lib/setup/config"; import { FormatUserDoc } from "utils/user"; +import { optNull } from "utils/prudence"; const logger = CreateLogCtx(__filename); @@ -97,6 +98,7 @@ router.post( name: req.body.name, author: req.session.tachi.user.id, redirectUri: req.body.redirectUri, + webhookUri: null, }; await db["oauth2-clients"].insert(clientDoc); @@ -134,8 +136,8 @@ router.get("/:clientID", GetClientFromID, (req, res) => { * Update an existing client. The requester must be the owner of this * client, and must also be making a session-level request. * - * @param name - Change the name of this client. This is the only option at - * the moment, and I do not imagine that will change. + * @param name - Change the name of this client. + * @param webhookUri - Change a bound webhookUri for this client. * * @name PATCH /api/v1/oauth/clients/:clientID */ @@ -143,18 +145,39 @@ router.patch( "/:clientID", GetClientFromID, RequireOwnershipOfClient, - prValidate({ name: p.isBoundedString(3, 80) }), + prValidate({ + name: p.optional(p.isBoundedString(3, 80)), + webhookUri: optNull((self) => { + if (typeof self !== "string") { + return "Expected a string."; + } + const res = IsValidURL(self); + + if (!res) { + return "Invalid URL."; + } + + return true; + }), + }), async (req, res) => { const client = req[SYMBOL_TachiData]!.oauth2ClientDoc!; + DeleteUndefinedProps(req.body); + + if (Object.keys(req.body).length === 0) { + return res.status(400).json({ + success: false, + description: `No changes to make.`, + }); + } + const newClient = await db["oauth2-clients"].findOneAndUpdate( { clientID: client.clientID, }, { - $set: { - name: req.body.name, - }, + $set: req.body, } ); diff --git a/server/src/test-utils/mock-db/oauth2-clients.json b/server/src/test-utils/mock-db/oauth2-clients.json index bbe580883..017026de5 100644 --- a/server/src/test-utils/mock-db/oauth2-clients.json +++ b/server/src/test-utils/mock-db/oauth2-clients.json @@ -4,5 +4,6 @@ "name": "Test_Service", "author": 1, "requestedPermissions": ["customise_profile"], - "redirectUri": "https://example.com/callback" + "redirectUri": "https://example.com/callback", + "webhookUri": null }] \ No newline at end of file