ultisuite-client/app/api/auth/callback/route.ts
R3D347HR4Y 5304790ed5
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat(auth): enhance session management and identity provider settings
- Added SessionGuard component to manage session expiration and online status.
- Updated AuthProvider to streamline session fetching and handling.
- Introduced IdentityProvidersSection for managing OAuth, SAML, and LDAP identity providers.
- Implemented identity provider guides for easier configuration.
- Enhanced mail settings with infinite scroll option for improved user experience.
- Updated global styles and layout components for better consistency across the application.
2026-06-09 09:36:46 +02:00

136 lines
3.7 KiB
TypeScript

import { cookies } from "next/headers"
import { NextResponse } from "next/server"
import {
resolveServerOidcConfig,
getAppOrigin,
oidcServerFetchHeaders,
} from "@/lib/auth/oidc-config"
import { platformUserFromToken } from "@/lib/auth/jwt-claims"
import {
applySessionCookies,
type TokenResponse,
} from "@/lib/auth/session"
const PKCE_COOKIE = "ulti_pkce_verifier"
const STATE_COOKIE = "ulti_oauth_state"
const INTENT_COOKIE = "ulti_auth_intent"
const PREVIOUS_SUB_COOKIE = "ulti_auth_previous_sub"
export async function GET(request: Request) {
const url = new URL(request.url)
const appOrigin = getAppOrigin()
const code = url.searchParams.get("code")
const state = url.searchParams.get("state")
const oauthError = url.searchParams.get("error")
if (oauthError) {
const desc = url.searchParams.get("error_description") ?? oauthError
return NextResponse.redirect(
new URL(`/login?error=${encodeURIComponent(desc)}`, appOrigin)
)
}
if (!code || !state) {
return NextResponse.redirect(
new URL("/login?error=missing_code", appOrigin)
)
}
const jar = await cookies()
const expectedState = jar.get(STATE_COOKIE)?.value
const verifier = jar.get(PKCE_COOKIE)?.value
const returnTo = jar.get("ulti_auth_return")?.value ?? "/mail/inbox"
const authIntent = jar.get(INTENT_COOKIE)?.value
const previousSub = jar.get(PREVIOUS_SUB_COOKIE)?.value
if (!expectedState || state !== expectedState || !verifier) {
return NextResponse.redirect(
new URL(
`/login?error=${encodeURIComponent(
!expectedState || !verifier
? "invalid_state:missing_oauth_cookies"
: "invalid_state:state_mismatch"
)}`,
appOrigin
)
)
}
let cfg
try {
cfg = await resolveServerOidcConfig()
} catch (err) {
const message =
err instanceof Error ? err.message : "oidc_discovery_failed"
return NextResponse.redirect(
new URL(`/login?error=${encodeURIComponent(message)}`, appOrigin)
)
}
const body = new URLSearchParams({
grant_type: "authorization_code",
client_id: cfg.clientId,
client_secret: cfg.clientSecret,
code,
redirect_uri: cfg.redirectUri,
code_verifier: verifier,
})
let tokens: TokenResponse
try {
const res = await fetch(cfg.tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
...oidcServerFetchHeaders(),
},
body,
})
if (!res.ok) {
const text = await res.text()
return NextResponse.redirect(
new URL(
`/login?error=${encodeURIComponent(`token_exchange_failed:${text.slice(0, 120)}`)}`,
appOrigin
)
)
}
tokens = (await res.json()) as TokenResponse
} catch (err) {
const message = err instanceof Error ? err.message : "token_exchange_failed"
return NextResponse.redirect(
new URL(`/login?error=${encodeURIComponent(message)}`, appOrigin)
)
}
if (!tokens.id_token) {
return NextResponse.redirect(
new URL("/login?error=no_id_token", appOrigin)
)
}
const bearer = tokens.id_token
const newUser = platformUserFromToken(bearer)
const completeUrl = new URL("/auth/complete", appOrigin)
completeUrl.searchParams.set("returnTo", returnTo)
if (
authIntent === "add_account" &&
previousSub &&
newUser?.sub === previousSub
) {
completeUrl.searchParams.set("accountNotice", "same")
}
const response = NextResponse.redirect(completeUrl)
response.cookies.delete(PKCE_COOKIE)
response.cookies.delete(STATE_COOKIE)
response.cookies.delete("ulti_auth_return")
response.cookies.delete(INTENT_COOKIE)
response.cookies.delete(PREVIOUS_SUB_COOKIE)
applySessionCookies(response, tokens, bearer)
return response
}