mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-07 03:56:46 +03:00
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
import type { APIContext } from "astro";
|
|
import { createSecureErrorResponse } from "@/lib/utils";
|
|
import { requireAuth } from "@/lib/utils/auth-helpers";
|
|
|
|
// POST /api/sso/discover - Discover OIDC configuration from issuer URL
|
|
export async function POST(context: APIContext) {
|
|
try {
|
|
const { user, response } = await requireAuth(context);
|
|
if (response) return response;
|
|
|
|
const { issuer } = await context.request.json();
|
|
|
|
if (!issuer || typeof issuer !== 'string' || issuer.trim() === '') {
|
|
return new Response(JSON.stringify({ error: "Issuer URL is required and must be a valid string" }), {
|
|
status: 400,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// Validate issuer URL format while keeping trailing slash if provided
|
|
const trimmedIssuer = issuer.trim();
|
|
let parsedIssuer: URL;
|
|
try {
|
|
parsedIssuer = new URL(trimmedIssuer);
|
|
} catch (e) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
error: "Invalid issuer URL format",
|
|
details: `The provided URL "${issuer}" is not a valid URL. For Authentik, use format: https://your-authentik-domain/application/o/<app-slug>/`
|
|
}),
|
|
{
|
|
status: 400,
|
|
headers: { "Content-Type": "application/json" },
|
|
}
|
|
);
|
|
}
|
|
|
|
const issuerForDiscovery = trimmedIssuer.replace(/\/$/, "");
|
|
const discoveryUrl = `${issuerForDiscovery}/.well-known/openid-configuration`;
|
|
|
|
try {
|
|
// Fetch OIDC discovery document with timeout
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
|
|
let response: Response;
|
|
try {
|
|
response = await fetch(discoveryUrl, {
|
|
signal: controller.signal,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
}
|
|
});
|
|
} catch (fetchError) {
|
|
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
|
|
throw new Error(`Request timeout: The OIDC provider at ${trimmedIssuer} did not respond within 10 seconds`);
|
|
}
|
|
throw new Error(`Network error: Could not connect to ${trimmedIssuer}. Please verify the URL is correct and accessible.`);
|
|
} finally {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) {
|
|
throw new Error(`OIDC discovery document not found at ${discoveryUrl}. For Authentik, ensure you're using the correct application slug in the URL.`);
|
|
} else if (response.status >= 500) {
|
|
throw new Error(`OIDC provider error (${response.status}): The server at ${trimmedIssuer} returned an error.`);
|
|
} else {
|
|
throw new Error(`Failed to fetch discovery document (${response.status}): ${response.statusText}`);
|
|
}
|
|
}
|
|
|
|
let config: any;
|
|
try {
|
|
config = await response.json();
|
|
} catch (parseError) {
|
|
throw new Error(`Invalid response: The discovery document from ${trimmedIssuer} is not valid JSON.`);
|
|
}
|
|
|
|
// Extract the essential endpoints
|
|
const discoveredConfig = {
|
|
issuer: config.issuer || trimmedIssuer,
|
|
authorizationEndpoint: config.authorization_endpoint,
|
|
tokenEndpoint: config.token_endpoint,
|
|
userInfoEndpoint: config.userinfo_endpoint,
|
|
jwksEndpoint: config.jwks_uri,
|
|
// Additional useful fields
|
|
scopes: config.scopes_supported || ["openid", "profile", "email"],
|
|
responseTypes: config.response_types_supported || ["code"],
|
|
grantTypes: config.grant_types_supported || ["authorization_code"],
|
|
// Suggested domain from issuer
|
|
suggestedDomain: parsedIssuer.hostname.replace("www.", ""),
|
|
};
|
|
|
|
return new Response(JSON.stringify(discoveredConfig), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
} catch (error) {
|
|
console.error("OIDC discovery error:", error);
|
|
return new Response(
|
|
JSON.stringify({
|
|
error: "Failed to discover OIDC configuration",
|
|
details: error instanceof Error ? error.message : "Unknown error"
|
|
}),
|
|
{
|
|
status: 400,
|
|
headers: { "Content-Type": "application/json" },
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
return createSecureErrorResponse(error, "SSO discover API");
|
|
}
|
|
}
|