feat(auth): enterprise authentication and user management
This commit is contained in:
parent
4bc8b20a21
commit
86a32a942c
36 changed files with 2239 additions and 324 deletions
68
frontend/athena/app/api/login/route.ts
Normal file
68
frontend/athena/app/api/login/route.ts
Normal 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;
|
||||
}
|
||||
18
frontend/athena/app/api/logout/route.ts
Normal file
18
frontend/athena/app/api/logout/route.ts
Normal 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;
|
||||
}
|
||||
87
frontend/athena/app/api/users/[id]/route.ts
Normal file
87
frontend/athena/app/api/users/[id]/route.ts
Normal 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);
|
||||
}
|
||||
65
frontend/athena/app/api/users/route.ts
Normal file
65
frontend/athena/app/api/users/route.ts
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue