feat(auth): enterprise authentication and user management

This commit is contained in:
Schubert Ferenc 2026-07-02 22:19:12 +02:00
parent 4bc8b20a21
commit 86a32a942c
36 changed files with 2239 additions and 324 deletions

View file

@ -0,0 +1,68 @@
import { NextRequest, NextResponse } from "next/server";
import {
getHermesUrl,
readJson,
setAuthCookie,
upstreamConfigurationErrorResponse,
upstreamUnavailableResponse,
} from "@/lib/server/hermes";
import { assertSameOrigin } from "@/lib/server/request-guards";
type HermesLoginResponse = {
access_token: string;
expires_in: number;
user: {
id: number;
username: string;
email: string;
};
};
export async function POST(request: NextRequest) {
const originError = assertSameOrigin(request);
if (originError) {
return originError;
}
const credentials = await request.json();
const hermesUrl = getHermesUrl();
if (!hermesUrl) {
return upstreamConfigurationErrorResponse();
}
let hermesResponse: Response;
try {
hermesResponse = await fetch(`${hermesUrl}/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(credentials),
cache: "no-store",
});
} catch {
return upstreamUnavailableResponse();
}
const data = await readJson(hermesResponse);
if (!hermesResponse.ok) {
return NextResponse.json(data, {
status: hermesResponse.status,
});
}
const loginData = data as HermesLoginResponse;
const response = NextResponse.json({
user: loginData.user,
});
setAuthCookie(response, loginData.access_token, loginData.expires_in);
return response;
}

View file

@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from "next/server";
import { clearAuthCookie } from "@/lib/server/hermes";
import { assertSameOrigin } from "@/lib/server/request-guards";
export async function POST(request: NextRequest) {
const originError = assertSameOrigin(request);
if (originError) {
return originError;
}
const response = NextResponse.json({ ok: true });
clearAuthCookie(response);
return response;
}

View file

@ -0,0 +1,87 @@
import { NextRequest } from "next/server";
import {
clearAuthCookie,
getAccessToken,
getHermesUrl,
hermesJsonResponse,
unauthorizedResponse,
upstreamConfigurationErrorResponse,
upstreamUnavailableResponse,
} from "@/lib/server/hermes";
import { assertSameOrigin } from "@/lib/server/request-guards";
type Params = {
params: Promise<{
id: string;
}>;
};
async function proxyUserRequest(request: NextRequest, { params }: Params) {
const token = await getAccessToken();
if (!token) {
return unauthorizedResponse();
}
const hermesUrl = getHermesUrl();
if (!hermesUrl) {
return upstreamConfigurationErrorResponse();
}
const { id } = await params;
const action = request.nextUrl.searchParams.get("action");
const path = action === "password" ? `/users/${id}/password` : `/users/${id}`;
let hermesResponse: Response;
try {
hermesResponse = await fetch(`${hermesUrl}${path}`, {
method: request.method,
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: request.method === "GET" || request.method === "DELETE"
? undefined
: await request.text(),
cache: "no-store",
});
} catch {
return upstreamUnavailableResponse();
}
if (hermesResponse.status === 401) {
const response = await hermesJsonResponse(hermesResponse);
clearAuthCookie(response);
return response;
}
return hermesJsonResponse(hermesResponse);
}
export async function GET(request: NextRequest, context: Params) {
return proxyUserRequest(request, context);
}
export async function PUT(request: NextRequest, context: Params) {
const originError = assertSameOrigin(request);
if (originError) {
return originError;
}
return proxyUserRequest(request, context);
}
export async function DELETE(request: NextRequest, context: Params) {
const originError = assertSameOrigin(request);
if (originError) {
return originError;
}
return proxyUserRequest(request, context);
}

View file

@ -0,0 +1,65 @@
import { NextRequest } from "next/server";
import {
clearAuthCookie,
getAccessToken,
getHermesUrl,
hermesJsonResponse,
unauthorizedResponse,
upstreamConfigurationErrorResponse,
upstreamUnavailableResponse,
} from "@/lib/server/hermes";
import { assertSameOrigin } from "@/lib/server/request-guards";
async function proxyUsersRequest(request: NextRequest) {
const token = await getAccessToken();
if (!token) {
return unauthorizedResponse();
}
const hermesUrl = getHermesUrl();
if (!hermesUrl) {
return upstreamConfigurationErrorResponse();
}
let hermesResponse: Response;
try {
hermesResponse = await fetch(`${hermesUrl}/users${request.nextUrl.search}`, {
method: request.method,
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: request.method === "GET" ? undefined : await request.text(),
cache: "no-store",
});
} catch {
return upstreamUnavailableResponse();
}
if (hermesResponse.status === 401) {
const response = await hermesJsonResponse(hermesResponse);
clearAuthCookie(response);
return response;
}
return hermesJsonResponse(hermesResponse);
}
export async function GET(request: NextRequest) {
return proxyUsersRequest(request);
}
export async function POST(request: NextRequest) {
const originError = assertSameOrigin(request);
if (originError) {
return originError;
}
return proxyUsersRequest(request);
}