@better-auth/sso 1.5.0-beta.8 → 1.5.0

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.
@@ -1,169 +0,0 @@
1
- import type { GenericEndpointContext, OAuth2Tokens, User } from "better-auth";
2
- import type { SSOOptions, SSOProvider } from "../types";
3
- import type { NormalizedSSOProfile } from "./types";
4
-
5
- export interface OrganizationProvisioningOptions {
6
- disabled?: boolean;
7
- defaultRole?: "member" | "admin";
8
- getRole?: (data: {
9
- user: User & Record<string, any>;
10
- userInfo: Record<string, any>;
11
- token?: OAuth2Tokens;
12
- provider: SSOProvider<SSOOptions>;
13
- }) => Promise<"member" | "admin">;
14
- }
15
-
16
- export interface AssignOrganizationFromProviderOptions {
17
- user: User;
18
- profile: NormalizedSSOProfile;
19
- provider: SSOProvider<SSOOptions>;
20
- token?: OAuth2Tokens;
21
- provisioningOptions?: OrganizationProvisioningOptions;
22
- }
23
-
24
- /**
25
- * Assigns a user to an organization based on the SSO provider's organizationId.
26
- * Used in SSO flows (OIDC, SAML) where the provider is already linked to an org.
27
- */
28
- export async function assignOrganizationFromProvider(
29
- ctx: GenericEndpointContext,
30
- options: AssignOrganizationFromProviderOptions,
31
- ): Promise<void> {
32
- const { user, profile, provider, token, provisioningOptions } = options;
33
-
34
- if (!provider.organizationId) {
35
- return;
36
- }
37
-
38
- if (provisioningOptions?.disabled) {
39
- return;
40
- }
41
-
42
- const isOrgPluginEnabled = ctx.context.options.plugins?.find(
43
- (plugin) => plugin.id === "organization",
44
- );
45
-
46
- if (!isOrgPluginEnabled) {
47
- return;
48
- }
49
-
50
- const isAlreadyMember = await ctx.context.adapter.findOne({
51
- model: "member",
52
- where: [
53
- { field: "organizationId", value: provider.organizationId },
54
- { field: "userId", value: user.id },
55
- ],
56
- });
57
-
58
- if (isAlreadyMember) {
59
- return;
60
- }
61
-
62
- const role = provisioningOptions?.getRole
63
- ? await provisioningOptions.getRole({
64
- user,
65
- userInfo: profile.rawAttributes || {},
66
- token,
67
- provider,
68
- })
69
- : provisioningOptions?.defaultRole || "member";
70
-
71
- await ctx.context.adapter.create({
72
- model: "member",
73
- data: {
74
- organizationId: provider.organizationId,
75
- userId: user.id,
76
- role,
77
- createdAt: new Date(),
78
- },
79
- });
80
- }
81
-
82
- export interface AssignOrganizationByDomainOptions {
83
- user: User;
84
- provisioningOptions?: OrganizationProvisioningOptions;
85
- domainVerification?: {
86
- enabled?: boolean;
87
- };
88
- }
89
-
90
- /**
91
- * Assigns a user to an organization based on their email domain.
92
- * Looks up SSO providers that match the user's email domain and assigns
93
- * the user to the associated organization.
94
- *
95
- * This enables domain-based org assignment for non-SSO sign-in methods
96
- * (e.g., Google OAuth with @acme.com email gets added to Acme's org).
97
- */
98
- export async function assignOrganizationByDomain(
99
- ctx: GenericEndpointContext,
100
- options: AssignOrganizationByDomainOptions,
101
- ): Promise<void> {
102
- const { user, provisioningOptions, domainVerification } = options;
103
-
104
- if (provisioningOptions?.disabled) {
105
- return;
106
- }
107
-
108
- const isOrgPluginEnabled = ctx.context.options.plugins?.find(
109
- (plugin) => plugin.id === "organization",
110
- );
111
-
112
- if (!isOrgPluginEnabled) {
113
- return;
114
- }
115
-
116
- const domain = user.email.split("@")[1];
117
- if (!domain) {
118
- return;
119
- }
120
-
121
- const whereClause: { field: string; value: string | boolean }[] = [
122
- { field: "domain", value: domain },
123
- ];
124
-
125
- if (domainVerification?.enabled) {
126
- whereClause.push({ field: "domainVerified", value: true });
127
- }
128
-
129
- const ssoProvider = await ctx.context.adapter.findOne<
130
- SSOProvider<SSOOptions>
131
- >({
132
- model: "ssoProvider",
133
- where: whereClause,
134
- });
135
-
136
- if (!ssoProvider || !ssoProvider.organizationId) {
137
- return;
138
- }
139
-
140
- const isAlreadyMember = await ctx.context.adapter.findOne({
141
- model: "member",
142
- where: [
143
- { field: "organizationId", value: ssoProvider.organizationId },
144
- { field: "userId", value: user.id },
145
- ],
146
- });
147
-
148
- if (isAlreadyMember) {
149
- return;
150
- }
151
-
152
- const role = provisioningOptions?.getRole
153
- ? await provisioningOptions.getRole({
154
- user,
155
- userInfo: {},
156
- provider: ssoProvider,
157
- })
158
- : provisioningOptions?.defaultRole || "member";
159
-
160
- await ctx.context.adapter.create({
161
- model: "member",
162
- data: {
163
- organizationId: ssoProvider.organizationId,
164
- userId: user.id,
165
- role,
166
- createdAt: new Date(),
167
- },
168
- });
169
- }
@@ -1,10 +0,0 @@
1
- export interface NormalizedSSOProfile {
2
- providerType: "saml" | "oidc";
3
- providerId: string;
4
- accountId: string;
5
- email: string;
6
- emailVerified: boolean;
7
- name?: string;
8
- image?: string;
9
- rawAttributes?: Record<string, unknown>;
10
- }