Add shared_token API

This commit is contained in:
2024-08-11 18:07:11 +08:00
parent 10eeeaf1ac
commit c75d2d9dbb
7 changed files with 229 additions and 10 deletions

View File

@@ -2,7 +2,7 @@ import { FreshContext } 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";
import type { Token } from "../../db.ts";
import { SharedTokenType, type Token } from "../../db.ts";
function handle_auth(req: Request, ctx: FreshContext) {
if (req.method === "OPTIONS") return true;
@@ -44,7 +44,45 @@ function handle_auth(req: Request, ctx: FreshContext) {
if (!token) return check();
const t = m.db.get_token(token);
const now = (new Date()).getTime();
if (!t || t.expired.getTime() < now) return check();
if (!t) {
const st = m.db.get_shared_token(token);
if (!st || (st.expired !== null && st.expired.getTime() < now)) {
return check();
}
if (
u.pathname === "/api/shared_token" && req.method === "GET"
) { /*Nothing to do*/ } else if (st.type == SharedTokenType.Gallery) {
const check_g = () => {
if (
u.pathname === `/api/gallery/${st.info.gid}` &&
req.method === "GET"
) return true;
if (u.pathname === "/api/tag/rows" && req.method === "GET") {
return true;
}
// Follow API need extra checks.
if (
u.pathname.match(/^\/api\/file\/\d+$/) &&
req.method === "GET"
) return true;
if (
u.pathname.match(/^\/api\/thumbnail\/\d+$/) &&
req.method === "GET"
) return true;
if (
u.pathname.startsWith("/api/files/") && req.method === "GET"
) return true;
return false;
};
if (!check_g()) return check();
} else {
return check();
}
ctx.state.is_from_cookie = false;
ctx.state.shared_token = st;
return true;
}
if (t.expired.getTime() < now) return check();
const user = m.db.get_user(t.uid);
if (!user) {
m.db.delete_token(token);

View File

@@ -9,9 +9,14 @@ import pbkdf2Hmac from "pbkdf2-hmac";
import { encodeBase64 as encode } from "@std/encoding/base64";
import { get_host, return_data, return_error } from "../../../server/utils.ts";
import type { EhFileExtend } from "../../../server/files.ts";
import { User, UserPermission } from "../../../db.ts";
import {
SharedToken,
SharedTokenType,
User,
UserPermission,
} from "../../../db.ts";
import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.ts";
import { isNumNaN, parseBigInt } from "../../../utils.ts";
import { compareNum, isNumNaN, parseBigInt } from "../../../utils.ts";
import { extname } from "@std/path";
export const handler: Handlers = {
@@ -23,6 +28,7 @@ export const handler: Handlers = {
) {
return return_error(403, "Permission denied.");
}
const st = <SharedToken | undefined> ctx.state.shared_token;
const u = new URL(req.url);
const m = get_task_manager();
const token = u.searchParams.get("token");
@@ -51,9 +57,20 @@ export const handler: Handlers = {
}
const f = m.db.get_file(id);
if (!f) {
if (st && st.type == SharedTokenType.Gallery) {
if (data) return return_error(403, "Permission denied.");
return new Response("Permission denied.", { status: 403 });
}
if (data) return return_error(404, "File not found.");
return new Response("File not found.", { status: 404 });
}
if (st && st.type == SharedTokenType.Gallery) {
const pmetas = m.db.get_pmeta_by_token_only(f.token);
if (!pmetas.some((m) => !compareNum(m.gid, st.info.gid))) {
if (data) return return_error(403, "Permission denied.");
return new Response("Permission denied.", { status: 403 });
}
}
if (data) {
return return_data<EhFileExtend>({
id: f.id,

View File

@@ -2,7 +2,13 @@ import { Handlers } from "$fresh/server.ts";
import { get_task_manager } from "../../../server.ts";
import type { EhFiles } from "../../../server/files.ts";
import { return_data, return_error } from "../../../server/utils.ts";
import { User, UserPermission } from "../../../db.ts";
import {
SharedToken,
SharedTokenType,
User,
UserPermission,
} from "../../../db.ts";
import { compareNum } from "../../../utils.ts";
export const handler: Handlers = {
GET(_req, ctx) {
@@ -10,6 +16,7 @@ export const handler: Handlers = {
if (u && !u.is_admin && !(u.permissions & UserPermission.ReadGallery)) {
return return_error(403, "Permission denied.");
}
const st = <SharedToken | undefined> ctx.state.shared_token;
const tokens = ctx.params.token.split(",");
const m = get_task_manager();
const enable_server_timing = m.cfg.enable_server_timing;
@@ -17,6 +24,12 @@ export const handler: Handlers = {
const headers: HeadersInit = {};
const data: EhFiles = {};
for (const token of tokens) {
if (st && st.type == SharedTokenType.Gallery) {
const pmetas = m.db.get_pmeta_by_token_only(token);
if (!pmetas.some((m) => !compareNum(m.gid, st.info.gid))) {
return return_error(403, "Permission denied.");
}
}
data[token] = m.db.get_files(token).map((d) => {
/**@ts-ignore */
delete d.path;

View File

@@ -0,0 +1,64 @@
import { Handlers } from "$fresh/server.ts";
import { exists } from "@std/fs/exists";
import {
SharedToken,
SharedTokenType,
User,
UserPermission,
} from "../../db.ts";
import { get_task_manager } from "../../server.ts";
import {
get_string,
parse_big_int,
parse_int,
} from "../../server/parse_form.ts";
import { get_host, return_data, return_error } from "../../server/utils.ts";
export const handler: Handlers = {
GET(_req, ctx) {
const st = <SharedToken | undefined> ctx.state.shared_token;
if (!st) return return_error(1, "No token.");
return return_data(st);
},
async PUT(req, ctx) {
const user = <User | undefined> ctx.state.user;
let form: FormData | undefined;
try {
form = await req.formData();
} catch (_) {
return return_error(400, "Bad Request");
}
const typ = await get_string(form.get("type"));
const expired = await parse_int(form.get("expired"), null);
if (typ == "gallery") {
if (
user && !user.is_admin &&
!(user.permissions & UserPermission.ShareGallery)
) {
return return_error(403, "Permission denied.");
}
const gid = await parse_big_int(form.get("gid"), null);
if (!gid) return return_error(2, "gid not specified.");
const m = get_task_manager();
const st = m.db.add_shared_token(SharedTokenType.Gallery, {
gid: gid,
}, expired ? new Date(expired) : null);
let flutter_base = import.meta.resolve("../../static/flutter")
.slice(7);
if (Deno.build.os === "windows") {
flutter_base = flutter_base.slice(1);
}
if (m.cfg.flutter_frontend) {
flutter_base = m.cfg.flutter_frontend;
}
const existed = await exists(flutter_base);
const base = existed
? `${get_host(req)}/flutter`
: "https://dev.ehf.lifegpc.com/#";
const url = `${base}/gallery/${gid}?share=${st.token}`;
return return_data({ url, token: st }, 201);
} else {
return return_error(1, "Unknown type");
}
},
};

View File

@@ -10,7 +10,7 @@ import {
ThumbnailConfig,
ThumbnailGenMethod,
} from "../../../thumbnail/base.ts";
import { isNumNaN, parseBigInt, sure_dir } from "../../../utils.ts";
import { compareNum, isNumNaN, parseBigInt, sure_dir } from "../../../utils.ts";
import { ThumbnailMethod } from "../../../config.ts";
import { fb_generate_thumbnail } from "../../../thumbnail/ffmpeg_binary.ts";
import {
@@ -22,7 +22,12 @@ import pbkdf2Hmac from "pbkdf2-hmac";
import { encodeBase64 as encode } from "@std/encoding/base64";
import { SortableURLSearchParams } from "../../../server/SortableURLSearchParams.ts";
import type * as FFMPEG_API from "../../../thumbnail/ffmpeg_api.ts";
import { User, UserPermission } from "../../../db.ts";
import {
SharedToken,
SharedTokenType,
User,
UserPermission,
} from "../../../db.ts";
let ffmpeg_api: typeof FFMPEG_API | undefined;
@@ -35,6 +40,7 @@ export const handler: Handlers = {
) {
return new Response("Permission denied", { status: 403 });
}
const st = <SharedToken | undefined> ctx.state.shared_token;
const id = parseBigInt(ctx.params.id);
const m = get_task_manager();
const u = new URL(req.url);
@@ -64,8 +70,17 @@ export const handler: Handlers = {
await sure_dir(b);
const f = m.db.get_file(id);
if (!f) {
if (st && st.type == SharedTokenType.Gallery) {
return new Response("Permission denied.", { status: 403 });
}
return new Response("File not found.", { status: 404 });
}
if (st && st.type == SharedTokenType.Gallery) {
const pmetas = m.db.get_pmeta_by_token_only(f.token);
if (!pmetas.some((m) => !compareNum(m.gid, st.info.gid))) {
return new Response("Permission denied.", { status: 403 });
}
}
const max = await parse_int(u.searchParams.get("max"), 400);
const width = await parse_int(u.searchParams.get("width"), null);
const height = await parse_int(u.searchParams.get("height"), null);