diff --git a/fresh.gen.ts b/fresh.gen.ts index 52a4977..76ad339 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -29,6 +29,8 @@ import * as $api_task_export_zip_cfg from "./routes/api/task/export_zip_cfg.ts"; import * as $api_thumbnail_id_ from "./routes/api/thumbnail/[id].ts"; import * as $api_token from "./routes/api/token.ts"; import * as $api_user from "./routes/api/user.ts"; +import * as $api_user_change_name from "./routes/api/user/change_name.ts"; +import * as $api_user_change_password from "./routes/api/user/change_password.ts"; import * as $api_user_list from "./routes/api/user/list.ts"; import * as $file_id_ from "./routes/file/[id].ts"; import * as $file_verify_id_ from "./routes/file/[verify]/[id].ts"; @@ -70,6 +72,8 @@ const manifest = { "./routes/api/thumbnail/[id].ts": $api_thumbnail_id_, "./routes/api/token.ts": $api_token, "./routes/api/user.ts": $api_user, + "./routes/api/user/change_name.ts": $api_user_change_name, + "./routes/api/user/change_password.ts": $api_user_change_password, "./routes/api/user/list.ts": $api_user_list, "./routes/file/[id].ts": $file_id_, "./routes/file/[verify]/[id].ts": $file_verify_id_, diff --git a/routes/api/token.ts b/routes/api/token.ts index e35143e..dae6e46 100644 --- a/routes/api/token.ts +++ b/routes/api/token.ts @@ -35,8 +35,8 @@ class TimestampCache { } } -const timestamp_cache = new TimestampCache(); -const cache_mutex = new Mutex(); +export const timestamp_cache = new TimestampCache(); +export const cache_mutex = new Mutex(); export const handler: Handlers = { async DELETE(req, ctx) { diff --git a/routes/api/user/change_name.ts b/routes/api/user/change_name.ts new file mode 100644 index 0000000..40319d3 --- /dev/null +++ b/routes/api/user/change_name.ts @@ -0,0 +1,42 @@ +import { Handlers } from "$fresh/server.ts"; +import { User } from "../../../db.ts"; +import { get_task_manager } from "../../../server.ts"; +import { get_string } from "../../../server/parse_form.ts"; +import { BUser } from "../../../server/user.ts"; +import { return_data, return_error } from "../../../server/utils.ts"; + +export const handler: Handlers = { + async POST(req, ctx) { + const user = ctx.state.user; + if (!user) { + return return_error(403, "Permission denied."); + } + let d: FormData | null = null; + try { + d = await req.formData(); + } catch (_) { + return return_error(1, "Invalid parameters."); + } + const username = await get_string(d.get("username")); + if (!username) return return_error(2, "User name not specified."); + if (user.username == username) { + return return_error(3, "Name not changed."); + } + const m = get_task_manager(); + const u = m.db.get_user_by_name(username); + if (u) { + return return_error( + 4, + "User name is already used by other user, please use another name.", + ); + } + user.username = username; + m.db.update_user(user); + return return_data({ + id: user.id, + is_admin: user.is_admin, + permissions: user.permissions, + username: user.username, + }); + }, +}; diff --git a/routes/api/user/change_password.ts b/routes/api/user/change_password.ts new file mode 100644 index 0000000..192c1bc --- /dev/null +++ b/routes/api/user/change_password.ts @@ -0,0 +1,74 @@ +import { Handlers } from "$fresh/server.ts"; +import isEqual from "lodash/isEqual"; +import pbkdf2Hmac from "pbkdf2-hmac"; +import { decodeBase64 } from "std/encoding/base64.ts"; +import { User } from "../../../db.ts"; +import { get_task_manager } from "../../../server.ts"; +import { get_string, parse_int } from "../../../server/parse_form.ts"; +import { return_data, return_error } from "../../../server/utils.ts"; +import { cache_mutex, timestamp_cache } from "../token.ts"; + +export const handler: Handlers = { + async POST(req, ctx) { + const user = ctx.state.user; + if (!user) { + return return_error(403, "Permission denied."); + } + let d: FormData | null = null; + try { + d = await req.formData(); + } catch (_) { + return return_error(1, "Invalid parameters."); + } + const oldp = await get_string(d.get("old")); + if (!oldp) return return_error(2, "Old password is needed."); + let old: Uint8Array | null = null; + try { + old = decodeBase64(oldp); + } catch (_) { + return return_error( + 3, + "Failed to decode old password with base64.", + ); + } + if (old.length !== 64) { + return return_error(3, "Old password need 64 bytes."); + } + const t = await parse_int(d.get("t"), null); + if (t === null) return return_error(2, "t not specified."); + const now = Date.now(); + if (t > now + 60000 || t < now - 60000) { + return return_error(4, "Time is not corrected."); + } + const newp = await get_string(d.get("new")); + if (!newp) return return_error(2, "New password not specified."); + const pa = new Uint8Array( + await pbkdf2Hmac(user.password, t.toString(), 1000, 64, "SHA-512"), + ); + if (!isEqual(pa, old)) { + return return_error(5, "Incorrect password"); + } + await cache_mutex.acquire(); + try { + timestamp_cache.clear_expired(user.username, now); + if (timestamp_cache.is_in_cache(user.username, t)) { + return return_error(6, "This request has been used."); + } + timestamp_cache.add(user.username, t); + } finally { + cache_mutex.release(); + } + user.password = new Uint8Array( + await pbkdf2Hmac( + newp, + "eh-downloader-salt", + 210000, + 64, + "SHA-512", + ), + ); + const m = get_task_manager(); + m.db.update_user(user); + return return_data(true); + }, +};