@lunora/auth 1.0.0-alpha.20 → 1.0.0-alpha.22
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/index.d.mts
CHANGED
|
@@ -91,6 +91,35 @@ interface AuthInvitation {
|
|
|
91
91
|
role?: null | string;
|
|
92
92
|
status?: null | string;
|
|
93
93
|
}
|
|
94
|
+
/** One team row (from the `organization` plugin with `teams.enabled`). */
|
|
95
|
+
interface AuthTeam {
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
createdAt?: AuthTimestamp;
|
|
98
|
+
id: string;
|
|
99
|
+
name?: null | string;
|
|
100
|
+
organizationId: string;
|
|
101
|
+
}
|
|
102
|
+
/** One team-membership row (teams). */
|
|
103
|
+
interface AuthTeamMember {
|
|
104
|
+
[key: string]: unknown;
|
|
105
|
+
createdAt?: AuthTimestamp;
|
|
106
|
+
id: string;
|
|
107
|
+
teamId: string;
|
|
108
|
+
userId: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One custom organization role (from the organization plugin's dynamic
|
|
112
|
+
* access-control). `permission` is a JSON string of a `resource → actions[]` map
|
|
113
|
+
* as stored; the studio parses it for display/editing.
|
|
114
|
+
*/
|
|
115
|
+
interface AuthOrgRole {
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
createdAt?: AuthTimestamp;
|
|
118
|
+
id: string;
|
|
119
|
+
organizationId: string;
|
|
120
|
+
permission?: null | string;
|
|
121
|
+
role?: null | string;
|
|
122
|
+
}
|
|
94
123
|
/** One registered passkey. Credential secrets (`publicKey`) are stripped. */
|
|
95
124
|
interface AuthPasskey {
|
|
96
125
|
[key: string]: unknown;
|
|
@@ -124,6 +153,63 @@ interface AuthCapabilities {
|
|
|
124
153
|
/** The `two-factor` plugin: per-user 2FA status / disable. */
|
|
125
154
|
twoFactor: boolean;
|
|
126
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* One app/plugin-defined field the create-user form should render, derived from
|
|
158
|
+
* the merged better-auth `user` table (core + plugin + `additionalFields`). Only
|
|
159
|
+
* user-settable columns are surfaced — server-managed flags (`input: false`),
|
|
160
|
+
* foreign keys (`references`), and the core columns the form already handles
|
|
161
|
+
* (`email`/`name`/`role`/ban state/…) are filtered out upstream.
|
|
162
|
+
*/
|
|
163
|
+
interface AuthUserFieldSpec {
|
|
164
|
+
/** Logical field name (the key passed back in `createUser`'s `data`). */
|
|
165
|
+
name: string;
|
|
166
|
+
/** Best-effort plugin id the field originates from (`username`, `phone-number`, …); `undefined` for app `additionalFields`. */
|
|
167
|
+
plugin?: string;
|
|
168
|
+
required: boolean;
|
|
169
|
+
/** Coarse input kind the studio maps to a control (checkbox / number / date / text). */
|
|
170
|
+
type: "boolean" | "date" | "number" | "string";
|
|
171
|
+
unique: boolean;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A rich, read-only description of the deployment's auth configuration for the
|
|
175
|
+
* studio's config panel and dynamic create-user form. Unlike
|
|
176
|
+
* {@link AuthCapabilities} (five booleans that gate panels), this exposes *what*
|
|
177
|
+
* is configured — enabled plugins, email/password + social sign-in, the
|
|
178
|
+
* user-settable fields, organization sub-features (teams / custom roles), and
|
|
179
|
+
* the session + rate-limit policy — without ever leaking a secret.
|
|
180
|
+
*/
|
|
181
|
+
interface AuthConfigInfo {
|
|
182
|
+
/** The same capability booleans {@link AuthAdmin.capabilities} returns, embedded so a single call drives the whole panel. */
|
|
183
|
+
capabilities: AuthCapabilities;
|
|
184
|
+
/** Whether email + password sign-in is enabled. */
|
|
185
|
+
emailAndPassword: boolean;
|
|
186
|
+
/** Organization plugin sub-features. */
|
|
187
|
+
organization: {
|
|
188
|
+
enabled: boolean; /** Custom roles / dynamic access control (`organizationRole` table present). */
|
|
189
|
+
roles: boolean;
|
|
190
|
+
/** Teams (`team` table present). */
|
|
191
|
+
teams: boolean;
|
|
192
|
+
};
|
|
193
|
+
/** Enabled better-auth plugin ids, sorted. */
|
|
194
|
+
plugins: string[];
|
|
195
|
+
/** Rate-limit policy (window is in seconds). */
|
|
196
|
+
rateLimit: {
|
|
197
|
+
enabled: boolean;
|
|
198
|
+
max?: number;
|
|
199
|
+
window?: number;
|
|
200
|
+
};
|
|
201
|
+
/** Session policy (all durations in seconds). */
|
|
202
|
+
session: {
|
|
203
|
+
cookieCache?: boolean;
|
|
204
|
+
expiresIn?: number;
|
|
205
|
+
freshAge?: number;
|
|
206
|
+
updateAge?: number;
|
|
207
|
+
};
|
|
208
|
+
/** Configured social/OAuth provider ids, sorted. */
|
|
209
|
+
socialProviders: string[];
|
|
210
|
+
/** User-settable extra fields for the create-user form (plugin + app `additionalFields`). */
|
|
211
|
+
userFields: AuthUserFieldSpec[];
|
|
212
|
+
}
|
|
127
213
|
/** A scalar value usable in an adapter `where` clause / filter. */
|
|
128
214
|
type WhereValue = boolean | number | string;
|
|
129
215
|
/** Filtering / paging options for {@link AuthAdmin.listUsers}. */
|
|
@@ -159,6 +245,17 @@ interface ImpersonationResult {
|
|
|
159
245
|
* surfaces the underlying adapter error.
|
|
160
246
|
*/
|
|
161
247
|
interface AuthAdmin {
|
|
248
|
+
/** Directly add an existing user as an org member (server-side, no invitation/acceptance). */
|
|
249
|
+
addMember: (input: {
|
|
250
|
+
organizationId: string;
|
|
251
|
+
role?: string;
|
|
252
|
+
userId: string;
|
|
253
|
+
}) => Promise<AuthMember>;
|
|
254
|
+
/** Add a user to a team. */
|
|
255
|
+
addTeamMember: (input: {
|
|
256
|
+
teamId: string;
|
|
257
|
+
userId: string;
|
|
258
|
+
}) => Promise<AuthTeamMember>;
|
|
162
259
|
banUser: (input: {
|
|
163
260
|
expiresInSeconds?: number;
|
|
164
261
|
reason?: string;
|
|
@@ -168,6 +265,27 @@ interface AuthAdmin {
|
|
|
168
265
|
invitationId: string;
|
|
169
266
|
}) => Promise<void>;
|
|
170
267
|
capabilities: () => Promise<AuthCapabilities>;
|
|
268
|
+
/** Rich, read-only description of the auth configuration (plugins, fields, session policy, …). */
|
|
269
|
+
config: () => Promise<AuthConfigInfo>;
|
|
270
|
+
/** Create an organization; optionally seed an `owner` member for `ownerId`. */
|
|
271
|
+
createOrganization: (input: {
|
|
272
|
+
logo?: string;
|
|
273
|
+
metadata?: Record<string, unknown>;
|
|
274
|
+
name: string;
|
|
275
|
+
ownerId?: string;
|
|
276
|
+
slug?: string;
|
|
277
|
+
}) => Promise<AuthOrganization>;
|
|
278
|
+
/** Create a custom org role with a permission grant (a `resource → actions[]` map). */
|
|
279
|
+
createOrgRole: (input: {
|
|
280
|
+
organizationId: string;
|
|
281
|
+
permission: Record<string, string[]>;
|
|
282
|
+
role: string;
|
|
283
|
+
}) => Promise<AuthOrgRole>;
|
|
284
|
+
/** Create a team under an organization. */
|
|
285
|
+
createTeam: (input: {
|
|
286
|
+
name: string;
|
|
287
|
+
organizationId: string;
|
|
288
|
+
}) => Promise<AuthTeam>;
|
|
171
289
|
createUser: (input: {
|
|
172
290
|
data?: Record<string, unknown>;
|
|
173
291
|
email: string;
|
|
@@ -175,6 +293,14 @@ interface AuthAdmin {
|
|
|
175
293
|
password?: string;
|
|
176
294
|
role?: string | string[];
|
|
177
295
|
}) => Promise<AuthAdminUser>;
|
|
296
|
+
/** Delete an organization and cascade-delete its members, invitations, teams, and custom roles. */
|
|
297
|
+
deleteOrganization: (input: {
|
|
298
|
+
organizationId: string;
|
|
299
|
+
}) => Promise<void>;
|
|
300
|
+
/** Delete a custom org role. */
|
|
301
|
+
deleteOrgRole: (input: {
|
|
302
|
+
roleId: string;
|
|
303
|
+
}) => Promise<void>;
|
|
178
304
|
deletePasskey: (input: {
|
|
179
305
|
passkeyId: string;
|
|
180
306
|
}) => Promise<void>;
|
|
@@ -184,6 +310,13 @@ interface AuthAdmin {
|
|
|
184
310
|
impersonateUser: (input: {
|
|
185
311
|
userId: string;
|
|
186
312
|
}) => Promise<ImpersonationResult>;
|
|
313
|
+
/** Create a pending email invitation to an org (no acceptance side effects). */
|
|
314
|
+
inviteMember: (input: {
|
|
315
|
+
email: string;
|
|
316
|
+
inviterId?: string;
|
|
317
|
+
organizationId: string;
|
|
318
|
+
role?: string;
|
|
319
|
+
}) => Promise<AuthInvitation>;
|
|
187
320
|
listAccounts: (input: {
|
|
188
321
|
userId: string;
|
|
189
322
|
}) => Promise<AuthAccount[]>;
|
|
@@ -201,6 +334,12 @@ interface AuthAdmin {
|
|
|
201
334
|
limit?: number;
|
|
202
335
|
offset?: number;
|
|
203
336
|
}) => Promise<AuthPage<AuthOrganization>>;
|
|
337
|
+
/** List an org's custom roles. */
|
|
338
|
+
listOrgRoles: (options: {
|
|
339
|
+
limit?: number;
|
|
340
|
+
offset?: number;
|
|
341
|
+
organizationId: string;
|
|
342
|
+
}) => Promise<AuthPage<AuthOrgRole>>;
|
|
204
343
|
listPasskeys: (input: {
|
|
205
344
|
userId: string;
|
|
206
345
|
}) => Promise<AuthPasskey[]>;
|
|
@@ -209,10 +348,30 @@ interface AuthAdmin {
|
|
|
209
348
|
offset?: number;
|
|
210
349
|
userId?: string;
|
|
211
350
|
}) => Promise<AuthPage<AuthAdminSession>>;
|
|
351
|
+
/** List a team's members. */
|
|
352
|
+
listTeamMembers: (options: {
|
|
353
|
+
limit?: number;
|
|
354
|
+
offset?: number;
|
|
355
|
+
teamId: string;
|
|
356
|
+
}) => Promise<AuthPage<AuthTeamMember>>;
|
|
357
|
+
/** List an org's teams. */
|
|
358
|
+
listTeams: (options: {
|
|
359
|
+
limit?: number;
|
|
360
|
+
offset?: number;
|
|
361
|
+
organizationId: string;
|
|
362
|
+
}) => Promise<AuthPage<AuthTeam>>;
|
|
212
363
|
listUsers: (options: ListUsersOptions) => Promise<AuthPage<AuthAdminUser>>;
|
|
213
364
|
removeMember: (input: {
|
|
214
365
|
memberId: string;
|
|
215
366
|
}) => Promise<void>;
|
|
367
|
+
/** Delete a team and its memberships. */
|
|
368
|
+
removeTeam: (input: {
|
|
369
|
+
teamId: string;
|
|
370
|
+
}) => Promise<void>;
|
|
371
|
+
/** Remove a member from a team. */
|
|
372
|
+
removeTeamMember: (input: {
|
|
373
|
+
teamMemberId: string;
|
|
374
|
+
}) => Promise<void>;
|
|
216
375
|
removeUser: (input: {
|
|
217
376
|
userId: string;
|
|
218
377
|
}) => Promise<void>;
|
|
@@ -237,6 +396,29 @@ interface AuthAdmin {
|
|
|
237
396
|
accountId: string;
|
|
238
397
|
userId: string;
|
|
239
398
|
}) => Promise<void>;
|
|
399
|
+
/** Change a member's role. */
|
|
400
|
+
updateMemberRole: (input: {
|
|
401
|
+
memberId: string;
|
|
402
|
+
role: string | string[];
|
|
403
|
+
}) => Promise<AuthMember>;
|
|
404
|
+
/** Update an organization's name/slug/logo/metadata. */
|
|
405
|
+
updateOrganization: (input: {
|
|
406
|
+
logo?: string;
|
|
407
|
+
metadata?: Record<string, unknown>;
|
|
408
|
+
name?: string;
|
|
409
|
+
organizationId: string;
|
|
410
|
+
slug?: string;
|
|
411
|
+
}) => Promise<AuthOrganization>;
|
|
412
|
+
/** Replace a custom org role's permission grant. */
|
|
413
|
+
updateOrgRole: (input: {
|
|
414
|
+
permission: Record<string, string[]>;
|
|
415
|
+
roleId: string;
|
|
416
|
+
}) => Promise<AuthOrgRole>;
|
|
417
|
+
/** Rename a team. */
|
|
418
|
+
updateTeam: (input: {
|
|
419
|
+
name: string;
|
|
420
|
+
teamId: string;
|
|
421
|
+
}) => Promise<AuthTeam>;
|
|
240
422
|
updateUser: (input: {
|
|
241
423
|
data: Record<string, unknown>;
|
|
242
424
|
userId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -91,6 +91,35 @@ interface AuthInvitation {
|
|
|
91
91
|
role?: null | string;
|
|
92
92
|
status?: null | string;
|
|
93
93
|
}
|
|
94
|
+
/** One team row (from the `organization` plugin with `teams.enabled`). */
|
|
95
|
+
interface AuthTeam {
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
createdAt?: AuthTimestamp;
|
|
98
|
+
id: string;
|
|
99
|
+
name?: null | string;
|
|
100
|
+
organizationId: string;
|
|
101
|
+
}
|
|
102
|
+
/** One team-membership row (teams). */
|
|
103
|
+
interface AuthTeamMember {
|
|
104
|
+
[key: string]: unknown;
|
|
105
|
+
createdAt?: AuthTimestamp;
|
|
106
|
+
id: string;
|
|
107
|
+
teamId: string;
|
|
108
|
+
userId: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One custom organization role (from the organization plugin's dynamic
|
|
112
|
+
* access-control). `permission` is a JSON string of a `resource → actions[]` map
|
|
113
|
+
* as stored; the studio parses it for display/editing.
|
|
114
|
+
*/
|
|
115
|
+
interface AuthOrgRole {
|
|
116
|
+
[key: string]: unknown;
|
|
117
|
+
createdAt?: AuthTimestamp;
|
|
118
|
+
id: string;
|
|
119
|
+
organizationId: string;
|
|
120
|
+
permission?: null | string;
|
|
121
|
+
role?: null | string;
|
|
122
|
+
}
|
|
94
123
|
/** One registered passkey. Credential secrets (`publicKey`) are stripped. */
|
|
95
124
|
interface AuthPasskey {
|
|
96
125
|
[key: string]: unknown;
|
|
@@ -124,6 +153,63 @@ interface AuthCapabilities {
|
|
|
124
153
|
/** The `two-factor` plugin: per-user 2FA status / disable. */
|
|
125
154
|
twoFactor: boolean;
|
|
126
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* One app/plugin-defined field the create-user form should render, derived from
|
|
158
|
+
* the merged better-auth `user` table (core + plugin + `additionalFields`). Only
|
|
159
|
+
* user-settable columns are surfaced — server-managed flags (`input: false`),
|
|
160
|
+
* foreign keys (`references`), and the core columns the form already handles
|
|
161
|
+
* (`email`/`name`/`role`/ban state/…) are filtered out upstream.
|
|
162
|
+
*/
|
|
163
|
+
interface AuthUserFieldSpec {
|
|
164
|
+
/** Logical field name (the key passed back in `createUser`'s `data`). */
|
|
165
|
+
name: string;
|
|
166
|
+
/** Best-effort plugin id the field originates from (`username`, `phone-number`, …); `undefined` for app `additionalFields`. */
|
|
167
|
+
plugin?: string;
|
|
168
|
+
required: boolean;
|
|
169
|
+
/** Coarse input kind the studio maps to a control (checkbox / number / date / text). */
|
|
170
|
+
type: "boolean" | "date" | "number" | "string";
|
|
171
|
+
unique: boolean;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A rich, read-only description of the deployment's auth configuration for the
|
|
175
|
+
* studio's config panel and dynamic create-user form. Unlike
|
|
176
|
+
* {@link AuthCapabilities} (five booleans that gate panels), this exposes *what*
|
|
177
|
+
* is configured — enabled plugins, email/password + social sign-in, the
|
|
178
|
+
* user-settable fields, organization sub-features (teams / custom roles), and
|
|
179
|
+
* the session + rate-limit policy — without ever leaking a secret.
|
|
180
|
+
*/
|
|
181
|
+
interface AuthConfigInfo {
|
|
182
|
+
/** The same capability booleans {@link AuthAdmin.capabilities} returns, embedded so a single call drives the whole panel. */
|
|
183
|
+
capabilities: AuthCapabilities;
|
|
184
|
+
/** Whether email + password sign-in is enabled. */
|
|
185
|
+
emailAndPassword: boolean;
|
|
186
|
+
/** Organization plugin sub-features. */
|
|
187
|
+
organization: {
|
|
188
|
+
enabled: boolean; /** Custom roles / dynamic access control (`organizationRole` table present). */
|
|
189
|
+
roles: boolean;
|
|
190
|
+
/** Teams (`team` table present). */
|
|
191
|
+
teams: boolean;
|
|
192
|
+
};
|
|
193
|
+
/** Enabled better-auth plugin ids, sorted. */
|
|
194
|
+
plugins: string[];
|
|
195
|
+
/** Rate-limit policy (window is in seconds). */
|
|
196
|
+
rateLimit: {
|
|
197
|
+
enabled: boolean;
|
|
198
|
+
max?: number;
|
|
199
|
+
window?: number;
|
|
200
|
+
};
|
|
201
|
+
/** Session policy (all durations in seconds). */
|
|
202
|
+
session: {
|
|
203
|
+
cookieCache?: boolean;
|
|
204
|
+
expiresIn?: number;
|
|
205
|
+
freshAge?: number;
|
|
206
|
+
updateAge?: number;
|
|
207
|
+
};
|
|
208
|
+
/** Configured social/OAuth provider ids, sorted. */
|
|
209
|
+
socialProviders: string[];
|
|
210
|
+
/** User-settable extra fields for the create-user form (plugin + app `additionalFields`). */
|
|
211
|
+
userFields: AuthUserFieldSpec[];
|
|
212
|
+
}
|
|
127
213
|
/** A scalar value usable in an adapter `where` clause / filter. */
|
|
128
214
|
type WhereValue = boolean | number | string;
|
|
129
215
|
/** Filtering / paging options for {@link AuthAdmin.listUsers}. */
|
|
@@ -159,6 +245,17 @@ interface ImpersonationResult {
|
|
|
159
245
|
* surfaces the underlying adapter error.
|
|
160
246
|
*/
|
|
161
247
|
interface AuthAdmin {
|
|
248
|
+
/** Directly add an existing user as an org member (server-side, no invitation/acceptance). */
|
|
249
|
+
addMember: (input: {
|
|
250
|
+
organizationId: string;
|
|
251
|
+
role?: string;
|
|
252
|
+
userId: string;
|
|
253
|
+
}) => Promise<AuthMember>;
|
|
254
|
+
/** Add a user to a team. */
|
|
255
|
+
addTeamMember: (input: {
|
|
256
|
+
teamId: string;
|
|
257
|
+
userId: string;
|
|
258
|
+
}) => Promise<AuthTeamMember>;
|
|
162
259
|
banUser: (input: {
|
|
163
260
|
expiresInSeconds?: number;
|
|
164
261
|
reason?: string;
|
|
@@ -168,6 +265,27 @@ interface AuthAdmin {
|
|
|
168
265
|
invitationId: string;
|
|
169
266
|
}) => Promise<void>;
|
|
170
267
|
capabilities: () => Promise<AuthCapabilities>;
|
|
268
|
+
/** Rich, read-only description of the auth configuration (plugins, fields, session policy, …). */
|
|
269
|
+
config: () => Promise<AuthConfigInfo>;
|
|
270
|
+
/** Create an organization; optionally seed an `owner` member for `ownerId`. */
|
|
271
|
+
createOrganization: (input: {
|
|
272
|
+
logo?: string;
|
|
273
|
+
metadata?: Record<string, unknown>;
|
|
274
|
+
name: string;
|
|
275
|
+
ownerId?: string;
|
|
276
|
+
slug?: string;
|
|
277
|
+
}) => Promise<AuthOrganization>;
|
|
278
|
+
/** Create a custom org role with a permission grant (a `resource → actions[]` map). */
|
|
279
|
+
createOrgRole: (input: {
|
|
280
|
+
organizationId: string;
|
|
281
|
+
permission: Record<string, string[]>;
|
|
282
|
+
role: string;
|
|
283
|
+
}) => Promise<AuthOrgRole>;
|
|
284
|
+
/** Create a team under an organization. */
|
|
285
|
+
createTeam: (input: {
|
|
286
|
+
name: string;
|
|
287
|
+
organizationId: string;
|
|
288
|
+
}) => Promise<AuthTeam>;
|
|
171
289
|
createUser: (input: {
|
|
172
290
|
data?: Record<string, unknown>;
|
|
173
291
|
email: string;
|
|
@@ -175,6 +293,14 @@ interface AuthAdmin {
|
|
|
175
293
|
password?: string;
|
|
176
294
|
role?: string | string[];
|
|
177
295
|
}) => Promise<AuthAdminUser>;
|
|
296
|
+
/** Delete an organization and cascade-delete its members, invitations, teams, and custom roles. */
|
|
297
|
+
deleteOrganization: (input: {
|
|
298
|
+
organizationId: string;
|
|
299
|
+
}) => Promise<void>;
|
|
300
|
+
/** Delete a custom org role. */
|
|
301
|
+
deleteOrgRole: (input: {
|
|
302
|
+
roleId: string;
|
|
303
|
+
}) => Promise<void>;
|
|
178
304
|
deletePasskey: (input: {
|
|
179
305
|
passkeyId: string;
|
|
180
306
|
}) => Promise<void>;
|
|
@@ -184,6 +310,13 @@ interface AuthAdmin {
|
|
|
184
310
|
impersonateUser: (input: {
|
|
185
311
|
userId: string;
|
|
186
312
|
}) => Promise<ImpersonationResult>;
|
|
313
|
+
/** Create a pending email invitation to an org (no acceptance side effects). */
|
|
314
|
+
inviteMember: (input: {
|
|
315
|
+
email: string;
|
|
316
|
+
inviterId?: string;
|
|
317
|
+
organizationId: string;
|
|
318
|
+
role?: string;
|
|
319
|
+
}) => Promise<AuthInvitation>;
|
|
187
320
|
listAccounts: (input: {
|
|
188
321
|
userId: string;
|
|
189
322
|
}) => Promise<AuthAccount[]>;
|
|
@@ -201,6 +334,12 @@ interface AuthAdmin {
|
|
|
201
334
|
limit?: number;
|
|
202
335
|
offset?: number;
|
|
203
336
|
}) => Promise<AuthPage<AuthOrganization>>;
|
|
337
|
+
/** List an org's custom roles. */
|
|
338
|
+
listOrgRoles: (options: {
|
|
339
|
+
limit?: number;
|
|
340
|
+
offset?: number;
|
|
341
|
+
organizationId: string;
|
|
342
|
+
}) => Promise<AuthPage<AuthOrgRole>>;
|
|
204
343
|
listPasskeys: (input: {
|
|
205
344
|
userId: string;
|
|
206
345
|
}) => Promise<AuthPasskey[]>;
|
|
@@ -209,10 +348,30 @@ interface AuthAdmin {
|
|
|
209
348
|
offset?: number;
|
|
210
349
|
userId?: string;
|
|
211
350
|
}) => Promise<AuthPage<AuthAdminSession>>;
|
|
351
|
+
/** List a team's members. */
|
|
352
|
+
listTeamMembers: (options: {
|
|
353
|
+
limit?: number;
|
|
354
|
+
offset?: number;
|
|
355
|
+
teamId: string;
|
|
356
|
+
}) => Promise<AuthPage<AuthTeamMember>>;
|
|
357
|
+
/** List an org's teams. */
|
|
358
|
+
listTeams: (options: {
|
|
359
|
+
limit?: number;
|
|
360
|
+
offset?: number;
|
|
361
|
+
organizationId: string;
|
|
362
|
+
}) => Promise<AuthPage<AuthTeam>>;
|
|
212
363
|
listUsers: (options: ListUsersOptions) => Promise<AuthPage<AuthAdminUser>>;
|
|
213
364
|
removeMember: (input: {
|
|
214
365
|
memberId: string;
|
|
215
366
|
}) => Promise<void>;
|
|
367
|
+
/** Delete a team and its memberships. */
|
|
368
|
+
removeTeam: (input: {
|
|
369
|
+
teamId: string;
|
|
370
|
+
}) => Promise<void>;
|
|
371
|
+
/** Remove a member from a team. */
|
|
372
|
+
removeTeamMember: (input: {
|
|
373
|
+
teamMemberId: string;
|
|
374
|
+
}) => Promise<void>;
|
|
216
375
|
removeUser: (input: {
|
|
217
376
|
userId: string;
|
|
218
377
|
}) => Promise<void>;
|
|
@@ -237,6 +396,29 @@ interface AuthAdmin {
|
|
|
237
396
|
accountId: string;
|
|
238
397
|
userId: string;
|
|
239
398
|
}) => Promise<void>;
|
|
399
|
+
/** Change a member's role. */
|
|
400
|
+
updateMemberRole: (input: {
|
|
401
|
+
memberId: string;
|
|
402
|
+
role: string | string[];
|
|
403
|
+
}) => Promise<AuthMember>;
|
|
404
|
+
/** Update an organization's name/slug/logo/metadata. */
|
|
405
|
+
updateOrganization: (input: {
|
|
406
|
+
logo?: string;
|
|
407
|
+
metadata?: Record<string, unknown>;
|
|
408
|
+
name?: string;
|
|
409
|
+
organizationId: string;
|
|
410
|
+
slug?: string;
|
|
411
|
+
}) => Promise<AuthOrganization>;
|
|
412
|
+
/** Replace a custom org role's permission grant. */
|
|
413
|
+
updateOrgRole: (input: {
|
|
414
|
+
permission: Record<string, string[]>;
|
|
415
|
+
roleId: string;
|
|
416
|
+
}) => Promise<AuthOrgRole>;
|
|
417
|
+
/** Rename a team. */
|
|
418
|
+
updateTeam: (input: {
|
|
419
|
+
name: string;
|
|
420
|
+
teamId: string;
|
|
421
|
+
}) => Promise<AuthTeam>;
|
|
240
422
|
updateUser: (input: {
|
|
241
423
|
data: Record<string, unknown>;
|
|
242
424
|
userId: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { lunoraAuthAdapter, lunoraD1Adapter } from './adapter.mjs';
|
|
2
|
-
export { LunoraAuthAdminError, createAuthAdmin } from './packem_shared/LunoraAuthAdminError-
|
|
2
|
+
export { LunoraAuthAdminError, createAuthAdmin } from './packem_shared/LunoraAuthAdminError-D4L7n6gN.mjs';
|
|
3
3
|
export { createAuth, resolveAuthOptions } from './packem_shared/createAuth-BVMMllTm.mjs';
|
|
4
4
|
export { DEFAULT_AUTH_BASE_PATH, handleAuthRequest } from './packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs';
|
|
5
5
|
export { LunoraAuthHeadersError, withAuthPlugins } from './middleware.mjs';
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { getAuthTables } from 'better-auth/db';
|
|
3
|
+
|
|
4
|
+
class LunoraAuthAdminError extends LunoraError {
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(code, message, { name: "LunoraAuthAdminError" });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const DEFAULT_LIMIT = 50;
|
|
10
|
+
const MAX_LIMIT = 500;
|
|
11
|
+
const DEFAULT_IMPERSONATION_SECONDS = 3600;
|
|
12
|
+
const MAX_IMPERSONATION_SECONDS = DEFAULT_IMPERSONATION_SECONDS * 24;
|
|
13
|
+
const MAX_BAN_SECONDS = 100 * 365 * 24 * 60 * 60;
|
|
14
|
+
const INVITATION_TTL_MS = 48 * 60 * 60 * 1e3;
|
|
15
|
+
const SENSITIVE_FIELDS = /* @__PURE__ */ new Set(["accessToken", "backupCodes", "idToken", "password", "publicKey", "refreshToken", "secret", "token"]);
|
|
16
|
+
const clampLimit = (limit) => Math.min(Math.max(Math.trunc(limit ?? DEFAULT_LIMIT), 1), MAX_LIMIT);
|
|
17
|
+
const clampOffset = (offset) => Math.max(0, Math.trunc(offset ?? 0));
|
|
18
|
+
const normalizeRow = (row) => {
|
|
19
|
+
const out = {};
|
|
20
|
+
for (const [key, value] of Object.entries(row)) {
|
|
21
|
+
if (SENSITIVE_FIELDS.has(key)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
out[key] = value instanceof Date ? value.getTime() : value;
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
};
|
|
28
|
+
const serializeRole = (role) => Array.isArray(role) ? role.join(",") : role;
|
|
29
|
+
const slugify = (value) => value.toLowerCase().replaceAll(/[^\da-z]+/g, "-").replaceAll(/^-|-$/g, "");
|
|
30
|
+
const CORE_USER_FIELDS = /* @__PURE__ */ new Set(["banExpires", "banned", "banReason", "createdAt", "email", "emailVerified", "id", "name", "role", "updatedAt"]);
|
|
31
|
+
const USER_FIELD_PLUGIN = {
|
|
32
|
+
displayUsername: "username",
|
|
33
|
+
phoneNumber: "phone-number",
|
|
34
|
+
phoneNumberVerified: "phone-number",
|
|
35
|
+
username: "username"
|
|
36
|
+
};
|
|
37
|
+
const mapUserFieldType = (type) => {
|
|
38
|
+
if (type === "boolean") {
|
|
39
|
+
return "boolean";
|
|
40
|
+
}
|
|
41
|
+
if (type === "date") {
|
|
42
|
+
return "date";
|
|
43
|
+
}
|
|
44
|
+
if (type === "number") {
|
|
45
|
+
return "number";
|
|
46
|
+
}
|
|
47
|
+
return "string";
|
|
48
|
+
};
|
|
49
|
+
const buildUserFields = (userFields) => {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const [name, attribute] of Object.entries(userFields)) {
|
|
52
|
+
if (attribute.input === false || attribute.references !== void 0 || CORE_USER_FIELDS.has(name)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
out.push({
|
|
56
|
+
name,
|
|
57
|
+
plugin: USER_FIELD_PLUGIN[name],
|
|
58
|
+
required: attribute.required === true,
|
|
59
|
+
type: mapUserFieldType(attribute.type),
|
|
60
|
+
unique: attribute.unique === true
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
const asAdminError = (error) => {
|
|
66
|
+
if (error instanceof LunoraAuthAdminError) {
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
const candidate = error;
|
|
70
|
+
const code = candidate?.body?.code ?? candidate?.code ?? "AUTH_ADMIN_ERROR";
|
|
71
|
+
const message = candidate?.body?.message ?? candidate?.message ?? "auth admin operation failed";
|
|
72
|
+
return new LunoraAuthAdminError(message, code);
|
|
73
|
+
};
|
|
74
|
+
const createAuthAdmin = (auth, options = {}) => {
|
|
75
|
+
const context = auth.$context;
|
|
76
|
+
const features = options.features ?? {};
|
|
77
|
+
const withContext = async (function_) => {
|
|
78
|
+
try {
|
|
79
|
+
return await function_(await context);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw asAdminError(error);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const toUser = (row) => normalizeRow(row);
|
|
85
|
+
const page = async (context_, model, options_) => {
|
|
86
|
+
const where = options_.where && options_.where.length > 0 ? options_.where : void 0;
|
|
87
|
+
const [rows, total] = await Promise.all([
|
|
88
|
+
context_.adapter.findMany({
|
|
89
|
+
limit: clampLimit(options_.limit),
|
|
90
|
+
model,
|
|
91
|
+
offset: clampOffset(options_.offset),
|
|
92
|
+
sortBy: options_.sortBy,
|
|
93
|
+
where
|
|
94
|
+
}),
|
|
95
|
+
context_.adapter.count({ model, where })
|
|
96
|
+
]);
|
|
97
|
+
return { rows: rows.map((row) => normalizeRow(row)), total };
|
|
98
|
+
};
|
|
99
|
+
return {
|
|
100
|
+
banUser: ({ expiresInSeconds, reason, userId }) => withContext(async (context_) => {
|
|
101
|
+
const seconds = typeof expiresInSeconds === "number" && Number.isFinite(expiresInSeconds) ? Math.min(Math.trunc(expiresInSeconds), MAX_BAN_SECONDS) : 0;
|
|
102
|
+
const banExpires = seconds > 0 ? new Date(Date.now() + seconds * 1e3) : void 0;
|
|
103
|
+
const user = await context_.internalAdapter.updateUser(userId, {
|
|
104
|
+
banExpires,
|
|
105
|
+
banned: true,
|
|
106
|
+
banReason: reason ?? "No reason"
|
|
107
|
+
});
|
|
108
|
+
await context_.internalAdapter.deleteUserSessions(userId);
|
|
109
|
+
return toUser(user);
|
|
110
|
+
}),
|
|
111
|
+
cancelInvitation: ({ invitationId }) => withContext(async (context_) => {
|
|
112
|
+
await context_.adapter.delete({ model: "invitation", where: [{ field: "id", value: invitationId }] });
|
|
113
|
+
}),
|
|
114
|
+
capabilities: () => withContext((context_) => {
|
|
115
|
+
const ids = new Set((context_.options.plugins ?? []).map((plugin) => plugin.id));
|
|
116
|
+
const has = (id) => ids.has(id);
|
|
117
|
+
return Promise.resolve({
|
|
118
|
+
accounts: features.accounts ?? true,
|
|
119
|
+
admin: features.admin ?? has("admin"),
|
|
120
|
+
organization: features.organization ?? has("organization"),
|
|
121
|
+
passkey: features.passkey ?? has("passkey"),
|
|
122
|
+
twoFactor: features.twoFactor ?? has("two-factor")
|
|
123
|
+
});
|
|
124
|
+
}),
|
|
125
|
+
// ── Directly add an existing user to an org (no invitation/acceptance). ──
|
|
126
|
+
addMember: ({ organizationId, role, userId }) => withContext(async (context_) => {
|
|
127
|
+
const member = await context_.adapter.create({
|
|
128
|
+
data: { createdAt: /* @__PURE__ */ new Date(), organizationId, role: role === void 0 || role === "" ? "member" : role, userId },
|
|
129
|
+
model: "member"
|
|
130
|
+
});
|
|
131
|
+
return normalizeRow(member);
|
|
132
|
+
}),
|
|
133
|
+
addTeamMember: ({ teamId, userId }) => withContext(async (context_) => {
|
|
134
|
+
const teamMember = await context_.adapter.create({
|
|
135
|
+
data: { createdAt: /* @__PURE__ */ new Date(), teamId, userId },
|
|
136
|
+
model: "teamMember"
|
|
137
|
+
});
|
|
138
|
+
return normalizeRow(teamMember);
|
|
139
|
+
}),
|
|
140
|
+
// Rich introspection for the config panel + dynamic create-user form. Reads
|
|
141
|
+
// only from the resolved better-auth options (no DB, no secrets).
|
|
142
|
+
config: () => withContext((context_) => {
|
|
143
|
+
const authOptions = context_.options;
|
|
144
|
+
const ids = new Set((authOptions.plugins ?? []).map((plugin) => plugin.id));
|
|
145
|
+
const has = (id) => ids.has(id);
|
|
146
|
+
const capabilities = {
|
|
147
|
+
accounts: features.accounts ?? true,
|
|
148
|
+
admin: features.admin ?? has("admin"),
|
|
149
|
+
organization: features.organization ?? has("organization"),
|
|
150
|
+
passkey: features.passkey ?? has("passkey"),
|
|
151
|
+
twoFactor: features.twoFactor ?? has("two-factor")
|
|
152
|
+
};
|
|
153
|
+
const tables = getAuthTables(authOptions);
|
|
154
|
+
const session = authOptions.session ?? {};
|
|
155
|
+
const rateLimit = authOptions.rateLimit ?? {};
|
|
156
|
+
return Promise.resolve({
|
|
157
|
+
capabilities,
|
|
158
|
+
emailAndPassword: authOptions.emailAndPassword?.enabled ?? false,
|
|
159
|
+
organization: {
|
|
160
|
+
enabled: capabilities.organization,
|
|
161
|
+
roles: Boolean(tables["organizationRole"]),
|
|
162
|
+
teams: Boolean(tables["team"])
|
|
163
|
+
},
|
|
164
|
+
plugins: [...ids].toSorted((a, b) => a.localeCompare(b)),
|
|
165
|
+
rateLimit: { enabled: rateLimit.enabled ?? false, max: rateLimit.max, window: rateLimit.window },
|
|
166
|
+
session: {
|
|
167
|
+
cookieCache: session.cookieCache?.enabled,
|
|
168
|
+
expiresIn: session.expiresIn,
|
|
169
|
+
freshAge: session.freshAge,
|
|
170
|
+
updateAge: session.updateAge
|
|
171
|
+
},
|
|
172
|
+
socialProviders: Object.keys(authOptions.socialProviders ?? {}).toSorted((a, b) => a.localeCompare(b)),
|
|
173
|
+
userFields: buildUserFields(tables["user"]?.fields ?? {})
|
|
174
|
+
});
|
|
175
|
+
}),
|
|
176
|
+
createOrganization: ({ logo, metadata, name, ownerId, slug }) => withContext(async (context_) => {
|
|
177
|
+
const finalSlug = slug !== void 0 && slug !== "" ? slugify(slug) : slugify(name);
|
|
178
|
+
if (finalSlug === "") {
|
|
179
|
+
throw new LunoraAuthAdminError("could not derive a slug from the organization name", "ORG_SLUG_INVALID");
|
|
180
|
+
}
|
|
181
|
+
const existing = await context_.adapter.findOne({
|
|
182
|
+
model: "organization",
|
|
183
|
+
where: [{ field: "slug", value: finalSlug }]
|
|
184
|
+
});
|
|
185
|
+
if (existing) {
|
|
186
|
+
throw new LunoraAuthAdminError("an organization with this slug already exists", "ORG_SLUG_TAKEN");
|
|
187
|
+
}
|
|
188
|
+
const organization = await context_.adapter.create({
|
|
189
|
+
data: {
|
|
190
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
191
|
+
logo: logo === void 0 || logo === "" ? void 0 : logo,
|
|
192
|
+
metadata: metadata === void 0 ? void 0 : JSON.stringify(metadata),
|
|
193
|
+
name,
|
|
194
|
+
slug: finalSlug
|
|
195
|
+
},
|
|
196
|
+
model: "organization"
|
|
197
|
+
});
|
|
198
|
+
if (ownerId !== void 0 && ownerId !== "") {
|
|
199
|
+
await context_.adapter.create({
|
|
200
|
+
data: { createdAt: /* @__PURE__ */ new Date(), organizationId: organization.id, role: "owner", userId: ownerId },
|
|
201
|
+
model: "member"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return normalizeRow(organization);
|
|
205
|
+
}),
|
|
206
|
+
createOrgRole: ({ organizationId, permission, role }) => withContext(async (context_) => {
|
|
207
|
+
const created = await context_.adapter.create({
|
|
208
|
+
data: { createdAt: /* @__PURE__ */ new Date(), organizationId, permission: JSON.stringify(permission), role },
|
|
209
|
+
model: "organizationRole"
|
|
210
|
+
});
|
|
211
|
+
return normalizeRow(created);
|
|
212
|
+
}),
|
|
213
|
+
createTeam: ({ name, organizationId }) => withContext(async (context_) => {
|
|
214
|
+
const team = await context_.adapter.create({
|
|
215
|
+
data: { createdAt: /* @__PURE__ */ new Date(), name, organizationId },
|
|
216
|
+
model: "team"
|
|
217
|
+
});
|
|
218
|
+
return normalizeRow(team);
|
|
219
|
+
}),
|
|
220
|
+
deleteOrganization: ({ organizationId }) => withContext(async (context_) => {
|
|
221
|
+
const tables = getAuthTables(context_.options);
|
|
222
|
+
await context_.adapter.deleteMany({ model: "member", where: [{ field: "organizationId", value: organizationId }] });
|
|
223
|
+
await context_.adapter.deleteMany({ model: "invitation", where: [{ field: "organizationId", value: organizationId }] });
|
|
224
|
+
if (tables["team"]) {
|
|
225
|
+
const teams = await context_.adapter.findMany({
|
|
226
|
+
model: "team",
|
|
227
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
228
|
+
});
|
|
229
|
+
for (const team of teams) {
|
|
230
|
+
await context_.adapter.deleteMany({ model: "teamMember", where: [{ field: "teamId", value: team.id }] });
|
|
231
|
+
}
|
|
232
|
+
await context_.adapter.deleteMany({ model: "team", where: [{ field: "organizationId", value: organizationId }] });
|
|
233
|
+
}
|
|
234
|
+
if (tables["organizationRole"]) {
|
|
235
|
+
await context_.adapter.deleteMany({ model: "organizationRole", where: [{ field: "organizationId", value: organizationId }] });
|
|
236
|
+
}
|
|
237
|
+
await context_.adapter.delete({ model: "organization", where: [{ field: "id", value: organizationId }] });
|
|
238
|
+
}),
|
|
239
|
+
deleteOrgRole: ({ roleId }) => withContext(async (context_) => {
|
|
240
|
+
await context_.adapter.delete({ model: "organizationRole", where: [{ field: "id", value: roleId }] });
|
|
241
|
+
}),
|
|
242
|
+
// Create a pending email invitation. `inviterId` is DB-required; when the
|
|
243
|
+
// caller omits it, attribute the invite to the org's owner (else any member).
|
|
244
|
+
inviteMember: ({ email, inviterId, organizationId, role }) => withContext(async (context_) => {
|
|
245
|
+
let resolvedInviter = inviterId;
|
|
246
|
+
if (resolvedInviter === void 0 || resolvedInviter === "") {
|
|
247
|
+
const members = await context_.adapter.findMany({
|
|
248
|
+
model: "member",
|
|
249
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
250
|
+
});
|
|
251
|
+
const owner = members.find((member) => typeof member.role === "string" && member.role.includes("owner"));
|
|
252
|
+
resolvedInviter = (owner ?? members[0])?.userId;
|
|
253
|
+
}
|
|
254
|
+
if (resolvedInviter === void 0 || resolvedInviter === "") {
|
|
255
|
+
throw new LunoraAuthAdminError("provide an inviter — the organization has no members to attribute the invitation to", "INVITER_REQUIRED");
|
|
256
|
+
}
|
|
257
|
+
const invitation = await context_.adapter.create({
|
|
258
|
+
data: {
|
|
259
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
260
|
+
email: email.toLowerCase(),
|
|
261
|
+
expiresAt: new Date(Date.now() + INVITATION_TTL_MS),
|
|
262
|
+
inviterId: resolvedInviter,
|
|
263
|
+
organizationId,
|
|
264
|
+
role: role === void 0 || role === "" ? "member" : role,
|
|
265
|
+
status: "pending"
|
|
266
|
+
},
|
|
267
|
+
model: "invitation"
|
|
268
|
+
});
|
|
269
|
+
return normalizeRow(invitation);
|
|
270
|
+
}),
|
|
271
|
+
listOrgRoles: ({ limit, offset, organizationId }) => withContext(
|
|
272
|
+
(context_) => page(context_, "organizationRole", {
|
|
273
|
+
limit,
|
|
274
|
+
offset,
|
|
275
|
+
sortBy: { direction: "desc", field: "createdAt" },
|
|
276
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
277
|
+
})
|
|
278
|
+
),
|
|
279
|
+
listTeamMembers: ({ limit, offset, teamId }) => withContext(
|
|
280
|
+
(context_) => page(context_, "teamMember", {
|
|
281
|
+
limit,
|
|
282
|
+
offset,
|
|
283
|
+
where: [{ field: "teamId", value: teamId }]
|
|
284
|
+
})
|
|
285
|
+
),
|
|
286
|
+
listTeams: ({ limit, offset, organizationId }) => withContext(
|
|
287
|
+
(context_) => page(context_, "team", {
|
|
288
|
+
limit,
|
|
289
|
+
offset,
|
|
290
|
+
sortBy: { direction: "desc", field: "createdAt" },
|
|
291
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
292
|
+
})
|
|
293
|
+
),
|
|
294
|
+
removeTeam: ({ teamId }) => withContext(async (context_) => {
|
|
295
|
+
await context_.adapter.deleteMany({ model: "teamMember", where: [{ field: "teamId", value: teamId }] });
|
|
296
|
+
await context_.adapter.delete({ model: "team", where: [{ field: "id", value: teamId }] });
|
|
297
|
+
}),
|
|
298
|
+
removeTeamMember: ({ teamMemberId }) => withContext(async (context_) => {
|
|
299
|
+
await context_.adapter.delete({ model: "teamMember", where: [{ field: "id", value: teamMemberId }] });
|
|
300
|
+
}),
|
|
301
|
+
updateMemberRole: ({ memberId, role }) => withContext(async (context_) => {
|
|
302
|
+
const member = await context_.adapter.update({
|
|
303
|
+
model: "member",
|
|
304
|
+
update: { role: serializeRole(role) },
|
|
305
|
+
where: [{ field: "id", value: memberId }]
|
|
306
|
+
});
|
|
307
|
+
return normalizeRow(member ?? { id: memberId, role: serializeRole(role) });
|
|
308
|
+
}),
|
|
309
|
+
updateOrganization: ({ logo, metadata, name, organizationId, slug }) => withContext(async (context_) => {
|
|
310
|
+
const update = {};
|
|
311
|
+
if (name !== void 0) {
|
|
312
|
+
update["name"] = name;
|
|
313
|
+
}
|
|
314
|
+
if (slug !== void 0 && slug !== "") {
|
|
315
|
+
update["slug"] = slugify(slug);
|
|
316
|
+
}
|
|
317
|
+
if (logo !== void 0) {
|
|
318
|
+
update["logo"] = logo === "" ? void 0 : logo;
|
|
319
|
+
}
|
|
320
|
+
if (metadata !== void 0) {
|
|
321
|
+
update["metadata"] = JSON.stringify(metadata);
|
|
322
|
+
}
|
|
323
|
+
if (Object.keys(update).length === 0) {
|
|
324
|
+
return normalizeRow({ id: organizationId });
|
|
325
|
+
}
|
|
326
|
+
const organization = await context_.adapter.update({
|
|
327
|
+
model: "organization",
|
|
328
|
+
update,
|
|
329
|
+
where: [{ field: "id", value: organizationId }]
|
|
330
|
+
});
|
|
331
|
+
return normalizeRow(organization ?? { id: organizationId });
|
|
332
|
+
}),
|
|
333
|
+
updateOrgRole: ({ permission, roleId }) => withContext(async (context_) => {
|
|
334
|
+
const updated = await context_.adapter.update({
|
|
335
|
+
model: "organizationRole",
|
|
336
|
+
update: { permission: JSON.stringify(permission), updatedAt: /* @__PURE__ */ new Date() },
|
|
337
|
+
where: [{ field: "id", value: roleId }]
|
|
338
|
+
});
|
|
339
|
+
return normalizeRow(updated ?? { id: roleId, permission: JSON.stringify(permission) });
|
|
340
|
+
}),
|
|
341
|
+
updateTeam: ({ name, teamId }) => withContext(async (context_) => {
|
|
342
|
+
const team = await context_.adapter.update({
|
|
343
|
+
model: "team",
|
|
344
|
+
update: { name, updatedAt: /* @__PURE__ */ new Date() },
|
|
345
|
+
where: [{ field: "id", value: teamId }]
|
|
346
|
+
});
|
|
347
|
+
return normalizeRow(team ?? { id: teamId, name });
|
|
348
|
+
}),
|
|
349
|
+
// The one op that genuinely builds a row rather than mutating one. Replicates
|
|
350
|
+
// the plugin's create-user handler over `internalAdapter` (lowercase + dedupe
|
|
351
|
+
// email, create the row, then link a credential account when a password is given).
|
|
352
|
+
createUser: ({ data, email, name, password, role }) => withContext(async (context_) => {
|
|
353
|
+
const normalizedEmail = email.toLowerCase();
|
|
354
|
+
if (await context_.internalAdapter.findUserByEmail(normalizedEmail)) {
|
|
355
|
+
throw new LunoraAuthAdminError("a user with this email already exists", "USER_ALREADY_EXISTS");
|
|
356
|
+
}
|
|
357
|
+
const user = await context_.internalAdapter.createUser({
|
|
358
|
+
email: normalizedEmail,
|
|
359
|
+
name,
|
|
360
|
+
role: role === void 0 ? void 0 : serializeRole(role),
|
|
361
|
+
...data
|
|
362
|
+
});
|
|
363
|
+
if (password !== void 0 && password !== "") {
|
|
364
|
+
const hashed = await context_.password.hash(password);
|
|
365
|
+
await context_.internalAdapter.linkAccount({
|
|
366
|
+
accountId: user.id,
|
|
367
|
+
password: hashed,
|
|
368
|
+
providerId: "credential",
|
|
369
|
+
userId: user.id
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return toUser(user);
|
|
373
|
+
}),
|
|
374
|
+
deletePasskey: ({ passkeyId }) => withContext(async (context_) => {
|
|
375
|
+
await context_.adapter.delete({ model: "passkey", where: [{ field: "id", value: passkeyId }] });
|
|
376
|
+
}),
|
|
377
|
+
disableTwoFactor: ({ userId }) => withContext(async (context_) => {
|
|
378
|
+
await context_.adapter.deleteMany({ model: "twoFactor", where: [{ field: "userId", value: userId }] });
|
|
379
|
+
await context_.internalAdapter.updateUser(userId, { twoFactorEnabled: false });
|
|
380
|
+
}),
|
|
381
|
+
impersonateUser: ({ userId }) => withContext(async (context_) => {
|
|
382
|
+
const user = await context_.internalAdapter.findUserById(userId);
|
|
383
|
+
if (!user) {
|
|
384
|
+
throw new LunoraAuthAdminError("user not found", "USER_NOT_FOUND");
|
|
385
|
+
}
|
|
386
|
+
const rawSeconds = options.impersonationSeconds;
|
|
387
|
+
let ttlSeconds = DEFAULT_IMPERSONATION_SECONDS;
|
|
388
|
+
if (rawSeconds !== void 0) {
|
|
389
|
+
if (!Number.isInteger(rawSeconds) || !Number.isFinite(rawSeconds) || rawSeconds <= 0) {
|
|
390
|
+
throw new LunoraAuthAdminError("impersonationSeconds must be a positive finite integer", "INVALID_IMPERSONATION_SECONDS");
|
|
391
|
+
}
|
|
392
|
+
ttlSeconds = Math.min(rawSeconds, MAX_IMPERSONATION_SECONDS);
|
|
393
|
+
}
|
|
394
|
+
const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
|
|
395
|
+
const session = await context_.internalAdapter.createSession(
|
|
396
|
+
userId,
|
|
397
|
+
true,
|
|
398
|
+
{ expiresAt, impersonatedBy: options.impersonatedBy ?? userId },
|
|
399
|
+
true
|
|
400
|
+
);
|
|
401
|
+
return {
|
|
402
|
+
expiresAt: session.expiresAt instanceof Date ? session.expiresAt.getTime() : expiresAt.getTime(),
|
|
403
|
+
token: session.token,
|
|
404
|
+
user: toUser(user)
|
|
405
|
+
};
|
|
406
|
+
}),
|
|
407
|
+
listAccounts: ({ userId }) => withContext(async (context_) => {
|
|
408
|
+
const rows = await context_.adapter.findMany({ model: "account", where: [{ field: "userId", value: userId }] });
|
|
409
|
+
return rows.map((row) => normalizeRow(row));
|
|
410
|
+
}),
|
|
411
|
+
listInvitations: ({ limit, offset, organizationId }) => withContext(
|
|
412
|
+
(context_) => page(context_, "invitation", {
|
|
413
|
+
limit,
|
|
414
|
+
offset,
|
|
415
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
416
|
+
})
|
|
417
|
+
),
|
|
418
|
+
listMembers: ({ limit, offset, organizationId }) => withContext(
|
|
419
|
+
(context_) => page(context_, "member", {
|
|
420
|
+
limit,
|
|
421
|
+
offset,
|
|
422
|
+
sortBy: { direction: "desc", field: "createdAt" },
|
|
423
|
+
where: [{ field: "organizationId", value: organizationId }]
|
|
424
|
+
})
|
|
425
|
+
),
|
|
426
|
+
listOrganizations: ({ limit, offset }) => withContext((context_) => page(context_, "organization", { limit, offset, sortBy: { direction: "desc", field: "createdAt" } })),
|
|
427
|
+
listPasskeys: ({ userId }) => withContext(async (context_) => {
|
|
428
|
+
const rows = await context_.adapter.findMany({ model: "passkey", where: [{ field: "userId", value: userId }] });
|
|
429
|
+
return rows.map((row) => normalizeRow(row));
|
|
430
|
+
}),
|
|
431
|
+
listSessions: ({ limit, offset, userId }) => withContext(
|
|
432
|
+
(context_) => page(context_, "session", {
|
|
433
|
+
limit,
|
|
434
|
+
offset,
|
|
435
|
+
sortBy: { direction: "desc", field: "createdAt" },
|
|
436
|
+
where: userId === void 0 || userId === "" ? void 0 : [{ field: "userId", value: userId }]
|
|
437
|
+
})
|
|
438
|
+
),
|
|
439
|
+
listUsers: ({ filterField, filterValue, limit, offset, search, searchField, sortBy, sortDirection }) => withContext((context_) => {
|
|
440
|
+
const where = [];
|
|
441
|
+
if (search !== void 0 && search !== "") {
|
|
442
|
+
where.push({ field: searchField ?? "email", operator: "contains", value: search });
|
|
443
|
+
}
|
|
444
|
+
if (filterValue !== void 0) {
|
|
445
|
+
where.push({ field: filterField ?? "email", operator: "eq", value: filterValue });
|
|
446
|
+
}
|
|
447
|
+
return page(context_, "user", {
|
|
448
|
+
limit,
|
|
449
|
+
offset,
|
|
450
|
+
sortBy: { direction: sortDirection ?? "desc", field: sortBy ?? "createdAt" },
|
|
451
|
+
where
|
|
452
|
+
});
|
|
453
|
+
}),
|
|
454
|
+
removeMember: ({ memberId }) => withContext(async (context_) => {
|
|
455
|
+
await context_.adapter.delete({ model: "member", where: [{ field: "id", value: memberId }] });
|
|
456
|
+
}),
|
|
457
|
+
removeUser: ({ userId }) => withContext(async (context_) => {
|
|
458
|
+
await context_.internalAdapter.deleteUserSessions(userId);
|
|
459
|
+
await context_.internalAdapter.deleteUser(userId);
|
|
460
|
+
}),
|
|
461
|
+
// Keyed on the session *id*, not its token: tokens are bearer credentials we
|
|
462
|
+
// deliberately never surface to the studio. Resolve the row to recover its
|
|
463
|
+
// token, then delete via `internalAdapter.deleteSession` — which also clears
|
|
464
|
+
// secondary (KV) storage, unlike a raw `adapter.delete` on the DB row.
|
|
465
|
+
revokeUserSession: ({ sessionId }) => withContext(async (context_) => {
|
|
466
|
+
const session = await context_.adapter.findOne({ model: "session", where: [{ field: "id", value: sessionId }] });
|
|
467
|
+
if (session?.token) {
|
|
468
|
+
await context_.internalAdapter.deleteSession(session.token);
|
|
469
|
+
}
|
|
470
|
+
}),
|
|
471
|
+
revokeUserSessions: ({ userId }) => withContext(async (context_) => {
|
|
472
|
+
await context_.internalAdapter.deleteUserSessions(userId);
|
|
473
|
+
}),
|
|
474
|
+
setRole: ({ role, userId }) => withContext(async (context_) => {
|
|
475
|
+
const user = await context_.internalAdapter.updateUser(userId, { role: serializeRole(role) });
|
|
476
|
+
return toUser(user);
|
|
477
|
+
}),
|
|
478
|
+
setUserPassword: ({ newPassword, userId }) => withContext(async (context_) => {
|
|
479
|
+
const min = context_.password.config.minPasswordLength;
|
|
480
|
+
const max = context_.password.config.maxPasswordLength;
|
|
481
|
+
if (newPassword.length < min) {
|
|
482
|
+
throw new LunoraAuthAdminError(`password must be at least ${min.toString()} characters`, "PASSWORD_TOO_SHORT");
|
|
483
|
+
}
|
|
484
|
+
if (newPassword.length > max) {
|
|
485
|
+
throw new LunoraAuthAdminError(`password must be at most ${max.toString()} characters`, "PASSWORD_TOO_LONG");
|
|
486
|
+
}
|
|
487
|
+
const hashed = await context_.password.hash(newPassword);
|
|
488
|
+
await context_.internalAdapter.updatePassword(userId, hashed);
|
|
489
|
+
}),
|
|
490
|
+
unbanUser: ({ userId }) => withContext(async (context_) => {
|
|
491
|
+
const user = await context_.internalAdapter.updateUser(userId, { banExpires: null, banned: false, banReason: null });
|
|
492
|
+
return toUser(user);
|
|
493
|
+
}),
|
|
494
|
+
unlinkAccount: ({ accountId, userId }) => withContext(async (context_) => {
|
|
495
|
+
await context_.adapter.delete({
|
|
496
|
+
model: "account",
|
|
497
|
+
where: [
|
|
498
|
+
{ field: "id", value: accountId },
|
|
499
|
+
{ connector: "AND", field: "userId", value: userId }
|
|
500
|
+
]
|
|
501
|
+
});
|
|
502
|
+
}),
|
|
503
|
+
updateUser: ({ data, userId }) => withContext(async (context_) => {
|
|
504
|
+
const user = await context_.internalAdapter.updateUser(userId, data);
|
|
505
|
+
return toUser(user);
|
|
506
|
+
})
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
export { LunoraAuthAdminError, createAuthAdmin };
|
package/package.json
CHANGED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
|
|
3
|
-
class LunoraAuthAdminError extends LunoraError {
|
|
4
|
-
constructor(message, code) {
|
|
5
|
-
super(code, message, { name: "LunoraAuthAdminError" });
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
const DEFAULT_LIMIT = 50;
|
|
9
|
-
const MAX_LIMIT = 500;
|
|
10
|
-
const DEFAULT_IMPERSONATION_SECONDS = 3600;
|
|
11
|
-
const MAX_IMPERSONATION_SECONDS = DEFAULT_IMPERSONATION_SECONDS * 24;
|
|
12
|
-
const MAX_BAN_SECONDS = 100 * 365 * 24 * 60 * 60;
|
|
13
|
-
const SENSITIVE_FIELDS = /* @__PURE__ */ new Set(["accessToken", "backupCodes", "idToken", "password", "publicKey", "refreshToken", "secret", "token"]);
|
|
14
|
-
const clampLimit = (limit) => Math.min(Math.max(Math.trunc(limit ?? DEFAULT_LIMIT), 1), MAX_LIMIT);
|
|
15
|
-
const clampOffset = (offset) => Math.max(0, Math.trunc(offset ?? 0));
|
|
16
|
-
const normalizeRow = (row) => {
|
|
17
|
-
const out = {};
|
|
18
|
-
for (const [key, value] of Object.entries(row)) {
|
|
19
|
-
if (SENSITIVE_FIELDS.has(key)) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
out[key] = value instanceof Date ? value.getTime() : value;
|
|
23
|
-
}
|
|
24
|
-
return out;
|
|
25
|
-
};
|
|
26
|
-
const serializeRole = (role) => Array.isArray(role) ? role.join(",") : role;
|
|
27
|
-
const asAdminError = (error) => {
|
|
28
|
-
if (error instanceof LunoraAuthAdminError) {
|
|
29
|
-
return error;
|
|
30
|
-
}
|
|
31
|
-
const candidate = error;
|
|
32
|
-
const code = candidate?.body?.code ?? candidate?.code ?? "AUTH_ADMIN_ERROR";
|
|
33
|
-
const message = candidate?.body?.message ?? candidate?.message ?? "auth admin operation failed";
|
|
34
|
-
return new LunoraAuthAdminError(message, code);
|
|
35
|
-
};
|
|
36
|
-
const createAuthAdmin = (auth, options = {}) => {
|
|
37
|
-
const context = auth.$context;
|
|
38
|
-
const features = options.features ?? {};
|
|
39
|
-
const withContext = async (function_) => {
|
|
40
|
-
try {
|
|
41
|
-
return await function_(await context);
|
|
42
|
-
} catch (error) {
|
|
43
|
-
throw asAdminError(error);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
const toUser = (row) => normalizeRow(row);
|
|
47
|
-
const page = async (context_, model, options_) => {
|
|
48
|
-
const where = options_.where && options_.where.length > 0 ? options_.where : void 0;
|
|
49
|
-
const [rows, total] = await Promise.all([
|
|
50
|
-
context_.adapter.findMany({
|
|
51
|
-
limit: clampLimit(options_.limit),
|
|
52
|
-
model,
|
|
53
|
-
offset: clampOffset(options_.offset),
|
|
54
|
-
sortBy: options_.sortBy,
|
|
55
|
-
where
|
|
56
|
-
}),
|
|
57
|
-
context_.adapter.count({ model, where })
|
|
58
|
-
]);
|
|
59
|
-
return { rows: rows.map((row) => normalizeRow(row)), total };
|
|
60
|
-
};
|
|
61
|
-
return {
|
|
62
|
-
banUser: ({ expiresInSeconds, reason, userId }) => withContext(async (context_) => {
|
|
63
|
-
const seconds = typeof expiresInSeconds === "number" && Number.isFinite(expiresInSeconds) ? Math.min(Math.trunc(expiresInSeconds), MAX_BAN_SECONDS) : 0;
|
|
64
|
-
const banExpires = seconds > 0 ? new Date(Date.now() + seconds * 1e3) : void 0;
|
|
65
|
-
const user = await context_.internalAdapter.updateUser(userId, {
|
|
66
|
-
banExpires,
|
|
67
|
-
banned: true,
|
|
68
|
-
banReason: reason ?? "No reason"
|
|
69
|
-
});
|
|
70
|
-
await context_.internalAdapter.deleteUserSessions(userId);
|
|
71
|
-
return toUser(user);
|
|
72
|
-
}),
|
|
73
|
-
cancelInvitation: ({ invitationId }) => withContext(async (context_) => {
|
|
74
|
-
await context_.adapter.delete({ model: "invitation", where: [{ field: "id", value: invitationId }] });
|
|
75
|
-
}),
|
|
76
|
-
capabilities: () => withContext((context_) => {
|
|
77
|
-
const ids = new Set((context_.options.plugins ?? []).map((plugin) => plugin.id));
|
|
78
|
-
const has = (id) => ids.has(id);
|
|
79
|
-
return Promise.resolve({
|
|
80
|
-
accounts: features.accounts ?? true,
|
|
81
|
-
admin: features.admin ?? has("admin"),
|
|
82
|
-
organization: features.organization ?? has("organization"),
|
|
83
|
-
passkey: features.passkey ?? has("passkey"),
|
|
84
|
-
twoFactor: features.twoFactor ?? has("two-factor")
|
|
85
|
-
});
|
|
86
|
-
}),
|
|
87
|
-
// The one op that genuinely builds a row rather than mutating one. Replicates
|
|
88
|
-
// the plugin's create-user handler over `internalAdapter` (lowercase + dedupe
|
|
89
|
-
// email, create the row, then link a credential account when a password is given).
|
|
90
|
-
createUser: ({ data, email, name, password, role }) => withContext(async (context_) => {
|
|
91
|
-
const normalizedEmail = email.toLowerCase();
|
|
92
|
-
if (await context_.internalAdapter.findUserByEmail(normalizedEmail)) {
|
|
93
|
-
throw new LunoraAuthAdminError("a user with this email already exists", "USER_ALREADY_EXISTS");
|
|
94
|
-
}
|
|
95
|
-
const user = await context_.internalAdapter.createUser({
|
|
96
|
-
email: normalizedEmail,
|
|
97
|
-
name,
|
|
98
|
-
role: role === void 0 ? void 0 : serializeRole(role),
|
|
99
|
-
...data
|
|
100
|
-
});
|
|
101
|
-
if (password !== void 0 && password !== "") {
|
|
102
|
-
const hashed = await context_.password.hash(password);
|
|
103
|
-
await context_.internalAdapter.linkAccount({
|
|
104
|
-
accountId: user.id,
|
|
105
|
-
password: hashed,
|
|
106
|
-
providerId: "credential",
|
|
107
|
-
userId: user.id
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return toUser(user);
|
|
111
|
-
}),
|
|
112
|
-
deletePasskey: ({ passkeyId }) => withContext(async (context_) => {
|
|
113
|
-
await context_.adapter.delete({ model: "passkey", where: [{ field: "id", value: passkeyId }] });
|
|
114
|
-
}),
|
|
115
|
-
disableTwoFactor: ({ userId }) => withContext(async (context_) => {
|
|
116
|
-
await context_.adapter.deleteMany({ model: "twoFactor", where: [{ field: "userId", value: userId }] });
|
|
117
|
-
await context_.internalAdapter.updateUser(userId, { twoFactorEnabled: false });
|
|
118
|
-
}),
|
|
119
|
-
impersonateUser: ({ userId }) => withContext(async (context_) => {
|
|
120
|
-
const user = await context_.internalAdapter.findUserById(userId);
|
|
121
|
-
if (!user) {
|
|
122
|
-
throw new LunoraAuthAdminError("user not found", "USER_NOT_FOUND");
|
|
123
|
-
}
|
|
124
|
-
const rawSeconds = options.impersonationSeconds;
|
|
125
|
-
let ttlSeconds = DEFAULT_IMPERSONATION_SECONDS;
|
|
126
|
-
if (rawSeconds !== void 0) {
|
|
127
|
-
if (!Number.isInteger(rawSeconds) || !Number.isFinite(rawSeconds) || rawSeconds <= 0) {
|
|
128
|
-
throw new LunoraAuthAdminError("impersonationSeconds must be a positive finite integer", "INVALID_IMPERSONATION_SECONDS");
|
|
129
|
-
}
|
|
130
|
-
ttlSeconds = Math.min(rawSeconds, MAX_IMPERSONATION_SECONDS);
|
|
131
|
-
}
|
|
132
|
-
const expiresAt = new Date(Date.now() + ttlSeconds * 1e3);
|
|
133
|
-
const session = await context_.internalAdapter.createSession(
|
|
134
|
-
userId,
|
|
135
|
-
true,
|
|
136
|
-
{ expiresAt, impersonatedBy: options.impersonatedBy ?? userId },
|
|
137
|
-
true
|
|
138
|
-
);
|
|
139
|
-
return {
|
|
140
|
-
expiresAt: session.expiresAt instanceof Date ? session.expiresAt.getTime() : expiresAt.getTime(),
|
|
141
|
-
token: session.token,
|
|
142
|
-
user: toUser(user)
|
|
143
|
-
};
|
|
144
|
-
}),
|
|
145
|
-
listAccounts: ({ userId }) => withContext(async (context_) => {
|
|
146
|
-
const rows = await context_.adapter.findMany({ model: "account", where: [{ field: "userId", value: userId }] });
|
|
147
|
-
return rows.map((row) => normalizeRow(row));
|
|
148
|
-
}),
|
|
149
|
-
listInvitations: ({ limit, offset, organizationId }) => withContext(
|
|
150
|
-
(context_) => page(context_, "invitation", {
|
|
151
|
-
limit,
|
|
152
|
-
offset,
|
|
153
|
-
where: [{ field: "organizationId", value: organizationId }]
|
|
154
|
-
})
|
|
155
|
-
),
|
|
156
|
-
listMembers: ({ limit, offset, organizationId }) => withContext(
|
|
157
|
-
(context_) => page(context_, "member", {
|
|
158
|
-
limit,
|
|
159
|
-
offset,
|
|
160
|
-
sortBy: { direction: "desc", field: "createdAt" },
|
|
161
|
-
where: [{ field: "organizationId", value: organizationId }]
|
|
162
|
-
})
|
|
163
|
-
),
|
|
164
|
-
listOrganizations: ({ limit, offset }) => withContext((context_) => page(context_, "organization", { limit, offset, sortBy: { direction: "desc", field: "createdAt" } })),
|
|
165
|
-
listPasskeys: ({ userId }) => withContext(async (context_) => {
|
|
166
|
-
const rows = await context_.adapter.findMany({ model: "passkey", where: [{ field: "userId", value: userId }] });
|
|
167
|
-
return rows.map((row) => normalizeRow(row));
|
|
168
|
-
}),
|
|
169
|
-
listSessions: ({ limit, offset, userId }) => withContext(
|
|
170
|
-
(context_) => page(context_, "session", {
|
|
171
|
-
limit,
|
|
172
|
-
offset,
|
|
173
|
-
sortBy: { direction: "desc", field: "createdAt" },
|
|
174
|
-
where: userId === void 0 || userId === "" ? void 0 : [{ field: "userId", value: userId }]
|
|
175
|
-
})
|
|
176
|
-
),
|
|
177
|
-
listUsers: ({ filterField, filterValue, limit, offset, search, searchField, sortBy, sortDirection }) => withContext((context_) => {
|
|
178
|
-
const where = [];
|
|
179
|
-
if (search !== void 0 && search !== "") {
|
|
180
|
-
where.push({ field: searchField ?? "email", operator: "contains", value: search });
|
|
181
|
-
}
|
|
182
|
-
if (filterValue !== void 0) {
|
|
183
|
-
where.push({ field: filterField ?? "email", operator: "eq", value: filterValue });
|
|
184
|
-
}
|
|
185
|
-
return page(context_, "user", {
|
|
186
|
-
limit,
|
|
187
|
-
offset,
|
|
188
|
-
sortBy: { direction: sortDirection ?? "desc", field: sortBy ?? "createdAt" },
|
|
189
|
-
where
|
|
190
|
-
});
|
|
191
|
-
}),
|
|
192
|
-
removeMember: ({ memberId }) => withContext(async (context_) => {
|
|
193
|
-
await context_.adapter.delete({ model: "member", where: [{ field: "id", value: memberId }] });
|
|
194
|
-
}),
|
|
195
|
-
removeUser: ({ userId }) => withContext(async (context_) => {
|
|
196
|
-
await context_.internalAdapter.deleteUserSessions(userId);
|
|
197
|
-
await context_.internalAdapter.deleteUser(userId);
|
|
198
|
-
}),
|
|
199
|
-
// Keyed on the session *id*, not its token: tokens are bearer credentials we
|
|
200
|
-
// deliberately never surface to the studio. Resolve the row to recover its
|
|
201
|
-
// token, then delete via `internalAdapter.deleteSession` — which also clears
|
|
202
|
-
// secondary (KV) storage, unlike a raw `adapter.delete` on the DB row.
|
|
203
|
-
revokeUserSession: ({ sessionId }) => withContext(async (context_) => {
|
|
204
|
-
const session = await context_.adapter.findOne({ model: "session", where: [{ field: "id", value: sessionId }] });
|
|
205
|
-
if (session?.token) {
|
|
206
|
-
await context_.internalAdapter.deleteSession(session.token);
|
|
207
|
-
}
|
|
208
|
-
}),
|
|
209
|
-
revokeUserSessions: ({ userId }) => withContext(async (context_) => {
|
|
210
|
-
await context_.internalAdapter.deleteUserSessions(userId);
|
|
211
|
-
}),
|
|
212
|
-
setRole: ({ role, userId }) => withContext(async (context_) => {
|
|
213
|
-
const user = await context_.internalAdapter.updateUser(userId, { role: serializeRole(role) });
|
|
214
|
-
return toUser(user);
|
|
215
|
-
}),
|
|
216
|
-
setUserPassword: ({ newPassword, userId }) => withContext(async (context_) => {
|
|
217
|
-
const min = context_.password.config.minPasswordLength;
|
|
218
|
-
const max = context_.password.config.maxPasswordLength;
|
|
219
|
-
if (newPassword.length < min) {
|
|
220
|
-
throw new LunoraAuthAdminError(`password must be at least ${min.toString()} characters`, "PASSWORD_TOO_SHORT");
|
|
221
|
-
}
|
|
222
|
-
if (newPassword.length > max) {
|
|
223
|
-
throw new LunoraAuthAdminError(`password must be at most ${max.toString()} characters`, "PASSWORD_TOO_LONG");
|
|
224
|
-
}
|
|
225
|
-
const hashed = await context_.password.hash(newPassword);
|
|
226
|
-
await context_.internalAdapter.updatePassword(userId, hashed);
|
|
227
|
-
}),
|
|
228
|
-
unbanUser: ({ userId }) => withContext(async (context_) => {
|
|
229
|
-
const user = await context_.internalAdapter.updateUser(userId, { banExpires: null, banned: false, banReason: null });
|
|
230
|
-
return toUser(user);
|
|
231
|
-
}),
|
|
232
|
-
unlinkAccount: ({ accountId, userId }) => withContext(async (context_) => {
|
|
233
|
-
await context_.adapter.delete({
|
|
234
|
-
model: "account",
|
|
235
|
-
where: [
|
|
236
|
-
{ field: "id", value: accountId },
|
|
237
|
-
{ connector: "AND", field: "userId", value: userId }
|
|
238
|
-
]
|
|
239
|
-
});
|
|
240
|
-
}),
|
|
241
|
-
updateUser: ({ data, userId }) => withContext(async (context_) => {
|
|
242
|
-
const user = await context_.internalAdapter.updateUser(userId, data);
|
|
243
|
-
return toUser(user);
|
|
244
|
-
})
|
|
245
|
-
};
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
export { LunoraAuthAdminError, createAuthAdmin };
|