@better-auth/sso 1.3.24 → 1.3.26
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/.turbo/turbo-build.log +4 -4
- package/dist/index.cjs +73 -13
- package/dist/index.mjs +73 -13
- package/package.json +3 -3
- package/src/index.ts +90 -14
- package/src/oidc.test.ts +37 -0
- package/src/saml.test.ts +49 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/sso@1.3.
|
|
2
|
+
> @better-auth/sso@1.3.26 build /home/runner/work/better-auth/better-auth/packages/sso
|
|
3
3
|
> unbuild
|
|
4
4
|
|
|
5
5
|
[info] Automatically detected entries: src/index, src/client [esm] [cjs] [dts]
|
|
6
6
|
[info] Building sso
|
|
7
7
|
[success] Build succeeded for sso
|
|
8
|
-
[log] dist/index.cjs (total size:
|
|
8
|
+
[log] dist/index.cjs (total size: 69 kB, chunk size: 69 kB, exports: sso)
|
|
9
9
|
|
|
10
10
|
[log] dist/client.cjs (total size: 141 B, chunk size: 141 B, exports: ssoClient)
|
|
11
11
|
|
|
12
|
-
[log] dist/index.mjs (total size:
|
|
12
|
+
[log] dist/index.mjs (total size: 67.3 kB, chunk size: 67.3 kB, exports: sso)
|
|
13
13
|
|
|
14
14
|
[log] dist/client.mjs (total size: 117 B, chunk size: 117 B, exports: ssoClient)
|
|
15
15
|
|
|
16
|
-
Σ Total dist size (byte size):
|
|
16
|
+
Σ Total dist size (byte size): 263 kB
|
|
17
17
|
[log]
|
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,22 @@ const fastValidator = {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
saml__namespace.setSchemaValidator(fastValidator);
|
|
39
|
+
function safeJsonParse(value) {
|
|
40
|
+
if (!value) return null;
|
|
41
|
+
if (typeof value === "object") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(value);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
39
55
|
const sso = (options) => {
|
|
40
56
|
return {
|
|
41
57
|
id: "sso",
|
|
@@ -75,7 +91,14 @@ const sso = (options) => {
|
|
|
75
91
|
message: "No provider found for the given providerId"
|
|
76
92
|
});
|
|
77
93
|
}
|
|
78
|
-
const parsedSamlConfig =
|
|
94
|
+
const parsedSamlConfig = safeJsonParse(
|
|
95
|
+
provider.samlConfig
|
|
96
|
+
);
|
|
97
|
+
if (!parsedSamlConfig) {
|
|
98
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
99
|
+
message: "Invalid SAML configuration"
|
|
100
|
+
});
|
|
101
|
+
}
|
|
79
102
|
const sp = parsedSamlConfig.spMetadata.metadata ? saml__namespace.ServiceProvider({
|
|
80
103
|
metadata: parsedSamlConfig.spMetadata.metadata
|
|
81
104
|
}) : saml__namespace.SPMetadata({
|
|
@@ -448,6 +471,23 @@ const sso = (options) => {
|
|
|
448
471
|
});
|
|
449
472
|
}
|
|
450
473
|
}
|
|
474
|
+
const existingProvider = await ctx.context.adapter.findOne({
|
|
475
|
+
model: "ssoProvider",
|
|
476
|
+
where: [
|
|
477
|
+
{
|
|
478
|
+
field: "providerId",
|
|
479
|
+
value: body.providerId
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
});
|
|
483
|
+
if (existingProvider) {
|
|
484
|
+
ctx.context.logger.info(
|
|
485
|
+
`SSO provider creation attempt with existing providerId: ${body.providerId}`
|
|
486
|
+
);
|
|
487
|
+
throw new api.APIError("UNPROCESSABLE_ENTITY", {
|
|
488
|
+
message: "SSO provider with this providerId already exists"
|
|
489
|
+
});
|
|
490
|
+
}
|
|
451
491
|
const provider = await ctx.context.adapter.create({
|
|
452
492
|
model: "ssoProvider",
|
|
453
493
|
data: {
|
|
@@ -667,8 +707,12 @@ const sso = (options) => {
|
|
|
667
707
|
}
|
|
668
708
|
return {
|
|
669
709
|
...res,
|
|
670
|
-
oidcConfig: res.oidcConfig ?
|
|
671
|
-
|
|
710
|
+
oidcConfig: res.oidcConfig ? safeJsonParse(
|
|
711
|
+
res.oidcConfig
|
|
712
|
+
) || void 0 : void 0,
|
|
713
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
714
|
+
res.samlConfig
|
|
715
|
+
) || void 0 : void 0
|
|
672
716
|
};
|
|
673
717
|
});
|
|
674
718
|
}
|
|
@@ -715,7 +759,14 @@ const sso = (options) => {
|
|
|
715
759
|
});
|
|
716
760
|
}
|
|
717
761
|
if (provider.samlConfig) {
|
|
718
|
-
const parsedSamlConfig = typeof provider.samlConfig === "object" ? provider.samlConfig :
|
|
762
|
+
const parsedSamlConfig = typeof provider.samlConfig === "object" ? provider.samlConfig : safeJsonParse(
|
|
763
|
+
provider.samlConfig
|
|
764
|
+
);
|
|
765
|
+
if (!parsedSamlConfig) {
|
|
766
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
767
|
+
message: "Invalid SAML configuration"
|
|
768
|
+
});
|
|
769
|
+
}
|
|
719
770
|
const sp = saml__namespace.ServiceProvider({
|
|
720
771
|
metadata: parsedSamlConfig.spMetadata.metadata,
|
|
721
772
|
allowCreate: true
|
|
@@ -811,7 +862,7 @@ const sso = (options) => {
|
|
|
811
862
|
}
|
|
812
863
|
return {
|
|
813
864
|
...res,
|
|
814
|
-
oidcConfig:
|
|
865
|
+
oidcConfig: safeJsonParse(res.oidcConfig) || void 0
|
|
815
866
|
};
|
|
816
867
|
});
|
|
817
868
|
}
|
|
@@ -1059,7 +1110,9 @@ const sso = (options) => {
|
|
|
1059
1110
|
if (!res) return null;
|
|
1060
1111
|
return {
|
|
1061
1112
|
...res,
|
|
1062
|
-
samlConfig: res.samlConfig ?
|
|
1113
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
1114
|
+
res.samlConfig
|
|
1115
|
+
) || void 0 : void 0
|
|
1063
1116
|
};
|
|
1064
1117
|
});
|
|
1065
1118
|
}
|
|
@@ -1068,25 +1121,30 @@ const sso = (options) => {
|
|
|
1068
1121
|
message: "No provider found for the given providerId"
|
|
1069
1122
|
});
|
|
1070
1123
|
}
|
|
1071
|
-
const parsedSamlConfig =
|
|
1124
|
+
const parsedSamlConfig = safeJsonParse(
|
|
1072
1125
|
provider.samlConfig
|
|
1073
1126
|
);
|
|
1127
|
+
if (!parsedSamlConfig) {
|
|
1128
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
1129
|
+
message: "Invalid SAML configuration"
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1074
1132
|
const idpData = parsedSamlConfig.idpMetadata;
|
|
1075
1133
|
let idp = null;
|
|
1076
1134
|
if (!idpData?.metadata) {
|
|
1077
1135
|
idp = saml__namespace.IdentityProvider({
|
|
1078
|
-
entityID: idpData
|
|
1136
|
+
entityID: idpData?.entityID || parsedSamlConfig.issuer,
|
|
1079
1137
|
singleSignOnService: [
|
|
1080
1138
|
{
|
|
1081
1139
|
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
1082
1140
|
Location: parsedSamlConfig.entryPoint
|
|
1083
1141
|
}
|
|
1084
1142
|
],
|
|
1085
|
-
signingCert: idpData
|
|
1143
|
+
signingCert: idpData?.cert || parsedSamlConfig.cert,
|
|
1086
1144
|
wantAuthnRequestsSigned: parsedSamlConfig.wantAssertionsSigned || false,
|
|
1087
|
-
isAssertionEncrypted: idpData
|
|
1088
|
-
encPrivateKey: idpData
|
|
1089
|
-
encPrivateKeyPass: idpData
|
|
1145
|
+
isAssertionEncrypted: idpData?.isAssertionEncrypted || false,
|
|
1146
|
+
encPrivateKey: idpData?.encPrivateKey,
|
|
1147
|
+
encPrivateKeyPass: idpData?.encPrivateKeyPass
|
|
1090
1148
|
});
|
|
1091
1149
|
} else {
|
|
1092
1150
|
idp = saml__namespace.IdentityProvider({
|
|
@@ -1333,7 +1391,9 @@ const sso = (options) => {
|
|
|
1333
1391
|
if (!res) return null;
|
|
1334
1392
|
return {
|
|
1335
1393
|
...res,
|
|
1336
|
-
samlConfig: res.samlConfig ?
|
|
1394
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
1395
|
+
res.samlConfig
|
|
1396
|
+
) || void 0 : void 0
|
|
1337
1397
|
};
|
|
1338
1398
|
});
|
|
1339
1399
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -19,6 +19,22 @@ const fastValidator = {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
saml.setSchemaValidator(fastValidator);
|
|
22
|
+
function safeJsonParse(value) {
|
|
23
|
+
if (!value) return null;
|
|
24
|
+
if (typeof value === "object") {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === "string") {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(value);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
22
38
|
const sso = (options) => {
|
|
23
39
|
return {
|
|
24
40
|
id: "sso",
|
|
@@ -58,7 +74,14 @@ const sso = (options) => {
|
|
|
58
74
|
message: "No provider found for the given providerId"
|
|
59
75
|
});
|
|
60
76
|
}
|
|
61
|
-
const parsedSamlConfig =
|
|
77
|
+
const parsedSamlConfig = safeJsonParse(
|
|
78
|
+
provider.samlConfig
|
|
79
|
+
);
|
|
80
|
+
if (!parsedSamlConfig) {
|
|
81
|
+
throw new APIError("BAD_REQUEST", {
|
|
82
|
+
message: "Invalid SAML configuration"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
62
85
|
const sp = parsedSamlConfig.spMetadata.metadata ? saml.ServiceProvider({
|
|
63
86
|
metadata: parsedSamlConfig.spMetadata.metadata
|
|
64
87
|
}) : saml.SPMetadata({
|
|
@@ -431,6 +454,23 @@ const sso = (options) => {
|
|
|
431
454
|
});
|
|
432
455
|
}
|
|
433
456
|
}
|
|
457
|
+
const existingProvider = await ctx.context.adapter.findOne({
|
|
458
|
+
model: "ssoProvider",
|
|
459
|
+
where: [
|
|
460
|
+
{
|
|
461
|
+
field: "providerId",
|
|
462
|
+
value: body.providerId
|
|
463
|
+
}
|
|
464
|
+
]
|
|
465
|
+
});
|
|
466
|
+
if (existingProvider) {
|
|
467
|
+
ctx.context.logger.info(
|
|
468
|
+
`SSO provider creation attempt with existing providerId: ${body.providerId}`
|
|
469
|
+
);
|
|
470
|
+
throw new APIError("UNPROCESSABLE_ENTITY", {
|
|
471
|
+
message: "SSO provider with this providerId already exists"
|
|
472
|
+
});
|
|
473
|
+
}
|
|
434
474
|
const provider = await ctx.context.adapter.create({
|
|
435
475
|
model: "ssoProvider",
|
|
436
476
|
data: {
|
|
@@ -650,8 +690,12 @@ const sso = (options) => {
|
|
|
650
690
|
}
|
|
651
691
|
return {
|
|
652
692
|
...res,
|
|
653
|
-
oidcConfig: res.oidcConfig ?
|
|
654
|
-
|
|
693
|
+
oidcConfig: res.oidcConfig ? safeJsonParse(
|
|
694
|
+
res.oidcConfig
|
|
695
|
+
) || void 0 : void 0,
|
|
696
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
697
|
+
res.samlConfig
|
|
698
|
+
) || void 0 : void 0
|
|
655
699
|
};
|
|
656
700
|
});
|
|
657
701
|
}
|
|
@@ -698,7 +742,14 @@ const sso = (options) => {
|
|
|
698
742
|
});
|
|
699
743
|
}
|
|
700
744
|
if (provider.samlConfig) {
|
|
701
|
-
const parsedSamlConfig = typeof provider.samlConfig === "object" ? provider.samlConfig :
|
|
745
|
+
const parsedSamlConfig = typeof provider.samlConfig === "object" ? provider.samlConfig : safeJsonParse(
|
|
746
|
+
provider.samlConfig
|
|
747
|
+
);
|
|
748
|
+
if (!parsedSamlConfig) {
|
|
749
|
+
throw new APIError("BAD_REQUEST", {
|
|
750
|
+
message: "Invalid SAML configuration"
|
|
751
|
+
});
|
|
752
|
+
}
|
|
702
753
|
const sp = saml.ServiceProvider({
|
|
703
754
|
metadata: parsedSamlConfig.spMetadata.metadata,
|
|
704
755
|
allowCreate: true
|
|
@@ -794,7 +845,7 @@ const sso = (options) => {
|
|
|
794
845
|
}
|
|
795
846
|
return {
|
|
796
847
|
...res,
|
|
797
|
-
oidcConfig:
|
|
848
|
+
oidcConfig: safeJsonParse(res.oidcConfig) || void 0
|
|
798
849
|
};
|
|
799
850
|
});
|
|
800
851
|
}
|
|
@@ -1042,7 +1093,9 @@ const sso = (options) => {
|
|
|
1042
1093
|
if (!res) return null;
|
|
1043
1094
|
return {
|
|
1044
1095
|
...res,
|
|
1045
|
-
samlConfig: res.samlConfig ?
|
|
1096
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
1097
|
+
res.samlConfig
|
|
1098
|
+
) || void 0 : void 0
|
|
1046
1099
|
};
|
|
1047
1100
|
});
|
|
1048
1101
|
}
|
|
@@ -1051,25 +1104,30 @@ const sso = (options) => {
|
|
|
1051
1104
|
message: "No provider found for the given providerId"
|
|
1052
1105
|
});
|
|
1053
1106
|
}
|
|
1054
|
-
const parsedSamlConfig =
|
|
1107
|
+
const parsedSamlConfig = safeJsonParse(
|
|
1055
1108
|
provider.samlConfig
|
|
1056
1109
|
);
|
|
1110
|
+
if (!parsedSamlConfig) {
|
|
1111
|
+
throw new APIError("BAD_REQUEST", {
|
|
1112
|
+
message: "Invalid SAML configuration"
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1057
1115
|
const idpData = parsedSamlConfig.idpMetadata;
|
|
1058
1116
|
let idp = null;
|
|
1059
1117
|
if (!idpData?.metadata) {
|
|
1060
1118
|
idp = saml.IdentityProvider({
|
|
1061
|
-
entityID: idpData
|
|
1119
|
+
entityID: idpData?.entityID || parsedSamlConfig.issuer,
|
|
1062
1120
|
singleSignOnService: [
|
|
1063
1121
|
{
|
|
1064
1122
|
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
1065
1123
|
Location: parsedSamlConfig.entryPoint
|
|
1066
1124
|
}
|
|
1067
1125
|
],
|
|
1068
|
-
signingCert: idpData
|
|
1126
|
+
signingCert: idpData?.cert || parsedSamlConfig.cert,
|
|
1069
1127
|
wantAuthnRequestsSigned: parsedSamlConfig.wantAssertionsSigned || false,
|
|
1070
|
-
isAssertionEncrypted: idpData
|
|
1071
|
-
encPrivateKey: idpData
|
|
1072
|
-
encPrivateKeyPass: idpData
|
|
1128
|
+
isAssertionEncrypted: idpData?.isAssertionEncrypted || false,
|
|
1129
|
+
encPrivateKey: idpData?.encPrivateKey,
|
|
1130
|
+
encPrivateKeyPass: idpData?.encPrivateKeyPass
|
|
1073
1131
|
});
|
|
1074
1132
|
} else {
|
|
1075
1133
|
idp = saml.IdentityProvider({
|
|
@@ -1316,7 +1374,9 @@ const sso = (options) => {
|
|
|
1316
1374
|
if (!res) return null;
|
|
1317
1375
|
return {
|
|
1318
1376
|
...res,
|
|
1319
|
-
samlConfig: res.samlConfig ?
|
|
1377
|
+
samlConfig: res.samlConfig ? safeJsonParse(
|
|
1378
|
+
res.samlConfig
|
|
1379
|
+
) || void 0 : void 0
|
|
1320
1380
|
};
|
|
1321
1381
|
});
|
|
1322
1382
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/sso",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.26",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"body-parser": "^2.2.0",
|
|
59
59
|
"express": "^5.1.0",
|
|
60
60
|
"unbuild": "3.6.1",
|
|
61
|
-
"better-auth": "^1.3.
|
|
61
|
+
"better-auth": "^1.3.26"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"better-auth": "1.3.
|
|
64
|
+
"better-auth": "1.3.26"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"test": "vitest",
|
package/src/index.ts
CHANGED
|
@@ -38,6 +38,34 @@ const fastValidator = {
|
|
|
38
38
|
|
|
39
39
|
saml.setSchemaValidator(fastValidator);
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Safely parses a value that might be a JSON string or already a parsed object
|
|
43
|
+
* This handles cases where ORMs like Drizzle might return already parsed objects
|
|
44
|
+
* instead of JSON strings from TEXT/JSON columns
|
|
45
|
+
*/
|
|
46
|
+
function safeJsonParse<T>(value: string | T | null | undefined): T | null {
|
|
47
|
+
if (!value) return null;
|
|
48
|
+
|
|
49
|
+
// If it's already an object (not a string), return it as-is
|
|
50
|
+
if (typeof value === "object") {
|
|
51
|
+
return value as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If it's a string, try to parse it
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(value) as T;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// If parsing fails, this might indicate the string is not valid JSON
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Failed to parse JSON: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
41
69
|
export interface OIDCMapping {
|
|
42
70
|
id?: string;
|
|
43
71
|
email?: string;
|
|
@@ -269,7 +297,14 @@ export const sso = (options?: SSOOptions) => {
|
|
|
269
297
|
});
|
|
270
298
|
}
|
|
271
299
|
|
|
272
|
-
const parsedSamlConfig
|
|
300
|
+
const parsedSamlConfig = safeJsonParse<SAMLConfig>(
|
|
301
|
+
provider.samlConfig,
|
|
302
|
+
);
|
|
303
|
+
if (!parsedSamlConfig) {
|
|
304
|
+
throw new APIError("BAD_REQUEST", {
|
|
305
|
+
message: "Invalid SAML configuration",
|
|
306
|
+
});
|
|
307
|
+
}
|
|
273
308
|
const sp = parsedSamlConfig.spMetadata.metadata
|
|
274
309
|
? saml.ServiceProvider({
|
|
275
310
|
metadata: parsedSamlConfig.spMetadata.metadata,
|
|
@@ -745,6 +780,26 @@ export const sso = (options?: SSOOptions) => {
|
|
|
745
780
|
});
|
|
746
781
|
}
|
|
747
782
|
}
|
|
783
|
+
|
|
784
|
+
const existingProvider = await ctx.context.adapter.findOne({
|
|
785
|
+
model: "ssoProvider",
|
|
786
|
+
where: [
|
|
787
|
+
{
|
|
788
|
+
field: "providerId",
|
|
789
|
+
value: body.providerId,
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
if (existingProvider) {
|
|
795
|
+
ctx.context.logger.info(
|
|
796
|
+
`SSO provider creation attempt with existing providerId: ${body.providerId}`,
|
|
797
|
+
);
|
|
798
|
+
throw new APIError("UNPROCESSABLE_ENTITY", {
|
|
799
|
+
message: "SSO provider with this providerId already exists",
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
748
803
|
const provider = await ctx.context.adapter.create<
|
|
749
804
|
Record<string, any>,
|
|
750
805
|
SSOProvider
|
|
@@ -1040,10 +1095,14 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1040
1095
|
return {
|
|
1041
1096
|
...res,
|
|
1042
1097
|
oidcConfig: res.oidcConfig
|
|
1043
|
-
?
|
|
1098
|
+
? safeJsonParse<OIDCConfig>(
|
|
1099
|
+
res.oidcConfig as unknown as string,
|
|
1100
|
+
) || undefined
|
|
1044
1101
|
: undefined,
|
|
1045
1102
|
samlConfig: res.samlConfig
|
|
1046
|
-
?
|
|
1103
|
+
? safeJsonParse<SAMLConfig>(
|
|
1104
|
+
res.samlConfig as unknown as string,
|
|
1105
|
+
) || undefined
|
|
1047
1106
|
: undefined,
|
|
1048
1107
|
};
|
|
1049
1108
|
});
|
|
@@ -1094,10 +1153,17 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1094
1153
|
});
|
|
1095
1154
|
}
|
|
1096
1155
|
if (provider.samlConfig) {
|
|
1097
|
-
const parsedSamlConfig
|
|
1156
|
+
const parsedSamlConfig =
|
|
1098
1157
|
typeof provider.samlConfig === "object"
|
|
1099
1158
|
? provider.samlConfig
|
|
1100
|
-
:
|
|
1159
|
+
: safeJsonParse<SAMLConfig>(
|
|
1160
|
+
provider.samlConfig as unknown as string,
|
|
1161
|
+
);
|
|
1162
|
+
if (!parsedSamlConfig) {
|
|
1163
|
+
throw new APIError("BAD_REQUEST", {
|
|
1164
|
+
message: "Invalid SAML configuration",
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1101
1167
|
const sp = saml.ServiceProvider({
|
|
1102
1168
|
metadata: parsedSamlConfig.spMetadata.metadata,
|
|
1103
1169
|
allowCreate: true,
|
|
@@ -1206,7 +1272,8 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1206
1272
|
}
|
|
1207
1273
|
return {
|
|
1208
1274
|
...res,
|
|
1209
|
-
oidcConfig:
|
|
1275
|
+
oidcConfig:
|
|
1276
|
+
safeJsonParse<OIDCConfig>(res.oidcConfig) || undefined,
|
|
1210
1277
|
} as SSOProvider;
|
|
1211
1278
|
});
|
|
1212
1279
|
}
|
|
@@ -1533,7 +1600,9 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1533
1600
|
return {
|
|
1534
1601
|
...res,
|
|
1535
1602
|
samlConfig: res.samlConfig
|
|
1536
|
-
?
|
|
1603
|
+
? safeJsonParse<SAMLConfig>(
|
|
1604
|
+
res.samlConfig as unknown as string,
|
|
1605
|
+
) || undefined
|
|
1537
1606
|
: undefined,
|
|
1538
1607
|
};
|
|
1539
1608
|
});
|
|
@@ -1544,28 +1613,33 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1544
1613
|
message: "No provider found for the given providerId",
|
|
1545
1614
|
});
|
|
1546
1615
|
}
|
|
1547
|
-
const parsedSamlConfig =
|
|
1616
|
+
const parsedSamlConfig = safeJsonParse<SAMLConfig>(
|
|
1548
1617
|
provider.samlConfig as unknown as string,
|
|
1549
1618
|
);
|
|
1619
|
+
if (!parsedSamlConfig) {
|
|
1620
|
+
throw new APIError("BAD_REQUEST", {
|
|
1621
|
+
message: "Invalid SAML configuration",
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1550
1624
|
const idpData = parsedSamlConfig.idpMetadata;
|
|
1551
1625
|
let idp: IdentityProvider | null = null;
|
|
1552
1626
|
|
|
1553
1627
|
// Construct IDP with fallback to manual configuration
|
|
1554
1628
|
if (!idpData?.metadata) {
|
|
1555
1629
|
idp = saml.IdentityProvider({
|
|
1556
|
-
entityID: idpData
|
|
1630
|
+
entityID: idpData?.entityID || parsedSamlConfig.issuer,
|
|
1557
1631
|
singleSignOnService: [
|
|
1558
1632
|
{
|
|
1559
1633
|
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
1560
1634
|
Location: parsedSamlConfig.entryPoint,
|
|
1561
1635
|
},
|
|
1562
1636
|
],
|
|
1563
|
-
signingCert: idpData
|
|
1637
|
+
signingCert: idpData?.cert || parsedSamlConfig.cert,
|
|
1564
1638
|
wantAuthnRequestsSigned:
|
|
1565
1639
|
parsedSamlConfig.wantAssertionsSigned || false,
|
|
1566
|
-
isAssertionEncrypted: idpData
|
|
1567
|
-
encPrivateKey: idpData
|
|
1568
|
-
encPrivateKeyPass: idpData
|
|
1640
|
+
isAssertionEncrypted: idpData?.isAssertionEncrypted || false,
|
|
1641
|
+
encPrivateKey: idpData?.encPrivateKey,
|
|
1642
|
+
encPrivateKeyPass: idpData?.encPrivateKeyPass,
|
|
1569
1643
|
});
|
|
1570
1644
|
} else {
|
|
1571
1645
|
idp = saml.IdentityProvider({
|
|
@@ -1865,7 +1939,9 @@ export const sso = (options?: SSOOptions) => {
|
|
|
1865
1939
|
return {
|
|
1866
1940
|
...res,
|
|
1867
1941
|
samlConfig: res.samlConfig
|
|
1868
|
-
?
|
|
1942
|
+
? safeJsonParse<SAMLConfig>(
|
|
1943
|
+
res.samlConfig as unknown as string,
|
|
1944
|
+
) || undefined
|
|
1869
1945
|
: undefined,
|
|
1870
1946
|
};
|
|
1871
1947
|
});
|
package/src/oidc.test.ts
CHANGED
|
@@ -157,6 +157,43 @@ describe("SSO", async () => {
|
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
it("should not allow creating a provider with duplicate providerId", async () => {
|
|
161
|
+
const { headers } = await signInWithTestUser();
|
|
162
|
+
|
|
163
|
+
await auth.api.registerSSOProvider({
|
|
164
|
+
body: {
|
|
165
|
+
issuer: server.issuer.url!,
|
|
166
|
+
domain: "duplicate.com",
|
|
167
|
+
providerId: "duplicate-oidc-provider",
|
|
168
|
+
oidcConfig: {
|
|
169
|
+
clientId: "test",
|
|
170
|
+
clientSecret: "test",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
headers,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await expect(
|
|
177
|
+
auth.api.registerSSOProvider({
|
|
178
|
+
body: {
|
|
179
|
+
issuer: server.issuer.url!,
|
|
180
|
+
domain: "another-duplicate.com",
|
|
181
|
+
providerId: "duplicate-oidc-provider",
|
|
182
|
+
oidcConfig: {
|
|
183
|
+
clientId: "test2",
|
|
184
|
+
clientSecret: "test2",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
headers,
|
|
188
|
+
}),
|
|
189
|
+
).rejects.toMatchObject({
|
|
190
|
+
status: "UNPROCESSABLE_ENTITY",
|
|
191
|
+
body: {
|
|
192
|
+
message: "SSO provider with this providerId already exists",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
160
197
|
it("should sign in with SSO provider with email matching", async () => {
|
|
161
198
|
const headers = new Headers();
|
|
162
199
|
const res = await authClient.signIn.sso({
|
package/src/saml.test.ts
CHANGED
|
@@ -1069,4 +1069,53 @@ describe("SAML SSO", async () => {
|
|
|
1069
1069
|
},
|
|
1070
1070
|
});
|
|
1071
1071
|
});
|
|
1072
|
+
|
|
1073
|
+
it("should not allow creating a provider with duplicate providerId", async () => {
|
|
1074
|
+
const headers = await getAuthHeaders();
|
|
1075
|
+
await authClient.signIn.email(testUser, {
|
|
1076
|
+
throw: true,
|
|
1077
|
+
onSuccess: setCookieToHeader(headers),
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
await auth.api.registerSSOProvider({
|
|
1081
|
+
body: {
|
|
1082
|
+
providerId: "duplicate-provider",
|
|
1083
|
+
issuer: "http://localhost:8081",
|
|
1084
|
+
domain: "http://localhost:8081",
|
|
1085
|
+
samlConfig: {
|
|
1086
|
+
entryPoint: mockIdP.metadataUrl,
|
|
1087
|
+
cert: certificate,
|
|
1088
|
+
callbackUrl: "http://localhost:8081/api/sso/saml2/callback",
|
|
1089
|
+
spMetadata: {
|
|
1090
|
+
metadata: spMetadata,
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
headers,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
await expect(
|
|
1098
|
+
auth.api.registerSSOProvider({
|
|
1099
|
+
body: {
|
|
1100
|
+
providerId: "duplicate-provider",
|
|
1101
|
+
issuer: "http://localhost:8082",
|
|
1102
|
+
domain: "http://localhost:8082",
|
|
1103
|
+
samlConfig: {
|
|
1104
|
+
entryPoint: mockIdP.metadataUrl,
|
|
1105
|
+
cert: certificate,
|
|
1106
|
+
callbackUrl: "http://localhost:8082/api/sso/saml2/callback",
|
|
1107
|
+
spMetadata: {
|
|
1108
|
+
metadata: spMetadata,
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
headers,
|
|
1113
|
+
}),
|
|
1114
|
+
).rejects.toMatchObject({
|
|
1115
|
+
status: "UNPROCESSABLE_ENTITY",
|
|
1116
|
+
body: {
|
|
1117
|
+
message: "SSO provider with this providerId already exists",
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1072
1121
|
});
|