(input: BuildAuthorizationUrlInput)
| 207 | /** Build an RFC 6749 §4.1.1 authorization URL. Sync; pre-computed |
| 208 | * challenge lets this stay out of the Promise world. */ |
| 209 | export const buildAuthorizationUrl = (input: BuildAuthorizationUrlInput): string => { |
| 210 | const url = new URL( |
| 211 | assertSupportedOAuthEndpointUrl( |
| 212 | input.authorizationUrl, |
| 213 | "Authorization URL", |
| 214 | input.endpointUrlPolicy, |
| 215 | ), |
| 216 | ); |
| 217 | // Benign default kept by design: a single space is the RFC 6749 scope |
| 218 | // separator. Callers targeting a legacy comma-separated provider pass |
| 219 | // `scopeSeparator` explicitly (see the field's JSDoc). |
| 220 | const separator = input.scopeSeparator ?? " "; |
| 221 | url.searchParams.set("client_id", input.clientId); |
| 222 | url.searchParams.set("redirect_uri", input.redirectUrl); |
| 223 | url.searchParams.set("response_type", "code"); |
| 224 | if (input.scopes.length > 0) { |
| 225 | url.searchParams.set("scope", input.scopes.join(separator)); |
| 226 | } |
| 227 | url.searchParams.set("state", input.state); |
| 228 | url.searchParams.set("code_challenge_method", "S256"); |
| 229 | url.searchParams.set("code_challenge", input.codeChallenge); |
| 230 | if (input.resource) { |
| 231 | url.searchParams.set("resource", input.resource); |
| 232 | } |
| 233 | if (input.extraParams) { |
| 234 | for (const [k, v] of Object.entries(input.extraParams)) { |
| 235 | url.searchParams.set(k, v); |
| 236 | } |
| 237 | } |
| 238 | return url.toString(); |
| 239 | }; |
| 240 | |
| 241 | /** Provider-specific authorize-URL extras that are NOT RFC 6749 params, so the |
| 242 | * generic flow must add them per-provider (keyed off the authorization host). |
no test coverage detected