@better-auth/sso 1.6.22 → 1.6.24
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.
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { APIError } from "better-auth/api";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { Awaitable, BetterAuthPlugin, OAuth2Tokens, User } from "better-auth";
|
|
4
|
-
import * as better_call0 from "better-call";
|
|
5
|
-
|
|
6
4
|
//#region src/saml/algorithms.d.ts
|
|
7
5
|
declare const SignatureAlgorithm: {
|
|
8
6
|
readonly RSA_SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
|
|
@@ -80,6 +78,12 @@ interface SAMLConfig {
|
|
|
80
78
|
entryPoint: string;
|
|
81
79
|
cert: string;
|
|
82
80
|
callbackUrl: string;
|
|
81
|
+
/**
|
|
82
|
+
* Fallback absolute URL or same-origin relative path for IdP-initiated SAML
|
|
83
|
+
* responses when `RelayState` does not contain a safe callback, including
|
|
84
|
+
* validation error redirects.
|
|
85
|
+
*/
|
|
86
|
+
idpInitiatedCallbackUrl?: string | undefined;
|
|
83
87
|
audience?: string | undefined;
|
|
84
88
|
idpMetadata?: {
|
|
85
89
|
metadata?: string;
|
|
@@ -421,11 +425,17 @@ interface SSOOptions {
|
|
|
421
425
|
* @default false
|
|
422
426
|
*/
|
|
423
427
|
wantLogoutResponseSigned?: boolean;
|
|
428
|
+
/**
|
|
429
|
+
* Fallback absolute URL or same-origin relative path for IdP-initiated SAML
|
|
430
|
+
* responses when `RelayState` does not contain a safe callback, including
|
|
431
|
+
* validation error redirects.
|
|
432
|
+
*/
|
|
433
|
+
idpInitiatedCallbackUrl?: string | undefined;
|
|
424
434
|
};
|
|
425
435
|
}
|
|
426
436
|
//#endregion
|
|
427
437
|
//#region src/routes/domain-verification.d.ts
|
|
428
|
-
declare const requestDomainVerification: (options: SSOOptions) =>
|
|
438
|
+
declare const requestDomainVerification: (options: SSOOptions) => import("better-call").StrictEndpoint<"/sso/request-domain-verification", {
|
|
429
439
|
method: "POST";
|
|
430
440
|
body: z.ZodObject<{
|
|
431
441
|
providerId: z.ZodString;
|
|
@@ -447,7 +457,7 @@ declare const requestDomainVerification: (options: SSOOptions) => better_call0.S
|
|
|
447
457
|
};
|
|
448
458
|
};
|
|
449
459
|
};
|
|
450
|
-
use: ((inputContext:
|
|
460
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
451
461
|
session: {
|
|
452
462
|
session: Record<string, any> & {
|
|
453
463
|
id: string;
|
|
@@ -473,7 +483,7 @@ declare const requestDomainVerification: (options: SSOOptions) => better_call0.S
|
|
|
473
483
|
}, {
|
|
474
484
|
domainVerificationToken: string;
|
|
475
485
|
}>;
|
|
476
|
-
declare const verifyDomain: (options: SSOOptions) =>
|
|
486
|
+
declare const verifyDomain: (options: SSOOptions) => import("better-call").StrictEndpoint<"/sso/verify-domain", {
|
|
477
487
|
method: "POST";
|
|
478
488
|
body: z.ZodObject<{
|
|
479
489
|
providerId: z.ZodString;
|
|
@@ -498,7 +508,7 @@ declare const verifyDomain: (options: SSOOptions) => better_call0.StrictEndpoint
|
|
|
498
508
|
};
|
|
499
509
|
};
|
|
500
510
|
};
|
|
501
|
-
use: ((inputContext:
|
|
511
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
502
512
|
session: {
|
|
503
513
|
session: Record<string, any> & {
|
|
504
514
|
id: string;
|
|
@@ -524,9 +534,9 @@ declare const verifyDomain: (options: SSOOptions) => better_call0.StrictEndpoint
|
|
|
524
534
|
}, void>;
|
|
525
535
|
//#endregion
|
|
526
536
|
//#region src/routes/providers.d.ts
|
|
527
|
-
declare const listSSOProviders: () =>
|
|
537
|
+
declare const listSSOProviders: () => import("better-call").StrictEndpoint<"/sso/providers", {
|
|
528
538
|
method: "GET";
|
|
529
|
-
use: ((inputContext:
|
|
539
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
530
540
|
session: {
|
|
531
541
|
session: Record<string, any> & {
|
|
532
542
|
id: string;
|
|
@@ -583,6 +593,7 @@ declare const listSSOProviders: () => better_call0.StrictEndpoint<"/sso/provider
|
|
|
583
593
|
samlConfig: {
|
|
584
594
|
entryPoint: string;
|
|
585
595
|
callbackUrl: string;
|
|
596
|
+
idpInitiatedCallbackUrl: string | undefined;
|
|
586
597
|
audience: string | undefined;
|
|
587
598
|
wantAssertionsSigned: boolean | undefined;
|
|
588
599
|
authnRequestsSigned: boolean | undefined;
|
|
@@ -601,9 +612,9 @@ declare const listSSOProviders: () => better_call0.StrictEndpoint<"/sso/provider
|
|
|
601
612
|
spMetadataUrl: string;
|
|
602
613
|
}[];
|
|
603
614
|
}>;
|
|
604
|
-
declare const getSSOProvider: () =>
|
|
615
|
+
declare const getSSOProvider: () => import("better-call").StrictEndpoint<"/sso/get-provider", {
|
|
605
616
|
method: "GET";
|
|
606
|
-
use: ((inputContext:
|
|
617
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
607
618
|
session: {
|
|
608
619
|
session: Record<string, any> & {
|
|
609
620
|
id: string;
|
|
@@ -668,6 +679,7 @@ declare const getSSOProvider: () => better_call0.StrictEndpoint<"/sso/get-provid
|
|
|
668
679
|
samlConfig: {
|
|
669
680
|
entryPoint: string;
|
|
670
681
|
callbackUrl: string;
|
|
682
|
+
idpInitiatedCallbackUrl: string | undefined;
|
|
671
683
|
audience: string | undefined;
|
|
672
684
|
wantAssertionsSigned: boolean | undefined;
|
|
673
685
|
authnRequestsSigned: boolean | undefined;
|
|
@@ -685,9 +697,9 @@ declare const getSSOProvider: () => better_call0.StrictEndpoint<"/sso/get-provid
|
|
|
685
697
|
} | undefined;
|
|
686
698
|
spMetadataUrl: string;
|
|
687
699
|
}>;
|
|
688
|
-
declare const updateSSOProvider: (options: SSOOptions) =>
|
|
700
|
+
declare const updateSSOProvider: (options: SSOOptions) => import("better-call").StrictEndpoint<"/sso/update-provider", {
|
|
689
701
|
method: "POST";
|
|
690
|
-
use: ((inputContext:
|
|
702
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
691
703
|
session: {
|
|
692
704
|
session: Record<string, any> & {
|
|
693
705
|
id: string;
|
|
@@ -741,6 +753,7 @@ declare const updateSSOProvider: (options: SSOOptions) => better_call0.StrictEnd
|
|
|
741
753
|
entryPoint: z.ZodOptional<z.ZodString>;
|
|
742
754
|
cert: z.ZodOptional<z.ZodString>;
|
|
743
755
|
callbackUrl: z.ZodOptional<z.ZodString>;
|
|
756
|
+
idpInitiatedCallbackUrl: z.ZodOptional<z.ZodString>;
|
|
744
757
|
audience: z.ZodOptional<z.ZodString>;
|
|
745
758
|
idpMetadata: z.ZodOptional<z.ZodObject<{
|
|
746
759
|
metadata: z.ZodOptional<z.ZodString>;
|
|
@@ -825,6 +838,7 @@ declare const updateSSOProvider: (options: SSOOptions) => better_call0.StrictEnd
|
|
|
825
838
|
samlConfig: {
|
|
826
839
|
entryPoint: string;
|
|
827
840
|
callbackUrl: string;
|
|
841
|
+
idpInitiatedCallbackUrl: string | undefined;
|
|
828
842
|
audience: string | undefined;
|
|
829
843
|
wantAssertionsSigned: boolean | undefined;
|
|
830
844
|
authnRequestsSigned: boolean | undefined;
|
|
@@ -842,9 +856,9 @@ declare const updateSSOProvider: (options: SSOOptions) => better_call0.StrictEnd
|
|
|
842
856
|
} | undefined;
|
|
843
857
|
spMetadataUrl: string;
|
|
844
858
|
}>;
|
|
845
|
-
declare const deleteSSOProvider: () =>
|
|
859
|
+
declare const deleteSSOProvider: () => import("better-call").StrictEndpoint<"/sso/delete-provider", {
|
|
846
860
|
method: "POST";
|
|
847
|
-
use: ((inputContext:
|
|
861
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
848
862
|
session: {
|
|
849
863
|
session: Record<string, any> & {
|
|
850
864
|
id: string;
|
|
@@ -913,7 +927,7 @@ interface SAMLConditions {
|
|
|
913
927
|
declare function validateSAMLTimestamp(conditions: SAMLConditions | undefined, options?: TimestampValidationOptions): void;
|
|
914
928
|
//#endregion
|
|
915
929
|
//#region src/routes/sso.d.ts
|
|
916
|
-
declare const spMetadata: (options?: SSOOptions) =>
|
|
930
|
+
declare const spMetadata: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/saml2/sp/metadata", {
|
|
917
931
|
method: "GET";
|
|
918
932
|
query: z.ZodObject<{
|
|
919
933
|
providerId: z.ZodString;
|
|
@@ -935,7 +949,7 @@ declare const spMetadata: (options?: SSOOptions) => better_call0.StrictEndpoint<
|
|
|
935
949
|
};
|
|
936
950
|
};
|
|
937
951
|
}, Response>;
|
|
938
|
-
declare const registerSSOProvider: <O extends SSOOptions>(options: O) =>
|
|
952
|
+
declare const registerSSOProvider: <O extends SSOOptions>(options: O) => import("better-call").StrictEndpoint<"/sso/register", {
|
|
939
953
|
method: "POST";
|
|
940
954
|
body: z.ZodObject<{
|
|
941
955
|
providerId: z.ZodString;
|
|
@@ -969,6 +983,7 @@ declare const registerSSOProvider: <O extends SSOOptions>(options: O) => better_
|
|
|
969
983
|
entryPoint: z.ZodString;
|
|
970
984
|
cert: z.ZodString;
|
|
971
985
|
callbackUrl: z.ZodString;
|
|
986
|
+
idpInitiatedCallbackUrl: z.ZodOptional<z.ZodString>;
|
|
972
987
|
audience: z.ZodOptional<z.ZodString>;
|
|
973
988
|
idpMetadata: z.ZodOptional<z.ZodObject<{
|
|
974
989
|
metadata: z.ZodOptional<z.ZodString>;
|
|
@@ -1015,7 +1030,7 @@ declare const registerSSOProvider: <O extends SSOOptions>(options: O) => better_
|
|
|
1015
1030
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
1016
1031
|
overrideUserInfo: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
1017
1032
|
}, z.core.$strip>;
|
|
1018
|
-
use: ((inputContext:
|
|
1033
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
1019
1034
|
session: {
|
|
1020
1035
|
session: Record<string, any> & {
|
|
1021
1036
|
id: string;
|
|
@@ -1213,7 +1228,7 @@ declare const registerSSOProvider: <O extends SSOOptions>(options: O) => better_
|
|
|
1213
1228
|
oidcConfig: OIDCConfig | null;
|
|
1214
1229
|
samlConfig: SAMLConfig | null;
|
|
1215
1230
|
} & Omit<SSOProvider<O>, "oidcConfig" | "samlConfig">>;
|
|
1216
|
-
declare const signInSSO: (options?: SSOOptions) =>
|
|
1231
|
+
declare const signInSSO: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sign-in/sso", {
|
|
1217
1232
|
method: "POST";
|
|
1218
1233
|
body: z.ZodObject<{
|
|
1219
1234
|
email: z.ZodOptional<z.ZodString>;
|
|
@@ -1307,7 +1322,7 @@ declare const signInSSO: (options?: SSOOptions) => better_call0.StrictEndpoint<"
|
|
|
1307
1322
|
url: string;
|
|
1308
1323
|
redirect: boolean;
|
|
1309
1324
|
}>;
|
|
1310
|
-
declare const callbackSSO: (options?: SSOOptions) =>
|
|
1325
|
+
declare const callbackSSO: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/callback/:providerId", {
|
|
1311
1326
|
method: "GET";
|
|
1312
1327
|
query: z.ZodObject<{
|
|
1313
1328
|
code: z.ZodOptional<z.ZodString>;
|
|
@@ -1335,7 +1350,7 @@ declare const callbackSSO: (options?: SSOOptions) => better_call0.StrictEndpoint
|
|
|
1335
1350
|
* Used when `options.redirectURI` is set — the `providerId` is read from
|
|
1336
1351
|
* the OAuth state instead of the URL path.
|
|
1337
1352
|
*/
|
|
1338
|
-
declare const callbackSSOShared: (options?: SSOOptions) =>
|
|
1353
|
+
declare const callbackSSOShared: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/callback", {
|
|
1339
1354
|
metadata: {
|
|
1340
1355
|
openapi: {
|
|
1341
1356
|
operationId: string;
|
|
@@ -1358,7 +1373,7 @@ declare const callbackSSOShared: (options?: SSOOptions) => better_call0.StrictEn
|
|
|
1358
1373
|
}, z.core.$strip>;
|
|
1359
1374
|
allowedMediaTypes: readonly ["application/x-www-form-urlencoded", "application/json"];
|
|
1360
1375
|
}, void>;
|
|
1361
|
-
declare const callbackSSOSAML: (options?: SSOOptions) =>
|
|
1376
|
+
declare const callbackSSOSAML: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/saml2/callback/:providerId", {
|
|
1362
1377
|
method: ("GET" | "POST")[];
|
|
1363
1378
|
body: z.ZodOptional<z.ZodObject<{
|
|
1364
1379
|
SAMLResponse: z.ZodString;
|
|
@@ -1388,7 +1403,7 @@ declare const callbackSSOSAML: (options?: SSOOptions) => better_call0.StrictEndp
|
|
|
1388
1403
|
scope: "server";
|
|
1389
1404
|
};
|
|
1390
1405
|
}, never>;
|
|
1391
|
-
declare const acsEndpoint: (options?: SSOOptions) =>
|
|
1406
|
+
declare const acsEndpoint: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/saml2/sp/acs/:providerId", {
|
|
1392
1407
|
method: "POST";
|
|
1393
1408
|
body: z.ZodObject<{
|
|
1394
1409
|
SAMLResponse: z.ZodString;
|
|
@@ -1409,7 +1424,7 @@ declare const acsEndpoint: (options?: SSOOptions) => better_call0.StrictEndpoint
|
|
|
1409
1424
|
scope: "server";
|
|
1410
1425
|
};
|
|
1411
1426
|
}, never>;
|
|
1412
|
-
declare const sloEndpoint: (options?: SSOOptions) =>
|
|
1427
|
+
declare const sloEndpoint: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/saml2/sp/slo/:providerId", {
|
|
1413
1428
|
method: ("GET" | "POST")[];
|
|
1414
1429
|
body: z.ZodOptional<z.ZodObject<{
|
|
1415
1430
|
SAMLRequest: z.ZodOptional<z.ZodString>;
|
|
@@ -1430,12 +1445,12 @@ declare const sloEndpoint: (options?: SSOOptions) => better_call0.StrictEndpoint
|
|
|
1430
1445
|
scope: "server";
|
|
1431
1446
|
};
|
|
1432
1447
|
}, void | Response>;
|
|
1433
|
-
declare const initiateSLO: (options?: SSOOptions) =>
|
|
1448
|
+
declare const initiateSLO: (options?: SSOOptions) => import("better-call").StrictEndpoint<"/sso/saml2/logout/:providerId", {
|
|
1434
1449
|
method: "POST";
|
|
1435
1450
|
body: z.ZodObject<{
|
|
1436
1451
|
callbackURL: z.ZodOptional<z.ZodString>;
|
|
1437
1452
|
}, z.core.$strip>;
|
|
1438
|
-
use: ((inputContext:
|
|
1453
|
+
use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
|
|
1439
1454
|
session: {
|
|
1440
1455
|
session: Record<string, any> & {
|
|
1441
1456
|
id: string;
|
|
@@ -1560,7 +1575,27 @@ interface OIDCDiscoveryDocument {
|
|
|
1560
1575
|
/**
|
|
1561
1576
|
* Error codes for OIDC discovery operations.
|
|
1562
1577
|
*/
|
|
1563
|
-
type DiscoveryErrorCode =
|
|
1578
|
+
type DiscoveryErrorCode =
|
|
1579
|
+
/** Request to discovery endpoint timed out */
|
|
1580
|
+
"discovery_timeout" |
|
|
1581
|
+
/** Discovery endpoint returned 404 or similar */
|
|
1582
|
+
"discovery_not_found" |
|
|
1583
|
+
/** Discovery endpoint returned invalid JSON */
|
|
1584
|
+
"discovery_invalid_json" |
|
|
1585
|
+
/** OIDC endpoint URL (discovery or per-endpoint: authorization, token, userinfo, jwks) is invalid, malformed, or uses a non-`http(s)` scheme */
|
|
1586
|
+
"discovery_invalid_url" |
|
|
1587
|
+
/** OIDC endpoint URL is not trusted by the trusted origins configuration */
|
|
1588
|
+
"discovery_untrusted_origin" |
|
|
1589
|
+
/** OIDC endpoint URL (discovery or per-endpoint) points to a host that is not publicly routable (loopback, RFC 1918, link-local, cloud metadata FQDN, etc.) */
|
|
1590
|
+
"discovery_private_host" |
|
|
1591
|
+
/** Discovery document issuer doesn't match configured issuer */
|
|
1592
|
+
"issuer_mismatch" |
|
|
1593
|
+
/** Discovery document is missing required fields */
|
|
1594
|
+
"discovery_incomplete" |
|
|
1595
|
+
/** IdP only advertises token auth methods that Better Auth doesn't currently support */
|
|
1596
|
+
"unsupported_token_auth_method" |
|
|
1597
|
+
/** Catch-all for unexpected errors */
|
|
1598
|
+
"discovery_unexpected_error";
|
|
1564
1599
|
/**
|
|
1565
1600
|
* Custom error class for OIDC discovery failures.
|
|
1566
1601
|
* Can be caught and mapped to APIError at the edge.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-
|
|
2
|
-
export { AlgorithmValidationOptions, DEFAULT_CLOCK_SKEW_MS, DEFAULT_MAX_SAML_METADATA_SIZE, DEFAULT_MAX_SAML_RESPONSE_SIZE, DataEncryptionAlgorithm, DeprecatedAlgorithmBehavior, DigestAlgorithm, DiscoverOIDCConfigParams, DiscoveryError, DiscoveryErrorCode, HydratedOIDCConfig, KeyEncryptionAlgorithm, OIDCConfig, OIDCDiscoveryDocument, REQUIRED_DISCOVERY_FIELDS, RequiredDiscoveryField, SAMLConditions, SAMLConfig, SSOOptions, SSOPlugin, SSOProvider, SignatureAlgorithm, TimestampValidationOptions, computeDiscoveryUrl, discoverOIDCConfig, fetchDiscoveryDocument, needsRuntimeDiscovery, normalizeDiscoveryUrls, normalizeUrl, selectTokenEndpointAuthMethod, sso, validateDiscoveryDocument, validateDiscoveryUrl, validateSAMLTimestamp };
|
|
1
|
+
import { A as DataEncryptionAlgorithm, C as TimestampValidationOptions, D as SSOOptions, E as SAMLConfig, M as DigestAlgorithm, N as KeyEncryptionAlgorithm, O as SSOProvider, P as SignatureAlgorithm, S as SAMLConditions, T as OIDCConfig, _ as REQUIRED_DISCOVERY_FIELDS, a as fetchDiscoveryDocument, b as DEFAULT_MAX_SAML_METADATA_SIZE, c as normalizeUrl, d as validateDiscoveryUrl, f as DiscoverOIDCConfigParams, g as OIDCDiscoveryDocument, h as HydratedOIDCConfig, i as discoverOIDCConfig, j as DeprecatedAlgorithmBehavior, k as AlgorithmValidationOptions, l as selectTokenEndpointAuthMethod, m as DiscoveryErrorCode, n as sso, o as needsRuntimeDiscovery, p as DiscoveryError, r as computeDiscoveryUrl, s as normalizeDiscoveryUrls, t as SSOPlugin, u as validateDiscoveryDocument, v as RequiredDiscoveryField, w as validateSAMLTimestamp, x as DEFAULT_MAX_SAML_RESPONSE_SIZE, y as DEFAULT_CLOCK_SKEW_MS } from "./index-P-Jf_8P1.mjs";
|
|
2
|
+
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
1
|
+
import { t as PACKAGE_VERSION } from "./version-DsNmK8I4.mjs";
|
|
2
2
|
import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
|
|
3
3
|
import { XMLParser, XMLValidator } from "fast-xml-parser";
|
|
4
4
|
import { X509Certificate } from "node:crypto";
|
|
@@ -58,6 +58,8 @@ const DEFAULT_MAX_SAML_RESPONSE_SIZE = 256 * 1024;
|
|
|
58
58
|
const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region src/utils.ts
|
|
61
|
+
const unsafeSAMLRedirectPathPrefix = /^\/(?:\/|\\|%2f|%5c)/i;
|
|
62
|
+
const isSafeSAMLRedirectPath = (url) => url.startsWith("/") && !unsafeSAMLRedirectPathPrefix.test(url);
|
|
61
63
|
/**
|
|
62
64
|
* Safely parses a value that might be a JSON string or already a parsed object.
|
|
63
65
|
* This handles cases where ORMs like Drizzle might return already parsed objects
|
|
@@ -1105,6 +1107,8 @@ function validateSAMLResponseBinding(samlContent, options) {
|
|
|
1105
1107
|
}
|
|
1106
1108
|
//#endregion
|
|
1107
1109
|
//#region src/routes/schemas.ts
|
|
1110
|
+
const absoluteUrlSchema = z.url();
|
|
1111
|
+
const samlRedirectUrlSchema = z.string().refine((url) => isSafeSAMLRedirectPath(url) || absoluteUrlSchema.safeParse(url).success, { message: "Expected an absolute URL or a relative path starting with /" });
|
|
1108
1112
|
const oidcMappingSchema = z.object({
|
|
1109
1113
|
id: z.string().optional(),
|
|
1110
1114
|
email: z.string().optional(),
|
|
@@ -1140,6 +1144,7 @@ const samlConfigSchema = z.object({
|
|
|
1140
1144
|
entryPoint: z.string().url().optional(),
|
|
1141
1145
|
cert: z.string().optional(),
|
|
1142
1146
|
callbackUrl: z.string().url().optional(),
|
|
1147
|
+
idpInitiatedCallbackUrl: samlRedirectUrlSchema.optional(),
|
|
1143
1148
|
audience: z.string().optional(),
|
|
1144
1149
|
idpMetadata: z.object({
|
|
1145
1150
|
metadata: z.string().optional(),
|
|
@@ -1292,6 +1297,7 @@ function sanitizeProvider(provider, baseURL) {
|
|
|
1292
1297
|
samlConfig: samlConfig ? {
|
|
1293
1298
|
entryPoint: samlConfig.entryPoint,
|
|
1294
1299
|
callbackUrl: samlConfig.callbackUrl,
|
|
1300
|
+
idpInitiatedCallbackUrl: samlConfig.idpInitiatedCallbackUrl,
|
|
1295
1301
|
audience: samlConfig.audience,
|
|
1296
1302
|
wantAssertionsSigned: samlConfig.wantAssertionsSigned,
|
|
1297
1303
|
authnRequestsSigned: samlConfig.authnRequestsSigned,
|
|
@@ -1395,6 +1401,7 @@ function mergeSAMLConfig(current, updates, issuer) {
|
|
|
1395
1401
|
entryPoint: updates.entryPoint ?? current.entryPoint,
|
|
1396
1402
|
cert: updates.cert ?? current.cert,
|
|
1397
1403
|
callbackUrl: updates.callbackUrl ?? current.callbackUrl,
|
|
1404
|
+
idpInitiatedCallbackUrl: updates.idpInitiatedCallbackUrl ?? current.idpInitiatedCallbackUrl,
|
|
1398
1405
|
spMetadata: updates.spMetadata ?? current.spMetadata,
|
|
1399
1406
|
idpMetadata: updates.idpMetadata ?? current.idpMetadata,
|
|
1400
1407
|
mapping: updates.mapping ?? current.mapping,
|
|
@@ -1686,6 +1693,9 @@ async function generateRelayState(c, link, additionalData) {
|
|
|
1686
1693
|
errorURL: c.body.errorCallbackURL,
|
|
1687
1694
|
newUserURL: c.body.newUserCallbackURL,
|
|
1688
1695
|
link,
|
|
1696
|
+
/**
|
|
1697
|
+
* This is the actual expiry time of the state
|
|
1698
|
+
*/
|
|
1689
1699
|
expiresAt: Date.now() + 600 * 1e3,
|
|
1690
1700
|
requestSignUp: c.body.requestSignUp
|
|
1691
1701
|
};
|
|
@@ -1706,6 +1716,10 @@ async function parseRelayState(c) {
|
|
|
1706
1716
|
try {
|
|
1707
1717
|
parsedData = await parseGenericState(c, state, {
|
|
1708
1718
|
cookieName: "relay_state",
|
|
1719
|
+
/**
|
|
1720
|
+
* SAML ACS receives a POST from the IdP, which is typically cross-origin.
|
|
1721
|
+
* SameSite=Lax (default) cookies are not sent on cross-site POST requests.
|
|
1722
|
+
*/
|
|
1709
1723
|
skipStateCookieCheck: true
|
|
1710
1724
|
});
|
|
1711
1725
|
} catch (error) {
|
|
@@ -1864,37 +1878,65 @@ function validateSAMLTimestamp(conditions, options = {}) {
|
|
|
1864
1878
|
}
|
|
1865
1879
|
//#endregion
|
|
1866
1880
|
//#region src/routes/saml-pipeline.ts
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
* - Falls back to appOrigin if URL is invalid or unsafe
|
|
1872
|
-
*/
|
|
1873
|
-
function getSafeRedirectUrl(url, callbackPath, appOrigin, isTrustedOrigin) {
|
|
1874
|
-
if (!url) return appOrigin;
|
|
1875
|
-
if (url.startsWith("/") && !url.startsWith("//")) {
|
|
1881
|
+
function getSafeRedirectCandidate(url, callbackPathname, appOrigin, isTrustedOrigin) {
|
|
1882
|
+
if (!url) return;
|
|
1883
|
+
if (url.startsWith("/")) {
|
|
1884
|
+
if (!isSafeSAMLRedirectPath(url)) return;
|
|
1876
1885
|
try {
|
|
1877
1886
|
const absoluteUrl = new URL(url, appOrigin);
|
|
1878
|
-
if (absoluteUrl.origin !== appOrigin) return
|
|
1879
|
-
|
|
1880
|
-
if (absoluteUrl.pathname === callbackPathname) return appOrigin;
|
|
1887
|
+
if (absoluteUrl.origin !== appOrigin || absoluteUrl.pathname === callbackPathname) return;
|
|
1888
|
+
return url;
|
|
1881
1889
|
} catch {
|
|
1882
|
-
return
|
|
1890
|
+
return;
|
|
1883
1891
|
}
|
|
1884
|
-
return url;
|
|
1885
1892
|
}
|
|
1886
|
-
|
|
1893
|
+
let absoluteUrl;
|
|
1887
1894
|
try {
|
|
1888
|
-
|
|
1889
|
-
if (new URL(url).pathname === callbackPathname) return appOrigin;
|
|
1895
|
+
absoluteUrl = new URL(url);
|
|
1890
1896
|
} catch {
|
|
1891
|
-
|
|
1897
|
+
return;
|
|
1892
1898
|
}
|
|
1899
|
+
if (absoluteUrl.origin !== appOrigin && !isTrustedOrigin(url, { allowRelativePaths: false })) return;
|
|
1900
|
+
if (absoluteUrl.origin === appOrigin && absoluteUrl.pathname === callbackPathname) return;
|
|
1893
1901
|
return url;
|
|
1894
1902
|
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Returns the first safe redirect URL from an ordered list of candidates.
|
|
1905
|
+
* - Prevents open redirect attacks by validating against trusted origins
|
|
1906
|
+
* - Prevents redirect loops by checking if URL points to callback route
|
|
1907
|
+
* - Tries the next candidate when a URL is invalid or unsafe
|
|
1908
|
+
* - Falls back to appOrigin when no candidate is safe
|
|
1909
|
+
*/
|
|
1910
|
+
function getSafeRedirectUrl(candidates, callbackPath, appOrigin, isTrustedOrigin) {
|
|
1911
|
+
const callbackPathname = new URL(callbackPath).pathname;
|
|
1912
|
+
for (const candidate of candidates) {
|
|
1913
|
+
const safeCandidate = getSafeRedirectCandidate(candidate, callbackPathname, appOrigin, isTrustedOrigin);
|
|
1914
|
+
if (safeCandidate) return safeCandidate;
|
|
1915
|
+
}
|
|
1916
|
+
return appOrigin;
|
|
1917
|
+
}
|
|
1895
1918
|
function buildSAMLRedirectUrl(url, params) {
|
|
1896
1919
|
const searchParams = new URLSearchParams(params);
|
|
1897
|
-
|
|
1920
|
+
try {
|
|
1921
|
+
const isRelativePath = url.startsWith("/") && !url.startsWith("//");
|
|
1922
|
+
const parsedUrl = new URL(url, "http://better-auth.local");
|
|
1923
|
+
for (const [key, value] of searchParams) parsedUrl.searchParams.set(key, value);
|
|
1924
|
+
if (isRelativePath) return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
|
|
1925
|
+
return parsedUrl.toString();
|
|
1926
|
+
} catch {
|
|
1927
|
+
const hashIndex = url.indexOf("#");
|
|
1928
|
+
const urlWithoutFragment = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
1929
|
+
const fragment = hashIndex === -1 ? "" : url.slice(hashIndex);
|
|
1930
|
+
return `${urlWithoutFragment}${urlWithoutFragment.includes("?") ? "&" : "?"}${searchParams.toString()}${fragment}`;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
function getSAMLRedirectCandidates(relayStateCallbackUrl, samlConfig, samlOptions) {
|
|
1934
|
+
return [
|
|
1935
|
+
relayStateCallbackUrl,
|
|
1936
|
+
samlConfig?.idpInitiatedCallbackUrl,
|
|
1937
|
+
samlOptions?.idpInitiatedCallbackUrl,
|
|
1938
|
+
samlConfig?.callbackUrl
|
|
1939
|
+
];
|
|
1898
1940
|
}
|
|
1899
1941
|
function toArray(value) {
|
|
1900
1942
|
if (Array.isArray(value)) return value;
|
|
@@ -1962,7 +2004,10 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
1962
2004
|
if (!parsedSamlConfig) throw new APIError("BAD_REQUEST", { message: "Invalid SAML configuration" });
|
|
1963
2005
|
const sp = createSP(parsedSamlConfig, ctx.context.baseURL, providerId, { clockSkew: options?.saml?.clockSkew });
|
|
1964
2006
|
const idp = createIdP(parsedSamlConfig);
|
|
1965
|
-
const
|
|
2007
|
+
const redirectCandidates = getSAMLRedirectCandidates(relayState?.callbackURL, parsedSamlConfig, options?.saml);
|
|
2008
|
+
const samlRedirectUrl = getSafeRedirectUrl(redirectCandidates, currentCallbackPath, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
2009
|
+
const samlErrorRedirectUrl = getSafeRedirectUrl([relayState?.errorURL, samlRedirectUrl], currentCallbackPath, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
2010
|
+
params.onErrorRedirectResolved?.(samlErrorRedirectUrl);
|
|
1966
2011
|
validateSingleAssertion(SAMLResponse);
|
|
1967
2012
|
let parsedResponse;
|
|
1968
2013
|
try {
|
|
@@ -2007,7 +2052,7 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2007
2052
|
expectedAudiences: expectedAudiences.filter(Boolean),
|
|
2008
2053
|
expectedRecipients: expectedRecipients.filter(Boolean)
|
|
2009
2054
|
});
|
|
2010
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2055
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2011
2056
|
error: "invalid_saml_response",
|
|
2012
2057
|
error_description: error.body?.message || error.message || "Invalid SAML response"
|
|
2013
2058
|
}));
|
|
@@ -2018,7 +2063,7 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2018
2063
|
expectedAudiences: expectedAudiences.filter(Boolean),
|
|
2019
2064
|
expectedRecipients: expectedRecipients.filter(Boolean)
|
|
2020
2065
|
});
|
|
2021
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2066
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2022
2067
|
error: "invalid_saml_response",
|
|
2023
2068
|
error_description: "SAML response binding could not be validated"
|
|
2024
2069
|
}));
|
|
@@ -2039,7 +2084,7 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2039
2084
|
inResponseTo,
|
|
2040
2085
|
providerId
|
|
2041
2086
|
});
|
|
2042
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2087
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2043
2088
|
error: "invalid_saml_response",
|
|
2044
2089
|
error_description: "Unknown or expired request ID"
|
|
2045
2090
|
}));
|
|
@@ -2050,14 +2095,14 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2050
2095
|
expectedProvider: storedRequest.providerId,
|
|
2051
2096
|
actualProvider: providerId
|
|
2052
2097
|
});
|
|
2053
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2098
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2054
2099
|
error: "invalid_saml_response",
|
|
2055
2100
|
error_description: "Provider mismatch"
|
|
2056
2101
|
}));
|
|
2057
2102
|
}
|
|
2058
2103
|
} else if (!allowIdpInitiated) {
|
|
2059
2104
|
ctx.context.logger.error("SAML IdP-initiated SSO rejected: InResponseTo missing and allowIdpInitiated is false", { providerId });
|
|
2060
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2105
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2061
2106
|
error: "unsolicited_response",
|
|
2062
2107
|
error_description: "IdP-initiated SSO not allowed"
|
|
2063
2108
|
}));
|
|
@@ -2085,7 +2130,7 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2085
2130
|
issuer,
|
|
2086
2131
|
providerId
|
|
2087
2132
|
});
|
|
2088
|
-
throw ctx.redirect(buildSAMLRedirectUrl(
|
|
2133
|
+
throw ctx.redirect(buildSAMLRedirectUrl(samlErrorRedirectUrl, {
|
|
2089
2134
|
error: "replay_detected",
|
|
2090
2135
|
error_description: "SAML assertion has already been used"
|
|
2091
2136
|
}));
|
|
@@ -2114,8 +2159,8 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2114
2159
|
throw new APIError("BAD_REQUEST", { message: "Unable to extract user ID or email from SAML response" });
|
|
2115
2160
|
}
|
|
2116
2161
|
const isTrustedProvider = "domainVerified" in provider && !!provider.domainVerified && validateEmailDomain(userInfo.email, provider.domain);
|
|
2117
|
-
const callbackUrl =
|
|
2118
|
-
const errorUrl =
|
|
2162
|
+
const callbackUrl = redirectCandidates.some(Boolean) ? samlRedirectUrl : ctx.context.baseURL;
|
|
2163
|
+
const errorUrl = samlErrorRedirectUrl;
|
|
2119
2164
|
let result;
|
|
2120
2165
|
try {
|
|
2121
2166
|
result = await handleOAuthUserInfo(ctx, {
|
|
@@ -2187,7 +2232,7 @@ async function processSAMLResponse(ctx, params, options) {
|
|
|
2187
2232
|
expiresAt: session.expiresAt
|
|
2188
2233
|
}).catch((e) => ctx.context.logger.warn("Failed to create SAML session lookup record", e));
|
|
2189
2234
|
}
|
|
2190
|
-
return
|
|
2235
|
+
return samlRedirectUrl;
|
|
2191
2236
|
}
|
|
2192
2237
|
//#endregion
|
|
2193
2238
|
//#region src/routes/sso.ts
|
|
@@ -2282,6 +2327,7 @@ const ssoProviderBodySchema = z.object({
|
|
|
2282
2327
|
entryPoint: z.string({}).meta({ description: "The entry point of the provider" }),
|
|
2283
2328
|
cert: z.string({}).meta({ description: "The certificate of the provider" }),
|
|
2284
2329
|
callbackUrl: z.string({}).meta({ description: "The callback URL of the provider" }),
|
|
2330
|
+
idpInitiatedCallbackUrl: samlRedirectUrlSchema.meta({ description: "Fallback absolute URL or same-origin relative path for IdP-initiated SAML responses when RelayState does not contain a safe callback, including validation errors" }).optional(),
|
|
2285
2331
|
audience: z.string().optional(),
|
|
2286
2332
|
idpMetadata: z.object({
|
|
2287
2333
|
metadata: z.string().optional(),
|
|
@@ -2531,13 +2577,13 @@ const registerSSOProvider = (options) => {
|
|
|
2531
2577
|
if (!member) throw new APIError("BAD_REQUEST", { message: "You are not a member of the organization" });
|
|
2532
2578
|
if (ctx.context.hasPlugin("organization") && !hasOrgAdminRole(member)) throw new APIError("FORBIDDEN", { message: "You must be an organization owner or admin to register SSO providers" });
|
|
2533
2579
|
}
|
|
2534
|
-
if (new Set([
|
|
2580
|
+
if ((/* @__PURE__ */ new Set([
|
|
2535
2581
|
...BUILT_IN_ACCOUNT_PROVIDER_IDS,
|
|
2536
2582
|
...Object.keys(ctx.context.options.socialProviders ?? {}),
|
|
2537
2583
|
...ctx.context.socialProviders.map((p) => p.id),
|
|
2538
2584
|
...ctx.context.trustedProviders,
|
|
2539
2585
|
...options?.defaultSSO?.map((p) => p.providerId) ?? []
|
|
2540
|
-
]).has(body.providerId)) {
|
|
2586
|
+
])).has(body.providerId)) {
|
|
2541
2587
|
ctx.context.logger.warn(`SSO provider registration rejected for reserved providerId: ${body.providerId}`);
|
|
2542
2588
|
throw new APIError("UNPROCESSABLE_ENTITY", { message: "This providerId is reserved and cannot be used for an SSO provider" });
|
|
2543
2589
|
}
|
|
@@ -2647,6 +2693,7 @@ const registerSSOProvider = (options) => {
|
|
|
2647
2693
|
entryPoint: body.samlConfig.entryPoint,
|
|
2648
2694
|
cert: body.samlConfig.cert,
|
|
2649
2695
|
callbackUrl: body.samlConfig.callbackUrl,
|
|
2696
|
+
idpInitiatedCallbackUrl: body.samlConfig.idpInitiatedCallbackUrl,
|
|
2650
2697
|
audience: body.samlConfig.audience,
|
|
2651
2698
|
idpMetadata: body.samlConfig.idpMetadata,
|
|
2652
2699
|
spMetadata: body.samlConfig.spMetadata,
|
|
@@ -3205,7 +3252,7 @@ const callbackSSOSAML = (options) => {
|
|
|
3205
3252
|
if (ctx.method === "GET" && !ctx.body?.SAMLResponse) {
|
|
3206
3253
|
if (!(await getSessionFromCtx(ctx))?.session) throw ctx.redirect(`${errorURL}?error=invalid_request`);
|
|
3207
3254
|
const relayState = ctx.query?.RelayState;
|
|
3208
|
-
const safeRedirectUrl = getSafeRedirectUrl(relayState, currentCallbackPath, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3255
|
+
const safeRedirectUrl = getSafeRedirectUrl([relayState], currentCallbackPath, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3209
3256
|
throw ctx.redirect(safeRedirectUrl);
|
|
3210
3257
|
}
|
|
3211
3258
|
if (!ctx.body?.SAMLResponse) throw new APIError("BAD_REQUEST", { message: "SAMLResponse is required for POST requests" });
|
|
@@ -3240,12 +3287,16 @@ const acsEndpoint = (options) => {
|
|
|
3240
3287
|
const { providerId } = ctx.params;
|
|
3241
3288
|
const currentCallbackPath = `${ctx.context.baseURL}/sso/saml2/sp/acs/${providerId}`;
|
|
3242
3289
|
const appOrigin = new URL(ctx.context.baseURL).origin;
|
|
3290
|
+
let resolvedErrorRedirectUrl;
|
|
3243
3291
|
try {
|
|
3244
3292
|
const safeRedirectUrl = await processSAMLResponse(ctx, {
|
|
3245
3293
|
SAMLResponse: ctx.body.SAMLResponse,
|
|
3246
3294
|
RelayState: ctx.body.RelayState,
|
|
3247
3295
|
providerId,
|
|
3248
|
-
currentCallbackPath
|
|
3296
|
+
currentCallbackPath,
|
|
3297
|
+
onErrorRedirectResolved: (url) => {
|
|
3298
|
+
resolvedErrorRedirectUrl = url;
|
|
3299
|
+
}
|
|
3249
3300
|
}, options);
|
|
3250
3301
|
throw ctx.redirect(safeRedirectUrl);
|
|
3251
3302
|
} catch (error) {
|
|
@@ -3253,8 +3304,23 @@ const acsEndpoint = (options) => {
|
|
|
3253
3304
|
if (error instanceof APIError && error.statusCode === 400) {
|
|
3254
3305
|
const internalCode = error.body?.code || "";
|
|
3255
3306
|
const errorCode = internalCode === "SAML_MULTIPLE_ASSERTIONS" ? "multiple_assertions" : internalCode === "SAML_NO_ASSERTION" ? "no_assertion" : internalCode.toLowerCase() || "saml_error";
|
|
3256
|
-
|
|
3257
|
-
|
|
3307
|
+
let redirectUrl = resolvedErrorRedirectUrl;
|
|
3308
|
+
if (!redirectUrl) {
|
|
3309
|
+
let parsedSamlConfig;
|
|
3310
|
+
try {
|
|
3311
|
+
parsedSamlConfig = (await findSAMLProvider(providerId, options, ctx.context.adapter))?.samlConfig;
|
|
3312
|
+
} catch (providerLookupError) {
|
|
3313
|
+
ctx.context.logger.warn("Failed to resolve SAML provider for error redirect", {
|
|
3314
|
+
providerId,
|
|
3315
|
+
error: providerLookupError
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
redirectUrl = getSafeRedirectUrl(getSAMLRedirectCandidates(ctx.body.RelayState, parsedSamlConfig, options?.saml), currentCallbackPath, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3319
|
+
}
|
|
3320
|
+
throw ctx.redirect(buildSAMLRedirectUrl(redirectUrl, {
|
|
3321
|
+
error: errorCode,
|
|
3322
|
+
error_description: error.message
|
|
3323
|
+
}));
|
|
3258
3324
|
}
|
|
3259
3325
|
throw error;
|
|
3260
3326
|
}
|
|
@@ -3283,7 +3349,7 @@ const sloEndpoint = (options) => {
|
|
|
3283
3349
|
const samlResponse = ctx.body?.SAMLResponse || ctx.query?.SAMLResponse;
|
|
3284
3350
|
const relayState = ctx.body?.RelayState || ctx.query?.RelayState;
|
|
3285
3351
|
const appOrigin = new URL(ctx.context.baseURL).origin;
|
|
3286
|
-
const safeErrorURL = getSafeRedirectUrl(relayState, `${appOrigin}/sso/saml2/sp/slo/${providerId}`, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3352
|
+
const safeErrorURL = getSafeRedirectUrl([relayState], `${appOrigin}/sso/saml2/sp/slo/${providerId}`, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3287
3353
|
if (!samlRequest && !samlResponse) throw ctx.redirect(`${safeErrorURL}?error=invalid_request&error_description=missing_logout_data`);
|
|
3288
3354
|
const provider = await findSAMLProvider(providerId, options, ctx.context.adapter);
|
|
3289
3355
|
if (!provider?.samlConfig) throw APIError.from("NOT_FOUND", SAML_ERROR_CODES.SAML_PROVIDER_NOT_FOUND);
|
|
@@ -3323,7 +3389,7 @@ async function handleLogoutResponse(ctx, sp, idp, relayState, providerId) {
|
|
|
3323
3389
|
}
|
|
3324
3390
|
deleteSessionCookie(ctx);
|
|
3325
3391
|
const appOrigin = new URL(ctx.context.baseURL).origin;
|
|
3326
|
-
const safeRedirectUrl = getSafeRedirectUrl(relayState, `${appOrigin}/sso/saml2/sp/slo/${providerId}`, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3392
|
+
const safeRedirectUrl = getSafeRedirectUrl([relayState], `${appOrigin}/sso/saml2/sp/slo/${providerId}`, appOrigin, (url, settings) => ctx.context.isTrustedOrigin(url, settings));
|
|
3327
3393
|
throw ctx.redirect(safeRedirectUrl);
|
|
3328
3394
|
}
|
|
3329
3395
|
async function handleLogoutRequest(ctx, sp, idp, relayState, providerId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/sso",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.24",
|
|
4
4
|
"description": "SSO plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,16 +69,16 @@
|
|
|
69
69
|
"body-parser": "^2.2.2",
|
|
70
70
|
"express": "^5.2.1",
|
|
71
71
|
"oauth2-mock-server": "^8.2.2",
|
|
72
|
-
"tsdown": "0.
|
|
73
|
-
"@better-auth/core": "1.6.
|
|
74
|
-
"better-auth": "1.6.
|
|
72
|
+
"tsdown": "0.22.7",
|
|
73
|
+
"@better-auth/core": "1.6.24",
|
|
74
|
+
"better-auth": "1.6.24"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@better-auth/utils": "0.4.2",
|
|
78
78
|
"@better-fetch/fetch": "1.3.1",
|
|
79
79
|
"better-call": "1.3.7",
|
|
80
|
-
"better-auth": "^1.6.
|
|
81
|
-
"
|
|
80
|
+
"@better-auth/core": "^1.6.24",
|
|
81
|
+
"better-auth": "^1.6.24"
|
|
82
82
|
},
|
|
83
83
|
"scripts": {
|
|
84
84
|
"build": "tsdown",
|