This commit is contained in:
2023-07-02 17:29:37 +08:00
parent 71f5516c2c
commit b777b4758c
6 changed files with 166 additions and 9 deletions

105
db.ts
View File

@@ -10,6 +10,7 @@ import { SqliteError } from "sqlite/mod.ts";
import { Status } from "sqlite/src/constants.ts";
import { sleep, sure_dir_sync, try_remove_sync } from "./utils.ts";
import { Task, TaskType } from "./task.ts";
import { generate as randomstring } from "randomstring";
type SqliteMaster = {
type: string;
@@ -126,6 +127,38 @@ export type EhFileMetaRaw = {
is_nsfw: number;
is_ad: number;
};
export enum UserPermisson {
None = 0,
ReadGallery = 1 << 0,
EditGallery = 1 << 1,
All = ~(~0 << 2),
}
export type User = {
id: number;
username: string;
password: Uint8Array;
is_admin: boolean;
permissions: UserPermisson;
};
type UserRaw = {
id: number;
username: string;
password: Uint8Array;
is_admin: number;
permissions: UserPermisson;
};
export type Token = {
id: number;
uid: number;
token: string;
expired: Date;
};
type TokenRaw = {
id: number;
uid: number;
token: string;
expired: string;
};
const ALL_TABLES = [
"version",
"task",
@@ -135,6 +168,8 @@ const ALL_TABLES = [
"gtag",
"file",
"filemeta",
"user",
"token",
];
const VERSION_TABLE = `CREATE TABLE version (
id TEXT,
@@ -201,6 +236,19 @@ const FILEMETA_TABLE = `CREATE TABLE filemeta (
is_ad BOOLEAN,
PRIMARY KEY (token)
);`;
const USER_TABLE = `CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password BLOB,
is_admin BOOLEAN,
permissions INT
);`;
const TOKEN_TABLE = `CREATE TABLE token (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT,
token TEXT,
expired TEXT
);`;
export class EhDb {
db;
@@ -368,6 +416,12 @@ export class EhDb {
if (!this.#exist_table.has("filemeta")) {
this.db.execute(FILEMETA_TABLE);
}
if (!this.#exist_table.has("user")) {
this.db.execute(USER_TABLE);
}
if (!this.#exist_table.has("token")) {
this.db.execute(TOKEN_TABLE);
}
this.#updateExistsTable();
}
#read_version() {
@@ -518,6 +572,19 @@ export class EhDb {
)[0];
});
}
add_token(uid: number, added: number): Token {
let token = randomstring();
while (this.get_token(token)) {
token = randomstring();
}
this.db.query(
"INSERT INTO token (uid, token, expired) VALUES (?, ?, ?);",
[uid, token, new Date(added + 2592000000)],
);
const t = this.get_token(token);
if (!t) throw Error("Failed to add token.");
return t;
}
begin(type: SqliteTransactionType) {
try {
this.db.execute(`BEGIN ${type} TRANSACTION;`);
@@ -666,6 +733,23 @@ export class EhDb {
return t;
});
}
convert_token(m: TokenRaw[]) {
return m.map((m) => {
const e = new Date(m.expired);
const t = <Token> <unknown> m;
t.expired = e;
return t;
});
}
convert_user(m: UserRaw[]) {
return m.map((m) => {
const a = m.is_admin !== 0;
const t = <User> <unknown> m;
t.is_admin = a;
if (t.is_admin) t.permissions = UserPermisson.All;
return t;
});
}
delete_file(f: EhFile) {
this.db.query("DELETE FROM file WHERE id = ?;", [f.id]);
}
@@ -876,6 +960,27 @@ export class EhDb {
])
);
}
get_token(token: string) {
const s = this.convert_token(
this.db.queryEntries<TokenRaw>(
"SELECT * FROM token WHERE token = ?;",
[token],
),
);
return s.length ? s[0] : undefined;
}
get_user_by_name(name: string) {
const s = this.convert_user(
this.db.queryEntries<UserRaw>(
"SELECT * FROM user WHERE username = ?;",
[name],
),
);
return s.length ? s[0] : undefined;
}
get_user_count() {
return this.db.query<[number]>("SELECT COUNT(*) FROM user;")[0][0];
}
optimize() {
this.db.execute("VACUUM;");
}

View File

@@ -15,10 +15,11 @@ import * as $9 from "./routes/api/filemeta/[token].ts";
import * as $10 from "./routes/api/files/[token].ts";
import * as $11 from "./routes/api/gallery/[gid].ts";
import * as $12 from "./routes/api/gallery/list.ts";
import * as $13 from "./routes/api/status.ts";
import * as $14 from "./routes/api/task.ts";
import * as $15 from "./routes/api/thumbnail/[id].ts";
import * as $16 from "./routes/index.tsx";
import * as $13 from "./routes/api/login.ts";
import * as $14 from "./routes/api/status.ts";
import * as $15 from "./routes/api/task.ts";
import * as $16 from "./routes/api/thumbnail/[id].ts";
import * as $17 from "./routes/index.tsx";
import * as $$0 from "./islands/Container.tsx";
import * as $$1 from "./islands/Settings.tsx";
import * as $$2 from "./islands/TaskManager.tsx";
@@ -38,10 +39,11 @@ const manifest = {
"./routes/api/files/[token].ts": $10,
"./routes/api/gallery/[gid].ts": $11,
"./routes/api/gallery/list.ts": $12,
"./routes/api/status.ts": $13,
"./routes/api/task.ts": $14,
"./routes/api/thumbnail/[id].ts": $15,
"./routes/index.tsx": $16,
"./routes/api/login.ts": $13,
"./routes/api/status.ts": $14,
"./routes/api/task.ts": $15,
"./routes/api/thumbnail/[id].ts": $16,
"./routes/index.tsx": $17,
},
islands: {
"./islands/Container.tsx": $$0,

View File

@@ -21,6 +21,8 @@
"meilisearch": "https://esm.sh/[email protected]",
"lodash/": "https://esm.sh/[email protected]/",
"mime": "https://esm.sh/[email protected]",
"ua-parser-js": "https://esm.sh/[email protected]"
"ua-parser-js": "https://esm.sh/[email protected]",
"pbkdf2-hmac": "https://esm.sh/[email protected]",
"randomstring": "https://esm.sh/[email protected]"
}
}

45
routes/api/login.ts Normal file
View File

@@ -0,0 +1,45 @@
import { Handlers } from "$fresh/server.ts";
import { decode } from "std/encoding/base64.ts";
import { get_string, parse_int } from "../../server/parse_form.ts";
import { return_data, return_error } from "../../server/utils.ts";
import { get_task_manager } from "../../server.ts";
import pbkdf2Hmac from "pbkdf2-hmac";
import isEqual from "lodash/isEqual";
const USER_PASSWORD_ERROR = "Incorrect username or password.";
export const handler: Handlers = {
async POST(req, _ctx) {
const data = await req.formData();
const username = await get_string(data.get("username"));
if (!username) return return_error(1, "username not specified.");
const p = await get_string(data.get("password"));
if (!p) return return_error(1, "password not specified.");
let password = null;
try {
password = decode(p);
} catch (_) {
return return_error(2, "Failed to decode password with base64.");
}
if (password.length !== 64) {
return return_error(2, "Password need 64 bytes.");
}
const t = await parse_int(data.get("t"), null);
if (t === null) return return_error(1, "t not specified.");
const now = (new Date()).getTime();
if (t > now + 60000 || t < now - 60000) {
return return_error(3, "Time is not corrected.");
}
const m = get_task_manager();
const u = m.db.get_user_by_name(username);
if (!u) return return_error(4, USER_PASSWORD_ERROR);
const pa = new Uint8Array(
await pbkdf2Hmac(u.password, t.toString(), 1000, 64),
);
if (!isEqual(pa, password)) {
return return_error(4, USER_PASSWORD_ERROR);
}
const token = m.db.add_token(u.id, now);
return return_data(token);
},
};

View File

@@ -19,10 +19,12 @@ export const handler: Handlers = {
m.cfg.meili_update_api_key,
}
: undefined;
const no_user = m.db.get_user_count() === 0;
return return_data<StatusData>({
ffmpeg_binary_enabled,
meilisearch_enabled,
meilisearch,
no_user,
});
},
};

View File

@@ -5,4 +5,5 @@ export type StatusData = {
host: string;
key: string;
};
no_user: boolean;
};