diff --git a/components/Login.tsx b/components/Login.tsx index a54a02a..40c61eb 100644 --- a/components/Login.tsx +++ b/components/Login.tsx @@ -7,6 +7,7 @@ import { MdTonalButton } from "../server/dmodule.ts"; import { set_state } from "../server/state.ts"; import pbkdf2Hmac from "pbkdf2-hmac/?target=es2022"; import { encodeBase64 as encode } from "std/encoding/base64.ts"; +import { UserAgent } from "std/http/user_agent.ts"; type Props = { show: boolean; @@ -46,6 +47,17 @@ export default class Login extends Component { if (document.location.protocol === "https:") { b.append("secure", "1"); } + b.append("client", "fresh"); + b.append("client_version", "0.0.1"); + b.append("client_platform", "web"); + const ua = new UserAgent(navigator.userAgent); + let name = ua.browser.name; + if (name && ua.browser.version) { + name += " " + ua.browser.version; + } + if (name) { + b.append("device", name); + } const re2 = await fetch("/api/token", { method: "PUT", body: b }); const token = await re2.json(); if (!token.ok) { diff --git a/db.ts b/db.ts index 8aa1f1e..40b402d 100644 --- a/db.ts +++ b/db.ts @@ -155,6 +155,10 @@ export type Token = { http_only: boolean; secure: boolean; last_used: Date; + client: string | null; + device: string | null; + client_version: string | null; + client_platform: string | null; }; type TokenRaw = { id: number; @@ -164,6 +168,10 @@ type TokenRaw = { http_only: number; secure: number; last_used: string; + client: string | null; + device: string | null; + client_version: string | null; + client_platform: string | null; }; const ALL_TABLES = [ "version", @@ -257,7 +265,11 @@ const TOKEN_TABLE = `CREATE TABLE token ( expired TEXT, http_only BOOLEAN, secure BOOLEAN, - last_used TEXT + last_used TEXT, + client TEXT, + device TEXT, + client_version TEXT, + client_platform TEXT );`; function escape_fields(fields: string, namespace: string) { @@ -280,7 +292,7 @@ export class EhDb { #base_path: string; #db_path: string; #use_ffi = false; - readonly version = parse_ver("1.0.0-11"); + readonly version = parse_ver("1.0.0-12"); constructor(base_path: string) { this.#base_path = base_path; this.#db_path = join(base_path, "data.db"); @@ -434,6 +446,12 @@ export class EhDb { "UPDATE token SET last_used = '1970-01-01T00:00:00.000Z';", ); } + if (compare_ver(v, parse_ver("1.0.0-12")) === -1) { + this.db.execute("ALTER TABLE token ADD client TEXT;"); + this.db.execute("ALTER TABLE token ADD device TEXT;"); + this.db.execute("ALTER TABLE token ADD client_version TEXT;"); + this.db.execute("ALTER TABLE token ADD client_platform TEXT;"); + } this.#write_version(); if (need_optimize) this.optimize(); } @@ -640,13 +658,17 @@ export class EhDb { added: number, http_only: boolean, secure: boolean, + client: string | null, + device: string | null, + client_version: string | null, + client_platform: string | null, ): Token { let token = randomstring(); while (this.get_token(token)) { token = randomstring(); } this.db.query( - "INSERT INTO token (uid, token, expired, http_only, secure, last_used) VALUES (?, ?, ?, ?, ?, ?);", + "INSERT INTO token (uid, token, expired, http_only, secure, last_used, client, device, client_version, client_platform) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", [ uid, token, @@ -654,6 +676,10 @@ export class EhDb { http_only, secure, new Date(added), + client, + device, + client_version, + client_platform, ], ); const t = this.get_token(token); @@ -1289,6 +1315,45 @@ export class EhDb { if (!t) throw Error("Failed to update token."); return t; } + update_token_info( + token: string, + client: string | null, + device: string | null, + client_version: string | null, + client_platform: string | null, + ): Token { + if ( + client !== null || device !== null || client_version !== null || + client_platform !== null + ) { + const args = []; + const sets = []; + if (client !== null) { + sets.push("client = ?"); + args.push(client); + } + if (device !== null) { + sets.push("device = ?"); + args.push(device); + } + if (client_version !== null) { + sets.push("client_version = ?"); + args.push(client_version); + } + if (client_platform !== null) { + sets.push("client_platform = ?"); + args.push(client_platform); + } + args.push(token); + this.db.query( + `UPDATE token SET ${sets.join(", ")} WHERE token = ?;`, + args, + ); + } + const t = this.get_token(token); + if (!t) throw Error("Failed to update token."); + return t; + } update_token_last_used(token: string) { this.db.query( "UPDATE token SET last_used = ? WHERE token = ?;", diff --git a/routes/api/token.ts b/routes/api/token.ts index ea35f29..715ab05 100644 --- a/routes/api/token.ts +++ b/routes/api/token.ts @@ -80,6 +80,10 @@ export const handler: Handlers = { const set_cookie = await parse_bool(data.get("set_cookie"), false); const http_only = await parse_bool(data.get("http_only"), true); const secure = await parse_bool(data.get("secure"), false); + const client = await get_string(data.get("client")); + const client_version = await get_string(data.get("client_version")); + const client_platform = await get_string(data.get("client_platform")); + const device = await get_string(data.get("device")); const m = get_task_manager(); const u = m.db.get_user_by_name(username); if (!u) return return_error(4, USER_PASSWORD_ERROR); @@ -89,7 +93,16 @@ export const handler: Handlers = { if (!isEqual(pa, password)) { return return_error(4, USER_PASSWORD_ERROR); } - const token = m.db.add_token(u.id, now, http_only, secure); + const token = m.db.add_token( + u.id, + now, + http_only, + secure, + client, + device, + client_version, + client_platform, + ); const headers: HeadersInit = {}; if (set_cookie) { headers["Set-Cookie"] = @@ -99,4 +112,33 @@ export const handler: Handlers = { } return return_data(token, 201, headers); }, + async PATCH(req, ctx) { + try { + const data = await req.formData(); + let t = await get_string(data.get("token")); + const ttoken = ctx.state.token; + if (!t && ttoken) t = ttoken.token; + if (!t) return return_error(1, "token not specififed."); + const client = await get_string(data.get("client")); + const client_version = await get_string(data.get("client_version")); + const client_platform = await get_string( + data.get("client_platform"), + ); + const device = await get_string(data.get("device")); + const m = get_task_manager(); + const token = m.db.get_token(t); + if (!token) return return_error(404, "token not found."); + return return_data( + m.db.update_token_info( + t, + client, + device, + client_version, + client_platform, + ), + ); + } catch (e) { + return return_error(500, e.message); + } + }, }; diff --git a/server.ts b/server.ts index d10ca29..d364b08 100644 --- a/server.ts +++ b/server.ts @@ -37,7 +37,7 @@ export async function startServer(path: string) { await load_translation(task_manager.aborts); setInterval(() => { task_manager?.db.remove_expired_token(); - }, 86_400_000) + }, 86_400_000); return start(manifest, { signal: task_manager.aborts, plugins: [twindPlugin(twindConfig)],