GitHub Security Advisory: GHSA-x3f4-v83f-7wp2
Package: github.com/authorizerdev/authorizer (GO)
Severity: HIGH (CVSS 0.0)
CWE: CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
Vulnerable versions: < 0.0.0[PII_REDACTED]5140-6d9bef1aaba3
Patched in: 0.0.0[PII_REDACTED]5140-6d9bef1aaba3

Summary: Authorizer: Password reset token theft and full auth token redirect via unvalidated redirect_uri

Details:
Hi,

I found that 6 endpoints in Authorizer accept a user-controlled `redirect_uri` and append sensitive tokens to it without validating the URL against `AllowedOrigins`. The OAuth `/app` handler validates redirect_uri at `http_handlers/app.go:46`, but the GraphQL mutations and verify_email handler skip validation entirely. An attacker can steal password reset tokens, magic link tokens, and full auth sessions (access_token + id_token + refresh_token) by pointing redirect_uri to their server. Verified against HEAD (commit 73679fa).

## Affected Endpoints

1. **ForgotPassword** (`internal/graphql/forgot_password.go:76-77`) - password reset tokens
2. **MagicLinkLogin** (`internal/graphql/magic_link_login.go:150-151`) - magic link auth tokens
3. **Signup** (`internal/graphql/signup.go:211-212`) - email verification tokens
4. **InviteMembers** (`internal/graphql/invite_members.go:90-91`) - invitation tokens
5. **OAuthLoginHandler** (`internal/http_handlers/oauth_login.go:18-20`) - OAuth redirect stored in state
6. **VerifyEmailHandler** (`internal/http_handlers/verify_email.go:27,178`) - full auth tokens (access + id + refresh)

## Root Cause

Because these 6 endpoints completely lack the `validators.IsValidOrigin()` check, this vulnerability bypasses secure configurations. Even if a production administrator strictly configures `AllowedOrigins` to `["https://my-secure-app.com"]`, an attacker can still steal tokens by passing `https://attacker.com` to these specific GraphQL mutations. The validation only exists in the `/app` OAuth handler, not in any of the GraphQL mutations.

In `forgot_password.go:76-77`, the user-supplied `redirect_uri` is accepted without validation:

    if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" {
        redirectURI = refs.StringValue(params.RedirectURI)
    }

The reset token is appended to this URL at `internal/utils/common.go:77`:

    func GetForgotPasswordURL(token, redirectURI string) string {
        verificationURL := redirectURI + "?token=" + token
        return verificationURL
    }

Compare with the OAuth flow at `internal/http_handlers/app.go:46` which validates correctly:

    if !validators.IsValidOrigin(redirectURI, h.Config.AllowedOrigins) {
        c.JSON(400, gin.H{"error": "invalid redirect url"})
        return
    }

This validation is missing from all 6 endpoints listed above.

## Most Severe Path: Full Token Theft via verify_email

After a user clicks the verification link, `verify_email.go:178` generates full auth tokens and redirects to the (unvalidated) URL:

    params := "access_token=" + authToken.AccessToken.Token +
        "&token_type=bearer&expires_in=" + ... +
        "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce

The redirect_uri is stored in the JWT claim from the original request (attacker-controlled). The attacker receives the victim's access_token, id_token, and refresh_token directly.

Because tokens are appended as URL query parameters, they are also au