@hammadj/better-auth-sso 1.5.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +116 -0
  2. package/LICENSE.md +20 -0
  3. package/dist/client.d.mts +10 -0
  4. package/dist/client.mjs +15 -0
  5. package/dist/client.mjs.map +1 -0
  6. package/dist/index.d.mts +738 -0
  7. package/dist/index.mjs +2953 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +87 -0
  10. package/src/client.ts +29 -0
  11. package/src/constants.ts +58 -0
  12. package/src/domain-verification.test.ts +551 -0
  13. package/src/index.ts +265 -0
  14. package/src/linking/index.ts +2 -0
  15. package/src/linking/org-assignment.test.ts +325 -0
  16. package/src/linking/org-assignment.ts +176 -0
  17. package/src/linking/types.ts +10 -0
  18. package/src/oidc/discovery.test.ts +1157 -0
  19. package/src/oidc/discovery.ts +494 -0
  20. package/src/oidc/errors.ts +92 -0
  21. package/src/oidc/index.ts +31 -0
  22. package/src/oidc/types.ts +219 -0
  23. package/src/oidc.test.ts +688 -0
  24. package/src/providers.test.ts +1326 -0
  25. package/src/routes/domain-verification.ts +275 -0
  26. package/src/routes/providers.ts +565 -0
  27. package/src/routes/schemas.ts +96 -0
  28. package/src/routes/sso.ts +2750 -0
  29. package/src/saml/algorithms.test.ts +449 -0
  30. package/src/saml/algorithms.ts +338 -0
  31. package/src/saml/assertions.test.ts +239 -0
  32. package/src/saml/assertions.ts +62 -0
  33. package/src/saml/index.ts +13 -0
  34. package/src/saml/parser.ts +56 -0
  35. package/src/saml-state.ts +78 -0
  36. package/src/saml.test.ts +4319 -0
  37. package/src/types.ts +365 -0
  38. package/src/utils.test.ts +103 -0
  39. package/src/utils.ts +81 -0
  40. package/tsconfig.json +14 -0
  41. package/tsdown.config.ts +9 -0
  42. package/vitest.config.ts +3 -0
