diff --git a/db.ts b/db.ts index 091e9a3..56abd2f 100644 --- a/db.ts +++ b/db.ts @@ -979,6 +979,20 @@ export class EhDb { delete_token(token: string) { this.db.query("DELETE FROM token WHERE token = ?;", [token]); } + delete_user(id: number) { + this.db.query("DELETE FROM user WHERE id = ?;", [id]); + this.db.query("DELETE FROM token WHERE uid = ?;", [id]); + this.db.query("DELETE FROM client_config WHERE uid = ?;", [id]); + } + delete_user_token(uid: number, excluded_token?: number) { + let where = ""; + const args = [uid]; + if (excluded_token) { + where = " AND id != ?"; + args.push(excluded_token); + } + this.db.query(`DELETE FROM token WHERE uid = ?${where};`, args); + } async flock() { if (!this.#file) return; await eval(`Deno.flock(${this.#file.rid}, true);`); @@ -1438,4 +1452,16 @@ export class EhDb { [new Date(), token], ); } + update_user(user: User) { + this.db.query( + "INSERT OR REPLACE INTO user VALUES (?, ?, ?, ?, ?);", + [ + user.id, + user.username, + user.password, + user.is_admin, + user.permissions, + ], + ); + } } diff --git a/routes/api/user.ts b/routes/api/user.ts index eef1f86..df50606 100644 --- a/routes/api/user.ts +++ b/routes/api/user.ts @@ -1,5 +1,5 @@ import { Handlers } from "$fresh/server.ts"; -import { User, UserPermission } from "../../db.ts"; +import { Token, User, UserPermission } from "../../db.ts"; import { get_task_manager } from "../../server.ts"; import { get_string, parse_bool, parse_int } from "../../server/parse_form.ts"; import type { BUser } from "../../server/user.ts"; @@ -7,6 +7,40 @@ import { return_data, return_error } from "../../server/utils.ts"; import pbkdf2Hmac from "pbkdf2-hmac"; export const handler: Handlers = { + async DELETE(req, ctx) { + const user = ctx.state.user; + if (user && !user.is_admin) { + return return_error(403, "Permission denied."); + } + let data: FormData | null = null; + try { + data = await req.formData(); + } catch (_) { + return return_error(3, "Invalid parameters."); + } + const id = await parse_int(data.get("id"), null); + const username = await get_string(data.get("username")); + if (id === null && !username && !user) { + return return_error(1, "user not specified."); + } + const m = get_task_manager(); + const us = id !== null + ? m.db.get_user(id) + : username + ? m.db.get_user_by_name(username) + : user; + if (!us) return return_error(404, "User not found."); + if (us.id == 0) return return_error(6, "root user can not be deleted."); + if (user && us.is_admin && user.id != 0) { + return return_error( + 7, + "Only root user can delete admin user.", + 403, + ); + } + m.db.delete_user(us.id); + return return_data(true); + }, async GET(req, ctx) { const u = new URL(req.url); const id = await parse_int(u.searchParams.get("id"), null); @@ -32,6 +66,89 @@ export const handler: Handlers = { permissions: us.permissions, }); }, + async PATCH(req, ctx) { + const user = ctx.state.user; + if (user && !user.is_admin) { + return return_error(403, "Permission denied."); + } + let data: FormData | null = null; + try { + data = await req.formData(); + } catch (_) { + return return_error(3, "Invalid parameters."); + } + const id = await parse_int(data.get("id"), null); + const username = await get_string(data.get("username")); + if (id === null && !username && !user) { + return return_error(1, "user not specified."); + } + const m = get_task_manager(); + const us = id !== null + ? m.db.get_user(id) + : username + ? m.db.get_user_by_name(username) + : user; + if (!us) return return_error(404, "User not found."); + if (user && us.is_admin && user.id != 0 && user.id != us.id) { + return return_error( + 4, + "Only root user can change other admin user's inforamtion.", + 403, + ); + } + const password = await get_string(data.get("password")); + const is_admin = await parse_bool(data.get("is_admin"), null); + const revoke_token = await parse_bool(data.get("revoke_token"), false); + let permissions: UserPermission | null = await parse_int( + data.get("permissions"), + null, + ); + if (is_admin && !us.is_admin && user && user.id != 0) { + return return_error( + 5, + "Only root user can prompt non-admin user to admin user.", + 403, + ); + } + if (is_admin) permissions = UserPermission.All; + if (username) { + if (us.username != username && m.db.get_user_by_name(username)) { + return return_error(2, "Please change to another name."); + } + us.username = username; + } + if (password) { + const hpassword = new Uint8Array( + await pbkdf2Hmac( + password, + "eh-downloader-salt", + 210000, + 64, + "SHA-512", + ), + ); + us.password = hpassword; + } + if (us.id != 0 && is_admin !== null) { + us.is_admin = is_admin; + } + if (us.id != 0 && permissions !== null) { + us.permissions = permissions; + } + m.db.update_user(us); + if (revoke_token) { + const token = ctx.state.token; + let tid: number | undefined = undefined; + if (user && us.id == user.id && token) tid = token.id; + m.db.delete_user_token(us.id, tid); + } + return return_data({ + id: us.id, + is_admin: us.is_admin, + permissions: us.permissions, + username: us.username, + }); + }, async PUT(req, ctx) { const data = await req.formData(); const user = ctx.state.user; @@ -45,6 +162,9 @@ export const handler: Handlers = { data.get("permissions"), UserPermission.None, ); + if (is_admin && user && user.id != 0) { + return return_error(8, "Only root user can add admin user.", 403); + } if (!name) return return_error(1, "name not specified."); if (!password) return return_error(1, "password not specified."); if (is_admin) permissions = UserPermission.All;