From 2d6c4ef5d3b67223ccffdd9bb8575f80481503e2 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Tue, 12 Dec 2023 23:47:21 +0100 Subject: [PATCH] [backend/web-api] Add endpoint security policy, response & request body types --- .pnp.cjs | 58 +++++++++++++++--- ...penapi-npm-2.7.0-37778d7452-21685db4ea.zip | 3 - ...penapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip | 3 + ...erator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip | 3 + ...chema-npm-7.0.15-fd16381786-1a3c3e0623.zip | 3 + ...ander-npm-11.1.0-56e979613c-66bd2d8a05.zip | 3 + ...ingify-npm-2.4.3-d895741b40-a6c192bbef.zip | 3 + packages/backend/package.json | 5 +- .../src/server/api/web/controllers/auth.ts | 23 ++++++-- .../src/server/api/web/controllers/note.ts | 7 ++- .../server/api/web/controllers/timeline.ts | 17 +++++- .../src/server/api/web/controllers/user.ts | 12 ++-- .../src/server/api/web/entities/error.ts | 6 ++ packages/backend/src/server/api/web/index.ts | 25 +++++++- .../backend/src/server/api/web/misc/schema.ts | 20 +++++++ yarn.lock | 59 ++++++++++++++++--- 16 files changed, 211 insertions(+), 39 deletions(-) delete mode 100644 .yarn/cache/@iceshrimp-koa-openapi-npm-2.7.0-37778d7452-21685db4ea.zip create mode 100644 .yarn/cache/@iceshrimp-koa-openapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip create mode 100644 .yarn/cache/@iceshrimp-ts-json-schema-generator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip create mode 100644 .yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-1a3c3e0623.zip create mode 100644 .yarn/cache/commander-npm-11.1.0-56e979613c-66bd2d8a05.zip create mode 100644 .yarn/cache/safe-stable-stringify-npm-2.4.3-d895741b40-a6c192bbef.zip create mode 100644 packages/backend/src/server/api/web/entities/error.ts create mode 100644 packages/backend/src/server/api/web/misc/schema.ts diff --git a/.pnp.cjs b/.pnp.cjs index a950f8b20..aec512e70 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -1898,10 +1898,10 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@iceshrimp/koa-openapi", [\ - ["npm:2.7.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.7.0%2Fkoa-openapi-2.7.0.tgz", {\ - "packageLocation": "./.yarn/cache/@iceshrimp-koa-openapi-npm-2.7.0-37778d7452-21685db4ea.zip/node_modules/@iceshrimp/koa-openapi/",\ + ["npm:3.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F3.2.0%2Fkoa-openapi-3.2.0.tgz", {\ + "packageLocation": "./.yarn/cache/@iceshrimp-koa-openapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip/node_modules/@iceshrimp/koa-openapi/",\ "packageDependencies": [\ - ["@iceshrimp/koa-openapi", "npm:2.7.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.7.0%2Fkoa-openapi-2.7.0.tgz"],\ + ["@iceshrimp/koa-openapi", "npm:3.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F3.2.0%2Fkoa-openapi-3.2.0.tgz"],\ ["@hapi/boom", "npm:10.0.1"],\ ["@koa/cors", "npm:4.0.0"],\ ["@koa/router", "npm:12.0.1"],\ @@ -1911,7 +1911,7 @@ const RAW_RUNTIME_STATE = ["koa", "npm:2.14.2"],\ ["koa-body", "npm:6.0.1"],\ ["koa-helmet", "npm:7.0.2"],\ - ["koa2-swagger-ui", "virtual:37778d7452aa22a4b9b696d3401d761eb63cf07fe0900455c93a17ab008fd0bea8cb487e82bff1386539d52523936a6d87cd46d4e8395c6f97dee7afea734531#npm:5.10.0"],\ + ["koa2-swagger-ui", "virtual:d2c290057b75f80fd76a22ec676ab3d63a5dabf80ff85b75aaf6bde56eebfb4dfa277221c743544b6d668bc070c759965f7368b428fc6cbd6ad12069d18f1396#npm:5.10.0"],\ ["lodash", "npm:4.17.21"],\ ["openapi-types", "npm:12.1.3"],\ ["reflect-metadata", "npm:0.1.13"]\ @@ -1919,6 +1919,22 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@iceshrimp/ts-json-schema-generator", [\ + ["npm:1.0.1::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fts-json-schema-generator%2F-%2F1.0.1%2Fts-json-schema-generator-1.0.1.tgz", {\ + "packageLocation": "./.yarn/cache/@iceshrimp-ts-json-schema-generator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip/node_modules/@iceshrimp/ts-json-schema-generator/",\ + "packageDependencies": [\ + ["@iceshrimp/ts-json-schema-generator", "npm:1.0.1::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fts-json-schema-generator%2F-%2F1.0.1%2Fts-json-schema-generator-1.0.1.tgz"],\ + ["@types/json-schema", "npm:7.0.15"],\ + ["commander", "npm:11.1.0"],\ + ["glob", "npm:8.1.0"],\ + ["json5", "npm:2.2.3"],\ + ["normalize-path", "npm:3.0.0"],\ + ["safe-stable-stringify", "npm:2.4.3"],\ + ["typescript", "patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@ioredis/commands", [\ ["npm:1.2.0", {\ "packageLocation": "./.yarn/cache/@ioredis-commands-npm-1.2.0-47541de88b-a8253c9539.zip/node_modules/@ioredis/commands/",\ @@ -4147,6 +4163,13 @@ const RAW_RUNTIME_STATE = ["@types/json-schema", "npm:7.0.12"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.0.15", {\ + "packageLocation": "./.yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-1a3c3e0623.zip/node_modules/@types/json-schema/",\ + "packageDependencies": [\ + ["@types/json-schema", "npm:7.0.15"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@types/json5", [\ @@ -7217,7 +7240,8 @@ const RAW_RUNTIME_STATE = ["@bull-board/ui", "npm:5.6.0"],\ ["@discordapp/twemoji", "npm:14.1.2"],\ ["@hapi/boom", "npm:10.0.1"],\ - ["@iceshrimp/koa-openapi", "npm:2.7.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.7.0%2Fkoa-openapi-2.7.0.tgz"],\ + ["@iceshrimp/koa-openapi", "npm:3.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F3.2.0%2Fkoa-openapi-3.2.0.tgz"],\ + ["@iceshrimp/ts-json-schema-generator", "npm:1.0.1::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fts-json-schema-generator%2F-%2F1.0.1%2Fts-json-schema-generator-1.0.1.tgz"],\ ["@koa/cors", "npm:3.4.3"],\ ["@koa/multer", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:3.0.2"],\ ["@koa/router", "npm:9.0.1"],\ @@ -7238,6 +7262,7 @@ const RAW_RUNTIME_STATE = ["@types/formidable", "npm:2.0.6"],\ ["@types/js-yaml", "npm:4.0.5"],\ ["@types/jsdom", "npm:21.1.1"],\ + ["@types/json-schema", "npm:7.0.15"],\ ["@types/jsonld", "npm:1.5.9"],\ ["@types/jsrsasign", "npm:10.5.8"],\ ["@types/koa", "npm:2.13.7"],\ @@ -7344,6 +7369,7 @@ const RAW_RUNTIME_STATE = ["node-fetch", "npm:3.3.1"],\ ["nodemailer", "npm:6.9.3"],\ ["oauth", "npm:0.10.0"],\ + ["openapi-types", "npm:12.1.3"],\ ["os-utils", "npm:0.0.14"],\ ["otpauth", "npm:9.1.4"],\ ["parse-duration", "npm:1.1.0"],\ @@ -9067,6 +9093,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["npm:11.1.0", {\ + "packageLocation": "./.yarn/cache/commander-npm-11.1.0-56e979613c-66bd2d8a05.zip/node_modules/commander/",\ + "packageDependencies": [\ + ["commander", "npm:11.1.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:2.20.3", {\ "packageLocation": "./.yarn/cache/commander-npm-2.20.3-d8dcbaa39b-90c5b68986.zip/node_modules/commander/",\ "packageDependencies": [\ @@ -17145,10 +17178,10 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ - ["virtual:37778d7452aa22a4b9b696d3401d761eb63cf07fe0900455c93a17ab008fd0bea8cb487e82bff1386539d52523936a6d87cd46d4e8395c6f97dee7afea734531#npm:5.10.0", {\ - "packageLocation": "./.yarn/__virtual__/koa2-swagger-ui-virtual-c4b42f8a3a/0/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip/node_modules/koa2-swagger-ui/",\ + ["virtual:d2c290057b75f80fd76a22ec676ab3d63a5dabf80ff85b75aaf6bde56eebfb4dfa277221c743544b6d668bc070c759965f7368b428fc6cbd6ad12069d18f1396#npm:5.10.0", {\ + "packageLocation": "./.yarn/__virtual__/koa2-swagger-ui-virtual-285d8b91f4/0/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip/node_modules/koa2-swagger-ui/",\ "packageDependencies": [\ - ["koa2-swagger-ui", "virtual:37778d7452aa22a4b9b696d3401d761eb63cf07fe0900455c93a17ab008fd0bea8cb487e82bff1386539d52523936a6d87cd46d4e8395c6f97dee7afea734531#npm:5.10.0"],\ + ["koa2-swagger-ui", "virtual:d2c290057b75f80fd76a22ec676ab3d63a5dabf80ff85b75aaf6bde56eebfb4dfa277221c743544b6d668bc070c759965f7368b428fc6cbd6ad12069d18f1396#npm:5.10.0"],\ ["@types/koa", null],\ ["handlebars", "npm:4.7.8"],\ ["lodash", "npm:4.17.21"],\ @@ -22153,6 +22186,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["safe-stable-stringify", [\ + ["npm:2.4.3", {\ + "packageLocation": "./.yarn/cache/safe-stable-stringify-npm-2.4.3-d895741b40-a6c192bbef.zip/node_modules/safe-stable-stringify/",\ + "packageDependencies": [\ + ["safe-stable-stringify", "npm:2.4.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["safer-buffer", [\ ["npm:2.1.2", {\ "packageLocation": "./.yarn/cache/safer-buffer-npm-2.1.2-8d5c0b705e-7eaf7a0cf3.zip/node_modules/safer-buffer/",\ diff --git a/.yarn/cache/@iceshrimp-koa-openapi-npm-2.7.0-37778d7452-21685db4ea.zip b/.yarn/cache/@iceshrimp-koa-openapi-npm-2.7.0-37778d7452-21685db4ea.zip deleted file mode 100644 index 306386968..000000000 --- a/.yarn/cache/@iceshrimp-koa-openapi-npm-2.7.0-37778d7452-21685db4ea.zip +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3406d33f6247cfd9a25ede089dcacde3982d5f8947c6265d1e4b16c4dc6257e8 -size 103526 diff --git a/.yarn/cache/@iceshrimp-koa-openapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip b/.yarn/cache/@iceshrimp-koa-openapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip new file mode 100644 index 000000000..a75764c96 --- /dev/null +++ b/.yarn/cache/@iceshrimp-koa-openapi-npm-3.2.0-d2c290057b-5ec6ea58be.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c9bce6b74d29dd10ac03c8dddb67e5415afb6801a8a9d262f89868b4e0910ba +size 106725 diff --git a/.yarn/cache/@iceshrimp-ts-json-schema-generator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip b/.yarn/cache/@iceshrimp-ts-json-schema-generator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip new file mode 100644 index 000000000..808d74c47 --- /dev/null +++ b/.yarn/cache/@iceshrimp-ts-json-schema-generator-npm-1.0.1-fe26c7afc1-dbac3ba178.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05b5d40eb5c5b78bc11a3387645ae87fd00398643d88ec1505d40c66208225df +size 487043 diff --git a/.yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-1a3c3e0623.zip b/.yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-1a3c3e0623.zip new file mode 100644 index 000000000..e6b603f80 --- /dev/null +++ b/.yarn/cache/@types-json-schema-npm-7.0.15-fd16381786-1a3c3e0623.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b24195bad73274f4cbfb09460be0eacca689dbeb6dbb7a53cab91e5d344146d2 +size 6988 diff --git a/.yarn/cache/commander-npm-11.1.0-56e979613c-66bd2d8a05.zip b/.yarn/cache/commander-npm-11.1.0-56e979613c-66bd2d8a05.zip new file mode 100644 index 000000000..880759041 --- /dev/null +++ b/.yarn/cache/commander-npm-11.1.0-56e979613c-66bd2d8a05.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f01f352e10f943d88b05ea60f041ac84d06d5b0264629928630d2bf66ec6324 +size 51097 diff --git a/.yarn/cache/safe-stable-stringify-npm-2.4.3-d895741b40-a6c192bbef.zip b/.yarn/cache/safe-stable-stringify-npm-2.4.3-d895741b40-a6c192bbef.zip new file mode 100644 index 000000000..acdb6d565 --- /dev/null +++ b/.yarn/cache/safe-stable-stringify-npm-2.4.3-d895741b40-a6c192bbef.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce0aa5a818c1e9777cdc22bde5ce2e055885232c1c27720cad6a7ba3014726e5 +size 9052 diff --git a/packages/backend/package.json b/packages/backend/package.json index 4f3b029dd..edf8604b7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -28,7 +28,7 @@ "@bull-board/ui": "5.6.0", "@discordapp/twemoji": "14.1.2", "@hapi/boom": "^10.0.1", - "@iceshrimp/koa-openapi": "^2.7.0", + "@iceshrimp/koa-openapi": "^3.2.0", "@koa/cors": "3.4.3", "@koa/multer": "3.0.2", "@koa/router": "9.0.1", @@ -144,6 +144,7 @@ "xev": "3.0.2" }, "devDependencies": { + "@iceshrimp/ts-json-schema-generator": "^1.0.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.68", "@types/adm-zip": "^0.5.0", @@ -154,6 +155,7 @@ "@types/fluent-ffmpeg": "2.1.21", "@types/js-yaml": "4.0.5", "@types/jsdom": "21.1.1", + "@types/json-schema": "^7.0.15", "@types/jsonld": "1.5.9", "@types/jsrsasign": "10.5.8", "@types/koa": "2.13.7", @@ -197,6 +199,7 @@ "execa": "6.1.0", "json5-loader": "4.0.1", "mocha": "10.2.0", + "openapi-types": "^12.1.3", "pug": "3.0.2", "strict-event-emitter-types": "2.0.0", "swc-loader": "^0.2.3", diff --git a/packages/backend/src/server/api/web/controllers/auth.ts b/packages/backend/src/server/api/web/controllers/auth.ts index cc51c7b5d..d6ac7594e 100644 --- a/packages/backend/src/server/api/web/controllers/auth.ts +++ b/packages/backend/src/server/api/web/controllers/auth.ts @@ -1,4 +1,15 @@ -import { Controller, Get, Post, Body, CurrentUser, Flow, Description, Returns } from "@iceshrimp/koa-openapi"; +import { + Controller, + Get, + Post, + Body, + CurrentUser, + Flow, + Description, + Returns, + Security, + Requests +} from "@iceshrimp/koa-openapi"; import type { ILocalUser } from "@/models/entities/user.js"; import type { AuthRequest, AuthResponse } from "@/server/api/web/entities/auth.js"; import type { Session } from "@/models/entities/session.js"; @@ -9,8 +20,9 @@ import { AuthHandler } from "@/server/api/web/handlers/auth.js"; @Controller('/auth') export class AuthController { @Get('/') + @Security("user") @Description("Get the authentication status") - @Returns(200, "Successful response") + @Returns(200, "AuthResponse", "Successful response") async getAuthStatus( @CurrentUser() me: ILocalUser | null, @CurrentSession() session: Session | null, @@ -21,9 +33,10 @@ export class AuthController { @Post('/') @Flow([RatelimitRouteMiddleware("auth", 10, 60000, true)]) @Description("Log in as a user and receive a auth token on success") - @Returns(200, "Successful response") - @Returns(400, "Request body is missing or invalid") - @Returns(401, "Specified username or password are invalid") + @Requests("AuthRequest", "application/json") + @Returns(200, "AuthResponse", "Successful response") + @Returns(400, "ErrorResponse", "Request body is missing or invalid") + @Returns(401, "ErrorResponse", "Specified username or password are invalid") async login(@Body({ required: true }) request: AuthRequest): Promise { return AuthHandler.login(request); } diff --git a/packages/backend/src/server/api/web/controllers/note.ts b/packages/backend/src/server/api/web/controllers/note.ts index 73f5ad41f..ba4a5905e 100644 --- a/packages/backend/src/server/api/web/controllers/note.ts +++ b/packages/backend/src/server/api/web/controllers/note.ts @@ -1,4 +1,4 @@ -import { Controller, Get, CurrentUser, Params, Description, Returns } from "@iceshrimp/koa-openapi"; +import { Controller, Get, CurrentUser, Params, Description, Returns, Security } from "@iceshrimp/koa-openapi"; import type { ILocalUser } from "@/models/entities/user.js"; import { NoteHandler } from "@/server/api/web/handlers/note.js"; import { NoteResponse } from "@/server/api/web/entities/note.js"; @@ -7,9 +7,10 @@ import { notFound } from "@hapi/boom"; @Controller('/note') export class NoteController { @Get('/:id') + @Security("user") @Description("Returns the specified note") - @Returns(200, "Successful response") - @Returns(404, "The specified note either doesn't exist or is not visible for the authenticated user (if any)") + @Returns(200, "NoteResponse", "Successful response") + @Returns(404, "ErrorResponse", "The specified note either doesn't exist or is not visible for the authenticated user (if any)") async getNote( @CurrentUser() me: ILocalUser | null, @Params('id') id: string, diff --git a/packages/backend/src/server/api/web/controllers/timeline.ts b/packages/backend/src/server/api/web/controllers/timeline.ts index 1714e2f24..4f298d891 100644 --- a/packages/backend/src/server/api/web/controllers/timeline.ts +++ b/packages/backend/src/server/api/web/controllers/timeline.ts @@ -1,4 +1,14 @@ -import { Controller, CurrentUser, Description, Flow, Get, Params, Query, Returns } from "@iceshrimp/koa-openapi"; +import { + Controller, + CurrentUser, + Description, + Flow, + Get, + Params, + Query, + Returns, + Security +} from "@iceshrimp/koa-openapi"; import { UserResponse } from "@/server/api/web/entities/user.js"; import { TimelineResponse } from "@/server/api/web/entities/note.js"; import type { ILocalUser } from "@/models/entities/user.js"; @@ -10,9 +20,10 @@ import { AuthorizationMiddleware } from "@/server/api/web/middleware/auth.js"; export class TimelineController { @Get('/home') @Flow([AuthorizationMiddleware()]) + @Security("user") @Description("Get the home timeline") - @Returns(200, "Successful response") - @Returns(401, "Authorization header is missing or invalid") + @Returns(200, "TimelineResponse", "Successful response") + @Returns(401, "ErrorResponse", "Authorization header is missing or invalid") async getHomeTimeline( @CurrentUser() me: ILocalUser, @Query('replies') replies: boolean = true, diff --git a/packages/backend/src/server/api/web/controllers/user.ts b/packages/backend/src/server/api/web/controllers/user.ts index 985f6ad00..12c383693 100644 --- a/packages/backend/src/server/api/web/controllers/user.ts +++ b/packages/backend/src/server/api/web/controllers/user.ts @@ -1,4 +1,4 @@ -import { Controller, CurrentUser, Description, Get, Params, Query, Returns } from "@iceshrimp/koa-openapi"; +import { Controller, CurrentUser, Description, Get, Params, Query, Returns, Security } from "@iceshrimp/koa-openapi"; import { UserResponse } from "@/server/api/web/entities/user.js"; import { TimelineResponse } from "@/server/api/web/entities/note.js"; import type { ILocalUser } from "@/models/entities/user.js"; @@ -7,9 +7,10 @@ import { UserHandler } from "@/server/api/web/handlers/user.js"; @Controller('/user') export class UserController { @Get('/:id') + @Security("user") @Description("Returns information on the specified user") - @Returns(200, "Successful response") - @Returns(404, "The specified user does not exist") + @Returns(200, "UserResponse", "Successful response") + @Returns(404, "ErrorResponse", "The specified user does not exist") async getUser( @CurrentUser() me: ILocalUser | null, @Params('id') id: string, @@ -19,9 +20,10 @@ export class UserController { } @Get('/:id/notes') + @Security("user") @Description("Get the specified user's notes") - @Returns(200, "Successful response") - @Returns(404, "The specified user does not exist") + @Returns(200, "TimelineResponse", "Successful response") + @Returns(404, "ErrorResponse", "The specified user does not exist") async getUserNotes( @CurrentUser() me: ILocalUser | null, @Params('id') id: string, diff --git a/packages/backend/src/server/api/web/entities/error.ts b/packages/backend/src/server/api/web/entities/error.ts new file mode 100644 index 000000000..0cd867a85 --- /dev/null +++ b/packages/backend/src/server/api/web/entities/error.ts @@ -0,0 +1,6 @@ +export type ErrorResponse = { + statusCode: number; + error: string; + message: string; + errorDetails: string | null; +} diff --git a/packages/backend/src/server/api/web/index.ts b/packages/backend/src/server/api/web/index.ts index 083fafad5..f1a7ea626 100644 --- a/packages/backend/src/server/api/web/index.ts +++ b/packages/backend/src/server/api/web/index.ts @@ -9,6 +9,8 @@ import { AuthController } from "@/server/api/web/controllers/auth.js"; import { NoteController } from "@/server/api/web/controllers/note.js"; import { WebContext, WebRouter } from "@/server/api/web/misc/koa.js"; import { TimelineController } from "@/server/api/web/controllers/timeline.js"; +import { genSchema } from "@/server/api/web/misc/schema.js"; +import { OpenAPIV3_1 } from "openapi-types"; export class WebAPI { private readonly router: WebRouter; @@ -18,7 +20,7 @@ export class WebAPI { } public async setup(app: Koa): Promise { - await bootstrapControllers({ + await bootstrapControllers({ app: app, router: this.router, attachRoutes: true, @@ -43,13 +45,32 @@ export class WebAPI { enabled: true, publicURL: '/api/iceshrimp', options: { - title: "Iceshrimp Web API documentation" + title: "Iceshrimp Web API documentation", + swaggerOptions: { + urls: [{ url: "/api/iceshrimp/openapi.json", name: "/api/iceshrimp/openapi.json" }], + defaultModelsExpandDepth: "2", + defaultModelExpandDepth: "2", + persistAuthorization: "true", + docExpansion: "none", + }, + favicon: '/favicon.ico' }, spec: { info: { title: "Iceshrimp Web API", description: "Documentation for using Iceshrimp's Web API", version: "1.0.0" + }, + }, + schemas: genSchema().definitions as Record, + securitySchemes: { + "user": { + type: 'http', + scheme: 'bearer' + }, + "admin": { + type: 'http', + scheme: 'bearer' } } }, diff --git a/packages/backend/src/server/api/web/misc/schema.ts b/packages/backend/src/server/api/web/misc/schema.ts new file mode 100644 index 000000000..299966c1e --- /dev/null +++ b/packages/backend/src/server/api/web/misc/schema.ts @@ -0,0 +1,20 @@ +import { fileURLToPath } from "node:url"; +import { dirname } from "node:path"; +import { createGenerator } from "@iceshrimp/ts-json-schema-generator"; + +export function genSchema() { + const _filename = fileURLToPath(import.meta.url); + const _dirname = dirname(_filename); + + const config = { + path: `${_dirname}/../../../../../src/server/api/web/entities/*.ts`, + tsconfig: `${_dirname}/../../../../../tsconfig.json`, + skipTypeCheck: true, + discriminatorType: 'open-api' as const, + }; + + const pre = new Date().getTime(); + const schema = createGenerator(config).createSchema('*'); + console.log(`Generated JSON Schema in ${new Date().getTime() - pre}ms`); + return schema; +} diff --git a/yarn.lock b/yarn.lock index 85f224fd4..949f7eb16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1204,9 +1204,9 @@ __metadata: languageName: node linkType: hard -"@iceshrimp/koa-openapi@npm:^2.7.0": - version: 2.7.0 - resolution: "@iceshrimp/koa-openapi@npm:2.7.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.7.0%2Fkoa-openapi-2.7.0.tgz" +"@iceshrimp/koa-openapi@npm:^3.2.0": + version: 3.2.0 + resolution: "@iceshrimp/koa-openapi@npm:3.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F3.2.0%2Fkoa-openapi-3.2.0.tgz" dependencies: "@hapi/boom": "npm:^10.0.1" "@koa/cors": "npm:^4.0.0" @@ -1221,7 +1221,24 @@ __metadata: lodash: "npm:^4.17.21" openapi-types: "npm:^12.1.3" reflect-metadata: "npm:*" - checksum: 21685db4ea05494b885461bddd40312707106cdbf11e68d167136e827fd8b27fc709c0933b81e51432711d7eafc1f47fc03c909d977e38f3a98a8f700d9715ed + checksum: 5ec6ea58be8f06c65147232c3fac83a7cac4d8add7b39bfdca6b50c523ca307c13e4d533caebeadf0107a79ad555ebe906a52331900d6abd8fabe04c6f5e5690 + languageName: node + linkType: hard + +"@iceshrimp/ts-json-schema-generator@npm:^1.0.0": + version: 1.0.1 + resolution: "@iceshrimp/ts-json-schema-generator@npm:1.0.1::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fts-json-schema-generator%2F-%2F1.0.1%2Fts-json-schema-generator-1.0.1.tgz" + dependencies: + "@types/json-schema": "npm:^7.0.15" + commander: "npm:^11.1.0" + glob: "npm:^8.0.3" + json5: "npm:^2.2.3" + normalize-path: "npm:^3.0.0" + safe-stable-stringify: "npm:^2.4.3" + typescript: "npm:~5.3.3" + bin: + ts-json-schema-generator: bin/ts-json-schema-generator + checksum: dbac3ba1787a15f45b1b6371e37ba5058cc6cb5b408804806d06e3abdf5c7ee505f9d617b9749289e86667cabae73142d491ebb801a15ff526887d6a2e7b079a languageName: node linkType: hard @@ -3048,6 +3065,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -5414,7 +5438,8 @@ __metadata: "@bull-board/ui": "npm:5.6.0" "@discordapp/twemoji": "npm:14.1.2" "@hapi/boom": "npm:^10.0.1" - "@iceshrimp/koa-openapi": "npm:^2.7.0" + "@iceshrimp/koa-openapi": "npm:^3.2.0" + "@iceshrimp/ts-json-schema-generator": "npm:^1.0.0" "@koa/cors": "npm:3.4.3" "@koa/multer": "npm:3.0.2" "@koa/router": "npm:9.0.1" @@ -5435,6 +5460,7 @@ __metadata: "@types/formidable": "npm:^2.0.5" "@types/js-yaml": "npm:4.0.5" "@types/jsdom": "npm:21.1.1" + "@types/json-schema": "npm:^7.0.15" "@types/jsonld": "npm:1.5.9" "@types/jsrsasign": "npm:10.5.8" "@types/koa": "npm:2.13.7" @@ -5541,6 +5567,7 @@ __metadata: node-fetch: "npm:3.3.1" nodemailer: "npm:6.9.3" oauth: "npm:^0.10.0" + openapi-types: "npm:^12.1.3" os-utils: "npm:0.0.14" otpauth: "npm:^9.1.3" parse-duration: "npm:^1.1.0" @@ -7073,6 +7100,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^11.1.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: 66bd2d8a0547f6cb1d34022efb25f348e433b0e04ad76a65279b1b09da108f59a4d3001ca539c60a7a46ea38bcf399fc17d91adad76a8cf43845d8dcbaf5cda1 + languageName: node + linkType: hard + "commander@npm:^2.20.0, commander@npm:^2.8.1": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -10704,7 +10738,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.1.0": +"glob@npm:^8.0.3, glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -13529,7 +13563,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:2.2.3, json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.2": +"json5@npm:2.2.3, json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.1.3, json5@npm:^2.2.2, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -18560,6 +18594,13 @@ __metadata: languageName: node linkType: hard +"safe-stable-stringify@npm:^2.4.3": + version: 2.4.3 + resolution: "safe-stable-stringify@npm:2.4.3" + checksum: a6c192bbefe47770a11072b51b500ed29be7b1c15095371c1ee1dc13e45ce48ee3c80330214c56764d006c485b88bd0b24940d868948170dddc16eed312582d8 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:^2.1.2, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -20768,7 +20809,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.1.6, typescript@npm:^5.2.2": +"typescript@npm:^5.1.6, typescript@npm:^5.2.2, typescript@npm:~5.3.3": version: 5.3.3 resolution: "typescript@npm:5.3.3" bin: @@ -20788,7 +20829,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.1.6#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": +"typescript@patch:typescript@npm%3A^5.1.6#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A~5.3.3#optional!builtin": version: 5.3.3 resolution: "typescript@patch:typescript@npm%3A5.3.3#optional!builtin::version=5.3.3&hash=e012d7" bin: