diff --git a/backend/internal/service/oidc_service.go b/backend/internal/service/oidc_service.go index b42abfd7..115520fd 100644 --- a/backend/internal/service/oidc_service.go +++ b/backend/internal/service/oidc_service.go @@ -73,7 +73,7 @@ func (s *OidcService) Authorize(ctx context.Context, input dto.AuthorizeOidcClie } // Get the callback URL of the client. Return an error if the provided callback URL is not allowed - callbackURL, err := s.getCallbackURL(client.CallbackURLs, input.CallbackURL, input.ClientID, tx, ctx) + callbackURL, err := s.getCallbackURL(&client, input.CallbackURL, tx, ctx) if err != nil { return "", "", err } @@ -947,13 +947,12 @@ func (s *OidcService) ValidateEndSession(ctx context.Context, input dto.OidcLogo return "", &common.OidcNoCallbackURLError{} } - callbackURL, err := s.getCallbackURL(userAuthorizedOIDCClient.Client.LogoutCallbackURLs, input.PostLogoutRedirectUri, userAuthorizedOIDCClient.Client.ID, s.db, ctx) + callbackURL, err := s.getLogoutCallbackURL(&userAuthorizedOIDCClient.Client, input.PostLogoutRedirectUri) if err != nil { return "", err } return callbackURL, nil - } func (s *OidcService) createAuthorizationCode(ctx context.Context, clientID string, userID string, scope string, nonce string, codeChallenge string, codeChallengeMethod string, tx *gorm.DB) (string, error) { @@ -1006,50 +1005,71 @@ func (s *OidcService) validateCodeVerifier(codeVerifier, codeChallenge string, c return encodedVerifierHash == codeChallenge } -func (s *OidcService) getCallbackURL(urls []string, inputCallbackURL string, clientID string, tx *gorm.DB, ctx context.Context) (callbackURL string, err error) { +func (s *OidcService) getCallbackURL(client *model.OidcClient, inputCallbackURL string, tx *gorm.DB, ctx context.Context) (callbackURL string, err error) { // If no input callback URL provided, use the first configured URL if inputCallbackURL == "" { - if len(urls) > 0 { - return urls[0], nil + if len(client.CallbackURLs) > 0 { + return client.CallbackURLs[0], nil } // If no URLs are configured and no input URL, this is an error return "", &common.OidcMissingCallbackURLError{} } // If URLs are already configured, validate against them - if len(urls) > 0 { - for _, callbackPattern := range urls { - regexPattern := "^" + strings.ReplaceAll(regexp.QuoteMeta(callbackPattern), `\*`, ".*") + "$" - matched, err := regexp.MatchString(regexPattern, inputCallbackURL) - if err != nil { - return "", err - } - if matched { - return inputCallbackURL, nil - } + if len(client.CallbackURLs) > 0 { + matched, err := s.getCallbackURLFromList(client.CallbackURLs, inputCallbackURL) + if err != nil { + return "", err + } else if matched == "" { + return "", &common.OidcInvalidCallbackURLError{} } - return "", &common.OidcInvalidCallbackURLError{} + + return matched, nil } // If no URLs are configured, trust and store the first URL (TOFU) - err = s.addCallbackURLToClient(ctx, clientID, inputCallbackURL, tx) + err = s.addCallbackURLToClient(ctx, client, inputCallbackURL, tx) if err != nil { return "", err } return inputCallbackURL, nil } -func (s *OidcService) addCallbackURLToClient(ctx context.Context, clientID string, callbackURL string, tx *gorm.DB) error { - var client model.OidcClient - err := tx.WithContext(ctx).First(&client, "id = ?", clientID).Error - if err != nil { - return err +func (s *OidcService) getLogoutCallbackURL(client *model.OidcClient, inputLogoutCallbackURL string) (callbackURL string, err error) { + if inputLogoutCallbackURL == "" { + return client.LogoutCallbackURLs[0], nil } + matched, err := s.getCallbackURLFromList(client.LogoutCallbackURLs, inputLogoutCallbackURL) + if err != nil { + return "", err + } else if matched == "" { + return "", &common.OidcInvalidCallbackURLError{} + } + + return matched, nil +} + +func (s *OidcService) getCallbackURLFromList(urls []string, inputCallbackURL string) (callbackURL string, err error) { + for _, callbackPattern := range urls { + regexPattern := "^" + strings.ReplaceAll(regexp.QuoteMeta(callbackPattern), `\*`, ".*") + "$" + matched, err := regexp.MatchString(regexPattern, inputCallbackURL) + if err != nil { + return "", err + } + if matched { + return inputCallbackURL, nil + } + } + + return "", nil +} + +func (s *OidcService) addCallbackURLToClient(ctx context.Context, client *model.OidcClient, callbackURL string, tx *gorm.DB) error { // Add the new callback URL to the existing list client.CallbackURLs = append(client.CallbackURLs, callbackURL) - err = tx.WithContext(ctx).Save(&client).Error + err := tx.WithContext(ctx).Save(client).Error if err != nil { return err } diff --git a/frontend/messages/en-US.json b/frontend/messages/en-US.json index 0317f8cd..d798c524 100644 --- a/frontend/messages/en-US.json +++ b/frontend/messages/en-US.json @@ -341,6 +341,7 @@ "send_email": "Send Email", "show_code": "Show Code", "callback_url_description": "URL(s) provided by your client. Will be automatically added if left blank. Wildcards (*) are supported, but best avoided for better security.", + "logout_callback_url_description": "URL(s) provided by your client for logout. Wildcards (*) are supported, but best avoided for better security.", "api_key_expiration": "API Key Expiration", "send_an_email_to_the_user_when_their_api_key_is_about_to_expire": "Send an email to the user when their API key is about to expire.", "authorize_device": "Authorize Device", diff --git a/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte b/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte index cb10f56e..ea2a9850 100644 --- a/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte +++ b/frontend/src/routes/settings/admin/oidc-clients/oidc-callback-url-input.svelte @@ -9,11 +9,13 @@ let { label, + description, callbackURLs = $bindable(), error = $bindable(null), ...restProps }: HTMLAttributes & { label: string; + description: string; callbackURLs: string[]; error?: string | null; children?: Snippet; @@ -21,7 +23,7 @@
- +
{#each callbackURLs as _, i}
diff --git a/frontend/src/routes/settings/admin/oidc-clients/oidc-client-form.svelte b/frontend/src/routes/settings/admin/oidc-clients/oidc-client-form.svelte index 1c9f8767..ad8f5eba 100644 --- a/frontend/src/routes/settings/admin/oidc-clients/oidc-client-form.svelte +++ b/frontend/src/routes/settings/admin/oidc-clients/oidc-client-form.svelte @@ -84,12 +84,14 @@