mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 12:28:13 +03:00
Merge pull request #284 from zkldi:zkldi/issue-278-ARC-Integration-Management
zkldi/issue-278-ARC-Integration-Management
This commit is contained in:
+1
-1
@@ -70,7 +70,7 @@
|
||||
"redis": "3.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-json-stringify": "1.2.0",
|
||||
"tachi-common": "^0.1.9",
|
||||
"tachi-common": "^0.1.10",
|
||||
"typescript": "4.3.4",
|
||||
"winston": "3.3.3"
|
||||
},
|
||||
|
||||
Generated
+4
-4
@@ -51,7 +51,7 @@ specifiers:
|
||||
rimraf: 3.0.2
|
||||
safe-json-stringify: 1.2.0
|
||||
supertest: 6.1.3
|
||||
tachi-common: ^0.1.9
|
||||
tachi-common: ^0.1.10
|
||||
tap: 15.0.9
|
||||
ts-node: 10.0.0
|
||||
tsconfig-paths: 3.10.1
|
||||
@@ -84,7 +84,7 @@ dependencies:
|
||||
redis: 3.1.2
|
||||
rimraf: 3.0.2
|
||||
safe-json-stringify: 1.2.0
|
||||
tachi-common: 0.1.9_ts-node@10.0.0+typescript@4.3.4
|
||||
tachi-common: 0.1.10_ts-node@10.0.0+typescript@4.3.4
|
||||
typescript: 4.3.4
|
||||
winston: 3.3.3
|
||||
|
||||
@@ -3774,8 +3774,8 @@ packages:
|
||||
strip-ansi: 6.0.0
|
||||
dev: true
|
||||
|
||||
/tachi-common/0.1.9_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-nNChWe41kwizagY9tS5PCHTTSbT7VP87lejXn8sX19x+StNax/KJDyGhwXEuYyqo0pCoPYttHGZc4Aar6SgVyQ==}
|
||||
/tachi-common/0.1.10_ts-node@10.0.0+typescript@4.3.4:
|
||||
resolution: {integrity: sha512-yiiaQqgCGt8E9HWL9k4PFWAQ6b6WEK1LIIhJlPmS0zQf/qDx8aM9gugFhBogX9ilLFC0ZVj8i3X8YozydAILNQ==}
|
||||
dependencies:
|
||||
monk: 7.3.4
|
||||
tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import db from "external/mongo/db";
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { CreateFakeAuthCookie } from "test-utils/fake-auth";
|
||||
import mockApi from "test-utils/mock-api";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
|
||||
t.test("GET /api/v1/users/:userID/integrations/arc", async (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const cookie = await CreateFakeAuthCookie(mockApi);
|
||||
|
||||
t.test("Should return the current authentication state.", async (t) => {
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "foobar",
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 2,
|
||||
accountID: "barfoo",
|
||||
forImportType: "api/arc-sdvx",
|
||||
});
|
||||
|
||||
const res = await mockApi.get("/api/v1/users/1/integrations/arc").set("Cookie", cookie);
|
||||
|
||||
t.hasStrict(res.body.body, {
|
||||
iidx: { userID: 1, accountID: "foobar", forImportType: "api/arc-iidx" },
|
||||
sdvx: null,
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should reject APIKey authentication.", async (t) => {
|
||||
await db["api-tokens"].insert({
|
||||
identifier: "foobar",
|
||||
permissions: {},
|
||||
token: "foobar",
|
||||
userID: 1,
|
||||
});
|
||||
|
||||
const res = await mockApi
|
||||
.get("/api/v1/users/1/integrations/arc")
|
||||
.set("Authorization", "Bearer foobar");
|
||||
|
||||
t.equal(res.statusCode, 403);
|
||||
t.match(res.body.description, /this request cannot be performed by an api key/iu);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("PATCH /api/v1/users/:userID/integrations/arc", async (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const cookie = await CreateFakeAuthCookie(mockApi);
|
||||
|
||||
t.test("Should update a users configured accountIDs.", async (t) => {
|
||||
const res = await mockApi
|
||||
.patch("/api/v1/users/1/integrations/arc")
|
||||
.set("Cookie", cookie)
|
||||
.send({
|
||||
iidx: "newAccountID",
|
||||
});
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
|
||||
t.hasStrict(res.body.body, {
|
||||
iidx: {
|
||||
accountID: "newAccountID",
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
},
|
||||
sdvx: null,
|
||||
});
|
||||
|
||||
const dbRes = await db["arc-saved-profiles"].findOne({
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
t.strictSame(dbRes, {
|
||||
accountID: "newAccountID",
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should update accountIDs if one already exists", async (t) => {
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "OLD_ACCOUNT_ID",
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
const res = await mockApi
|
||||
.patch("/api/v1/users/1/integrations/arc")
|
||||
.set("Cookie", cookie)
|
||||
.send({
|
||||
iidx: "newAccountID",
|
||||
});
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
|
||||
t.hasStrict(res.body.body, {
|
||||
iidx: {
|
||||
accountID: "newAccountID",
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
},
|
||||
sdvx: null,
|
||||
});
|
||||
|
||||
const dbRes = await db["arc-saved-profiles"].findOne({
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
t.strictSame(dbRes, {
|
||||
accountID: "newAccountID",
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should remove accountIDs if nulled", async (t) => {
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: 1,
|
||||
accountID: "OLD_ACCOUNT_ID",
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
const res = await mockApi
|
||||
.patch("/api/v1/users/1/integrations/arc")
|
||||
.set("Cookie", cookie)
|
||||
.send({
|
||||
iidx: null,
|
||||
});
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
|
||||
t.hasStrict(res.body.body, {
|
||||
iidx: null,
|
||||
sdvx: null,
|
||||
});
|
||||
|
||||
const dbRes = await db["arc-saved-profiles"].findOne({
|
||||
userID: 1,
|
||||
forImportType: "api/arc-iidx",
|
||||
});
|
||||
|
||||
t.equal(dbRes, null);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should reject requests with no modifications", async (t) => {
|
||||
const res = await mockApi
|
||||
.patch("/api/v1/users/1/integrations/arc")
|
||||
.set("Cookie", cookie)
|
||||
.send({});
|
||||
|
||||
t.equal(res.statusCode, 400);
|
||||
t.match(res.body.description, "Invalid request to modify nothing.");
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Router } from "express";
|
||||
import db from "external/mongo/db";
|
||||
import { SYMBOL_TachiData } from "lib/constants/tachi";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import prValidate from "server/middleware/prudence-validate";
|
||||
import { RequireKamaitachi } from "server/middleware/type-require";
|
||||
import { GetArcAuth } from "utils/queries/auth";
|
||||
import { FormatUserDoc } from "utils/user";
|
||||
import { RequireSelfRequestFromUser } from "../../middleware";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
router.use(RequireKamaitachi);
|
||||
router.use(RequireSelfRequestFromUser);
|
||||
|
||||
/**
|
||||
* Return this users integration status for ARC.
|
||||
* @name GET /api/v1/users/:userID/integrations/arc
|
||||
*/
|
||||
router.get("/", async (req, res) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
|
||||
const [iidx, ddr, sdvx] = await Promise.all([
|
||||
GetArcAuth(user.id, "api/arc-iidx"),
|
||||
GetArcAuth(user.id, "api/arc-ddr"),
|
||||
GetArcAuth(user.id, "api/arc-sdvx"),
|
||||
]);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: `Retrieved integration information for ARC.`,
|
||||
body: {
|
||||
iidx,
|
||||
ddr,
|
||||
sdvx,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Modify this users integrations for ARC.
|
||||
* @name PATCH /api/v1/users/:userID/integrations/arc
|
||||
*/
|
||||
router.patch(
|
||||
"/",
|
||||
prValidate({ iidx: "*?string", ddr: "*?string", sdvx: "*?string" }),
|
||||
async (req, res) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
|
||||
if (Object.keys(req.body).length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: `Invalid request to modify nothing.`,
|
||||
});
|
||||
}
|
||||
|
||||
const [iidx, ddr, sdvx] = await Promise.all([
|
||||
GetArcAuth(user.id, "api/arc-iidx"),
|
||||
GetArcAuth(user.id, "api/arc-ddr"),
|
||||
GetArcAuth(user.id, "api/arc-sdvx"),
|
||||
]);
|
||||
|
||||
const existingData = { iidx, ddr, sdvx };
|
||||
|
||||
for (const key of ["iidx", "ddr", "sdvx"] as const) {
|
||||
const importType = `api/arc-${key}` as const;
|
||||
|
||||
if (req.body[key] === null) {
|
||||
logger.info(
|
||||
`User ${FormatUserDoc(user)} removed ARC integration for ${importType}.`
|
||||
);
|
||||
|
||||
await db["arc-saved-profiles"].remove(
|
||||
{
|
||||
userID: user.id,
|
||||
forImportType: importType,
|
||||
},
|
||||
{
|
||||
single: true,
|
||||
}
|
||||
);
|
||||
} else if (req.body[key]) {
|
||||
if (existingData[key]) {
|
||||
logger.info(`User updated ARC integration for ${importType}.`);
|
||||
await db["arc-saved-profiles"].update(
|
||||
{
|
||||
userID: user.id,
|
||||
forImportType: importType,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
accountID: req.body[key],
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
logger.info(`User created ARC integration for ${importType}.`);
|
||||
await db["arc-saved-profiles"].insert({
|
||||
userID: user.id,
|
||||
forImportType: importType,
|
||||
accountID: req.body[key],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [iidx2, ddr2, sdvx2] = await Promise.all([
|
||||
GetArcAuth(user.id, "api/arc-iidx"),
|
||||
GetArcAuth(user.id, "api/arc-ddr"),
|
||||
GetArcAuth(user.id, "api/arc-sdvx"),
|
||||
]);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: `Updated ARC integrations.`,
|
||||
body: {
|
||||
iidx: iidx2,
|
||||
ddr: ddr2,
|
||||
sdvx: sdvx2,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import t from "tap";
|
||||
import expMiddlewareMock from "express-request-mock";
|
||||
import { ValidateKaiType } from "./middleware";
|
||||
|
||||
t.test("#ValidateKaiType", (t) => {
|
||||
const k = async (k: string) => {
|
||||
const { res } = await expMiddlewareMock(ValidateKaiType, { params: { kaiType: k } });
|
||||
|
||||
return res.statusCode;
|
||||
};
|
||||
|
||||
t.test("Should allow flo, eag or min, case insensitively.", async (t) => {
|
||||
t.equal(await k("flo"), 200);
|
||||
t.equal(await k("eag"), 200);
|
||||
t.equal(await k("min"), 200);
|
||||
t.equal(await k("FLO"), 200);
|
||||
t.equal(await k("EAG"), 200);
|
||||
t.equal(await k("MIN"), 200);
|
||||
t.equal(await k("FlO"), 200);
|
||||
t.equal(await k("EaG"), 200);
|
||||
t.equal(await k("MiN"), 200);
|
||||
t.equal(await k("nonsense"), 400);
|
||||
t.equal(await k("bad"), 400);
|
||||
t.equal(await k(""), 400);
|
||||
t.equal(await k("FLO2"), 400);
|
||||
t.equal(await k("2FLO"), 400);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const ValidateKaiType: RequestHandler = (req, res, next) => {
|
||||
if (!["min", "flo", "eag"].includes(req.params.kaiType.toLowerCase())) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: `Invalid kaiType - Expected min, flo or eag.`,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import db from "external/mongo/db";
|
||||
import t from "tap";
|
||||
import { CloseAllConnections } from "test-utils/close-connections";
|
||||
import { CreateFakeAuthCookie } from "test-utils/fake-auth";
|
||||
import mockApi from "test-utils/mock-api";
|
||||
import ResetDBState from "test-utils/resets";
|
||||
|
||||
t.test("GET /api/v1/users/:userID/integrations/kai/:kaiType", async (t) => {
|
||||
t.beforeEach(ResetDBState);
|
||||
|
||||
const cookie = await CreateFakeAuthCookie(mockApi);
|
||||
|
||||
t.test("Should return false if unauthed with this kaiType", async (t) => {
|
||||
const res = await mockApi.get("/api/v1/users/1/integrations/kai/flo").set("Cookie", cookie);
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
t.equal(res.body.body.authStatus, false);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return true if authed with this kaiType", async (t) => {
|
||||
await db["kai-auth-tokens"].insert({
|
||||
refreshToken: "refresh",
|
||||
service: "FLO",
|
||||
token: "bar",
|
||||
userID: 1,
|
||||
});
|
||||
|
||||
const res = await mockApi.get("/api/v1/users/1/integrations/kai/flo").set("Cookie", cookie);
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
t.equal(res.body.body.authStatus, true);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test("Should return the auth status of this user specifically.", async (t) => {
|
||||
await db["kai-auth-tokens"].insert([
|
||||
{
|
||||
refreshToken: "refresh",
|
||||
service: "EAG",
|
||||
token: "bar",
|
||||
userID: 1,
|
||||
},
|
||||
{
|
||||
refreshToken: "refresh",
|
||||
service: "FLO",
|
||||
token: "baz",
|
||||
userID: 2,
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await mockApi.get("/api/v1/users/1/integrations/kai/flo").set("Cookie", cookie);
|
||||
|
||||
t.equal(res.statusCode, 200);
|
||||
t.equal(res.body.body.authStatus, false);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
// test is currently undoable due to issues with mocking out real Fetch calls.
|
||||
t.todo("PATCH /api/v1/userse/:userID/integrations/kai/:kaiType/oauth2callback");
|
||||
|
||||
t.teardown(CloseAllConnections);
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Router } from "express";
|
||||
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 { RequireKamaitachi } from "server/middleware/type-require";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
router.use(RequireKamaitachi, RequireSelfRequestFromUser, ValidateKaiType);
|
||||
|
||||
/**
|
||||
* Return the authentication status for this kaiType.
|
||||
* @note - Express's types infer arg0 of "/" to mean no params, for some reason.
|
||||
* the <any> generic overrides this behaviour.
|
||||
*
|
||||
* @name GET /api/v1/users/:userID/integrations/kai/:kaiType
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
router.get<any>("/", async (req, res) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
const kaiType = req.params.kaiType.toUpperCase() as "FLO" | "EAG" | "MIN";
|
||||
|
||||
const authDoc = await GetKaiAuth(user.id, kaiType);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: authDoc ? `User is authenticated.` : `User is unauthenticated.`,
|
||||
body: {
|
||||
authStatus: !!authDoc,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const KAI_OAUTH2_RETURN_SCHEMA = {
|
||||
access_token: "string",
|
||||
refresh_token: "string",
|
||||
};
|
||||
|
||||
/**
|
||||
* The OAuth2 callback used by Kai to send an intermediate token to.
|
||||
* @note The way this is implemented is *really* weird due to the fact that
|
||||
* the tachi-server code cannot have any knowledge of the tachi-client code,
|
||||
* and the two must be agnostic.
|
||||
*
|
||||
* This means the tachi-client will handle the redirecting, and will check
|
||||
* query params for ?code=12345 to know when to POST us with the code
|
||||
* to perform an update.
|
||||
*
|
||||
* @param code - An intermediate code to use to get the real auth token.
|
||||
*
|
||||
* @name POST /api/v1/users/:userID/integrations/kai/:kaiType/oauth2callback
|
||||
*/
|
||||
router.post(
|
||||
"/oauth2callback",
|
||||
prValidate({ code: "string" }, {}, { allowExcessKeys: true }),
|
||||
async (req, res) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
const kaiType = req.params.kaiType.toUpperCase() as "FLO" | "EAG" | "MIN";
|
||||
|
||||
const baseUrl = KaiTypeToBaseURL(kaiType);
|
||||
|
||||
const maybeCredentials = GetKaiTypeClientCredentials(kaiType);
|
||||
|
||||
if (!maybeCredentials) {
|
||||
logger.severe(
|
||||
`Attempted to /callback ${kaiType}, but this server has no oauth2 credentials configured for that type.`
|
||||
);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `A fatal error has occured, This has been reported.`,
|
||||
});
|
||||
}
|
||||
|
||||
const { CLIENT_SECRET, CLIENT_ID } = maybeCredentials;
|
||||
|
||||
const url = new URL(`${baseUrl}/oauth/token`);
|
||||
|
||||
url.searchParams.append("code", req.body.code);
|
||||
url.searchParams.append("grant_type", "authorization_code");
|
||||
url.searchParams.append("client_secret", CLIENT_SECRET);
|
||||
url.searchParams.append("client_id", CLIENT_ID);
|
||||
url.searchParams.append("redirect_uri", "somewhere?");
|
||||
|
||||
// this also isn't a POST??
|
||||
const getTokenRes = await fetch(url.href);
|
||||
|
||||
if (getTokenRes.status !== 200) {
|
||||
logger.error(
|
||||
`Unexpected status of ${getTokenRes.status} from ${url.href} oauth2 flow.`
|
||||
);
|
||||
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `A fatal error has occured, This has been reported.`,
|
||||
});
|
||||
}
|
||||
|
||||
let json;
|
||||
try {
|
||||
json = await getTokenRes.json();
|
||||
} catch (err) {
|
||||
logger.error(`Error parsing JSON in response body from getTokenRes.`, {
|
||||
res: getTokenRes,
|
||||
err,
|
||||
});
|
||||
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `A fatal error has occured, This has been reported.`,
|
||||
});
|
||||
}
|
||||
|
||||
const err = p(json, KAI_OAUTH2_RETURN_SCHEMA, {}, { allowExcessKeys: true });
|
||||
|
||||
if (err) {
|
||||
logger.error(`Validation error in JSON return from ${url.href}.`, { err });
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `A fatal error has occured, This has been reported.`,
|
||||
});
|
||||
}
|
||||
|
||||
await db["kai-auth-tokens"].update(
|
||||
{
|
||||
userID: user.id,
|
||||
service: kaiType,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
userID: user.id,
|
||||
service: kaiType,
|
||||
refreshToken: json.refresh_token,
|
||||
token: json.access_token,
|
||||
},
|
||||
},
|
||||
{
|
||||
upsert: true,
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Updated Auth for ${kaiType} for user ${FormatUserDoc(user)}.`);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: `Successfully updated auth for ${kaiType}`,
|
||||
body: {},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Router } from "express";
|
||||
import arcRouter from "./arc/router";
|
||||
import kaiKaiTypeRouter from "./kai/_kaiType/router";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
router.use("/arc", arcRouter);
|
||||
router.use("/kai/:kaiType", kaiKaiTypeRouter);
|
||||
|
||||
export default router;
|
||||
@@ -38,6 +38,9 @@ export const GetUserFromParam: RequestHandler = async (req, res, next) => {
|
||||
return next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Require the user making this request to also be the user in the :userID param.
|
||||
*/
|
||||
export const RequireAuthedAsUser: RequestHandler = (req, res, next) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
|
||||
@@ -50,3 +53,21 @@ export const RequireAuthedAsUser: RequestHandler = (req, res, next) => {
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Require that this request is made with a Cookie, instead of any
|
||||
* API key. This is for things that services should not be allowed to
|
||||
* alter/access, like integration information.
|
||||
*/
|
||||
export const RequireSelfRequestFromUser: RequestHandler = (req, res, next) => {
|
||||
const user = req[SYMBOL_TachiData]!.requestedUser!;
|
||||
|
||||
if (!req.session.tachi?.userID || req[SYMBOL_TachiAPIAuth].userID !== user.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
description: `This request cannot be performed by an API key, and requires authentication.`,
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { GetUserFromParam } from "./middleware";
|
||||
import gamePTRouter from "./games/_game/_playtype/router";
|
||||
import bannerRouter from "./banner/router";
|
||||
import pfpRouter from "./pfp/router";
|
||||
import integrationsRouter from "./integrations/router";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@@ -46,5 +47,6 @@ router.get("/game-stats", async (req, res) => {
|
||||
router.use("/games/:game/:playtype", gamePTRouter);
|
||||
router.use("/pfp", pfpRouter);
|
||||
router.use("/banner", bannerRouter);
|
||||
router.use("/integrations", integrationsRouter);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
Reference in New Issue
Block a user