mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-07-08 01:31:00 +08:00
Update
This commit is contained in:
56
db.ts
56
db.ts
@@ -127,7 +127,7 @@ export type EhFileMetaRaw = {
|
||||
is_nsfw: number;
|
||||
is_ad: number;
|
||||
};
|
||||
export enum UserPermisson {
|
||||
export enum UserPermission {
|
||||
None = 0,
|
||||
ReadGallery = 1 << 0,
|
||||
EditGallery = 1 << 1,
|
||||
@@ -138,14 +138,14 @@ export type User = {
|
||||
username: string;
|
||||
password: Uint8Array;
|
||||
is_admin: boolean;
|
||||
permissions: UserPermisson;
|
||||
permissions: UserPermission;
|
||||
};
|
||||
type UserRaw = {
|
||||
id: number;
|
||||
username: string;
|
||||
password: Uint8Array;
|
||||
is_admin: number;
|
||||
permissions: UserPermisson;
|
||||
permissions: UserPermission;
|
||||
};
|
||||
export type Token = {
|
||||
id: number;
|
||||
@@ -537,6 +537,15 @@ export class EhDb {
|
||||
pmeta,
|
||||
);
|
||||
}
|
||||
add_root_user(username: string, password: Uint8Array) {
|
||||
this.db.query("INSERT OR REPLACE INTO user VALUES (?, ?, ?, ?, ?);", [
|
||||
0,
|
||||
username,
|
||||
password,
|
||||
true,
|
||||
UserPermission.All,
|
||||
]);
|
||||
}
|
||||
add_task(task: Task) {
|
||||
return this.transaction(() => {
|
||||
this.db.query(
|
||||
@@ -585,6 +594,33 @@ export class EhDb {
|
||||
if (!t) throw Error("Failed to add token.");
|
||||
return t;
|
||||
}
|
||||
add_user(user: User) {
|
||||
if (user.id === 0) {
|
||||
this.db.query(
|
||||
"INSERT INTO user (username, password, is_admin, permissions) VALUES (?, ?, ?, ?);",
|
||||
[
|
||||
user.username,
|
||||
user.password,
|
||||
user.is_admin,
|
||||
user.permissions,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
this.db.query(
|
||||
"INSERT OR REPLACE INTO user VALUES (?, ?, ?, ?, ?);",
|
||||
[
|
||||
user.id,
|
||||
user.username,
|
||||
user.password,
|
||||
user.is_admin,
|
||||
user.permissions,
|
||||
],
|
||||
);
|
||||
}
|
||||
const u = this.get_user_by_name(user.username);
|
||||
if (!u) throw Error("Failed to add/update user.");
|
||||
return u;
|
||||
}
|
||||
begin(type: SqliteTransactionType) {
|
||||
try {
|
||||
this.db.execute(`BEGIN ${type} TRANSACTION;`);
|
||||
@@ -746,7 +782,7 @@ export class EhDb {
|
||||
const a = m.is_admin !== 0;
|
||||
const t = <User> <unknown> m;
|
||||
t.is_admin = a;
|
||||
if (t.is_admin) t.permissions = UserPermisson.All;
|
||||
if (t.is_admin) t.permissions = UserPermission.All;
|
||||
return t;
|
||||
});
|
||||
}
|
||||
@@ -784,6 +820,9 @@ export class EhDb {
|
||||
this.db.query("DELETE FROM task WHERE id = ?;", [task.id]);
|
||||
});
|
||||
}
|
||||
delete_token(token: string) {
|
||||
this.db.query("DELETE FROM token WHERE token = ?;", [token]);
|
||||
}
|
||||
async flock() {
|
||||
if (!this.#file) return;
|
||||
await eval(`Deno.flock(${this.#file.rid}, true);`);
|
||||
@@ -969,6 +1008,15 @@ export class EhDb {
|
||||
);
|
||||
return s.length ? s[0] : undefined;
|
||||
}
|
||||
get_user(id: number) {
|
||||
const s = this.convert_user(
|
||||
this.db.queryEntries<UserRaw>(
|
||||
"SELECT * FROM user WHERE id = ?;",
|
||||
[id],
|
||||
),
|
||||
);
|
||||
return s.length ? s[0] : undefined;
|
||||
}
|
||||
get_user_by_name(name: string) {
|
||||
const s = this.convert_user(
|
||||
this.db.queryEntries<UserRaw>(
|
||||
|
||||
22
fresh.gen.ts
22
fresh.gen.ts
@@ -15,11 +15,12 @@ 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/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 $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/api/token.ts";
|
||||
import * as $17 from "./routes/api/user.ts";
|
||||
import * as $18 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";
|
||||
@@ -39,11 +40,12 @@ const manifest = {
|
||||
"./routes/api/files/[token].ts": $10,
|
||||
"./routes/api/gallery/[gid].ts": $11,
|
||||
"./routes/api/gallery/list.ts": $12,
|
||||
"./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,
|
||||
"./routes/api/status.ts": $13,
|
||||
"./routes/api/task.ts": $14,
|
||||
"./routes/api/thumbnail/[id].ts": $15,
|
||||
"./routes/api/token.ts": $16,
|
||||
"./routes/api/user.ts": $17,
|
||||
"./routes/index.tsx": $18,
|
||||
},
|
||||
islands: {
|
||||
"./islands/Container.tsx": $$0,
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
import { MiddlewareHandlerContext } from "$fresh/server.ts";
|
||||
import { get_task_manager } from "../../server.ts";
|
||||
import { parse_cookies } from "../../server/cookies.ts";
|
||||
import { return_error } from "../../server/utils.ts";
|
||||
|
||||
function handle_auth(req: Request, ctx: MiddlewareHandlerContext) {
|
||||
if (req.method === "OPTIONS") return true;
|
||||
const m = get_task_manager();
|
||||
if (m.db.get_user_count() === 0) return true;
|
||||
const u = new URL(req.url);
|
||||
let token: string | null | undefined = req.headers.get("X-TOKEN");
|
||||
const cookies = parse_cookies(req.headers.get("Cookies"));
|
||||
if (!token) {
|
||||
token = cookies.get("token");
|
||||
}
|
||||
if (!token) {
|
||||
if (u.pathname === "/api/token" && req.method === "PUT") return true;
|
||||
if (u.pathname === "/api/status" && req.method === "GET") return true;
|
||||
return false;
|
||||
}
|
||||
const t = m.db.get_token(token);
|
||||
const now = (new Date()).getTime();
|
||||
if (!t || t.expired.getTime() < now) return false;
|
||||
const user = m.db.get_user(t.uid);
|
||||
if (!user) {
|
||||
m.db.delete_token(token);
|
||||
return false;
|
||||
}
|
||||
ctx.state.user = user;
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function handler(req: Request, ctx: MiddlewareHandlerContext) {
|
||||
if (!handle_auth(req, ctx)) {
|
||||
return return_error(401, "Unauthorized");
|
||||
}
|
||||
const res = await ctx.next();
|
||||
if (req.method === "OPTIONS" && res.status === 405) {
|
||||
const headers = new Headers();
|
||||
|
||||
@@ -9,7 +9,34 @@ import isEqual from "lodash/isEqual";
|
||||
const USER_PASSWORD_ERROR = "Incorrect username or password.";
|
||||
|
||||
export const handler: Handlers = {
|
||||
async POST(req, _ctx) {
|
||||
async DELETE(req, _ctx) {
|
||||
const data = await req.formData();
|
||||
const t = await get_string(data.get("token"));
|
||||
if (!t) return return_error(1, "token not specified.");
|
||||
const m = get_task_manager();
|
||||
const token = m.db.get_token(t);
|
||||
if (!token) return return_error(404, "token not found.");
|
||||
m.db.delete_token(t);
|
||||
return return_data(true);
|
||||
},
|
||||
GET(req, _ctx) {
|
||||
const u = new URL(req.url);
|
||||
const t = u.searchParams.get("token");
|
||||
if (!t) return return_error(1, "token not specififed.");
|
||||
const m = get_task_manager();
|
||||
const token = m.db.get_token(t);
|
||||
if (!token) return return_error(404, "token not found.");
|
||||
const user = m.db.get_user(token.uid);
|
||||
m.db.delete_token(t);
|
||||
if (!user) return return_error(404, "user not found.");
|
||||
return return_data({
|
||||
token,
|
||||
name: user.username,
|
||||
is_admin: user.is_admin,
|
||||
permissions: user.permissions,
|
||||
});
|
||||
},
|
||||
async PUT(req, _ctx) {
|
||||
const data = await req.formData();
|
||||
const username = await get_string(data.get("username"));
|
||||
if (!username) return return_error(1, "username not specified.");
|
||||
@@ -34,12 +61,12 @@ export const handler: Handlers = {
|
||||
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),
|
||||
await pbkdf2Hmac(u.password, t.toString(), 1000, 64, "SHA-512"),
|
||||
);
|
||||
if (!isEqual(pa, password)) {
|
||||
return return_error(4, USER_PASSWORD_ERROR);
|
||||
}
|
||||
const token = m.db.add_token(u.id, now);
|
||||
return return_data(token);
|
||||
return return_data(token, 201);
|
||||
},
|
||||
};
|
||||
77
routes/api/user.ts
Normal file
77
routes/api/user.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { 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 { return_data, return_error } from "../../server/utils.ts";
|
||||
import pbkdf2Hmac from "pbkdf2-hmac";
|
||||
|
||||
export const handler: Handlers = {
|
||||
async GET(req, ctx) {
|
||||
const u = new URL(req.url);
|
||||
const id = await parse_int(u.searchParams.get("id"), null);
|
||||
const username = u.searchParams.get("username");
|
||||
const user = <User | undefined> ctx.state.user;
|
||||
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 && !user.is_admin && us.id !== user.id) {
|
||||
return return_error(403, "Permission denied.");
|
||||
}
|
||||
return return_data({
|
||||
id: us.id,
|
||||
username: us.username,
|
||||
is_admin: us.is_admin,
|
||||
permissions: us.permissions,
|
||||
});
|
||||
},
|
||||
async PUT(req, ctx) {
|
||||
const data = await req.formData();
|
||||
const user = <User | undefined> ctx.state.user;
|
||||
if (user && !user.is_admin) {
|
||||
return return_error(403, "Permission denied.");
|
||||
}
|
||||
const name = await get_string(data.get("name"));
|
||||
const password = await get_string(data.get("password"));
|
||||
const is_admin = await parse_bool(data.get("is_admin"), false);
|
||||
let permissions: UserPermission = await parse_int(
|
||||
data.get("permissions"),
|
||||
UserPermission.None,
|
||||
);
|
||||
if (!name) return return_error(1, "name not specified.");
|
||||
if (!password) return return_error(1, "password not specified.");
|
||||
if (is_admin) permissions = UserPermission.All;
|
||||
const m = get_task_manager();
|
||||
if (m.db.get_user_by_name(name)) {
|
||||
return return_error(2, "Please change to another name.");
|
||||
}
|
||||
const hpassword = new Uint8Array(
|
||||
await pbkdf2Hmac(
|
||||
password,
|
||||
"eh-downloader-salt",
|
||||
210000,
|
||||
64,
|
||||
"SHA-512",
|
||||
),
|
||||
);
|
||||
if (m.db.get_user_count() === 0) {
|
||||
m.db.add_root_user(name, hpassword);
|
||||
return return_data(0, 201);
|
||||
} else {
|
||||
const t = m.db.add_user({
|
||||
id: 0,
|
||||
username: name,
|
||||
password: hpassword,
|
||||
is_admin,
|
||||
permissions,
|
||||
});
|
||||
return return_data(t.id, 201);
|
||||
}
|
||||
},
|
||||
};
|
||||
12
server/cookies.ts
Normal file
12
server/cookies.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export function parse_cookies(c: string | null) {
|
||||
const m = new Map<string, string>();
|
||||
if (c === null) return m;
|
||||
for (const a of c.split(";")) {
|
||||
const b = a.trim();
|
||||
const d = b.split("=");
|
||||
if (d.length > 1) {
|
||||
m.set(d[0], d.slice(1).join("="));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
Reference in New Issue
Block a user