From 0e890794eb9e278ba1f0178bc6f284782faf36d3 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 29 Oct 2024 09:57:22 +0800 Subject: [PATCH] Add api to list tokens --- api.yml | 105 ++++++++++++++++++++++++++++++++++--- db.ts | 34 +++++++++--- deno.json | 6 ++- fresh.gen.ts | 2 +- routes/api/token/manage.ts | 55 +++++++++++++++++-- 5 files changed, 183 insertions(+), 19 deletions(-) diff --git a/api.yml b/api.yml index 62ce87f..5607940 100644 --- a/api.yml +++ b/api.yml @@ -1028,9 +1028,28 @@ components: const: 1 description: ffmpeg BINARY Token: + description: Token information + allOf: + - $ref: "#/components/schemas/TokenWithoutToken" + - type: object + required: [token] + properties: + token: + type: string + description: Token + TokenApiResult: + description: Api response for createToken + allOf: + - $ref: "#/components/schemas/ApiResponse" + - type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Token' + TokenWithoutToken: description: Token information type: object - required: [id, uid, token, expired, http_only, secure, last_used] + required: [id, uid, expired, http_only, secure, last_used] properties: id: type: integer @@ -1038,9 +1057,6 @@ components: uid: type: integer description: User id - token: - type: string - description: Token expired: type: string format: date-time @@ -1067,15 +1083,20 @@ components: client_platform: type: string description: Client platform - TokenApiResult: - description: Api response for createToken + TokenWithoutTokenList: + description: A list of tokens + type: array + items: + $ref: "#/components/schemas/TokenWithoutToken" + TokenWithoutTokenListApiResult: + description: Api response for listTokens allOf: - $ref: "#/components/schemas/ApiResponse" - type: object required: [data] properties: data: - $ref: '#/components/schemas/Token' + $ref: '#/components/schemas/TokenWithoutTokenList' TokenWithUserInfo: description: Token information with user information type: object @@ -3377,6 +3398,76 @@ paths: value: { "ok": false, "status": 404, "error": "User not found." } token: value: { "ok": false, "status": 404, "error": "Token not found." } + get: + operationId: listTokens + summary: List user's tokens + parameters: + - name: uid + in: query + description: User id + schema: + type: integer + - name: offset + in: query + schema: + type: integer + format: int64 + description: Page offset + - name: limit + in: query + schema: + type: integer + description: Page size + - name: all_user + in: query + schema: + type: boolean + default: false + description: Return all users' tokens + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TokenWithoutTokenListApiResult" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponseError" + example: { "ok": false, "status": 2, "error": "User id not specified." } + "401": + description: Authorization information is missing or invalid + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponseError" + example: { "ok": false, "status": 401, "error": "Unauthorized" } + "403": + description: Permission denied + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponseError" + examples: + admin: + description: Requires administrator privileges. + value: { "ok": false, "status": 403, "error": "Permission denied." } + root: + description: Requires root user. + value: { "ok": false, "status": 3, "error": "Only root user can delete admin user's token." } + all_user: + description: Requires root user. + value: { "ok": false, "status": 4, "error": "Only root user can get all user's tokens." } + "404": + description: Not found + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponseError" + example: { "ok": false, "status": 404, "error": "User not found." } /user: delete: operationId: deleteUser diff --git a/db.ts b/db.ts index f6e2359..c7ac761 100644 --- a/db.ts +++ b/db.ts @@ -346,7 +346,6 @@ function escape_fields(fields: string, namespace: string) { export class EhDb { // @ts-ignore Ignore db: Db; - #flock_enabled: boolean = eval('typeof Deno.flock !== "undefined"'); #file: Deno.FsFile | undefined; #dblock: Deno.FsFile | undefined; #exist_table: Set = new Set(); @@ -372,7 +371,7 @@ export class EhDb { this.db = new DB(this.#db_path); } if (!this.#check_database()) this.#create_table(); - if (!this.#use_ffi && this.#flock_enabled) { + if (!this.#use_ffi) { this.#lock_file = join(this.#base_path, "db.lock"); this.#dblock_file = join(this.#base_path, "eh.locked"); this.#file = Deno.openSync(this.#lock_file, { @@ -384,11 +383,6 @@ export class EhDb { write: true, }); this.dblock(); - } else if (!this.#use_ffi) { - console.log( - "%cFile locking is disabled. Use --unstable to enable file locking.", - "color: yellow;", - ); } this.remove_expired_token(); } @@ -1410,6 +1404,32 @@ export class EhDb { ); return s.length ? s[0] : undefined; } + get_tokens( + offset: number | bigint | null = 0, + limit: number | bigint | null = 20, + uid: number | bigint | null = null, + ) { + let sql = ""; + const args = []; + if (uid !== null) { + sql = " WHERE uid = ?"; + args.push(uid); + } + if (limit !== null) { + sql += " LIMIT ?"; + args.push(limit); + } + if (offset !== null) { + sql += " OFFSET ?"; + args.push(offset); + } + return this.convert_token( + this.db.queryEntries( + `SELECT * FROM token${sql};`, + args, + ), + ); + } get_user(id: number | bigint) { const s = this.convert_user( this.db.queryEntries( diff --git a/deno.json b/deno.json index c6f7e56..a174be4 100644 --- a/deno.json +++ b/deno.json @@ -21,7 +21,11 @@ "static/sw.js", "static/sw.meta.json", "_fresh", - "extensions/build" + "extensions/build", + "api.yml", + "docker-compose.yml", + ".github/workflows/deno.yml", + ".github/workflows/docker.yaml" ] }, "compilerOptions": { diff --git a/fresh.gen.ts b/fresh.gen.ts index 0ce30fd..cbca142 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -47,7 +47,7 @@ import * as $thumbnail_verify_id_ from "./routes/thumbnail/[verify]/[id].ts"; import * as $thumbnail_middleware from "./routes/thumbnail/_middleware.ts"; import * as $upload from "./routes/upload.tsx"; import * as $Upload from "./islands/Upload.tsx"; -import { type Manifest } from "$fresh/server.ts"; +import type { Manifest } from "$fresh/server.ts"; const manifest = { routes: { diff --git a/routes/api/token/manage.ts b/routes/api/token/manage.ts index 01eea6f..e944fad 100644 --- a/routes/api/token/manage.ts +++ b/routes/api/token/manage.ts @@ -1,7 +1,7 @@ import { Handlers } from "$fresh/server.ts"; import { User } from "../../../db.ts"; import { get_task_manager } from "../../../server.ts"; -import { parse_big_int } from "../../../server/parse_form.ts"; +import { parse_big_int, parse_bool } from "../../../server/parse_form.ts"; import { return_data, return_error } from "../../../server/utils.ts"; export const handler: Handlers = { @@ -32,11 +32,60 @@ export const handler: Handlers = { return return_error(404, "User not found."); } if (u.is_admin) { - return return_error(3, "Only root user can delete admin user's token.", 403); + return return_error( + 3, + "Only root user can delete admin user's token.", + 403, + ); } } } m.db.delete_token(token.token); return return_data(true); }, -} + async GET(req, ctx) { + const user = ctx.state.user; + const data = new URL(req.url).searchParams; + let uid = await parse_big_int(data.get("uid"), null); + const offset = await parse_big_int(data.get("offset"), null); + const limit = await parse_big_int(data.get("limit"), null); + const all_user = await parse_bool(data.get("all_user"), false); + const m = get_task_manager(); + if (user) { + if (!all_user && uid === null) uid = user.id; + if (!user.is_admin && uid && user.id != uid) { + return return_error(403, "Permission denied."); + } + if (user.is_admin && uid && user.id != uid && user.id != 0) { + const u = m.db.get_user(uid); + if (!u) { + return return_error(404, "User not found."); + } + if (u.is_admin) { + return return_error( + 3, + "Only root user can get admin user's tokens.", + 403, + ); + } + } + if (user.id != 0 && uid === null) { + return return_error( + 4, + "Only root user can get all user's tokens.", + 403, + ); + } + } + if (!all_user && uid === null) { + return return_error(2, "User id not specified."); + } + return return_data( + m.db.get_tokens(offset, limit, uid).map((e) => { + // @ts-ignore Ignore + delete e.token; + return e; + }), + ); + }, +};