@@ -0,0 +1,738 @@
1
+ import { APIError } from "better-auth/api";
2
+ import { Awaitable, BetterAuthPlugin, OAuth2Tokens, User } from "better-auth";
3
+
4
+ //#region src/saml/algorithms.d.ts
5
+ declare const SignatureAlgorithm: {
6
+ readonly RSA_SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
7
+ readonly RSA_SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
8
+ readonly RSA_SHA384: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
9
+ readonly RSA_SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
10
+ readonly ECDSA_SHA256: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
11
+ readonly ECDSA_SHA384: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384";
12
+ readonly ECDSA_SHA512: "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512";
13
+ };
14
+ declare const DigestAlgorithm: {
15
+ readonly SHA1: "http://www.w3.org/2000/09/xmldsig#sha1";
16
+ readonly SHA256: "http://www.w3.org/2001/04/xmlenc#sha256";
17
+ readonly SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384";
18
+ readonly SHA512: "http://www.w3.org/2001/04/xmlenc#sha512";
19
+ };
20
+ declare const KeyEncryptionAlgorithm: {
21
+ readonly RSA_1_5: "http://www.w3.org/2001/04/xmlenc#rsa-1_5";
22
+ readonly RSA_OAEP: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p";
23
+ readonly RSA_OAEP_SHA256: "http://www.w3.org/2009/xmlenc11#rsa-oaep";
24
+ };
25
+ declare const DataEncryptionAlgorithm: {
26
+ readonly TRIPLEDES_CBC: "http://www.w3.org/2001/04/xmlenc#tripledes-cbc";
27
+ readonly AES_128_CBC: "http://www.w3.org/2001/04/xmlenc#aes128-cbc";
28
+ readonly AES_192_CBC: "http://www.w3.org/2001/04/xmlenc#aes192-cbc";
29
+ readonly AES_256_CBC: "http://www.w3.org/2001/04/xmlenc#aes256-cbc";
30
+ readonly AES_128_GCM: "http://www.w3.org/2009/xmlenc11#aes128-gcm";
31
+ readonly AES_192_GCM: "http://www.w3.org/2009/xmlenc11#aes192-gcm";
32
+ readonly AES_256_GCM: "http://www.w3.org/2009/xmlenc11#aes256-gcm";
33
+ };
34
+ type DeprecatedAlgorithmBehavior = "reject" | "warn" | "allow";
35
+ interface AlgorithmValidationOptions {
36
+ onDeprecated?: DeprecatedAlgorithmBehavior;
37
+ allowedSignatureAlgorithms?: string[];
38
+ allowedDigestAlgorithms?: string[];
39
+ allowedKeyEncryptionAlgorithms?: string[];
40
+ allowedDataEncryptionAlgorithms?: string[];
41
+ }
42
+ //#endregion
43
+ //#region src/types.d.ts
44
+ interface OIDCMapping {
45
+ id?: string | undefined;
46
+ email?: string | undefined;
47
+ emailVerified?: string | undefined;
48
+ name?: string | undefined;
49
+ image?: string | undefined;
50
+ extraFields?: Record<string, string> | undefined;
51
+ }
52
+ interface SAMLMapping {
53
+ id?: string | undefined;
54
+ email?: string | undefined;
55
+ emailVerified?: string | undefined;
56
+ name?: string | undefined;
57
+ firstName?: string | undefined;
58
+ lastName?: string | undefined;
59
+ extraFields?: Record<string, string> | undefined;
60
+ }
61
+ interface OIDCConfig {
62
+ issuer: string;
63
+ pkce: boolean;
64
+ clientId: string;
65
+ clientSecret: string;
66
+ authorizationEndpoint?: string | undefined;
67
+ discoveryEndpoint: string;
68
+ userInfoEndpoint?: string | undefined;
69
+ scopes?: string[] | undefined;
70
+ overrideUserInfo?: boolean | undefined;
71
+ tokenEndpoint?: string | undefined;
72
+ tokenEndpointAuthentication?: ("client_secret_post" | "client_secret_basic") | undefined;
73
+ jwksEndpoint?: string | undefined;
74
+ mapping?: OIDCMapping | undefined;
75
+ }
76
+ interface SAMLConfig {
77
+ issuer: string;
78
+ entryPoint: string;
79
+ cert: string;
80
+ callbackUrl: string;
81
+ audience?: string | undefined;
82
+ idpMetadata?: {
83
+ metadata?: string;
84
+ entityID?: string;
85
+ entityURL?: string;
86
+ redirectURL?: string;
87
+ cert?: string;
88
+ privateKey?: string;
89
+ privateKeyPass?: string;
90
+ isAssertionEncrypted?: boolean;
91
+ encPrivateKey?: string;
92
+ encPrivateKeyPass?: string;
93
+ singleSignOnService?: Array<{
94
+ Binding: string;
95
+ Location: string;
96
+ }>;
97
+ } | undefined;
98
+ spMetadata: {
99
+ metadata?: string | undefined;
100
+ entityID?: string | undefined;
101
+ binding?: string | undefined;
102
+ privateKey?: string | undefined;
103
+ privateKeyPass?: string | undefined;
104
+ isAssertionEncrypted?: boolean | undefined;
105
+ encPrivateKey?: string | undefined;
106
+ encPrivateKeyPass?: string | undefined;
107
+ };
108
+ wantAssertionsSigned?: boolean | undefined;
109
+ authnRequestsSigned?: boolean | undefined;
110
+ signatureAlgorithm?: string | undefined;
111
+ digestAlgorithm?: string | undefined;
112
+ identifierFormat?: string | undefined;
113
+ privateKey?: string | undefined;
114
+ decryptionPvk?: string | undefined;
115
+ additionalParams?: Record<string, any> | undefined;
116
+ mapping?: SAMLMapping | undefined;
117
+ }
118
+ type BaseSSOProvider = {
119
+ issuer: string;
120
+ oidcConfig?: OIDCConfig | undefined;
121
+ samlConfig?: SAMLConfig | undefined;
122
+ userId: string;
123
+ providerId: string;
124
+ organizationId?: string | undefined;
125
+ domain: string;
126
+ };
127
+ type SSOProvider<O extends SSOOptions> = O["domainVerification"] extends {
128
+ enabled: true;
129
+ } ? {
130
+ domainVerified: boolean;
131
+ } & BaseSSOProvider : BaseSSOProvider;
132
+ interface SSOOptions {
133
+ /**
134
+ * custom function to provision a user when they sign in with an SSO provider.
135
+ */
136
+ provisionUser?: ((data: {
137
+ /**
138
+ * The user object from the database
139
+ */
140
+ user: User & Record<string, any>;
141
+ /**
142
+ * The user info object from the provider
143
+ */
144
+ userInfo: Record<string, any>;
145
+ /**
146
+ * The OAuth2 tokens from the provider
147
+ */
148
+ token?: OAuth2Tokens;
149
+ /**
150
+ * The SSO provider
151
+ */
152
+ provider: SSOProvider<SSOOptions>;
153
+ }) => Awaitable<void>) | undefined;
154
+ /**
155
+ * Organization provisioning options
156
+ */
157
+ organizationProvisioning?: {
158
+ disabled?: boolean;
159
+ defaultRole?: "member" | "admin";
160
+ getRole?: (data: {
161
+ /**
162
+ * The user object from the database
163
+ */
164
+ user: User & Record<string, any>;
165
+ /**
166
+ * The user info object from the provider
167
+ */
168
+ userInfo: Record<string, any>;
169
+ /**
170
+ * The OAuth2 tokens from the provider
171
+ */
172
+ token?: OAuth2Tokens;
173
+ /**
174
+ * The SSO provider
175
+ */
176
+ provider: SSOProvider<SSOOptions>;
177
+ }) => Promise<"member" | "admin">;
178
+ } | undefined;
179
+ /**
180
+ * Default SSO provider configurations for testing.
181
+ * These will take the precedence over the database providers.
182
+ */
183
+ defaultSSO?: Array<{
184
+ /**
185
+ * The domain to match for this default provider.
186
+ * This is only used to match incoming requests to this default provider.
187
+ */
188
+ domain: string;
189
+ /**
190
+ * The provider ID to use
191
+ */
192
+ providerId: string;
193
+ /**
194
+ * SAML configuration
195
+ */
196
+ samlConfig?: SAMLConfig;
197
+ /**
198
+ * OIDC configuration
199
+ */
200
+ oidcConfig?: OIDCConfig;
201
+ }> | undefined;
202
+ /**
203
+ * Override user info with the provider info.
204
+ * @default false
205
+ */
206
+ defaultOverrideUserInfo?: boolean | undefined;
207
+ /**
208
+ * Disable implicit sign up for new users. When set to true for the provider,
209
+ * sign-in need to be called with with requestSignUp as true to create new users.
210
+ */
211
+ disableImplicitSignUp?: boolean | undefined;
212
+ /**
213
+ * The model name for the SSO provider table. Defaults to "ssoProvider".
214
+ */
215
+ modelName?: string;
216
+ /**
217
+ * Map fields
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * {
222
+ * samlConfig: "saml_config"
223
+ * }
224
+ * ```
225
+ */
226
+ fields?: {
227
+ issuer?: string | undefined;
228
+ oidcConfig?: string | undefined;
229
+ samlConfig?: string | undefined;
230
+ userId?: string | undefined;
231
+ providerId?: string | undefined;
232
+ organizationId?: string | undefined;
233
+ domain?: string | undefined;
234
+ };
235
+ /**
236
+ * Configure the maximum number of SSO providers a user can register.
237
+ * You can also pass a function that returns a number.
238
+ * Set to 0 to disable SSO provider registration.
239
+ *
240
+ * @example
241
+ * ```ts
242
+ * providersLimit: async (user) => {
243
+ * const plan = await getUserPlan(user);
244
+ * return plan.name === "pro" ? 10 : 1;
245
+ * }
246
+ * ```
247
+ * @default 10
248
+ */
249
+ providersLimit?: (number | ((user: User) => Awaitable<number>)) | undefined;
250
+ /**
251
+ * Trust the email verified flag from the provider.
252
+ *
253
+ * ⚠️ Use this with caution — it can lead to account takeover if misused. Only enable it if users **cannot freely register new providers**. You can
254
+ * prevent that by using `disabledPaths` or other safeguards to block provider registration from the client.
255
+ *
256
+ * If you want to allow account linking for specific trusted providers, enable the `accountLinking` option in your auth config and specify those
257
+ * providers in the `trustedProviders` list.
258
+ *
259
+ * @default false
260
+ *
261
+ * @deprecated This option is discouraged for new projects. Relying on provider-level `email_verified` is a weaker
262
+ * trust signal compared to using `trustedProviders` in `accountLinking` or enabling `domainVerification` for SSO.
263
+ * Existing configurations will continue to work, but new integrations should use explicit trust mechanisms.
264
+ * This option may be removed in a future major version.
265
+ */
266
+ trustEmailVerified?: boolean | undefined;
267
+ /**
268
+ * Enable domain verification on SSO providers
269
+ *
270
+ * When this option is enabled, new SSO providers will require the associated domain to be verified by the owner
271
+ * prior to allowing sign-ins.
272
+ */
273
+ domainVerification?: {
274
+ /**
275
+ * Enables or disables the domain verification feature
276
+ */
277
+ enabled?: boolean;
278
+ /**
279
+ * Prefix used to generate the domain verification token
280
+ *
281
+ * @default "better-auth-token-"
282
+ */
283
+ tokenPrefix?: string;
284
+ };
285
+ /**
286
+ * SAML security options for AuthnRequest/InResponseTo validation.
287
+ * This prevents unsolicited responses, replay attacks, and cross-provider injection.
288
+ */
289
+ saml?: {
290
+ /**
291
+ * Enable InResponseTo validation for SP-initiated SAML flows.
292
+ * When enabled, AuthnRequest IDs are tracked and validated against SAML responses.
293
+ *
294
+ * Storage behavior:
295
+ * - Uses `secondaryStorage` (e.g., Redis) if configured in your auth options
296
+ * - Falls back to the verification table in the database otherwise
297
+ *
298
+ * This works correctly in serverless environments without any additional configuration.
299
+ *
300
+ * @default false
301
+ */
302
+ enableInResponseToValidation?: boolean;
303
+ /**
304
+ * Allow IdP-initiated SSO (unsolicited SAML responses).
305
+ * When true, responses without InResponseTo are accepted.
306
+ * When false, all responses must correlate to a stored AuthnRequest.
307
+ *
308
+ * Only applies when InResponseTo validation is enabled.
309
+ *
310
+ * @default true
311
+ */
312
+ allowIdpInitiated?: boolean;
313
+ /**
314
+ * TTL for AuthnRequest records in milliseconds.
315
+ * Requests older than this will be rejected.
316
+ *
317
+ * Only applies when InResponseTo validation is enabled.
318
+ *
319
+ * @default 300000 (5 minutes)
320
+ */
321
+ requestTTL?: number;
322
+ /**
323
+ * Clock skew tolerance for SAML assertion timestamp validation in milliseconds.
324
+ * Allows for minor time differences between IdP and SP servers.
325
+ *
326
+ * Defaults to 300000 (5 minutes) to accommodate:
327
+ * - Network latency and processing time
328
+ * - Clock synchronization differences (NTP drift)
329
+ * - Distributed systems across timezones
330
+ *
331
+ * For stricter security, reduce to 1-2 minutes (60000-120000).
332
+ * For highly distributed systems, increase up to 10 minutes (600000).
333
+ *
334
+ * @default 300000 (5 minutes)
335
+ */
336
+ clockSkew?: number;
337
+ /**
338
+ * Require timestamp conditions (NotBefore/NotOnOrAfter) in SAML assertions.
339
+ * When enabled, assertions without timestamp conditions will be rejected.
340
+ *
341
+ * When disabled (default), assertions without timestamps are accepted
342
+ * but a warning is logged.
343
+ *
344
+ * **SAML Spec Notes:**
345
+ * - SAML 2.0 Core: Timestamps are OPTIONAL
346
+ * - SAML2Int (enterprise profile): Timestamps are REQUIRED
347
+ *
348
+ * **Recommendation:** Enable for enterprise/production deployments
349
+ * where your IdP follows SAML2Int (Okta, Azure AD, OneLogin, etc.)
350
+ *
351
+ * @default false
352
+ */
353
+ requireTimestamps?: boolean;
354
+ /**
355
+ * Algorithm validation options for SAML responses.
356
+ *
357
+ * Controls behavior when deprecated algorithms (SHA-1, RSA1_5, 3DES)
358
+ * are detected in SAML responses.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * algorithms: {
363
+ * onDeprecated: "reject" // Reject deprecated algorithms
364
+ * }
365
+ * ```
366
+ */
367
+ algorithms?: AlgorithmValidationOptions;
368
+ /**
369
+ * Maximum allowed size for SAML responses in bytes.
370
+ *
371
+ * @default 262144 (256KB)
372
+ */
373
+ maxResponseSize?: number;
374
+ /**
375
+ * Maximum allowed size for IdP metadata XML in bytes.
376
+ *
377
+ * @default 102400 (100KB)
378
+ */
379
+ maxMetadataSize?: number;
380
+ };
381
+ }
382
+ //#endregion
383
+ //#region src/routes/domain-verification.d.ts
384
+ declare const requestDomainVerification: (options: SSOOptions) => any;
385
+ declare const verifyDomain: (options: SSOOptions) => any;
386
+ //#endregion
387
+ //#region src/routes/providers.d.ts
388
+ declare const listSSOProviders: () => any;
389
+ declare const getSSOProvider: () => any;
390
+ declare const updateSSOProvider: (options: SSOOptions) => any;
391
+ declare const deleteSSOProvider: () => any;
392
+ //#endregion
393
+ //#region src/routes/sso.d.ts
394
+ interface TimestampValidationOptions {
395
+ clockSkew?: number;
396
+ requireTimestamps?: boolean;
397
+ logger?: {
398
+ warn: (message: string, data?: Record<string, unknown>) => void;
399
+ };
400
+ }
401
+ /** Conditions extracted from SAML assertion */
402
+ interface SAMLConditions {
403
+ notBefore?: string;
404
+ notOnOrAfter?: string;
405
+ }
406
+ /**
407
+ * Validates SAML assertion timestamp conditions (NotBefore/NotOnOrAfter).
408
+ * Prevents acceptance of expired or future-dated assertions.
409
+ * @throws {APIError} If timestamps are invalid, expired, or not yet valid
410
+ */
411
+ declare function validateSAMLTimestamp(conditions: SAMLConditions | undefined, options?: TimestampValidationOptions): void;
412
+ declare const spMetadata: () => any;
413
+ declare const registerSSOProvider: <O extends SSOOptions>(options: O) => any;
414
+ declare const signInSSO: (options?: SSOOptions) => any;
415
+ declare const callbackSSO: (options?: SSOOptions) => any;
416
+ declare const callbackSSOSAML: (options?: SSOOptions) => any;
417
+ declare const acsEndpoint: (options?: SSOOptions) => any;
418
+ //#endregion
419
+ //#region src/constants.d.ts
420
+ /**
421
+ * Default clock skew tolerance (5 minutes).
422
+ * Allows for minor time differences between IdP and SP servers.
423
+ *
424
+ * Accommodates:
425
+ * - Network latency and processing time
426
+ * - Clock synchronization differences (NTP drift)
427
+ * - Distributed systems across timezones
428
+ */
429
+ declare const DEFAULT_CLOCK_SKEW_MS: number;
430
+ /**
431
+ * Default maximum size for SAML responses (256 KB).
432
+ * Protects against memory exhaustion from oversized SAML payloads.
433
+ */
434
+ declare const DEFAULT_MAX_SAML_RESPONSE_SIZE: number;
435
+ /**
436
+ * Default maximum size for IdP metadata (100 KB).
437
+ * Protects against oversized metadata documents.
438
+ */
439
+ declare const DEFAULT_MAX_SAML_METADATA_SIZE: number;
440
+ //#endregion
441
+ //#region src/oidc/types.d.ts
442
+ /**
443
+ * OIDC Discovery Types
444
+ *
445
+ * Types for the OIDC discovery document and hydrated configuration.
446
+ * Based on OpenID Connect Discovery 1.0 specification.
447
+ *
448
+ * @see https://openid.net/specs/openid-connect-discovery-1_0.html
449
+ */
450
+ /**
451
+ * Raw OIDC Discovery Document as returned by the IdP's
452
+ * .well-known/openid-configuration endpoint.
453
+ *
454
+ * Required fields for Better Auth's OIDC support:
455
+ * - issuer
456
+ * - authorization_endpoint
457
+ * - token_endpoint
458
+ * - jwks_uri (required for ID token validation)
459
+ *
460
+ */
461
+ interface OIDCDiscoveryDocument {
462
+ /** REQUIRED. URL using the https scheme that the OP asserts as its Issuer Identifier. */
463
+ issuer: string;
464
+ /** REQUIRED. URL of the OP's OAuth 2.0 Authorization Endpoint. */
465
+ authorization_endpoint: string;
466
+ /**
467
+ * REQUIRED (spec says "unless only implicit flow is used").
468
+ * URL of the OP's OAuth 2.0 Token Endpoint.
469
+ * We only support authorization code flow.
470
+ */
471
+ token_endpoint: string;
472
+ /** REQUIRED. URL of the OP's JSON Web Key Set document for ID token validation. */
473
+ jwks_uri: string;
474
+ /** RECOMMENDED. URL of the OP's UserInfo Endpoint. */
475
+ userinfo_endpoint?: string;
476
+ /**
477
+ * OPTIONAL. JSON array containing a list of Client Authentication methods
478
+ * supported by this Token Endpoint.
479
+ * Default: ["client_secret_basic"]
480
+ */
481
+ token_endpoint_auth_methods_supported?: string[];
482
+ /** OPTIONAL. JSON array containing a list of the OAuth 2.0 scope values that this server supports. */
483
+ scopes_supported?: string[];
484
+ /** OPTIONAL. JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. */
485
+ response_types_supported?: string[];
486
+ /** OPTIONAL. JSON array containing a list of the Subject Identifier types that this OP supports. */
487
+ subject_types_supported?: string[];
488
+ /** OPTIONAL. JSON array containing a list of the JWS signing algorithms supported by the OP. */
489
+ id_token_signing_alg_values_supported?: string[];
490
+ /** OPTIONAL. JSON array containing a list of the claim names that the OP may supply values for. */
491
+ claims_supported?: string[];
492
+ /** OPTIONAL. URL of a page containing human-readable information about the OP. */
493
+ service_documentation?: string;
494
+ /** OPTIONAL. Boolean value specifying whether the OP supports use of the claims parameter. */
495
+ claims_parameter_supported?: boolean;
496
+ /** OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter. */
497
+ request_parameter_supported?: boolean;
498
+ /** OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter. */
499
+ request_uri_parameter_supported?: boolean;
500
+ /** OPTIONAL. Boolean value specifying whether the OP requires any request_uri values to be pre-registered. */
501
+ require_request_uri_registration?: boolean;
502
+ /** OPTIONAL. URL of the OP's end session endpoint. */
503
+ end_session_endpoint?: string;
504
+ /** OPTIONAL. URL of the OP's revocation endpoint. */
505
+ revocation_endpoint?: string;
506
+ /** OPTIONAL. URL of the OP's introspection endpoint. */
507
+ introspection_endpoint?: string;
508
+ /** OPTIONAL. JSON array of PKCE code challenge methods supported (e.g., "S256", "plain"). */
509
+ code_challenge_methods_supported?: string[];
510
+ /** Allow additional fields from the discovery document */
511
+ [key: string]: unknown;
512
+ }
513
+ /**
514
+ * Error codes for OIDC discovery operations.
515
+ */
516
+ type DiscoveryErrorCode = /** Request to discovery endpoint timed out */"discovery_timeout" /** Discovery endpoint returned 404 or similar */ | "discovery_not_found" /** Discovery endpoint returned invalid JSON */ | "discovery_invalid_json" /** Discovery URL is invalid or malformed */ | "discovery_invalid_url" /** Discovery URL is not trusted by the trusted origins configuration */ | "discovery_untrusted_origin" /** Discovery document issuer doesn't match configured issuer */ | "issuer_mismatch" /** Discovery document is missing required fields */ | "discovery_incomplete" /** IdP only advertises token auth methods that Better Auth doesn't currently support */ | "unsupported_token_auth_method" /** Catch-all for unexpected errors */ | "discovery_unexpected_error";
517
+ /**
518
+ * Custom error class for OIDC discovery failures.
519
+ * Can be caught and mapped to APIError at the edge.
520
+ */
521
+ declare class DiscoveryError extends Error {
522
+ readonly code: DiscoveryErrorCode;
523
+ readonly details?: Record<string, unknown>;
524
+ constructor(code: DiscoveryErrorCode, message: string, details?: Record<string, unknown>, options?: {
525
+ cause?: unknown;
526
+ });
527
+ }
528
+ /**
529
+ * Hydrated OIDC configuration after discovery.
530
+ * This is the normalized shape that gets persisted to the database
531
+ * or merged into provider config at runtime.
532
+ *
533
+ * Field names are camelCase to match Better Auth conventions.
534
+ */
535
+ interface HydratedOIDCConfig {
536
+ /** The issuer URL (validated to match configured issuer) */
537
+ issuer: string;
538
+ /** The discovery endpoint URL */
539
+ discoveryEndpoint: string;
540
+ /** URL of the authorization endpoint */
541
+ authorizationEndpoint: string;
542
+ /** URL of the token endpoint */
543
+ tokenEndpoint: string;
544
+ /** URL of the JWKS endpoint */
545
+ jwksEndpoint: string;
546
+ /** URL of the userinfo endpoint (optional) */
547
+ userInfoEndpoint?: string;
548
+ /** Token endpoint authentication method */
549
+ tokenEndpointAuthentication?: "client_secret_basic" | "client_secret_post";
550
+ /** Scopes supported by the IdP */
551
+ scopesSupported?: string[];
552
+ }
553
+ /**
554
+ * Parameters for the discoverOIDCConfig function.
555
+ */
556
+ interface DiscoverOIDCConfigParams {
557
+ /** The issuer URL to discover configuration from */
558
+ issuer: string;
559
+ /**
560
+ * Optional existing configuration.
561
+ * Values provided here will override discovered values.
562
+ */
563
+ existingConfig?: Partial<HydratedOIDCConfig>;
564
+ /**
565
+ * Optional custom discovery endpoint URL.
566
+ * If not provided, defaults to <issuer>/.well-known/openid-configuration
567
+ */
568
+ discoveryEndpoint?: string;
569
+ /**
570
+ * Optional timeout in milliseconds for the discovery request.
571
+ * @default 10000 (10 seconds)
572
+ */
573
+ timeout?: number;
574
+ /**
575
+ * Trusted origin predicate. See "trustedOrigins" option
576
+ * @param url the url to test
577
+ * @returns {boolean} return true for urls that belong to a trusted origin and false otherwise
578
+ */
579
+ isTrustedOrigin: (url: string) => boolean;
580
+ }
581
+ /**
582
+ * Required fields that must be present in a valid discovery document.
583
+ */
584
+ declare const REQUIRED_DISCOVERY_FIELDS: readonly ["issuer", "authorization_endpoint", "token_endpoint", "jwks_uri"];
585
+ type RequiredDiscoveryField = (typeof REQUIRED_DISCOVERY_FIELDS)[number];
586
+ //#endregion
587
+ //#region src/oidc/discovery.d.ts
588
+ /**
589
+ * Main entry point: Discover and hydrate OIDC configuration from an issuer.
590
+ *
591
+ * This function:
592
+ * 1. Computes the discovery URL from the issuer
593
+ * 2. Validates the discovery URL
594
+ * 3. Fetches the discovery document
595
+ * 4. Validates the discovery document (issuer match + required fields)
596
+ * 5. Normalizes URLs
597
+ * 6. Selects token endpoint auth method
598
+ * 7. Merges with existing config (existing values take precedence)
599
+ *
600
+ * @param params - Discovery parameters
601
+ * @param isTrustedOrigin - Origin verification tester function
602
+ * @returns Hydrated OIDC configuration ready for persistence
603
+ * @throws DiscoveryError on any failure
604
+ */
605
+ declare function discoverOIDCConfig(params: DiscoverOIDCConfigParams): Promise<HydratedOIDCConfig>;
606
+ /**
607
+ * Compute the discovery URL from an issuer URL.
608
+ *
609
+ * Per OIDC Discovery spec, the discovery document is located at:
610
+ * <issuer>/.well-known/openid-configuration
611
+ *
612
+ * Handles trailing slashes correctly.
613
+ */
614
+ declare function computeDiscoveryUrl(issuer: string): string;
615
+ /**
616
+ * Validate a discovery URL before fetching.
617
+ *
618
+ * @param url - The discovery URL to validate
619
+ * @param isTrustedOrigin - Origin verification tester function
620
+ * @throws DiscoveryError if URL is invalid
621
+ */
622
+ declare function validateDiscoveryUrl(url: string, isTrustedOrigin: DiscoverOIDCConfigParams["isTrustedOrigin"]): void;
623
+ /**
624
+ * Fetch the OIDC discovery document from the IdP.
625
+ *
626
+ * @param url - The discovery endpoint URL
627
+ * @param timeout - Request timeout in milliseconds
628
+ * @returns The parsed discovery document
629
+ * @throws DiscoveryError on network errors, timeouts, or invalid responses
630
+ */
631
+ declare function fetchDiscoveryDocument(url: string, timeout?: number): Promise<OIDCDiscoveryDocument>;
632
+ /**
633
+ * Validate a discovery document.
634
+ *
635
+ * Checks:
636
+ * 1. All required fields are present
637
+ * 2. Issuer matches the configured issuer (case-sensitive, exact match)
638
+ *
639
+ * Invariant: If this function returns without throwing, the document is safe
640
+ * to use for hydrating OIDC config (required fields present, issuer matches
641
+ * configured value, basic structural sanity verified).
642
+ *
643
+ * @param doc - The discovery document to validate
644
+ * @param configuredIssuer - The expected issuer value
645
+ * @throws DiscoveryError if validation fails
646
+ */
647
+ declare function validateDiscoveryDocument(doc: OIDCDiscoveryDocument, configuredIssuer: string): void;
648
+ /**
649
+ * Normalize URLs in the discovery document.
650
+ *
651
+ * @param document - The discovery document
652
+ * @param issuer - The base issuer URL
653
+ * @param isTrustedOrigin - Origin verification tester function
654
+ * @returns The normalized discovery document
655
+ */
656
+ declare function normalizeDiscoveryUrls(document: OIDCDiscoveryDocument, issuer: string, isTrustedOrigin: DiscoverOIDCConfigParams["isTrustedOrigin"]): OIDCDiscoveryDocument;
657
+ /**
658
+ * Normalize a single URL endpoint.
659
+ *
660
+ * @param name - The endpoint name (e.g token_endpoint)
661
+ * @param endpoint - The endpoint URL to normalize
662
+ * @param issuer - The base issuer URL
663
+ * @returns The normalized endpoint URL
664
+ */
665
+ declare function normalizeUrl(name: string, endpoint: string, issuer: string): string;
666
+ /**
667
+ * Select the token endpoint authentication method.
668
+ *
669
+ * @param doc - The discovery document
670
+ * @param existing - Existing authentication method from config
671
+ * @returns The selected authentication method
672
+ */
673
+ declare function selectTokenEndpointAuthMethod(doc: OIDCDiscoveryDocument, existing?: "client_secret_basic" | "client_secret_post"): "client_secret_basic" | "client_secret_post";
674
+ /**
675
+ * Check if a provider configuration needs runtime discovery.
676
+ *
677
+ * Returns true if we need discovery at runtime to complete the token exchange
678
+ * and validation. Specifically checks for:
679
+ * - `tokenEndpoint` - required for exchanging authorization code for tokens
680
+ * - `jwksEndpoint` - required for validating ID token signatures
681
+ *
682
+ * Note: `authorizationEndpoint` is handled separately in the sign-in flow,
683
+ * so it's not checked here.
684
+ *
685
+ * @param config - Partial OIDC config from the provider
686
+ * @returns true if runtime discovery should be performed
687
+ */
688
+ declare function needsRuntimeDiscovery(config: Partial<HydratedOIDCConfig> | undefined): boolean;
689
+ //#endregion
690
+ //#region src/index.d.ts
691
+ declare module "@better-auth/core" {
692
+ interface BetterAuthPluginRegistry<AuthOptions, Options> {
693
+ sso: {
694
+ creator: typeof sso;
695
+ };
696
+ }
697
+ }
698
+ type DomainVerificationEndpoints = {
699
+ requestDomainVerification: ReturnType<typeof requestDomainVerification>;
700
+ verifyDomain: ReturnType<typeof verifyDomain>;
701
+ };
702
+ type SSOEndpoints<O extends SSOOptions> = {
703
+ spMetadata: ReturnType<typeof spMetadata>;
704
+ registerSSOProvider: ReturnType<typeof registerSSOProvider<O>>;
705
+ signInSSO: ReturnType<typeof signInSSO>;
706
+ callbackSSO: ReturnType<typeof callbackSSO>;
707
+ callbackSSOSAML: ReturnType<typeof callbackSSOSAML>;
708
+ acsEndpoint: ReturnType<typeof acsEndpoint>;
709
+ listSSOProviders: ReturnType<typeof listSSOProviders>;
710
+ getSSOProvider: ReturnType<typeof getSSOProvider>;
711
+ updateSSOProvider: ReturnType<typeof updateSSOProvider>;
712
+ deleteSSOProvider: ReturnType<typeof deleteSSOProvider>;
713
+ };
714
+ type SSOPlugin<O extends SSOOptions> = {
715
+ id: "sso";
716
+ endpoints: SSOEndpoints<O> & (O extends {
717
+ domainVerification: {
718
+ enabled: true;
719
+ };
720
+ } ? DomainVerificationEndpoints : {});
721
+ };
722
+ declare function sso<O extends SSOOptions & {
723
+ domainVerification?: {
724
+ enabled: true;
725
+ };
726
+ }>(options?: O | undefined): {
727
+ id: "sso";
728
+ endpoints: SSOEndpoints<O> & DomainVerificationEndpoints;
729
+ schema: NonNullable<BetterAuthPlugin["schema"]>;
730
+ options: O;
731
+ };
732
+ declare function sso<O extends SSOOptions>(options?: O | undefined): {
733
+ id: "sso";
734
+ endpoints: SSOEndpoints<O>;
735
+ };
736
+ //#endregion
737
+ export { type AlgorithmValidationOptions, DEFAULT_CLOCK_SKEW_MS, DEFAULT_MAX_SAML_METADATA_SIZE, DEFAULT_MAX_SAML_RESPONSE_SIZE, DataEncryptionAlgorithm, type DeprecatedAlgorithmBehavior, DigestAlgorithm, type DiscoverOIDCConfigParams, DiscoveryError, type DiscoveryErrorCode, type HydratedOIDCConfig, KeyEncryptionAlgorithm, type OIDCConfig, type OIDCDiscoveryDocument, REQUIRED_DISCOVERY_FIELDS, type RequiredDiscoveryField, type SAMLConditions, type SAMLConfig, type SSOOptions, SSOPlugin, type SSOProvider, SignatureAlgorithm, type TimestampValidationOptions, computeDiscoveryUrl, discoverOIDCConfig, fetchDiscoveryDocument, needsRuntimeDiscovery, normalizeDiscoveryUrls, normalizeUrl, selectTokenEndpointAuthMethod, sso, validateDiscoveryDocument, validateDiscoveryUrl, validateSAMLTimestamp };
738
+ //# sourceMappingURL=index.d.mts.map