(input: BuildAuthorizationUrlInput)
| 138 | /** Build an RFC 6749 §4.1.1 authorization URL. Sync; pre-computed |
| 139 | * challenge lets this stay out of the Promise world. */ |
| 140 | export const buildAuthorizationUrl = (input: BuildAuthorizationUrlInput): string => { |
| 141 | const url = new URL( |
| 142 | assertSupportedOAuthEndpointUrl( |
| 143 | input.authorizationUrl, |
| 144 | "Authorization URL", |
| 145 | input.endpointUrlPolicy, |
| 146 | ), |
| 147 | ); |
| 148 | // Benign default kept by design: a single space is the RFC 6749 scope |
| 149 | // separator. Callers targeting a legacy comma-separated provider pass |
| 150 | // `scopeSeparator` explicitly (see the field's JSDoc). |
| 151 | const separator = input.scopeSeparator ?? " "; |
| 152 | url.searchParams.set("client_id", input.clientId); |
| 153 | url.searchParams.set("redirect_uri", input.redirectUrl); |
| 154 | url.searchParams.set("response_type", "code"); |
| 155 | if (input.scopes.length > 0) { |
| 156 | url.searchParams.set("scope", input.scopes.join(separator)); |
| 157 | } |
| 158 | url.searchParams.set("state", input.state); |
| 159 | url.searchParams.set("code_challenge_method", "S256"); |
| 160 | url.searchParams.set("code_challenge", input.codeChallenge); |
| 161 | if (input.resource) { |
| 162 | url.searchParams.set("resource", input.resource); |
| 163 | } |
| 164 | if (input.extraParams) { |
| 165 | for (const [k, v] of Object.entries(input.extraParams)) { |
| 166 | url.searchParams.set(k, v); |
| 167 | } |
| 168 | } |
| 169 | return url.toString(); |
| 170 | }; |
| 171 | |
| 172 | /** Provider-specific authorize-URL extras that are NOT RFC 6749 params, so the |
| 173 | * generic flow must add them per-provider (keyed off the authorization host). |
no test coverage detected