Move all OAuth2/clients stuff to just /clients.

This commit is contained in:
zkldi
2021-11-06 17:50:02 +00:00
parent 3842302dda
commit 56af58a795
15 changed files with 132 additions and 97 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ async function AnonymiseDB(nsTo: string) {
logger.info(`Stripped username info.`, { r2 });
for (const collection of [
"oauth2-clients",
"api-clients",
"oauth2-auth-codes",
"password-reset-codes",
"api-tokens",
+2 -2
View File
@@ -170,7 +170,7 @@ const db = {
"arc-saved-profiles": monkDB.get<ARCSavedProfileDocument>("arc-saved-profiles"),
"user-settings": monkDB.get<UserSettings>("user-settings"),
"user-private-information": monkDB.get<PrivateUserInfoDocument>("user-private-information"),
"oauth2-clients": monkDB.get<OAuth2ApplicationDocument>("oauth2-clients"),
"api-clients": monkDB.get<OAuth2ApplicationDocument>("api-clients"),
"oauth2-auth-codes":
// i've inlined this one because i don't see it appearing anywhere else.
monkDB.get<{ code: string; userID: integer; createdOn: number }>("oauth2-auth-codes"),
@@ -212,7 +212,7 @@ export type StaticDatabases =
| "game-stats-snapshots"
| "arc-saved-profiles"
| "user-private-information"
| "oauth2-clients"
| "api-clients"
| "oauth2-auth-codes"
| "fer-settings"
| "orphan-chart-queue"
+1
View File
@@ -84,6 +84,7 @@ const staticIndexes: Partial<Record<Databases, Index[]>> = {
"fer-settings": [index({ userID: 1 }, UNIQUE)],
counters: [index({ counterName: 1 }, UNIQUE)],
"class-achievements": [index({ game: 1, playtype: 1, timeAchieved: 1 })],
"api-clients": [index({ clientID: 1 }, UNIQUE)],
};
const indexes: Partial<Record<Databases, Index[]>> = staticIndexes;
+3 -1
View File
@@ -429,7 +429,7 @@ export const DatabaseSchemas: Record<Databases, ValidatorFunction> = {
userID: p.isPositiveNonZeroInteger,
createdOn: p.isPositive,
}),
"oauth2-clients": prSchemaify({
"api-clients": prSchemaify({
clientID: "string",
clientSecret: "string",
name: "string",
@@ -437,6 +437,8 @@ export const DatabaseSchemas: Record<Databases, ValidatorFunction> = {
requestedPermissions: [p.isIn(Object.keys(AllPermissions))],
redirectUri: "string",
webhookUri: "?string",
apiKeyTemplate: "?string",
apiKeyFilename: "?string",
}),
"orphan-chart-queue": prSchemaify({
idString: p.isIn(allIDStrings),
+1 -1
View File
@@ -6,7 +6,7 @@ const logger = CreateLogCtx(__filename);
// @todo make use of aggressive caching here?
export async function GetWebhookUrlInfo() {
const urls = await db["oauth2-clients"].find(
const urls = await db["api-clients"].find(
{ webhookUri: { $ne: null } },
{ projection: { webhookUri: 1, clientSecret: 1 } }
);
@@ -24,6 +24,8 @@ t.test("#GetClientFromID", (t) => {
requestedPermissions: ["customise_profile"],
redirectUri: "https://example.com/callback",
webhookUri: null,
apiKeyTemplate: null,
apiKeyFilename: null,
},
"Should assign clientDoc with secret ommitted."
);
@@ -6,7 +6,7 @@ import { OAuth2ApplicationDocument } from "tachi-common";
import { AssignToReqTachiData } from "utils/req-tachi-data";
export const GetClientFromID: RequestHandler = async (req, res, next) => {
const client = await db["oauth2-clients"].findOne(
const client = await db["api-clients"].findOne(
{
clientID: req.params.clientID,
},
@@ -36,17 +36,17 @@ const clientDataset: OAuth2ApplicationDocument[] = [
},
];
t.test("GET /api/v1/oauth/clients", async (t) => {
t.test("GET /api/v1/clients", async (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db["oauth2-clients"].remove({});
await db["oauth2-clients"].insert(clientDataset);
await db["api-clients"].remove({});
await db["api-clients"].insert(clientDataset);
});
const cookie = await CreateFakeAuthCookie(mockApi);
t.test("Should retrieve your clients.", async (t) => {
const res = await mockApi.get("/api/v1/oauth/clients").set("Cookie", cookie);
const res = await mockApi.get("/api/v1/clients").set("Cookie", cookie);
t.equal(res.statusCode, 200);
@@ -80,12 +80,12 @@ t.test("GET /api/v1/oauth/clients", async (t) => {
});
t.test("Requires self-key level authentication.", async (t) => {
const res = await mockApi.get("/api/v1/oauth/clients");
const res = await mockApi.get("/api/v1/clients");
t.equal(res.statusCode, 401);
const res2 = await mockApi
.get("/api/v1/oauth/clients")
.get("/api/v1/clients")
.set("Authorization", "Bearer fake_api_token");
t.equal(res2.statusCode, 401);
@@ -96,14 +96,14 @@ t.test("GET /api/v1/oauth/clients", async (t) => {
t.end();
});
t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.test("POST /api/v1/clients/create", async (t) => {
t.beforeEach(ResetDBState);
const cookie = await CreateFakeAuthCookie(mockApi);
t.test("Should create a new client.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "Hello World",
redirectUri: "https://example.com/callback",
@@ -113,7 +113,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.equal(res.statusCode, 200);
const dbRes = await db["oauth2-clients"].findOne({ clientID: res.body.body.clientID });
const dbRes = await db["api-clients"].findOne({ clientID: res.body.body.clientID });
t.not(dbRes, null, "Should be saved in the database.");
@@ -124,7 +124,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.test("Should validate names to be between 3 and 80 characters.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "2",
redirectUri: "https://example.com/callback",
@@ -135,7 +135,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.equal(res.statusCode, 400);
const res2 = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "2".repeat(100),
redirectUri: "https://example.com/callback",
@@ -150,7 +150,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.test("Should validate urls to be between 3 and 80 characters.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "Hello World",
redirectUri: "ftp://example.com/callback",
@@ -165,7 +165,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.test("Should validate permissions.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "Hello World",
redirectUri: "http://example.com/callback",
@@ -182,7 +182,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
for (let i = 0; i < ServerConfig.OAUTH_CLIENT_CAP; i++) {
// eslint-disable-next-line no-await-in-loop
await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "Hello World",
redirectUri: "https://example.com/callback",
@@ -192,7 +192,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
}
const res = await mockApi
.post("/api/v1/oauth/clients/create")
.post("/api/v1/clients/create")
.send({
name: "Hello World",
redirectUri: "https://example.com/callback",
@@ -202,7 +202,7 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.equal(res.statusCode, 400);
const dbCount = await db["oauth2-clients"].count({ author: 1 });
const dbCount = await db["api-clients"].count({ author: 1 });
t.equal(dbCount, ServerConfig.OAUTH_CLIENT_CAP);
@@ -212,11 +212,11 @@ t.test("POST /api/v1/oauth/clients/create", async (t) => {
t.end();
});
t.test("GET /api/v1/oauth/clients/:clientID", (t) => {
t.test("GET /api/v1/clients/:clientID", (t) => {
t.beforeEach(ResetDBState);
t.test("Should return information about the client at that ID.", async (t) => {
const res = await mockApi.get("/api/v1/oauth/clients/OAUTH2_CLIENT_ID");
const res = await mockApi.get("/api/v1/clients/OAUTH2_CLIENT_ID");
t.strictSame(res.body.body, {
clientID: "OAUTH2_CLIENT_ID",
@@ -226,13 +226,15 @@ t.test("GET /api/v1/oauth/clients/:clientID", (t) => {
requestedPermissions: ["customise_profile"],
redirectUri: "https://example.com/callback",
webhookUri: null,
apiKeyTemplate: null,
apiKeyFilename: null,
});
t.end();
});
t.test("Should return 404 if client doesn't exist.", async (t) => {
const res = await mockApi.get("/api/v1/oauth/clients/BAD_CLIENT");
const res = await mockApi.get("/api/v1/clients/BAD_CLIENT");
t.equal(res.statusCode, 404);
@@ -242,18 +244,18 @@ t.test("GET /api/v1/oauth/clients/:clientID", (t) => {
t.end();
});
t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.test("PATCH /api/v1/clients/:clientID", async (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db["oauth2-clients"].remove({});
await db["oauth2-clients"].insert(clientDataset);
await db["api-clients"].remove({});
await db["api-clients"].insert(clientDataset);
});
const cookie = await CreateFakeAuthCookie(mockApi);
t.test("Should be able to modify a clients name.", async (t) => {
const res = await mockApi
.patch("/api/v1/oauth/clients/CLIENT_1")
.patch("/api/v1/clients/CLIENT_1")
.send({ name: "NEW NAME" })
.set("Cookie", cookie);
@@ -261,7 +263,7 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.equal(res.body.body.name, "NEW NAME");
const dbRes = await db["oauth2-clients"].findOne({
const dbRes = await db["api-clients"].findOne({
clientID: "CLIENT_1",
});
@@ -272,7 +274,7 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.test("Should be able to modify a clients webhookUri.", async (t) => {
const res = await mockApi
.patch("/api/v1/oauth/clients/CLIENT_1")
.patch("/api/v1/clients/CLIENT_1")
.send({ webhookUri: "https://example.com" })
.set("Cookie", cookie);
@@ -280,7 +282,7 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.equal(res.body.body.webhookUri, "https://example.com");
const dbRes = await db["oauth2-clients"].findOne({
const dbRes = await db["api-clients"].findOne({
clientID: "CLIENT_1",
});
@@ -291,14 +293,14 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
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")
.patch("/api/v1/clients/CLIENT_1")
.send({ name: "2" })
.set("Cookie", cookie);
t.equal(res.statusCode, 400);
const res2 = await mockApi
.patch("/api/v1/oauth/clients/CLIENT_1")
.patch("/api/v1/clients/CLIENT_1")
.send({ name: "2".repeat(100) })
.set("Cookie", cookie);
@@ -308,10 +310,7 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
});
t.test("Must provide name to modify.", async (t) => {
const res = await mockApi
.patch("/api/v1/oauth/clients/CLIENT_1")
.send({})
.set("Cookie", cookie);
const res = await mockApi.patch("/api/v1/clients/CLIENT_1").send({}).set("Cookie", cookie);
t.equal(res.statusCode, 400);
@@ -320,13 +319,13 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.test("Must be owner of client.", async (t) => {
const res = await mockApi
.patch("/api/v1/oauth/clients/CLIENT_3")
.patch("/api/v1/clients/CLIENT_3")
.send({ name: "foo" })
.set("Cookie", cookie);
t.equal(res.statusCode, 403);
const res2 = await mockApi.patch("/api/v1/oauth/clients/CLIENT_3").send({ name: "foo" });
const res2 = await mockApi.patch("/api/v1/clients/CLIENT_3").send({ name: "foo" });
t.equal(res2.statusCode, 401);
@@ -336,25 +335,25 @@ t.test("PATCH /api/v1/oauth/clients/:clientID", async (t) => {
t.end();
});
t.test("POST /api/v1/oauth/clients/:clientID/reset-secret", async (t) => {
t.test("POST /api/v1/clients/:clientID/reset-secret", async (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db["oauth2-clients"].remove({});
await db["oauth2-clients"].insert(clientDataset);
await db["api-clients"].remove({});
await db["api-clients"].insert(clientDataset);
});
const cookie = await CreateFakeAuthCookie(mockApi);
t.test("Should reset the client's secret.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/CLIENT_1/reset-secret")
.post("/api/v1/clients/CLIENT_1/reset-secret")
.set("Cookie", cookie);
t.equal(res.statusCode, 200);
t.not(res.body.body.clientSecret, "SECRET_1", "Should return the new secret.");
const dbRes = await db["oauth2-clients"].findOne({
const dbRes = await db["api-clients"].findOne({
clientID: "CLIENT_1",
});
@@ -365,12 +364,12 @@ t.test("POST /api/v1/oauth/clients/:clientID/reset-secret", async (t) => {
t.test("Must be owner of client.", async (t) => {
const res = await mockApi
.post("/api/v1/oauth/clients/CLIENT_3/reset-secret")
.post("/api/v1/clients/CLIENT_3/reset-secret")
.set("Cookie", cookie);
t.equal(res.statusCode, 403);
const res2 = await mockApi.post("/api/v1/oauth/clients/CLIENT_3/reset-secret");
const res2 = await mockApi.post("/api/v1/clients/CLIENT_3/reset-secret");
t.equal(res2.statusCode, 401);
@@ -380,11 +379,11 @@ t.test("POST /api/v1/oauth/clients/:clientID/reset-secret", async (t) => {
t.end();
});
t.test("DELETE /api/v1/oauth/clients/:clientID", async (t) => {
t.test("DELETE /api/v1/clients/:clientID", async (t) => {
t.beforeEach(ResetDBState);
t.beforeEach(async () => {
await db["oauth2-clients"].remove({});
await db["oauth2-clients"].insert(clientDataset);
await db["api-clients"].remove({});
await db["api-clients"].insert(clientDataset);
});
const cookie = await CreateFakeAuthCookie(mockApi);
@@ -403,11 +402,11 @@ t.test("DELETE /api/v1/oauth/clients/:clientID", async (t) => {
},
] as APITokenDocument[]);
const res = await mockApi.delete("/api/v1/oauth/clients/CLIENT_1").set("Cookie", cookie);
const res = await mockApi.delete("/api/v1/clients/CLIENT_1").set("Cookie", cookie);
t.equal(res.statusCode, 200);
const dbRes = await db["oauth2-clients"].findOne({ clientID: "CLIENT_1" });
const dbRes = await db["api-clients"].findOne({ clientID: "CLIENT_1" });
t.equal(dbRes, null, "Should no longer exist.");
@@ -419,11 +418,11 @@ t.test("DELETE /api/v1/oauth/clients/:clientID", async (t) => {
});
t.test("Must be owner of client.", async (t) => {
const res = await mockApi.delete("/api/v1/oauth/clients/CLIENT_3").set("Cookie", cookie);
const res = await mockApi.delete("/api/v1/clients/CLIENT_3").set("Cookie", cookie);
t.equal(res.statusCode, 403);
const res2 = await mockApi.delete("/api/v1/oauth/clients/CLIENT_3");
const res2 = await mockApi.delete("/api/v1/clients/CLIENT_3");
t.equal(res2.statusCode, 401);
@@ -21,7 +21,7 @@ const router: Router = Router({ mergeParams: true });
*
* @warn This also returns the client_secrets! Those *have* to be kept secret.
*
* @name GET /api/v1/oauth/clients
* @name GET /api/v1/clients
*/
router.get("/", async (req, res) => {
const user = req.session.tachi?.user;
@@ -33,7 +33,7 @@ router.get("/", async (req, res) => {
});
}
const clients = await db["oauth2-clients"].find({
const clients = await db["api-clients"].find({
author: user.id,
});
@@ -45,19 +45,40 @@ router.get("/", async (req, res) => {
});
/**
* Create a new OAuth2 Client. Requires session-level auth.
* Create a new API Client. Requires session-level auth.
*
* @param name - A string that identifies this client.
* @param redirectUri - The redirectUri this client uses.
* @param webhookUri - Optionally, a webhookUri to call with webhook events.
* @param apiKeyTemplate - Optionally, a static format to apply when doing static auth.
* @param apiKeyFilename - Optionally, a filename to automatically download the template to, when doing
* static flow.
* @param permissions - An array of APIPermissions this client is expected to use.
*
* @name POST /api/v1/oauth/clients/create
* @name POST /api/v1/clients/create
*/
router.post(
"/create",
prValidate({
name: p.isBoundedString(3, 80),
redirectUri: "string",
webhookUri: "*string",
apiKeyTemplate: (self) => {
if (self === undefined) {
return true;
}
if (typeof self !== "string") {
return "Expected a string.";
}
if (!self.includes("%%TACHI_KEY%%")) {
return "Must contain %%TACHI_KEY%% as part of the template.";
}
return true;
},
apiKeyFilename: "*string",
permissions: [p.isIn(Object.keys(AllPermissions))],
}),
async (req, res) => {
@@ -68,7 +89,7 @@ router.post(
});
}
const existingClients = await db["oauth2-clients"].find({
const existingClients = await db["api-clients"].find({
author: req.session.tachi.user.id,
});
@@ -81,6 +102,13 @@ router.post(
const permissions = DedupeArr<APIPermissions>(req.body.permissions);
if (permissions.length === 0) {
return res.status(400).json({
success: false,
description: `Invalid permissions -- Need to require atleast one.`,
});
}
if (!IsValidURL(req.body.redirectUri)) {
return res.status(400).json({
success: false,
@@ -101,7 +129,7 @@ router.post(
webhookUri: null,
};
await db["oauth2-clients"].insert(clientDoc);
await db["api-clients"].insert(clientDoc);
logger.info(
`User ${FormatUserDoc(req.session.tachi.user)} created a new OAuth2 Client ${
@@ -120,7 +148,7 @@ router.post(
/**
* Retrieves information about the client at this ID.
*
* @name GET /api/v1/oauth/clients/:clientID
* @name GET /api/v1/clients/:clientID
*/
router.get("/:clientID", GetClientFromID, (req, res) => {
const client = req[SYMBOL_TachiData]!.oauth2ClientDoc!;
@@ -139,7 +167,7 @@ router.get("/:clientID", GetClientFromID, (req, res) => {
* @param name - Change the name of this client.
* @param webhookUri - Change a bound webhookUri for this client.
*
* @name PATCH /api/v1/oauth/clients/:clientID
* @name PATCH /api/v1/clients/:clientID
*/
router.patch(
"/:clientID",
@@ -159,7 +187,7 @@ router.patch(
return true;
}),
redirectUri: optNull((self) => {
redirectUri: p.optional((self) => {
if (typeof self !== "string") {
return "Expected a string.";
}
@@ -184,7 +212,7 @@ router.patch(
});
}
const newClient = await db["oauth2-clients"].findOneAndUpdate(
const newClient = await db["api-clients"].findOneAndUpdate(
{
clientID: client.clientID,
},
@@ -209,7 +237,7 @@ router.patch(
* Resets the clientSecret for this client.
* This will NOT invalidate any existing tokens, as per oauth2 spec.
*
* @name POST /api/v1/oauth/clients/:clientID/reset-secret
* @name POST /api/v1/clients/:clientID/reset-secret
*/
router.post(
"/:clientID/reset-secret",
@@ -223,7 +251,7 @@ router.post(
const newSecret = Random20Hex();
const newClient = await db["oauth2-clients"].findOneAndUpdate(
const newClient = await db["api-clients"].findOneAndUpdate(
{
clientID: client.clientID,
},
@@ -245,7 +273,7 @@ router.post(
/**
* Delete this client. Must be authorized at a session-request level.
*
* @name DELETE /api/v1/oauth/clients/:clientID
* @name DELETE /api/v1/clients/:clientID
*/
router.delete("/:clientID", GetClientFromID, RequireOwnershipOfClient, async (req, res) => {
const client = req[SYMBOL_TachiData]!.oauth2ClientDoc!;
@@ -255,7 +283,7 @@ router.delete("/:clientID", GetClientFromID, RequireOwnershipOfClient, async (re
logger.info(`Recieved request to destroy OAuth2 Client ${client.name} (${client.clientID})`);
logger.verbose(`Removing OAuth2 Client ${clientName}.`);
await db["oauth2-clients"].remove({
await db["api-clients"].remove({
clientID: client.clientID,
});
logger.info(`Removed OAuth2 Client ${clientName}.`);
@@ -3,7 +3,6 @@ import db from "external/mongo/db";
import p from "prudence";
import prValidate from "server/middleware/prudence-validate";
import { Random20Hex } from "utils/misc";
import clientsRouter from "./clients/router";
const router: Router = Router({ mergeParams: true });
@@ -32,7 +31,7 @@ router.post(
code: "string",
}),
async (req, res) => {
const client = await db["oauth2-clients"].findOne({
const client = await db["api-clients"].findOne({
clientID: req.body.client_id,
});
@@ -114,6 +113,4 @@ router.post("/create-code", async (req, res) => {
});
});
router.use("/clients", clientsRouter);
export default router;
+8 -6
View File
@@ -1,15 +1,16 @@
import { Router } from "express";
import adminRouter from "./admin/router";
import authRouter from "./auth/router";
import clientsRouter from "./clients/router";
import gamesRouter from "./games/router";
import importRouter from "./import/router";
import importsRouter from "./imports/router";
import oauthRouter from "./oauth/router";
import scoresRouter from "./scores/router";
import searchRouter from "./search/router";
import sessionsRouter from "./sessions/router";
import statusRouter from "./status/router";
import usersRouter from "./users/router";
import gamesRouter from "./games/router";
import searchRouter from "./search/router";
import scoresRouter from "./scores/router";
import sessionsRouter from "./sessions/router";
import oauthRouter from "./oauth/router";
import importsRouter from "./imports/router";
const router: Router = Router({ mergeParams: true });
@@ -24,6 +25,7 @@ router.use("/search", searchRouter);
router.use("/scores", scoresRouter);
router.use("/sessions", sessionsRouter);
router.use("/oauth", oauthRouter);
router.use("/clients", clientsRouter);
/**
* Return a JSON 404 response if an endpoint is hit that does not exist.
@@ -68,7 +68,7 @@ router.post(
let fromOAuth2Client;
if (req.body.clientID) {
const client = await db["oauth2-clients"].findOne(
const client = await db["api-clients"].findOne(
{
clientID: req.body.clientID,
},
@@ -1,21 +1,19 @@
import { Router } from "express";
import db from "external/mongo/db";
import { SYMBOL_TachiData } from "lib/constants/tachi";
import CreateLogCtx from "lib/logger/logger";
import {
GetKaiTypeClientCredentials,
KaiTypeToBaseURL,
} from "lib/score-import/import-types/common/api-kai/utils";
import prValidate from "server/middleware/prudence-validate";
import { ValidateKaiType } from "./middleware";
import p from "prudence";
import db from "external/mongo/db";
import { SYMBOL_TachiData } from "lib/constants/tachi";
import { FormatUserDoc } from "utils/user";
import { GetKaiAuth } from "utils/queries/auth";
import { RequireSelfRequestFromUser } from "../../../middleware";
import prValidate from "server/middleware/prudence-validate";
import { RequireKamaitachi } from "server/middleware/type-require";
import fetch from "utils/fetch";
import { Random20Hex } from "utils/misc";
import { Environment } from "lib/setup/config";
import { GetKaiAuth } from "utils/queries/auth";
import { FormatUserDoc } from "utils/user";
import { RequireSelfRequestFromUser } from "../../../middleware";
import { ValidateKaiType } from "./middleware";
const router: Router = Router({ mergeParams: true });
@@ -0,0 +1,15 @@
[
{
"clientID": "OAUTH2_CLIENT_ID",
"clientSecret": "OAUTH2_CLIENT_SECRET",
"name": "Test_Service",
"author": 1,
"requestedPermissions": [
"customise_profile"
],
"redirectUri": "https://example.com/callback",
"webhookUri": null,
"apiKeyTemplate": null,
"apiKeyFilename": null
}
]
@@ -1,9 +0,0 @@
[{
"clientID": "OAUTH2_CLIENT_ID",
"clientSecret": "OAUTH2_CLIENT_SECRET",
"name": "Test_Service",
"author": 1,
"requestedPermissions": ["customise_profile"],
"redirectUri": "https://example.com/callback",
"webhookUri": null
}]