Add api to list tokens

This commit is contained in:
2024-10-29 09:57:22 +08:00
parent 26aa3351f1
commit 0e890794eb
5 changed files with 183 additions and 19 deletions

105
api.yml
View File

@@ -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

34
db.ts
View File

@@ -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<string> = 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<TokenRaw>(
`SELECT * FROM token${sql};`,
args,
),
);
}
get_user(id: number | bigint) {
const s = this.convert_user(
this.db.queryEntries<UserRaw>(

View File

@@ -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": {

View File

@@ -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: {

View File

@@ -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 = <User | undefined> 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;
}),
);
},
};