Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce071ee14 | ||
|
|
ab62a2b3e2 | ||
|
|
c06da83f92 | ||
|
|
86353cbe57 |
3 changed files with 37 additions and 10 deletions
|
|
@ -29,11 +29,15 @@ export async function POST(request: Request) {
|
||||||
|
|
||||||
// ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret)
|
// ความผิด config ฝั่ง server ไม่ใช่ความผิดของผู้เรียก — ไม่ตั้ง = 500 (ห้าม log ค่า secret)
|
||||||
const secret = process.env.DOCVER_TOKEN_SECRET?.trim();
|
const secret = process.env.DOCVER_TOKEN_SECRET?.trim();
|
||||||
|
// const secret =
|
||||||
|
// "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e";
|
||||||
|
|
||||||
if (secret === undefined || secret === "") {
|
if (secret === undefined || secret === "") {
|
||||||
return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500);
|
return docverError("DOCVER_TOKEN_SECRET ยังไม่ถูกตั้งค่า", 500);
|
||||||
}
|
}
|
||||||
const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS);
|
const ttlRaw = Number(process.env.DOCVER_TOKEN_TTL_SECONDS);
|
||||||
const ttlSeconds = Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600;
|
const ttlSeconds =
|
||||||
|
Number.isFinite(ttlRaw) && ttlRaw > 0 ? Math.floor(ttlRaw) : 3600;
|
||||||
|
|
||||||
const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds);
|
const accessToken = issueDocverToken(parsed.claims, secret, ttlSeconds);
|
||||||
return new Response(
|
return new Response(
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,20 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
# db/index.ts throw ถ้าไม่มี DATABASE_URL ตอน next build เก็บ page data —
|
# db/index.ts throw ถ้าไม่มี DATABASE_URL ตอน next build เก็บ page data —
|
||||||
# ใส่ค่า dummy (postgres.js เชื่อมแบบ lazy ไม่มี query ตอน build) ตัวจริงฉีดตอน docker run
|
# ใส่ค่า dummy (postgres.js เชื่อมแบบ lazy ไม่มี query ตอน build) ตัวจริงฉีดตอน docker run
|
||||||
ENV DATABASE_URL=postgresql://placeholder:placeholder@127.0.0.1:5432/placeholder
|
ENV DATABASE_URL=postgresql://placeholder:placeholder@127.0.0.1:5432/placeholder
|
||||||
|
ENV MINIO_ENDPOINT=127.0.0.1
|
||||||
|
ENV MINIO_PORT=19000
|
||||||
|
ENV MINIO_USE_SSL=false
|
||||||
|
ENV MINIO_ACCESS_KEY=minioadmin
|
||||||
|
ENV MINIO_SECRET_KEY=minioadmin
|
||||||
|
ENV DOCVER_API_KEY=2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54
|
||||||
|
ENV DOCVER_TOKEN_SECRET=487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e
|
||||||
|
ENV DOCVER_TOKEN_TTL_SECONDS=604800
|
||||||
|
ENV MINIO_BUCKET=docver
|
||||||
|
ENV DOCVER_LINK_TTL_SECONDS=900
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
# ─── Stage 3: runtime ตัวเบา (แค่ standalone + public/) ───
|
# ─── Stage 3: runtime ตัวเบา (แค่ standalone + public/) ───
|
||||||
FROM node:24-alpine AS runner
|
FROM node:24-alpine AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,11 @@ function decodeJsonSegment(segment: string): Record<string, unknown> | null {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(text) as unknown;
|
const parsed = JSON.parse(text) as unknown;
|
||||||
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
if (
|
||||||
|
parsed === null ||
|
||||||
|
typeof parsed !== "object" ||
|
||||||
|
Array.isArray(parsed)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return parsed as Record<string, unknown>;
|
return parsed as Record<string, unknown>;
|
||||||
|
|
@ -64,7 +68,9 @@ export function issueDocverToken(
|
||||||
|
|
||||||
/** claims ที่ถอดได้จาก payload ที่ตรวจ signature ผ่านแล้ว — รูปร่างผิด = invalid
|
/** claims ที่ถอดได้จาก payload ที่ตรวจ signature ผ่านแล้ว — รูปร่างผิด = invalid
|
||||||
* (กัน token ที่เซ็นถูกแต่ claims ไม่ครบ/ผิด type หลุดไปใช้ต่อ) */
|
* (กัน token ที่เซ็นถูกแต่ claims ไม่ครบ/ผิด type หลุดไปใช้ต่อ) */
|
||||||
function claimsFromPayload(payload: Record<string, unknown>): DocverTokenClaims | null {
|
function claimsFromPayload(
|
||||||
|
payload: Record<string, unknown>,
|
||||||
|
): DocverTokenClaims | null {
|
||||||
const { userid, username, fid, template } = payload;
|
const { userid, username, fid, template } = payload;
|
||||||
if (typeof userid !== "string" || userid === "") return null;
|
if (typeof userid !== "string" || userid === "") return null;
|
||||||
if (typeof username !== "string" || username === "") return null;
|
if (typeof username !== "string" || username === "") return null;
|
||||||
|
|
@ -116,6 +122,8 @@ export function verifyDocverToken(
|
||||||
export function requireDocverApiKey(request: Request): Response | null {
|
export function requireDocverApiKey(request: Request): Response | null {
|
||||||
// เทียบกับค่า trim แล้ว — กัน trailing newline จาก K8s secret / docker env_file
|
// เทียบกับค่า trim แล้ว — กัน trailing newline จาก K8s secret / docker env_file
|
||||||
const envKey = process.env.DOCVER_API_KEY?.trim();
|
const envKey = process.env.DOCVER_API_KEY?.trim();
|
||||||
|
// const envKey =
|
||||||
|
// "2d9b76d49a4f5311f6a0dacf759ecec53f34b6d102f24c696890a4052c99cf54";
|
||||||
if (envKey === undefined || envKey === "") {
|
if (envKey === undefined || envKey === "") {
|
||||||
return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401);
|
return docverError("ระบบยังไม่ได้ตั้งค่า API key", 401);
|
||||||
}
|
}
|
||||||
|
|
@ -137,11 +145,14 @@ const BEARER_DENIED = "โทเคนไม่ถูกต้องหรือ
|
||||||
/** บังคับ Authorization: Bearer <token> ของกลุ่ม docver — เรียกเป็นอย่างแรกของ
|
/** บังคับ Authorization: Bearer <token> ของกลุ่ม docver — เรียกเป็นอย่างแรกของ
|
||||||
* ทุก endpoint ก่อน parse path param · env DOCVER_TOKEN_SECRET ไม่ตั้ง = ไม่มี
|
* ทุก endpoint ก่อน parse path param · env DOCVER_TOKEN_SECRET ไม่ตั้ง = ไม่มี
|
||||||
* token ใดผ่าน (fail closed — ไม่ใช่ 500 เพราะไม่ใช่ความผิดของผู้เรียก) */
|
* token ใดผ่าน (fail closed — ไม่ใช่ 500 เพราะไม่ใช่ความผิดของผู้เรียก) */
|
||||||
export function requireDocverBearer(request: Request):
|
export function requireDocverBearer(
|
||||||
| { ok: true; claims: DocverTokenClaims }
|
request: Request,
|
||||||
| { ok: false; response: Response } {
|
): { ok: true; claims: DocverTokenClaims } | { ok: false; response: Response } {
|
||||||
const denied = () => ({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const;
|
const denied = () =>
|
||||||
|
({ ok: false, response: docverError(BEARER_DENIED, 401) }) as const;
|
||||||
const secret = process.env.DOCVER_TOKEN_SECRET?.trim();
|
const secret = process.env.DOCVER_TOKEN_SECRET?.trim();
|
||||||
|
// const secret =
|
||||||
|
// "487023dd1c1c4d6a07475e96b1cc300fec1e68ac9c2dd7d5ac9b1bf75da1053e";
|
||||||
if (secret === undefined || secret === "") return denied();
|
if (secret === undefined || secret === "") return denied();
|
||||||
const header = request.headers.get("authorization");
|
const header = request.headers.get("authorization");
|
||||||
if (header === null) return denied();
|
if (header === null) return denied();
|
||||||
|
|
@ -156,9 +167,9 @@ export function requireDocverBearer(request: Request):
|
||||||
* convention ของกลุ่ม (key อื่น = 400) · trim-ว่าง = ถือว่าไม่ส่ง */
|
* convention ของกลุ่ม (key อื่น = 400) · trim-ว่าง = ถือว่าไม่ส่ง */
|
||||||
const TOKEN_BODY_FIELDS = ["userid", "username", "fid", "template"] as const;
|
const TOKEN_BODY_FIELDS = ["userid", "username", "fid", "template"] as const;
|
||||||
|
|
||||||
export function parseTokenRequestBody(raw: unknown):
|
export function parseTokenRequestBody(
|
||||||
| { ok: true; claims: DocverTokenClaims }
|
raw: unknown,
|
||||||
| DocverError {
|
): { ok: true; claims: DocverTokenClaims } | DocverError {
|
||||||
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
||||||
return { ok: false, error: "body ต้องเป็น JSON object" };
|
return { ok: false, error: "body ต้องเป็น JSON object" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue