diff --git a/backend/internal/oidc/authorization_handler.go b/backend/internal/oidc/authorization_handler.go index a95e043f..fa036c62 100644 --- a/backend/internal/oidc/authorization_handler.go +++ b/backend/internal/oidc/authorization_handler.go @@ -1,6 +1,8 @@ package oidc import ( + "context" + "errors" "log/slog" "net/http" "strings" @@ -49,7 +51,7 @@ func (h *authorizationHandler) authorize(c *gin.Context) { query, err := h.authorizationService.interactionRequestQuery(ctx, interactionID) if err != nil { slog.WarnContext(ctx, "Failed to restore authorize request from interaction session", "error", err.Error()) - h.provider.WriteAuthorizeError(ctx, c.Writer, fosite.NewAuthorizeRequest(), err) + h.writeAuthorizeError(ctx, c, fosite.NewAuthorizeRequest(), err) return } c.Request.URL.RawQuery = query.Encode() @@ -63,7 +65,7 @@ func (h *authorizationHandler) authorize(c *gin.Context) { ar, err := h.provider.NewAuthorizeRequest(ctx, c.Request) if err != nil { slog.ErrorContext(ctx, "Failed to create authorize request", "error", err.Error()) - h.provider.WriteAuthorizeError(ctx, c.Writer, ar, err) + h.writeAuthorizeError(ctx, c, ar, err) return } @@ -80,7 +82,7 @@ func (h *authorizationHandler) authorize(c *gin.Context) { }) if err != nil { slog.ErrorContext(ctx, "Failed to authorize request", "error", err.Error()) - h.provider.WriteAuthorizeError(ctx, c.Writer, ar, err) + h.writeAuthorizeError(ctx, c, ar, err) return } @@ -92,7 +94,7 @@ func (h *authorizationHandler) authorize(c *gin.Context) { response, err := h.provider.NewAuthorizeResponse(ctx, ar, authorization.Session) if err != nil { slog.ErrorContext(ctx, "Failed to create authorize response", "error", err.Error()) - h.provider.WriteAuthorizeError(ctx, c.Writer, ar, err) + h.writeAuthorizeError(ctx, c, ar, err) return } @@ -103,25 +105,6 @@ func (h *authorizationHandler) authorize(c *gin.Context) { h.provider.WriteAuthorizeResponse(ctx, c.Writer, ar, response) } -func requestMetaFromGin(c *gin.Context) requestMeta { - return requestMeta{ - IPAddress: c.ClientIP(), - UserAgent: c.Request.UserAgent(), - } -} - -func authorizeRequestParams(requester fosite.AuthorizeRequester) map[string]string { - params := make(map[string]string) - for key, values := range requester.GetRequestForm() { - if len(values) == 0 || key == "request_uri" || key == "interaction" { - continue - } - params[key] = values[0] - } - - return params -} - func (h *authorizationHandler) getInteractionSession(c *gin.Context) { interactionID := c.Param("id") @@ -154,3 +137,43 @@ func (h *authorizationHandler) completeInteraction(c *gin.Context) { c.JSON(http.StatusOK, response) } + +func (h *authorizationHandler) writeAuthorizeError(ctx context.Context, c *gin.Context, ar fosite.AuthorizeRequester, err error) { + if ar.IsRedirectURIValid() { + // Send the error to the client + h.provider.WriteAuthorizeError(ctx, c.Writer, ar, err) + return + } + + // If no redirect URI is available, we can't send the error to the client, + // so we redirect to a generic error page instead. + errorMessage := "An unknown error occurred during the authorization request." + if err, ok := errors.AsType[*fosite.RFC6749Error](err); ok { + if err.HintField != "" { + errorMessage = err.HintField + } else if err.DescriptionField != "" { + errorMessage = err.DescriptionField + } + } + + c.Redirect(http.StatusFound, "/interaction/error?error="+errorMessage) +} + +func requestMetaFromGin(c *gin.Context) requestMeta { + return requestMeta{ + IPAddress: c.ClientIP(), + UserAgent: c.Request.UserAgent(), + } +} + +func authorizeRequestParams(requester fosite.AuthorizeRequester) map[string]string { + params := make(map[string]string) + for key, values := range requester.GetRequestForm() { + if len(values) == 0 || key == "request_uri" || key == "interaction" { + continue + } + params[key] = values[0] + } + + return params +} diff --git a/frontend/src/lib/components/header/header.svelte b/frontend/src/lib/components/header/header.svelte index f9f4e4de..431ce4ba 100644 --- a/frontend/src/lib/components/header/header.svelte +++ b/frontend/src/lib/components/header/header.svelte @@ -9,6 +9,7 @@ const authUrls = [ /^\/interaction$/, + /^\/interaction\/error$/, /^\/device$/, /^\/login(?:\/.*)?$/, /^\/logout$/, diff --git a/frontend/src/lib/components/login-wrapper.svelte b/frontend/src/lib/components/login-wrapper.svelte index d8a4eaf9..04ee7bfe 100644 --- a/frontend/src/lib/components/login-wrapper.svelte +++ b/frontend/src/lib/components/login-wrapper.svelte @@ -68,9 +68,8 @@ : 'justify-center'}" >
diff --git a/frontend/src/lib/utils/redirection-util.ts b/frontend/src/lib/utils/redirection-util.ts index eb787288..bc3e7560 100644 --- a/frontend/src/lib/utils/redirection-util.ts +++ b/frontend/src/lib/utils/redirection-util.ts @@ -18,7 +18,7 @@ export function getAuthRedirectPath(url: URL, user: User | null) { const isPublicPath = path.startsWith('/lc/') || - ['/interaction', '/login/alternative/code', '/device', '/health', '/healthz'].includes(path); + ['/interaction', '/interaction/error', '/login/alternative/code', '/device', '/health', '/healthz'].includes(path); const isAdminPath = path == '/settings/admin' || path.startsWith('/settings/admin/'); diff --git a/frontend/src/routes/authorize/components/client-provider-images.svelte b/frontend/src/routes/authorize/components/client-provider-images.svelte index cf9380b0..a18db86f 100644 --- a/frontend/src/routes/authorize/components/client-provider-images.svelte +++ b/frontend/src/routes/authorize/components/client-provider-images.svelte @@ -13,18 +13,16 @@ error, client }: { - success: boolean; - error: boolean; - client: OidcClientMetaData; + success?: boolean; + error?: boolean; + client?: OidcClientMetaData; } = $props(); let animationDone = $state(false); $effect(() => { if (success || error) { - setTimeout(() => { - animationDone = true; - }, 500); + setTimeout(() => (animationDone = true), client ? 500 : 0); } else { animationDone = false; } @@ -61,14 +59,14 @@
- {:else if client.hasLogo} + {:else if client?.hasLogo} {m.client_logo()} - {:else} + {:else if client?.name}
{client.name.charAt(0).toUpperCase()}
diff --git a/frontend/src/routes/interaction/+page.svelte b/frontend/src/routes/interaction/+page.svelte index 36bc0124..b0371d95 100644 --- a/frontend/src/routes/interaction/+page.svelte +++ b/frontend/src/routes/interaction/+page.svelte @@ -170,7 +170,7 @@
{:else if currentStep === 'consent'}
- +

+ import SignInWrapper from '$lib/components/login-wrapper.svelte'; + import { Button } from '$lib/components/ui/button'; + import { m } from '$lib/paraglide/messages'; + import OidcService from '$lib/services/oidc-service'; + import WebAuthnService from '$lib/services/webauthn-service'; + import userStore from '$lib/stores/user-store'; + import ClientProviderImages from '../../authorize/components/client-provider-images.svelte'; + import type { PageProps } from './$types'; + + const webauthnService = new WebAuthnService(); + const oidcService = new OidcService(); + + let { data }: PageProps = $props(); + let { error } = data; + + + + {m.error()} + + + + +

+ {m.error()} +

+

+ {error} +

+ + + diff --git a/frontend/src/routes/interaction/error/+page.ts b/frontend/src/routes/interaction/error/+page.ts new file mode 100644 index 00000000..ac7a70a5 --- /dev/null +++ b/frontend/src/routes/interaction/error/+page.ts @@ -0,0 +1,9 @@ +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ url }) => { + const error = url.searchParams.get('error') ?? "An unknown error occured." + + return { + error + }; +}; diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 3262877a..6714c6cb 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -9,7 +9,6 @@ import { getWebauthnErrorMessage } from '$lib/utils/error-util'; import { startAuthentication } from '@simplewebauthn/browser'; import { fade } from 'svelte/transition'; - import { cn } from 'tailwind-variants'; import LoginLogoErrorSuccessIndicator from './components/login-logo-error-success-indicator.svelte'; let { data } = $props(); @@ -63,7 +62,7 @@ {/if}