Enable auth-service/api-gateway on production, build their images in CI

This commit is contained in:
2026-08-18 14:11:00 +07:00
parent e5afedfa2f
commit b68005be1c
70 changed files with 6781 additions and 263 deletions
+55
View File
@@ -0,0 +1,55 @@
import { NextResponse } from "next/server";
import type { LoginResponse } from "@duoc-thu/shared-types";
import { SESSION_COOKIE, SESSION_MAX_AGE_SECONDS } from "../session";
const GATEWAY_URL = process.env.API_GATEWAY_URL ?? "http://localhost:3000";
export async function POST(request: Request) {
let username: string;
let password: string;
try {
const body = await request.json();
username = typeof body?.username === "string" ? body.username : "";
password = typeof body?.password === "string" ? body.password : "";
} catch {
return NextResponse.json({ error: "invalid_body" }, { status: 400 });
}
if (!username || !password) {
return NextResponse.json({ error: "missing_credentials" }, { status: 400 });
}
let upstream: Response;
try {
upstream = await fetch(`${GATEWAY_URL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
cache: "no-store",
});
} catch {
return NextResponse.json({ error: "gateway_unreachable" }, { status: 502 });
}
if (!upstream.ok) {
return NextResponse.json(
{ error: "invalid_credentials" },
{ status: upstream.status === 401 ? 401 : 502 }
);
}
const data = (await upstream.json()) as LoginResponse;
const response = NextResponse.json({ user: data.user });
// httpOnly: never readable by client-side JS (XSS can't exfiltrate it).
// `secure` only outside local dev — Compose/k3s both terminate TLS in
// front of `web`, so the cookie is only ever sent in the clear on
// localhost, matching how every other secret in this repo treats
// local vs. deployed differently.
response.cookies.set(SESSION_COOKIE, data.token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: SESSION_MAX_AGE_SECONDS,
});
return response;
}
+8
View File
@@ -0,0 +1,8 @@
import { NextResponse } from "next/server";
import { SESSION_COOKIE } from "../session";
export async function POST() {
const response = NextResponse.json({ ok: true });
response.cookies.delete(SESSION_COOKIE);
return response;
}
+30
View File
@@ -0,0 +1,30 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import type { AuthUser } from "@duoc-thu/shared-types";
import { SESSION_COOKIE } from "../session";
const GATEWAY_URL = process.env.API_GATEWAY_URL ?? "http://localhost:3000";
export async function GET() {
const token = cookies().get(SESSION_COOKIE)?.value;
if (!token) {
return NextResponse.json({ error: "not_authenticated" }, { status: 401 });
}
let upstream: Response;
try {
upstream = await fetch(`${GATEWAY_URL}/auth/me`, {
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
} catch {
return NextResponse.json({ error: "gateway_unreachable" }, { status: 502 });
}
if (!upstream.ok) {
return NextResponse.json({ error: "not_authenticated" }, { status: 401 });
}
const user = (await upstream.json()) as AuthUser;
return NextResponse.json(user);
}
+5
View File
@@ -0,0 +1,5 @@
/** Shared between the login/logout/me route handlers and `middleware.ts` —
* kept dependency-free (no Node-only imports) so `middleware.ts` can import
* it too; Next's Edge runtime middleware can't use arbitrary Node APIs. */
export const SESSION_COOKIE = "dt_session";
export const SESSION_MAX_AGE_SECONDS = 12 * 60 * 60; // matches auth-service's default JWT_EXPIRES_IN