@better-auth/sso 1.4.0-beta.22 → 1.4.0-beta.23
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 +8 -8
- package/dist/client.d.mts +14 -3
- package/dist/client.mjs +1 -1
- package/dist/{index-DOws6HlV.d.mts → index-xXD__4zM.d.mts} +172 -19
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +252 -17
- package/package.json +4 -4
- package/src/client.ts +20 -3
- package/src/domain-verification.test.ts +550 -0
- package/src/index.ts +57 -11
- package/src/routes/domain-verification.ts +275 -0
- package/src/routes/sso.ts +116 -16
- package/src/types.ts +28 -3
- package/src/utils.ts +10 -0
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,197 @@
|
|
|
1
1
|
import { XMLValidator } from "fast-xml-parser";
|
|
2
2
|
import * as saml from "samlify";
|
|
3
|
+
import { APIError, createAuthEndpoint, sessionMiddleware } from "better-auth/api";
|
|
4
|
+
import { generateRandomString } from "better-auth/crypto";
|
|
5
|
+
import * as z from "zod/v4";
|
|
3
6
|
import { BetterFetchError, betterFetch } from "@better-fetch/fetch";
|
|
4
7
|
import { createAuthorizationURL, generateState, parseState, validateAuthorizationCode, validateToken } from "better-auth";
|
|
5
|
-
import { APIError, createAuthEndpoint, sessionMiddleware } from "better-auth/api";
|
|
6
8
|
import { setSessionCookie } from "better-auth/cookies";
|
|
7
9
|
import { handleOAuthUserInfo } from "better-auth/oauth2";
|
|
8
10
|
import { decodeJwt } from "jose";
|
|
9
|
-
import * as z from "zod/v4";
|
|
10
11
|
|
|
12
|
+
//#region src/routes/domain-verification.ts
|
|
13
|
+
const requestDomainVerification = (options) => {
|
|
14
|
+
return createAuthEndpoint("/sso/request-domain-verification", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
body: z.object({ providerId: z.string() }),
|
|
17
|
+
metadata: { openapi: {
|
|
18
|
+
summary: "Request a domain verification",
|
|
19
|
+
description: "Request a domain verification for the given SSO provider",
|
|
20
|
+
responses: {
|
|
21
|
+
"404": { description: "Provider not found" },
|
|
22
|
+
"409": { description: "Domain has already been verified" },
|
|
23
|
+
"201": { description: "Domain submitted for verification" }
|
|
24
|
+
}
|
|
25
|
+
} },
|
|
26
|
+
use: [sessionMiddleware]
|
|
27
|
+
}, async (ctx) => {
|
|
28
|
+
const body = ctx.body;
|
|
29
|
+
const provider = await ctx.context.adapter.findOne({
|
|
30
|
+
model: "ssoProvider",
|
|
31
|
+
where: [{
|
|
32
|
+
field: "providerId",
|
|
33
|
+
value: body.providerId
|
|
34
|
+
}]
|
|
35
|
+
});
|
|
36
|
+
if (!provider) throw new APIError("NOT_FOUND", {
|
|
37
|
+
message: "Provider not found",
|
|
38
|
+
code: "PROVIDER_NOT_FOUND"
|
|
39
|
+
});
|
|
40
|
+
const userId = ctx.context.session.user.id;
|
|
41
|
+
let isOrgMember = true;
|
|
42
|
+
if (provider.organizationId) isOrgMember = await ctx.context.adapter.count({
|
|
43
|
+
model: "member",
|
|
44
|
+
where: [{
|
|
45
|
+
field: "userId",
|
|
46
|
+
value: userId
|
|
47
|
+
}, {
|
|
48
|
+
field: "organizationId",
|
|
49
|
+
value: provider.organizationId
|
|
50
|
+
}]
|
|
51
|
+
}) > 0;
|
|
52
|
+
if (provider.userId !== userId || !isOrgMember) throw new APIError("FORBIDDEN", {
|
|
53
|
+
message: "User must be owner of or belong to the SSO provider organization",
|
|
54
|
+
code: "INSUFICCIENT_ACCESS"
|
|
55
|
+
});
|
|
56
|
+
if ("domainVerified" in provider && provider.domainVerified) throw new APIError("CONFLICT", {
|
|
57
|
+
message: "Domain has already been verified",
|
|
58
|
+
code: "DOMAIN_VERIFIED"
|
|
59
|
+
});
|
|
60
|
+
const activeVerification = await ctx.context.adapter.findOne({
|
|
61
|
+
model: "verification",
|
|
62
|
+
where: [{
|
|
63
|
+
field: "identifier",
|
|
64
|
+
value: options.domainVerification?.tokenPrefix ? `${options.domainVerification?.tokenPrefix}-${provider.providerId}` : `better-auth-token-${provider.providerId}`
|
|
65
|
+
}, {
|
|
66
|
+
field: "expiresAt",
|
|
67
|
+
value: /* @__PURE__ */ new Date(),
|
|
68
|
+
operator: "gt"
|
|
69
|
+
}]
|
|
70
|
+
});
|
|
71
|
+
if (activeVerification) {
|
|
72
|
+
ctx.setStatus(201);
|
|
73
|
+
return ctx.json({ domainVerificationToken: activeVerification.value });
|
|
74
|
+
}
|
|
75
|
+
const domainVerificationToken = generateRandomString(24);
|
|
76
|
+
await ctx.context.adapter.create({
|
|
77
|
+
model: "verification",
|
|
78
|
+
data: {
|
|
79
|
+
identifier: options.domainVerification?.tokenPrefix ? `${options.domainVerification?.tokenPrefix}-${provider.providerId}` : `better-auth-token-${provider.providerId}`,
|
|
80
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
81
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
82
|
+
value: domainVerificationToken,
|
|
83
|
+
expiresAt: new Date(Date.now() + 3600 * 24 * 7 * 1e3)
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
ctx.setStatus(201);
|
|
87
|
+
return ctx.json({ domainVerificationToken });
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const verifyDomain = (options) => {
|
|
91
|
+
return createAuthEndpoint("/sso/verify-domain", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
body: z.object({ providerId: z.string() }),
|
|
94
|
+
metadata: { openapi: {
|
|
95
|
+
summary: "Verify the provider domain ownership",
|
|
96
|
+
description: "Verify the provider domain ownership via DNS records",
|
|
97
|
+
responses: {
|
|
98
|
+
"404": { description: "Provider not found" },
|
|
99
|
+
"409": { description: "Domain has already been verified or no pending verification exists" },
|
|
100
|
+
"502": { description: "Unable to verify domain ownership due to upstream validator error" },
|
|
101
|
+
"204": { description: "Domain ownership was verified" }
|
|
102
|
+
}
|
|
103
|
+
} },
|
|
104
|
+
use: [sessionMiddleware]
|
|
105
|
+
}, async (ctx) => {
|
|
106
|
+
const body = ctx.body;
|
|
107
|
+
const provider = await ctx.context.adapter.findOne({
|
|
108
|
+
model: "ssoProvider",
|
|
109
|
+
where: [{
|
|
110
|
+
field: "providerId",
|
|
111
|
+
value: body.providerId
|
|
112
|
+
}]
|
|
113
|
+
});
|
|
114
|
+
if (!provider) throw new APIError("NOT_FOUND", {
|
|
115
|
+
message: "Provider not found",
|
|
116
|
+
code: "PROVIDER_NOT_FOUND"
|
|
117
|
+
});
|
|
118
|
+
const userId = ctx.context.session.user.id;
|
|
119
|
+
let isOrgMember = true;
|
|
120
|
+
if (provider.organizationId) isOrgMember = await ctx.context.adapter.count({
|
|
121
|
+
model: "member",
|
|
122
|
+
where: [{
|
|
123
|
+
field: "userId",
|
|
124
|
+
value: userId
|
|
125
|
+
}, {
|
|
126
|
+
field: "organizationId",
|
|
127
|
+
value: provider.organizationId
|
|
128
|
+
}]
|
|
129
|
+
}) > 0;
|
|
130
|
+
if (provider.userId !== userId || !isOrgMember) throw new APIError("FORBIDDEN", {
|
|
131
|
+
message: "User must be owner of or belong to the SSO provider organization",
|
|
132
|
+
code: "INSUFICCIENT_ACCESS"
|
|
133
|
+
});
|
|
134
|
+
if ("domainVerified" in provider && provider.domainVerified) throw new APIError("CONFLICT", {
|
|
135
|
+
message: "Domain has already been verified",
|
|
136
|
+
code: "DOMAIN_VERIFIED"
|
|
137
|
+
});
|
|
138
|
+
const activeVerification = await ctx.context.adapter.findOne({
|
|
139
|
+
model: "verification",
|
|
140
|
+
where: [{
|
|
141
|
+
field: "identifier",
|
|
142
|
+
value: options.domainVerification?.tokenPrefix ? `${options.domainVerification?.tokenPrefix}-${provider.providerId}` : `better-auth-token-${provider.providerId}`
|
|
143
|
+
}, {
|
|
144
|
+
field: "expiresAt",
|
|
145
|
+
value: /* @__PURE__ */ new Date(),
|
|
146
|
+
operator: "gt"
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
if (!activeVerification) throw new APIError("NOT_FOUND", {
|
|
150
|
+
message: "No pending domain verification exists",
|
|
151
|
+
code: "NO_PENDING_VERIFICATION"
|
|
152
|
+
});
|
|
153
|
+
let records = [];
|
|
154
|
+
let dns;
|
|
155
|
+
try {
|
|
156
|
+
dns = await import("node:dns/promises");
|
|
157
|
+
} catch (error) {
|
|
158
|
+
ctx.context.logger.error("The core node:dns module is required for the domain verification feature", error);
|
|
159
|
+
throw new APIError("INTERNAL_SERVER_ERROR", {
|
|
160
|
+
message: "Unable to verify domain ownership due to server error",
|
|
161
|
+
code: "DOMAIN_VERIFICATION_FAILED"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
records = (await dns.resolveTxt(new URL(provider.domain).hostname)).flat();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
ctx.context.logger.warn("DNS resolution failure while validating domain ownership", error);
|
|
168
|
+
}
|
|
169
|
+
if (!records.find((record) => record.includes(`${activeVerification.identifier}=${activeVerification.value}`))) throw new APIError("BAD_GATEWAY", {
|
|
170
|
+
message: "Unable to verify domain ownership. Try again later",
|
|
171
|
+
code: "DOMAIN_VERIFICATION_FAILED"
|
|
172
|
+
});
|
|
173
|
+
await ctx.context.adapter.update({
|
|
174
|
+
model: "ssoProvider",
|
|
175
|
+
where: [{
|
|
176
|
+
field: "providerId",
|
|
177
|
+
value: provider.providerId
|
|
178
|
+
}],
|
|
179
|
+
update: { domainVerified: true }
|
|
180
|
+
});
|
|
181
|
+
ctx.setStatus(204);
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/utils.ts
|
|
187
|
+
const validateEmailDomain = (email, domain) => {
|
|
188
|
+
const emailDomain = email.split("@")[1]?.toLowerCase();
|
|
189
|
+
const providerDomain = domain.toLowerCase();
|
|
190
|
+
if (!emailDomain || !providerDomain) return false;
|
|
191
|
+
return emailDomain === providerDomain || emailDomain.endsWith(`.${providerDomain}`);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
11
195
|
//#region src/routes/sso.ts
|
|
12
196
|
/**
|
|
13
197
|
* Safely parses a value that might be a JSON string or already a parsed object
|
|
@@ -155,6 +339,14 @@ const registerSSOProvider = (options) => {
|
|
|
155
339
|
type: "string",
|
|
156
340
|
description: "The domain of the provider, used for email matching"
|
|
157
341
|
},
|
|
342
|
+
domainVerified: {
|
|
343
|
+
type: "boolean",
|
|
344
|
+
description: "A boolean indicating whether the domain has been verified or not"
|
|
345
|
+
},
|
|
346
|
+
domainVerificationToken: {
|
|
347
|
+
type: "string",
|
|
348
|
+
description: "Domain verification token. It can be used to prove ownership over the SSO domain"
|
|
349
|
+
},
|
|
158
350
|
oidcConfig: {
|
|
159
351
|
type: "object",
|
|
160
352
|
properties: {
|
|
@@ -336,6 +528,7 @@ const registerSSOProvider = (options) => {
|
|
|
336
528
|
data: {
|
|
337
529
|
issuer: body.issuer,
|
|
338
530
|
domain: body.domain,
|
|
531
|
+
domainVerified: false,
|
|
339
532
|
oidcConfig: body.oidcConfig ? JSON.stringify({
|
|
340
533
|
issuer: body.issuer,
|
|
341
534
|
clientId: body.oidcConfig.clientId,
|
|
@@ -373,11 +566,29 @@ const registerSSOProvider = (options) => {
|
|
|
373
566
|
providerId: body.providerId
|
|
374
567
|
}
|
|
375
568
|
});
|
|
569
|
+
let domainVerificationToken;
|
|
570
|
+
let domainVerified;
|
|
571
|
+
if (options?.domainVerification?.enabled) {
|
|
572
|
+
domainVerified = false;
|
|
573
|
+
domainVerificationToken = generateRandomString(24);
|
|
574
|
+
await ctx.context.adapter.create({
|
|
575
|
+
model: "verification",
|
|
576
|
+
data: {
|
|
577
|
+
identifier: options.domainVerification?.tokenPrefix ? `${options.domainVerification?.tokenPrefix}-${provider.providerId}` : `better-auth-token-${provider.providerId}`,
|
|
578
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
579
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
580
|
+
value: domainVerificationToken,
|
|
581
|
+
expiresAt: new Date(Date.now() + 3600 * 24 * 7 * 1e3)
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
376
585
|
return ctx.json({
|
|
377
586
|
...provider,
|
|
378
587
|
oidcConfig: JSON.parse(provider.oidcConfig),
|
|
379
588
|
samlConfig: JSON.parse(provider.samlConfig),
|
|
380
|
-
redirectURI: `${ctx.context.baseURL}/sso/callback/${provider.providerId}
|
|
589
|
+
redirectURI: `${ctx.context.baseURL}/sso/callback/${provider.providerId}`,
|
|
590
|
+
...options?.domainVerification?.enabled ? { domainVerified } : {},
|
|
591
|
+
...options?.domainVerification?.enabled ? { domainVerificationToken } : {}
|
|
381
592
|
});
|
|
382
593
|
});
|
|
383
594
|
};
|
|
@@ -480,7 +691,8 @@ const signInSSO = (options) => {
|
|
|
480
691
|
userId: "default",
|
|
481
692
|
oidcConfig: matchingDefault.oidcConfig,
|
|
482
693
|
samlConfig: matchingDefault.samlConfig,
|
|
483
|
-
domain: matchingDefault.domain
|
|
694
|
+
domain: matchingDefault.domain,
|
|
695
|
+
...options.domainVerification?.enabled ? { domainVerified: true } : {}
|
|
484
696
|
};
|
|
485
697
|
}
|
|
486
698
|
if (!providerId && !orgId && !domain) throw new APIError("BAD_REQUEST", { message: "providerId, orgId or domain is required" });
|
|
@@ -503,6 +715,7 @@ const signInSSO = (options) => {
|
|
|
503
715
|
if (body.providerType === "oidc" && !provider.oidcConfig) throw new APIError("BAD_REQUEST", { message: "OIDC provider is not configured" });
|
|
504
716
|
if (body.providerType === "saml" && !provider.samlConfig) throw new APIError("BAD_REQUEST", { message: "SAML provider is not configured" });
|
|
505
717
|
}
|
|
718
|
+
if (options?.domainVerification?.enabled && !("domainVerified" in provider && provider.domainVerified)) throw new APIError("UNAUTHORIZED", { message: "Provider domain has not been verified" });
|
|
506
719
|
if (provider.oidcConfig && body.providerType !== "saml") {
|
|
507
720
|
let finalAuthUrl = provider.oidcConfig.authorizationEndpoint;
|
|
508
721
|
if (!finalAuthUrl && provider.oidcConfig.discoveryEndpoint) {
|
|
@@ -567,6 +780,7 @@ const callbackSSO = (options) => {
|
|
|
567
780
|
error: z.string().optional(),
|
|
568
781
|
error_description: z.string().optional()
|
|
569
782
|
}),
|
|
783
|
+
allowedMediaTypes: ["application/x-www-form-urlencoded", "application/json"],
|
|
570
784
|
metadata: {
|
|
571
785
|
isAction: false,
|
|
572
786
|
openapi: {
|
|
@@ -591,7 +805,8 @@ const callbackSSO = (options) => {
|
|
|
591
805
|
if (matchingDefault) provider = {
|
|
592
806
|
...matchingDefault,
|
|
593
807
|
issuer: matchingDefault.oidcConfig?.issuer || "",
|
|
594
|
-
userId: "default"
|
|
808
|
+
userId: "default",
|
|
809
|
+
...options.domainVerification?.enabled ? { domainVerified: true } : {}
|
|
595
810
|
};
|
|
596
811
|
}
|
|
597
812
|
if (!provider) provider = await ctx.context.adapter.findOne({
|
|
@@ -608,6 +823,7 @@ const callbackSSO = (options) => {
|
|
|
608
823
|
};
|
|
609
824
|
});
|
|
610
825
|
if (!provider) throw ctx.redirect(`${errorURL || callbackURL}/error?error=invalid_provider&error_description=provider not found`);
|
|
826
|
+
if (options?.domainVerification?.enabled && !("domainVerified" in provider && provider.domainVerified)) throw new APIError("UNAUTHORIZED", { message: "Provider domain has not been verified" });
|
|
611
827
|
let config = provider.oidcConfig;
|
|
612
828
|
if (!config) throw ctx.redirect(`${errorURL || callbackURL}/error?error=invalid_provider&error_description=provider not found`);
|
|
613
829
|
const discovery = await betterFetch(config.discoveryEndpoint);
|
|
@@ -769,7 +985,8 @@ const callbackSSOSAML = (options) => {
|
|
|
769
985
|
if (matchingDefault) provider = {
|
|
770
986
|
...matchingDefault,
|
|
771
987
|
userId: "default",
|
|
772
|
-
issuer: matchingDefault.samlConfig?.issuer || ""
|
|
988
|
+
issuer: matchingDefault.samlConfig?.issuer || "",
|
|
989
|
+
...options.domainVerification?.enabled ? { domainVerified: true } : {}
|
|
773
990
|
};
|
|
774
991
|
}
|
|
775
992
|
if (!provider) provider = await ctx.context.adapter.findOne({
|
|
@@ -786,6 +1003,7 @@ const callbackSSOSAML = (options) => {
|
|
|
786
1003
|
};
|
|
787
1004
|
});
|
|
788
1005
|
if (!provider) throw new APIError("NOT_FOUND", { message: "No provider found for the given providerId" });
|
|
1006
|
+
if (options?.domainVerification?.enabled && !("domainVerified" in provider && provider.domainVerified)) throw new APIError("UNAUTHORIZED", { message: "Provider domain has not been verified" });
|
|
789
1007
|
const parsedSamlConfig = safeJsonParse(provider.samlConfig);
|
|
790
1008
|
if (!parsedSamlConfig) throw new APIError("BAD_REQUEST", { message: "Invalid SAML configuration" });
|
|
791
1009
|
const idpData = parsedSamlConfig.idpMetadata;
|
|
@@ -985,7 +1203,8 @@ const acsEndpoint = (options) => {
|
|
|
985
1203
|
providerId: matchingDefault.providerId,
|
|
986
1204
|
userId: "default",
|
|
987
1205
|
samlConfig: matchingDefault.samlConfig,
|
|
988
|
-
domain: matchingDefault.domain
|
|
1206
|
+
domain: matchingDefault.domain,
|
|
1207
|
+
...options.domainVerification?.enabled ? { domainVerified: true } : {}
|
|
989
1208
|
};
|
|
990
1209
|
} else provider = await ctx.context.adapter.findOne({
|
|
991
1210
|
model: "ssoProvider",
|
|
@@ -1001,6 +1220,7 @@ const acsEndpoint = (options) => {
|
|
|
1001
1220
|
};
|
|
1002
1221
|
});
|
|
1003
1222
|
if (!provider?.samlConfig) throw new APIError("NOT_FOUND", { message: "No SAML provider found" });
|
|
1223
|
+
if (options?.domainVerification?.enabled && !("domainVerified" in provider && provider.domainVerified)) throw new APIError("UNAUTHORIZED", { message: "Provider domain has not been verified" });
|
|
1004
1224
|
const parsedSamlConfig = provider.samlConfig;
|
|
1005
1225
|
const sp = saml.ServiceProvider({
|
|
1006
1226
|
entityID: parsedSamlConfig.spMetadata?.entityID || parsedSamlConfig.issuer,
|
|
@@ -1101,7 +1321,7 @@ const acsEndpoint = (options) => {
|
|
|
1101
1321
|
}
|
|
1102
1322
|
]
|
|
1103
1323
|
})) {
|
|
1104
|
-
if (!ctx.context.options.account?.accountLinking?.trustedProviders?.includes(provider.providerId)) throw ctx.redirect(`${parsedSamlConfig.callbackUrl}?error=account_not_found`);
|
|
1324
|
+
if (!(ctx.context.options.account?.accountLinking?.trustedProviders?.includes(provider.providerId) || "domainVerified" in provider && provider.domainVerified && validateEmailDomain(userInfo.email, provider.domain))) throw ctx.redirect(`${parsedSamlConfig.callbackUrl}?error=account_not_found`);
|
|
1105
1325
|
await ctx.context.internalAdapter.createAccount({
|
|
1106
1326
|
userId: existingUser.id,
|
|
1107
1327
|
providerId: provider.providerId,
|
|
@@ -1179,16 +1399,27 @@ saml.setSchemaValidator({ async validate(xml) {
|
|
|
1179
1399
|
throw "ERR_INVALID_XML";
|
|
1180
1400
|
} });
|
|
1181
1401
|
function sso(options) {
|
|
1402
|
+
let endpoints = {
|
|
1403
|
+
spMetadata: spMetadata(),
|
|
1404
|
+
registerSSOProvider: registerSSOProvider(options),
|
|
1405
|
+
signInSSO: signInSSO(options),
|
|
1406
|
+
callbackSSO: callbackSSO(options),
|
|
1407
|
+
callbackSSOSAML: callbackSSOSAML(options),
|
|
1408
|
+
acsEndpoint: acsEndpoint(options)
|
|
1409
|
+
};
|
|
1410
|
+
if (options?.domainVerification?.enabled) {
|
|
1411
|
+
const domainVerificationEndpoints = {
|
|
1412
|
+
requestDomainVerification: requestDomainVerification(options),
|
|
1413
|
+
verifyDomain: verifyDomain(options)
|
|
1414
|
+
};
|
|
1415
|
+
endpoints = {
|
|
1416
|
+
...endpoints,
|
|
1417
|
+
...domainVerificationEndpoints
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1182
1420
|
return {
|
|
1183
1421
|
id: "sso",
|
|
1184
|
-
endpoints
|
|
1185
|
-
spMetadata: spMetadata(),
|
|
1186
|
-
registerSSOProvider: registerSSOProvider(options),
|
|
1187
|
-
signInSSO: signInSSO(options),
|
|
1188
|
-
callbackSSO: callbackSSO(options),
|
|
1189
|
-
callbackSSOSAML: callbackSSOSAML(options),
|
|
1190
|
-
acsEndpoint: acsEndpoint(options)
|
|
1191
|
-
},
|
|
1422
|
+
endpoints,
|
|
1192
1423
|
schema: { ssoProvider: {
|
|
1193
1424
|
modelName: options?.modelName ?? "ssoProvider",
|
|
1194
1425
|
fields: {
|
|
@@ -1230,7 +1461,11 @@ function sso(options) {
|
|
|
1230
1461
|
type: "string",
|
|
1231
1462
|
required: true,
|
|
1232
1463
|
fieldName: options?.fields?.domain ?? "domain"
|
|
1233
|
-
}
|
|
1464
|
+
},
|
|
1465
|
+
...options?.domainVerification?.enabled ? { domainVerified: {
|
|
1466
|
+
type: "boolean",
|
|
1467
|
+
required: false
|
|
1468
|
+
} } : {}
|
|
1234
1469
|
}
|
|
1235
1470
|
} }
|
|
1236
1471
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/sso",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.4.0-beta.
|
|
4
|
+
"version": "1.4.0-beta.23",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
7
7
|
"homepage": "https://www.better-auth.com/docs/plugins/sso",
|
|
@@ -60,15 +60,15 @@
|
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/body-parser": "^1.19.6",
|
|
62
62
|
"@types/express": "^5.0.5",
|
|
63
|
-
"better-call": "1.0.
|
|
63
|
+
"better-call": "1.0.28",
|
|
64
64
|
"body-parser": "^2.2.0",
|
|
65
65
|
"express": "^5.1.0",
|
|
66
66
|
"oauth2-mock-server": "^7.2.1",
|
|
67
67
|
"tsdown": "^0.16.0",
|
|
68
|
-
"better-auth": "1.4.0-beta.
|
|
68
|
+
"better-auth": "1.4.0-beta.23"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"better-auth": "1.4.0-beta.
|
|
71
|
+
"better-auth": "1.4.0-beta.23"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"test": "vitest",
|
package/src/client.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import type { BetterAuthClientPlugin } from "better-auth";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { SSOPlugin } from "./index";
|
|
3
|
+
|
|
4
|
+
interface SSOClientOptions {
|
|
5
|
+
domainVerification?:
|
|
6
|
+
| {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
| undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ssoClient = <CO extends SSOClientOptions>(
|
|
13
|
+
options?: CO | undefined,
|
|
14
|
+
) => {
|
|
4
15
|
return {
|
|
5
16
|
id: "sso-client",
|
|
6
|
-
$InferServerPlugin: {} as
|
|
17
|
+
$InferServerPlugin: {} as SSOPlugin<{
|
|
18
|
+
domainVerification: {
|
|
19
|
+
enabled: CO["domainVerification"] extends { enabled: true }
|
|
20
|
+
? true
|
|
21
|
+
: false;
|
|
22
|
+
};
|
|
23
|
+
}>,
|
|
7
24
|
} satisfies BetterAuthClientPlugin;
|
|
8
25
|
};
|