@objectstack/plugin-auth 4.0.4 → 4.1.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.
- package/README.md +4 -1
- package/dist/index.d.mts +441 -19940
- package/dist/index.d.ts +441 -19940
- package/dist/index.js +704 -900
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +699 -880
- package/dist/index.mjs.map +1 -1
- package/package.json +35 -12
- package/.turbo/turbo-build.log +0 -78
- package/ARCHITECTURE.md +0 -176
- package/CHANGELOG.md +0 -333
- package/IMPLEMENTATION_SUMMARY.md +0 -192
- package/examples/basic-usage.ts +0 -107
- package/objectstack.config.ts +0 -24
- package/src/auth-manager.test.ts +0 -883
- package/src/auth-manager.ts +0 -419
- package/src/auth-plugin.test.ts +0 -446
- package/src/auth-plugin.ts +0 -314
- package/src/auth-schema-config.ts +0 -339
- package/src/index.ts +0 -16
- package/src/objectql-adapter.test.ts +0 -281
- package/src/objectql-adapter.ts +0 -279
- package/src/objects/auth-account.object.ts +0 -7
- package/src/objects/auth-session.object.ts +0 -7
- package/src/objects/auth-user.object.ts +0 -7
- package/src/objects/auth-verification.object.ts +0 -7
- package/src/objects/index.ts +0 -40
- package/src/objects/sys-account.object.ts +0 -111
- package/src/objects/sys-api-key.object.ts +0 -104
- package/src/objects/sys-invitation.object.ts +0 -93
- package/src/objects/sys-member.object.ts +0 -68
- package/src/objects/sys-organization.object.ts +0 -82
- package/src/objects/sys-session.object.ts +0 -84
- package/src/objects/sys-team-member.object.ts +0 -61
- package/src/objects/sys-team.object.ts +0 -69
- package/src/objects/sys-two-factor.object.ts +0 -73
- package/src/objects/sys-user-preference.object.ts +0 -82
- package/src/objects/sys-user.object.ts +0 -91
- package/src/objects/sys-verification.object.ts +0 -75
- package/tsconfig.json +0 -18
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// src/auth-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/auth-plugin.ts
|
|
2
|
+
import {
|
|
3
|
+
SETUP_APP,
|
|
4
|
+
SystemOverviewDashboard,
|
|
5
|
+
SecurityOverviewDashboard
|
|
6
|
+
} from "@objectstack/platform-objects/apps";
|
|
6
7
|
|
|
7
8
|
// src/objectql-adapter.ts
|
|
8
9
|
import { createAdapterFactory } from "better-auth/adapters";
|
|
@@ -16,6 +17,41 @@ var AUTH_MODEL_TO_PROTOCOL = {
|
|
|
16
17
|
function resolveProtocolName(model) {
|
|
17
18
|
return AUTH_MODEL_TO_PROTOCOL[model] ?? model;
|
|
18
19
|
}
|
|
20
|
+
var LEGACY_DATETIME_FIELDS_BY_MODEL = {
|
|
21
|
+
user: ["created_at", "updated_at"],
|
|
22
|
+
session: ["expires_at", "created_at", "updated_at"],
|
|
23
|
+
account: [
|
|
24
|
+
"access_token_expires_at",
|
|
25
|
+
"refresh_token_expires_at",
|
|
26
|
+
"created_at",
|
|
27
|
+
"updated_at"
|
|
28
|
+
],
|
|
29
|
+
verification: ["expires_at", "created_at", "updated_at"]
|
|
30
|
+
};
|
|
31
|
+
var NUMERIC_STRING_RE = /^-?\d+(\.\d+)?$/;
|
|
32
|
+
function normaliseLegacyDate(value) {
|
|
33
|
+
if (typeof value !== "string") return value;
|
|
34
|
+
if (!NUMERIC_STRING_RE.test(value)) return value;
|
|
35
|
+
const n = parseFloat(value);
|
|
36
|
+
if (!Number.isFinite(n)) return value;
|
|
37
|
+
if (Math.abs(n) < 1e10) return value;
|
|
38
|
+
const d = new Date(n);
|
|
39
|
+
if (Number.isNaN(d.getTime())) return value;
|
|
40
|
+
return d.toISOString();
|
|
41
|
+
}
|
|
42
|
+
function normaliseLegacyDates(model, record) {
|
|
43
|
+
if (!record) return record;
|
|
44
|
+
const cols = LEGACY_DATETIME_FIELDS_BY_MODEL[model];
|
|
45
|
+
if (!cols) return record;
|
|
46
|
+
for (const col of cols) {
|
|
47
|
+
if (col in record) {
|
|
48
|
+
record[col] = normaliseLegacyDate(
|
|
49
|
+
record[col]
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return record;
|
|
54
|
+
}
|
|
19
55
|
function convertWhere(where) {
|
|
20
56
|
const filter = {};
|
|
21
57
|
for (const condition of where) {
|
|
@@ -44,20 +80,24 @@ function createObjectQLAdapterFactory(dataEngine) {
|
|
|
44
80
|
return createAdapterFactory({
|
|
45
81
|
config: {
|
|
46
82
|
adapterId: "objectql",
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
// We let better-auth handle Date↔string and boolean↔0/1 conversion so
|
|
84
|
+
// that values land in the underlying SQL driver as primitive strings
|
|
85
|
+
// and integers. Some drivers (e.g. libsql over the HTTP transport)
|
|
86
|
+
// otherwise mangle `Date` objects into `"<epoch>.0"` strings that
|
|
87
|
+
// break the client-side session parser.
|
|
88
|
+
supportsBooleans: false,
|
|
89
|
+
supportsDates: false,
|
|
50
90
|
supportsJSON: true
|
|
51
91
|
},
|
|
52
92
|
adapter: () => ({
|
|
53
93
|
create: async ({ model, data, select: _select }) => {
|
|
54
94
|
const result = await dataEngine.insert(model, data);
|
|
55
|
-
return result;
|
|
95
|
+
return normaliseLegacyDates(model, result);
|
|
56
96
|
},
|
|
57
97
|
findOne: async ({ model, where, select, join: _join }) => {
|
|
58
98
|
const filter = convertWhere(where);
|
|
59
99
|
const result = await dataEngine.findOne(model, { where: filter, fields: select });
|
|
60
|
-
return result ? result : null;
|
|
100
|
+
return result ? normaliseLegacyDates(model, result) : null;
|
|
61
101
|
},
|
|
62
102
|
findMany: async ({ model, where, limit, offset, sortBy, join: _join }) => {
|
|
63
103
|
const filter = where ? convertWhere(where) : {};
|
|
@@ -68,7 +108,7 @@ function createObjectQLAdapterFactory(dataEngine) {
|
|
|
68
108
|
offset,
|
|
69
109
|
orderBy
|
|
70
110
|
});
|
|
71
|
-
return results;
|
|
111
|
+
return results.map((r) => normaliseLegacyDates(model, r));
|
|
72
112
|
},
|
|
73
113
|
count: async ({ model, where }) => {
|
|
74
114
|
const filter = where ? convertWhere(where) : {};
|
|
@@ -79,7 +119,7 @@ function createObjectQLAdapterFactory(dataEngine) {
|
|
|
79
119
|
const record = await dataEngine.findOne(model, { where: filter });
|
|
80
120
|
if (!record) return null;
|
|
81
121
|
const result = await dataEngine.update(model, { ...update, id: record.id });
|
|
82
|
-
return result ? result : null;
|
|
122
|
+
return result ? normaliseLegacyDates(model, result) : null;
|
|
83
123
|
},
|
|
84
124
|
updateMany: async ({ model, where, update }) => {
|
|
85
125
|
const filter = convertWhere(where);
|
|
@@ -276,6 +316,88 @@ var AUTH_TWO_FACTOR_SCHEMA = {
|
|
|
276
316
|
var AUTH_TWO_FACTOR_USER_FIELDS = {
|
|
277
317
|
twoFactorEnabled: "two_factor_enabled"
|
|
278
318
|
};
|
|
319
|
+
var AUTH_ADMIN_USER_FIELDS = {
|
|
320
|
+
banReason: "ban_reason",
|
|
321
|
+
banExpires: "ban_expires"
|
|
322
|
+
};
|
|
323
|
+
var AUTH_ADMIN_SESSION_FIELDS = {
|
|
324
|
+
impersonatedBy: "impersonated_by"
|
|
325
|
+
};
|
|
326
|
+
var AUTH_OAUTH_CLIENT_SCHEMA = {
|
|
327
|
+
modelName: SystemObjectName2.OAUTH_APPLICATION,
|
|
328
|
+
// 'sys_oauth_application'
|
|
329
|
+
fields: {
|
|
330
|
+
clientId: "client_id",
|
|
331
|
+
clientSecret: "client_secret",
|
|
332
|
+
skipConsent: "skip_consent",
|
|
333
|
+
enableEndSession: "enable_end_session",
|
|
334
|
+
subjectType: "subject_type",
|
|
335
|
+
userId: "user_id",
|
|
336
|
+
createdAt: "created_at",
|
|
337
|
+
updatedAt: "updated_at",
|
|
338
|
+
redirectUris: "redirect_uris",
|
|
339
|
+
postLogoutRedirectUris: "post_logout_redirect_uris",
|
|
340
|
+
tokenEndpointAuthMethod: "token_endpoint_auth_method",
|
|
341
|
+
grantTypes: "grant_types",
|
|
342
|
+
responseTypes: "response_types",
|
|
343
|
+
requirePKCE: "require_pkce",
|
|
344
|
+
softwareId: "software_id",
|
|
345
|
+
softwareVersion: "software_version",
|
|
346
|
+
softwareStatement: "software_statement",
|
|
347
|
+
referenceId: "reference_id"
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var AUTH_OAUTH_APPLICATION_SCHEMA = AUTH_OAUTH_CLIENT_SCHEMA;
|
|
351
|
+
var AUTH_OAUTH_ACCESS_TOKEN_SCHEMA = {
|
|
352
|
+
modelName: SystemObjectName2.OAUTH_ACCESS_TOKEN,
|
|
353
|
+
// 'sys_oauth_access_token'
|
|
354
|
+
fields: {
|
|
355
|
+
clientId: "client_id",
|
|
356
|
+
sessionId: "session_id",
|
|
357
|
+
userId: "user_id",
|
|
358
|
+
referenceId: "reference_id",
|
|
359
|
+
refreshId: "refresh_id",
|
|
360
|
+
expiresAt: "expires_at",
|
|
361
|
+
createdAt: "created_at"
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
var AUTH_OAUTH_REFRESH_TOKEN_SCHEMA = {
|
|
365
|
+
modelName: SystemObjectName2.OAUTH_REFRESH_TOKEN,
|
|
366
|
+
// 'sys_oauth_refresh_token'
|
|
367
|
+
fields: {
|
|
368
|
+
clientId: "client_id",
|
|
369
|
+
sessionId: "session_id",
|
|
370
|
+
userId: "user_id",
|
|
371
|
+
referenceId: "reference_id",
|
|
372
|
+
expiresAt: "expires_at",
|
|
373
|
+
createdAt: "created_at",
|
|
374
|
+
authTime: "auth_time"
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
var AUTH_OAUTH_CONSENT_SCHEMA = {
|
|
378
|
+
modelName: SystemObjectName2.OAUTH_CONSENT,
|
|
379
|
+
// 'sys_oauth_consent'
|
|
380
|
+
fields: {
|
|
381
|
+
clientId: "client_id",
|
|
382
|
+
userId: "user_id",
|
|
383
|
+
referenceId: "reference_id",
|
|
384
|
+
createdAt: "created_at",
|
|
385
|
+
updatedAt: "updated_at"
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
var AUTH_DEVICE_CODE_SCHEMA = {
|
|
389
|
+
modelName: SystemObjectName2.DEVICE_CODE,
|
|
390
|
+
// 'sys_device_code'
|
|
391
|
+
fields: {
|
|
392
|
+
deviceCode: "device_code",
|
|
393
|
+
userCode: "user_code",
|
|
394
|
+
userId: "user_id",
|
|
395
|
+
expiresAt: "expires_at",
|
|
396
|
+
lastPolledAt: "last_polled_at",
|
|
397
|
+
pollingInterval: "polling_interval",
|
|
398
|
+
clientId: "client_id"
|
|
399
|
+
}
|
|
400
|
+
};
|
|
279
401
|
function buildTwoFactorPluginSchema() {
|
|
280
402
|
return {
|
|
281
403
|
twoFactor: AUTH_TWO_FACTOR_SCHEMA,
|
|
@@ -284,6 +406,16 @@ function buildTwoFactorPluginSchema() {
|
|
|
284
406
|
}
|
|
285
407
|
};
|
|
286
408
|
}
|
|
409
|
+
function buildAdminPluginSchema() {
|
|
410
|
+
return {
|
|
411
|
+
user: {
|
|
412
|
+
fields: AUTH_ADMIN_USER_FIELDS
|
|
413
|
+
},
|
|
414
|
+
session: {
|
|
415
|
+
fields: AUTH_ADMIN_SESSION_FIELDS
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
287
419
|
function buildOrganizationPluginSchema() {
|
|
288
420
|
return {
|
|
289
421
|
organization: AUTH_ORGANIZATION_SCHEMA,
|
|
@@ -296,6 +428,35 @@ function buildOrganizationPluginSchema() {
|
|
|
296
428
|
}
|
|
297
429
|
};
|
|
298
430
|
}
|
|
431
|
+
var AUTH_JWKS_SCHEMA = {
|
|
432
|
+
modelName: SystemObjectName2.JWKS,
|
|
433
|
+
// 'sys_jwks'
|
|
434
|
+
fields: {
|
|
435
|
+
publicKey: "public_key",
|
|
436
|
+
privateKey: "private_key",
|
|
437
|
+
createdAt: "created_at",
|
|
438
|
+
expiresAt: "expires_at"
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
function buildJwtPluginSchema() {
|
|
442
|
+
return {
|
|
443
|
+
jwks: AUTH_JWKS_SCHEMA
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function buildOauthProviderPluginSchema() {
|
|
447
|
+
return {
|
|
448
|
+
oauthClient: AUTH_OAUTH_CLIENT_SCHEMA,
|
|
449
|
+
oauthAccessToken: AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
|
|
450
|
+
oauthRefreshToken: AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
|
|
451
|
+
oauthConsent: AUTH_OAUTH_CONSENT_SCHEMA
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
var buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
|
|
455
|
+
function buildDeviceAuthorizationPluginSchema() {
|
|
456
|
+
return {
|
|
457
|
+
deviceCode: AUTH_DEVICE_CODE_SCHEMA
|
|
458
|
+
};
|
|
459
|
+
}
|
|
299
460
|
|
|
300
461
|
// src/auth-manager.ts
|
|
301
462
|
var AuthManager = class {
|
|
@@ -309,16 +470,18 @@ var AuthManager = class {
|
|
|
309
470
|
/**
|
|
310
471
|
* Get or create the better-auth instance (lazy initialization)
|
|
311
472
|
*/
|
|
312
|
-
getOrCreateAuth() {
|
|
473
|
+
async getOrCreateAuth() {
|
|
313
474
|
if (!this.auth) {
|
|
314
|
-
this.auth = this.createAuthInstance();
|
|
475
|
+
this.auth = await this.createAuthInstance();
|
|
315
476
|
}
|
|
316
477
|
return this.auth;
|
|
317
478
|
}
|
|
318
479
|
/**
|
|
319
480
|
* Create a better-auth instance from configuration
|
|
320
481
|
*/
|
|
321
|
-
createAuthInstance() {
|
|
482
|
+
async createAuthInstance() {
|
|
483
|
+
const { betterAuth } = await import("better-auth");
|
|
484
|
+
const plugins = await this.buildPluginList();
|
|
322
485
|
const betterAuthConfig = {
|
|
323
486
|
// Base configuration
|
|
324
487
|
secret: this.config.secret || this.generateSecret(),
|
|
@@ -334,7 +497,41 @@ var AuthManager = class {
|
|
|
334
497
|
...AUTH_USER_CONFIG
|
|
335
498
|
},
|
|
336
499
|
account: {
|
|
337
|
-
...AUTH_ACCOUNT_CONFIG
|
|
500
|
+
...AUTH_ACCOUNT_CONFIG,
|
|
501
|
+
// Allow OIDC/OAuth callbacks to implicitly link the incoming
|
|
502
|
+
// identity to a pre-existing local user when the emails match.
|
|
503
|
+
//
|
|
504
|
+
// ObjectStack's platform SSO ("objectstack-cloud" provider) is the
|
|
505
|
+
// canonical case: cloud is the IdP for every project, so a user
|
|
506
|
+
// arriving via SSO is — by construction — the same person who was
|
|
507
|
+
// auto-seeded as the project owner when the project was created.
|
|
508
|
+
// Without trusting the provider, better-auth's safety check rejects
|
|
509
|
+
// the link with `error=account_not_linked` because the seeded user
|
|
510
|
+
// row has `emailVerified=false` (no actual verification ever runs
|
|
511
|
+
// in the IdP-mediated flow). See packages/plugins/plugin-auth/
|
|
512
|
+
// node_modules/better-auth/dist/oauth2/link-account.mjs:22.
|
|
513
|
+
//
|
|
514
|
+
// Custom-deployment consumers can extend the trusted set via
|
|
515
|
+
// `config.account.accountLinking.trustedProviders`; we always
|
|
516
|
+
// include `objectstack-cloud` because it is the platform IdP.
|
|
517
|
+
accountLinking: {
|
|
518
|
+
enabled: true,
|
|
519
|
+
// better-auth's account-linking gate has TWO independent clauses
|
|
520
|
+
// (see link-account.mjs:22). Trusting the provider only satisfies
|
|
521
|
+
// the first clause; the second — `requireLocalEmailVerified &&
|
|
522
|
+
// !dbUser.user.emailVerified` — still blocks linking when the
|
|
523
|
+
// pre-existing local user row has `emailVerified=false` (the
|
|
524
|
+
// default for owner-seeded rows). Disabling the local-email gate
|
|
525
|
+
// is safe here because the OAuth side is what we actually trust:
|
|
526
|
+
// the incoming identity was verified by the IdP. Consumers who
|
|
527
|
+
// need the stricter behavior can override via config.
|
|
528
|
+
requireLocalEmailVerified: false,
|
|
529
|
+
...this.config?.account?.accountLinking ?? {},
|
|
530
|
+
trustedProviders: Array.from(/* @__PURE__ */ new Set([
|
|
531
|
+
"objectstack-cloud",
|
|
532
|
+
...this.config?.account?.accountLinking?.trustedProviders ?? []
|
|
533
|
+
]))
|
|
534
|
+
}
|
|
338
535
|
},
|
|
339
536
|
verification: {
|
|
340
537
|
...AUTH_VERIFICATION_CONFIG
|
|
@@ -350,15 +547,71 @@ var AuthManager = class {
|
|
|
350
547
|
...this.config.emailAndPassword?.maxPasswordLength != null ? { maxPasswordLength: this.config.emailAndPassword.maxPasswordLength } : {},
|
|
351
548
|
...this.config.emailAndPassword?.resetPasswordTokenExpiresIn != null ? { resetPasswordTokenExpiresIn: this.config.emailAndPassword.resetPasswordTokenExpiresIn } : {},
|
|
352
549
|
...this.config.emailAndPassword?.autoSignIn != null ? { autoSignIn: this.config.emailAndPassword.autoSignIn } : {},
|
|
353
|
-
...this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null ? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {}
|
|
550
|
+
...this.config.emailAndPassword?.revokeSessionsOnPasswordReset != null ? { revokeSessionsOnPasswordReset: this.config.emailAndPassword.revokeSessionsOnPasswordReset } : {},
|
|
551
|
+
sendResetPassword: async ({ user, url, token }) => {
|
|
552
|
+
const email = this.getEmailService();
|
|
553
|
+
if (!email) {
|
|
554
|
+
console.warn(
|
|
555
|
+
`[AuthManager] Password-reset requested for ${user.email} but no email service is wired. URL: ${url}`
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const ttlSec = this.config.emailAndPassword?.resetPasswordTokenExpiresIn ?? 60 * 60;
|
|
560
|
+
try {
|
|
561
|
+
await email.sendTemplate({
|
|
562
|
+
template: "auth.password_reset",
|
|
563
|
+
to: { address: user.email, ...user.name ? { name: user.name } : {} },
|
|
564
|
+
data: {
|
|
565
|
+
user: { name: user.name || user.email, email: user.email, id: user.id },
|
|
566
|
+
resetUrl: url,
|
|
567
|
+
token,
|
|
568
|
+
expiresInMinutes: Math.round(ttlSec / 60),
|
|
569
|
+
appName: this.getAppName()
|
|
570
|
+
},
|
|
571
|
+
relatedObject: "sys_user",
|
|
572
|
+
relatedId: user.id
|
|
573
|
+
});
|
|
574
|
+
} catch (err) {
|
|
575
|
+
console.error(`[AuthManager] sendResetPassword failed: ${err?.message ?? err}`);
|
|
576
|
+
throw err;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
354
579
|
},
|
|
355
580
|
// Email verification
|
|
356
|
-
...this.config.emailVerification ? {
|
|
581
|
+
...this.config.emailVerification || this.config.emailService ? {
|
|
357
582
|
emailVerification: {
|
|
358
|
-
...this.config.emailVerification
|
|
359
|
-
...this.config.emailVerification
|
|
360
|
-
...this.config.emailVerification
|
|
361
|
-
...this.config.emailVerification
|
|
583
|
+
...this.config.emailVerification?.sendOnSignUp != null ? { sendOnSignUp: this.config.emailVerification.sendOnSignUp } : {},
|
|
584
|
+
...this.config.emailVerification?.sendOnSignIn != null ? { sendOnSignIn: this.config.emailVerification.sendOnSignIn } : {},
|
|
585
|
+
...this.config.emailVerification?.autoSignInAfterVerification != null ? { autoSignInAfterVerification: this.config.emailVerification.autoSignInAfterVerification } : {},
|
|
586
|
+
...this.config.emailVerification?.expiresIn != null ? { expiresIn: this.config.emailVerification.expiresIn } : {},
|
|
587
|
+
sendVerificationEmail: async ({ user, url, token }) => {
|
|
588
|
+
const email = this.getEmailService();
|
|
589
|
+
if (!email) {
|
|
590
|
+
console.warn(
|
|
591
|
+
`[AuthManager] Verification email requested for ${user.email} but no email service is wired. URL: ${url}`
|
|
592
|
+
);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const ttlSec = this.config.emailVerification?.expiresIn ?? 60 * 60;
|
|
596
|
+
try {
|
|
597
|
+
await email.sendTemplate({
|
|
598
|
+
template: "auth.verify_email",
|
|
599
|
+
to: { address: user.email, ...user.name ? { name: user.name } : {} },
|
|
600
|
+
data: {
|
|
601
|
+
user: { name: user.name || user.email, email: user.email, id: user.id },
|
|
602
|
+
verificationUrl: url,
|
|
603
|
+
token,
|
|
604
|
+
expiresInMinutes: Math.round(ttlSec / 60),
|
|
605
|
+
appName: this.getAppName()
|
|
606
|
+
},
|
|
607
|
+
relatedObject: "sys_user",
|
|
608
|
+
relatedId: user.id
|
|
609
|
+
});
|
|
610
|
+
} catch (err) {
|
|
611
|
+
console.error(`[AuthManager] sendVerificationEmail failed: ${err?.message ?? err}`);
|
|
612
|
+
throw err;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
362
615
|
}
|
|
363
616
|
} : {},
|
|
364
617
|
// Session configuration
|
|
@@ -370,7 +623,7 @@ var AuthManager = class {
|
|
|
370
623
|
// 1 day default
|
|
371
624
|
},
|
|
372
625
|
// better-auth plugins — registered based on AuthPluginConfig flags
|
|
373
|
-
plugins
|
|
626
|
+
plugins,
|
|
374
627
|
// Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
|
|
375
628
|
// Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
|
|
376
629
|
...(() => {
|
|
@@ -383,6 +636,8 @@ var AuthManager = class {
|
|
|
383
636
|
}
|
|
384
637
|
if (!origins.length && (!corsOrigin || corsOrigin === "*")) {
|
|
385
638
|
origins.push("http://localhost:*");
|
|
639
|
+
origins.push("http://*.localhost:*");
|
|
640
|
+
origins.push("https://*.localhost:*");
|
|
386
641
|
}
|
|
387
642
|
return origins.length ? { trustedOrigins: origins } : {};
|
|
388
643
|
})(),
|
|
@@ -405,28 +660,224 @@ var AuthManager = class {
|
|
|
405
660
|
* a `schema` option containing the appropriate snake_case field mappings,
|
|
406
661
|
* so that `createAdapterFactory` transforms them automatically.
|
|
407
662
|
*/
|
|
408
|
-
buildPluginList() {
|
|
409
|
-
const pluginConfig = this.config.plugins;
|
|
663
|
+
async buildPluginList() {
|
|
664
|
+
const pluginConfig = this.config.plugins ?? {};
|
|
410
665
|
const plugins = [];
|
|
411
|
-
|
|
666
|
+
const enabled = {
|
|
667
|
+
organization: pluginConfig.organization ?? true,
|
|
668
|
+
twoFactor: pluginConfig.twoFactor ?? false,
|
|
669
|
+
passkeys: pluginConfig.passkeys ?? false,
|
|
670
|
+
magicLink: pluginConfig.magicLink ?? false,
|
|
671
|
+
oidcProvider: pluginConfig.oidcProvider ?? false,
|
|
672
|
+
deviceAuthorization: pluginConfig.deviceAuthorization ?? false,
|
|
673
|
+
admin: pluginConfig.admin ?? false
|
|
674
|
+
};
|
|
675
|
+
const { bearer } = await import("better-auth/plugins/bearer");
|
|
676
|
+
plugins.push(bearer());
|
|
677
|
+
if (enabled.organization) {
|
|
678
|
+
const { organization } = await import("better-auth/plugins/organization");
|
|
679
|
+
let customOrgRoles;
|
|
680
|
+
const extra = this.config.additionalOrgRoles;
|
|
681
|
+
if (extra && extra.length > 0) {
|
|
682
|
+
try {
|
|
683
|
+
const accessMod = await import("better-auth/plugins/organization/access");
|
|
684
|
+
const { defaultAc, memberAc, defaultRoles: importedDefaultRoles } = accessMod;
|
|
685
|
+
const defaultRoles = importedDefaultRoles || null;
|
|
686
|
+
if (defaultAc && memberAc && typeof memberAc.statements === "object") {
|
|
687
|
+
const built = defaultRoles ? { ...defaultRoles } : {};
|
|
688
|
+
const stmts = memberAc.statements;
|
|
689
|
+
for (const name of extra) {
|
|
690
|
+
if (!name) continue;
|
|
691
|
+
if (built[name]) continue;
|
|
692
|
+
built[name] = defaultAc.newRole(stmts);
|
|
693
|
+
}
|
|
694
|
+
customOrgRoles = built;
|
|
695
|
+
}
|
|
696
|
+
} catch {
|
|
697
|
+
customOrgRoles = void 0;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
412
700
|
plugins.push(organization({
|
|
413
|
-
schema: buildOrganizationPluginSchema()
|
|
701
|
+
schema: buildOrganizationPluginSchema(),
|
|
702
|
+
// Enable the team sub-feature so the framework's `sys_team` /
|
|
703
|
+
// `sys_team_member` tables (already declared in platform-objects)
|
|
704
|
+
// are actually wired up to better-auth's CRUD endpoints
|
|
705
|
+
// (`/organization/{create,update,remove,list}-team[s]` and
|
|
706
|
+
// `/organization/{add,remove,list}-team-member[s]`). The Account
|
|
707
|
+
// portal exposes a Teams page; without this flag those endpoints
|
|
708
|
+
// 404 and the section silently breaks.
|
|
709
|
+
teams: { enabled: true },
|
|
710
|
+
// Without a mailer wired in framework, requiring email verification
|
|
711
|
+
// before accepting invitations dead-ends every invite flow with
|
|
712
|
+
// FORBIDDEN EMAIL_VERIFICATION_REQUIRED…. Default-off here keeps
|
|
713
|
+
// the built-in /accept-invitation route usable for pilots; operators
|
|
714
|
+
// who wire a real mailer can re-enable downstream.
|
|
715
|
+
requireEmailVerificationOnInvitation: false,
|
|
716
|
+
...customOrgRoles ? { roles: customOrgRoles } : {},
|
|
717
|
+
// No mailer is wired in framework yet — log the accept URL so
|
|
718
|
+
// operators / UI can fall back to copy-paste flows. Replace this
|
|
719
|
+
// with a real mail integration when available.
|
|
720
|
+
sendInvitationEmail: async ({ email: recipientEmail, invitation, organization: org, inviter }) => {
|
|
721
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
722
|
+
const acceptUrl = `${baseUrl}/accept-invitation/${invitation.id}`;
|
|
723
|
+
const emailService = this.getEmailService();
|
|
724
|
+
if (!emailService) {
|
|
725
|
+
console.warn(
|
|
726
|
+
`[AuthManager] Invitation email not configured. To: ${recipientEmail} (org: ${org?.name ?? invitation.organizationId}, role: ${invitation.role}, inviter: ${inviter?.user?.email ?? "unknown"}) URL: ${acceptUrl}`
|
|
727
|
+
);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
await emailService.sendTemplate({
|
|
732
|
+
template: "auth.invitation",
|
|
733
|
+
to: recipientEmail,
|
|
734
|
+
data: {
|
|
735
|
+
inviter: {
|
|
736
|
+
name: inviter?.user?.name ?? inviter?.user?.email ?? "A teammate",
|
|
737
|
+
email: inviter?.user?.email ?? ""
|
|
738
|
+
},
|
|
739
|
+
organization: { name: org?.name ?? invitation.organizationId },
|
|
740
|
+
role: invitation.role || "",
|
|
741
|
+
acceptUrl,
|
|
742
|
+
appName: this.getAppName()
|
|
743
|
+
},
|
|
744
|
+
relatedObject: "sys_invitation",
|
|
745
|
+
relatedId: invitation.id
|
|
746
|
+
});
|
|
747
|
+
} catch (err) {
|
|
748
|
+
console.error(`[AuthManager] sendInvitationEmail failed: ${err?.message ?? err}`);
|
|
749
|
+
throw err;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
414
752
|
}));
|
|
415
753
|
}
|
|
416
|
-
if (
|
|
754
|
+
if (enabled.twoFactor) {
|
|
755
|
+
const { twoFactor } = await import("better-auth/plugins/two-factor");
|
|
417
756
|
plugins.push(twoFactor({
|
|
418
757
|
schema: buildTwoFactorPluginSchema()
|
|
419
758
|
}));
|
|
420
759
|
}
|
|
421
|
-
if (
|
|
760
|
+
if (enabled.admin) {
|
|
761
|
+
const { admin } = await import("better-auth/plugins/admin");
|
|
762
|
+
plugins.push(admin({
|
|
763
|
+
schema: buildAdminPluginSchema()
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
if (enabled.magicLink) {
|
|
767
|
+
const { magicLink } = await import("better-auth/plugins/magic-link");
|
|
422
768
|
plugins.push(magicLink({
|
|
423
|
-
sendMagicLink: async ({ email, url }) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
769
|
+
sendMagicLink: async ({ email: recipientEmail, url, token }) => {
|
|
770
|
+
const emailService = this.getEmailService();
|
|
771
|
+
if (!emailService) {
|
|
772
|
+
console.warn(
|
|
773
|
+
`[AuthManager] Magic-link requested for ${recipientEmail} but no email service is wired. URL: ${url}`
|
|
774
|
+
);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
await emailService.sendTemplate({
|
|
779
|
+
template: "auth.magic_link",
|
|
780
|
+
to: recipientEmail,
|
|
781
|
+
data: {
|
|
782
|
+
magicLinkUrl: url,
|
|
783
|
+
token,
|
|
784
|
+
expiresInMinutes: 10,
|
|
785
|
+
appName: this.getAppName()
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(`[AuthManager] sendMagicLink failed: ${err?.message ?? err}`);
|
|
790
|
+
throw err;
|
|
791
|
+
}
|
|
427
792
|
}
|
|
428
793
|
}));
|
|
429
794
|
}
|
|
795
|
+
if (this.config.oidcProviders?.length) {
|
|
796
|
+
const { genericOAuth } = await import("better-auth/plugins/generic-oauth");
|
|
797
|
+
plugins.push(genericOAuth({
|
|
798
|
+
config: this.config.oidcProviders.map((p) => ({
|
|
799
|
+
providerId: p.providerId,
|
|
800
|
+
...p.discoveryUrl ? { discoveryUrl: p.discoveryUrl } : {},
|
|
801
|
+
...p.issuer ? { issuer: p.issuer } : {},
|
|
802
|
+
...p.authorizationUrl ? { authorizationUrl: p.authorizationUrl } : {},
|
|
803
|
+
...p.tokenUrl ? { tokenUrl: p.tokenUrl } : {},
|
|
804
|
+
...p.userInfoUrl ? { userInfoUrl: p.userInfoUrl } : {},
|
|
805
|
+
clientId: p.clientId,
|
|
806
|
+
clientSecret: p.clientSecret,
|
|
807
|
+
...p.scopes ? { scopes: p.scopes } : {},
|
|
808
|
+
...p.pkce != null ? { pkce: p.pkce } : {}
|
|
809
|
+
}))
|
|
810
|
+
}));
|
|
811
|
+
}
|
|
812
|
+
if (enabled.oidcProvider) {
|
|
813
|
+
const { jwt } = await import("better-auth/plugins");
|
|
814
|
+
plugins.push(jwt({ schema: buildJwtPluginSchema() }));
|
|
815
|
+
const { oauthProvider } = await import("@better-auth/oauth-provider");
|
|
816
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
817
|
+
plugins.push(oauthProvider({
|
|
818
|
+
// Account SPA renders both pages — see apps/account.
|
|
819
|
+
loginPage: `${baseUrl}/_account/login`,
|
|
820
|
+
consentPage: `${baseUrl}/_account/oauth/consent`,
|
|
821
|
+
schema: buildOauthProviderPluginSchema()
|
|
822
|
+
}));
|
|
823
|
+
}
|
|
824
|
+
if (enabled.deviceAuthorization) {
|
|
825
|
+
const { deviceAuthorization } = await import("better-auth/plugins/device-authorization");
|
|
826
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
827
|
+
plugins.push(deviceAuthorization({
|
|
828
|
+
verificationUri: `${baseUrl}/_account/auth/device`,
|
|
829
|
+
schema: buildDeviceAuthorizationPluginSchema()
|
|
830
|
+
}));
|
|
831
|
+
}
|
|
832
|
+
const dataEngine = this.config.dataEngine;
|
|
833
|
+
if (dataEngine) {
|
|
834
|
+
const { customSession } = await import("better-auth/plugins/custom-session");
|
|
835
|
+
plugins.push(customSession(async ({ user, session }) => {
|
|
836
|
+
if (!user?.id) return { user, session };
|
|
837
|
+
const isPlatformAdmin = async () => {
|
|
838
|
+
try {
|
|
839
|
+
const links = await dataEngine.find("sys_user_permission_set", {
|
|
840
|
+
where: { user_id: user.id },
|
|
841
|
+
limit: 50
|
|
842
|
+
});
|
|
843
|
+
const platformLinks = (Array.isArray(links) ? links : []).filter(
|
|
844
|
+
(l) => !l.organization_id
|
|
845
|
+
);
|
|
846
|
+
if (platformLinks.length === 0) return false;
|
|
847
|
+
const sets = await dataEngine.find("sys_permission_set", { limit: 50 });
|
|
848
|
+
const adminSet = (Array.isArray(sets) ? sets : []).find(
|
|
849
|
+
(r) => r.name === "admin_full_access"
|
|
850
|
+
);
|
|
851
|
+
if (!adminSet) return false;
|
|
852
|
+
return platformLinks.some(
|
|
853
|
+
(l) => l.permission_set_id === adminSet.id
|
|
854
|
+
);
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
const isActiveOrgAdmin = async () => {
|
|
860
|
+
try {
|
|
861
|
+
const orgId = session?.activeOrganizationId;
|
|
862
|
+
if (!orgId) return false;
|
|
863
|
+
const members = await dataEngine.find("sys_member", {
|
|
864
|
+
where: { user_id: user.id, organization_id: orgId },
|
|
865
|
+
limit: 5
|
|
866
|
+
});
|
|
867
|
+
return (Array.isArray(members) ? members : []).some((m) => {
|
|
868
|
+
const raw = typeof m?.role === "string" ? m.role : "";
|
|
869
|
+
const roles = raw.split(",").map((s) => s.trim().toLowerCase());
|
|
870
|
+
return roles.includes("owner") || roles.includes("admin");
|
|
871
|
+
});
|
|
872
|
+
} catch {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
const promote = await isPlatformAdmin() || await isActiveOrgAdmin();
|
|
877
|
+
if (!promote) return { user, session };
|
|
878
|
+
return { user: { ...user, role: "admin" }, session };
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
430
881
|
return plugins;
|
|
431
882
|
}
|
|
432
883
|
/**
|
|
@@ -483,6 +934,26 @@ var AuthManager = class {
|
|
|
483
934
|
}
|
|
484
935
|
this.config = { ...this.config, baseUrl: url };
|
|
485
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* Inject (or replace) the outbound email service used by better-auth
|
|
939
|
+
* callbacks. Safe to call after construction but BEFORE the first
|
|
940
|
+
* request hits the auth handler — callbacks read this via
|
|
941
|
+
* {@link getEmailService} when invoked.
|
|
942
|
+
*
|
|
943
|
+
* AuthPlugin calls this on `kernel:ready` once `ctx.getService('email')`
|
|
944
|
+
* resolves. For tests / serverless, callers may invoke directly.
|
|
945
|
+
*/
|
|
946
|
+
setEmailService(email) {
|
|
947
|
+
this.config.emailService = email;
|
|
948
|
+
}
|
|
949
|
+
/** @internal Used by callback closures. */
|
|
950
|
+
getEmailService() {
|
|
951
|
+
return this.config.emailService;
|
|
952
|
+
}
|
|
953
|
+
/** @internal `{{appName}}` placeholder value for built-in templates. */
|
|
954
|
+
getAppName() {
|
|
955
|
+
return this.config.appName ?? "ObjectStack";
|
|
956
|
+
}
|
|
486
957
|
/**
|
|
487
958
|
* Get the underlying better-auth instance
|
|
488
959
|
* Useful for advanced use cases
|
|
@@ -502,7 +973,7 @@ var AuthManager = class {
|
|
|
502
973
|
* @returns Web standard Response object
|
|
503
974
|
*/
|
|
504
975
|
async handleRequest(request) {
|
|
505
|
-
const auth = this.getOrCreateAuth();
|
|
976
|
+
const auth = await this.getOrCreateAuth();
|
|
506
977
|
const response = await auth.handler(request);
|
|
507
978
|
if (response.status >= 500) {
|
|
508
979
|
try {
|
|
@@ -518,18 +989,19 @@ var AuthManager = class {
|
|
|
518
989
|
* Get the better-auth API for programmatic access
|
|
519
990
|
* Use this for server-side operations (e.g., creating users, checking sessions)
|
|
520
991
|
*/
|
|
521
|
-
|
|
522
|
-
|
|
992
|
+
async getApi() {
|
|
993
|
+
const auth = await this.getOrCreateAuth();
|
|
994
|
+
return auth.api;
|
|
523
995
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
996
|
+
// ---------------------------------------------------------------------------
|
|
997
|
+
// Device Flow (CLI browser-based login)
|
|
998
|
+
//
|
|
999
|
+
// The device authorization flow (RFC 8628) is now handled entirely by
|
|
1000
|
+
// better-auth's `device-authorization` plugin. Endpoints are exposed at
|
|
1001
|
+
// `${basePath}/device/{code,token,approve,deny}` and persisted in
|
|
1002
|
+
// `sys_device_code`. Enable via `plugins.deviceAuthorization: true` in
|
|
1003
|
+
// AuthPluginConfig.
|
|
1004
|
+
// ---------------------------------------------------------------------------
|
|
533
1005
|
getPublicConfig() {
|
|
534
1006
|
const socialProviders = [];
|
|
535
1007
|
if (this.config.socialProviders) {
|
|
@@ -549,11 +1021,22 @@ var AuthManager = class {
|
|
|
549
1021
|
socialProviders.push({
|
|
550
1022
|
id,
|
|
551
1023
|
name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
|
|
552
|
-
enabled: true
|
|
1024
|
+
enabled: true,
|
|
1025
|
+
type: "social"
|
|
553
1026
|
});
|
|
554
1027
|
}
|
|
555
1028
|
}
|
|
556
1029
|
}
|
|
1030
|
+
if (this.config.oidcProviders?.length) {
|
|
1031
|
+
for (const p of this.config.oidcProviders) {
|
|
1032
|
+
socialProviders.push({
|
|
1033
|
+
id: p.providerId,
|
|
1034
|
+
name: p.name ?? p.providerId.charAt(0).toUpperCase() + p.providerId.slice(1),
|
|
1035
|
+
enabled: true,
|
|
1036
|
+
type: "oidc"
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
557
1040
|
const emailPasswordConfig = this.config.emailAndPassword ?? {};
|
|
558
1041
|
const emailPassword = {
|
|
559
1042
|
enabled: emailPasswordConfig.enabled !== false,
|
|
@@ -566,7 +1049,9 @@ var AuthManager = class {
|
|
|
566
1049
|
twoFactor: pluginConfig.twoFactor ?? false,
|
|
567
1050
|
passkeys: pluginConfig.passkeys ?? false,
|
|
568
1051
|
magicLink: pluginConfig.magicLink ?? false,
|
|
569
|
-
organization: pluginConfig.organization ??
|
|
1052
|
+
organization: pluginConfig.organization ?? true,
|
|
1053
|
+
oidcProvider: pluginConfig.oidcProvider ?? false,
|
|
1054
|
+
deviceAuthorization: pluginConfig.deviceAuthorization ?? false
|
|
570
1055
|
};
|
|
571
1056
|
return {
|
|
572
1057
|
emailPassword,
|
|
@@ -574,776 +1059,69 @@ var AuthManager = class {
|
|
|
574
1059
|
features
|
|
575
1060
|
};
|
|
576
1061
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
label: "User",
|
|
585
|
-
pluralLabel: "Users",
|
|
586
|
-
icon: "user",
|
|
587
|
-
isSystem: true,
|
|
588
|
-
description: "User accounts for authentication",
|
|
589
|
-
titleFormat: "{name} ({email})",
|
|
590
|
-
compactLayout: ["name", "email", "email_verified"],
|
|
591
|
-
fields: {
|
|
592
|
-
id: Field.text({
|
|
593
|
-
label: "User ID",
|
|
594
|
-
required: true,
|
|
595
|
-
readonly: true
|
|
596
|
-
}),
|
|
597
|
-
created_at: Field.datetime({
|
|
598
|
-
label: "Created At",
|
|
599
|
-
defaultValue: "NOW()",
|
|
600
|
-
readonly: true
|
|
601
|
-
}),
|
|
602
|
-
updated_at: Field.datetime({
|
|
603
|
-
label: "Updated At",
|
|
604
|
-
defaultValue: "NOW()",
|
|
605
|
-
readonly: true
|
|
606
|
-
}),
|
|
607
|
-
email: Field.email({
|
|
608
|
-
label: "Email",
|
|
609
|
-
required: true,
|
|
610
|
-
searchable: true
|
|
611
|
-
}),
|
|
612
|
-
email_verified: Field.boolean({
|
|
613
|
-
label: "Email Verified",
|
|
614
|
-
defaultValue: false
|
|
615
|
-
}),
|
|
616
|
-
name: Field.text({
|
|
617
|
-
label: "Name",
|
|
618
|
-
required: true,
|
|
619
|
-
searchable: true,
|
|
620
|
-
maxLength: 255
|
|
621
|
-
}),
|
|
622
|
-
image: Field.url({
|
|
623
|
-
label: "Profile Image",
|
|
624
|
-
required: false
|
|
625
|
-
})
|
|
626
|
-
},
|
|
627
|
-
indexes: [
|
|
628
|
-
{ fields: ["email"], unique: true },
|
|
629
|
-
{ fields: ["created_at"], unique: false }
|
|
630
|
-
],
|
|
631
|
-
enable: {
|
|
632
|
-
trackHistory: true,
|
|
633
|
-
searchable: true,
|
|
634
|
-
apiEnabled: true,
|
|
635
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
636
|
-
trash: true,
|
|
637
|
-
mru: true
|
|
638
|
-
},
|
|
639
|
-
validations: [
|
|
640
|
-
{
|
|
641
|
-
name: "email_unique",
|
|
642
|
-
type: "unique",
|
|
643
|
-
severity: "error",
|
|
644
|
-
message: "Email must be unique",
|
|
645
|
-
fields: ["email"],
|
|
646
|
-
caseSensitive: false
|
|
647
|
-
}
|
|
648
|
-
]
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
// src/objects/sys-session.object.ts
|
|
652
|
-
import { ObjectSchema as ObjectSchema2, Field as Field2 } from "@objectstack/spec/data";
|
|
653
|
-
var SysSession = ObjectSchema2.create({
|
|
654
|
-
namespace: "sys",
|
|
655
|
-
name: "session",
|
|
656
|
-
label: "Session",
|
|
657
|
-
pluralLabel: "Sessions",
|
|
658
|
-
icon: "key",
|
|
659
|
-
isSystem: true,
|
|
660
|
-
description: "Active user sessions",
|
|
661
|
-
titleFormat: "Session {token}",
|
|
662
|
-
compactLayout: ["user_id", "expires_at", "ip_address"],
|
|
663
|
-
fields: {
|
|
664
|
-
id: Field2.text({
|
|
665
|
-
label: "Session ID",
|
|
666
|
-
required: true,
|
|
667
|
-
readonly: true
|
|
668
|
-
}),
|
|
669
|
-
created_at: Field2.datetime({
|
|
670
|
-
label: "Created At",
|
|
671
|
-
defaultValue: "NOW()",
|
|
672
|
-
readonly: true
|
|
673
|
-
}),
|
|
674
|
-
updated_at: Field2.datetime({
|
|
675
|
-
label: "Updated At",
|
|
676
|
-
defaultValue: "NOW()",
|
|
677
|
-
readonly: true
|
|
678
|
-
}),
|
|
679
|
-
user_id: Field2.text({
|
|
680
|
-
label: "User ID",
|
|
681
|
-
required: true
|
|
682
|
-
}),
|
|
683
|
-
expires_at: Field2.datetime({
|
|
684
|
-
label: "Expires At",
|
|
685
|
-
required: true
|
|
686
|
-
}),
|
|
687
|
-
token: Field2.text({
|
|
688
|
-
label: "Session Token",
|
|
689
|
-
required: true
|
|
690
|
-
}),
|
|
691
|
-
ip_address: Field2.text({
|
|
692
|
-
label: "IP Address",
|
|
693
|
-
required: false,
|
|
694
|
-
maxLength: 45
|
|
695
|
-
// Support IPv6
|
|
696
|
-
}),
|
|
697
|
-
user_agent: Field2.textarea({
|
|
698
|
-
label: "User Agent",
|
|
699
|
-
required: false
|
|
700
|
-
})
|
|
701
|
-
},
|
|
702
|
-
indexes: [
|
|
703
|
-
{ fields: ["token"], unique: true },
|
|
704
|
-
{ fields: ["user_id"], unique: false },
|
|
705
|
-
{ fields: ["expires_at"], unique: false }
|
|
706
|
-
],
|
|
707
|
-
enable: {
|
|
708
|
-
trackHistory: false,
|
|
709
|
-
searchable: false,
|
|
710
|
-
apiEnabled: true,
|
|
711
|
-
apiMethods: ["get", "list", "create", "delete"],
|
|
712
|
-
trash: false,
|
|
713
|
-
mru: false
|
|
714
|
-
}
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
// src/objects/sys-account.object.ts
|
|
718
|
-
import { ObjectSchema as ObjectSchema3, Field as Field3 } from "@objectstack/spec/data";
|
|
719
|
-
var SysAccount = ObjectSchema3.create({
|
|
720
|
-
namespace: "sys",
|
|
721
|
-
name: "account",
|
|
722
|
-
label: "Account",
|
|
723
|
-
pluralLabel: "Accounts",
|
|
724
|
-
icon: "link",
|
|
725
|
-
isSystem: true,
|
|
726
|
-
description: "OAuth and authentication provider accounts",
|
|
727
|
-
titleFormat: "{provider_id} - {account_id}",
|
|
728
|
-
compactLayout: ["provider_id", "user_id", "account_id"],
|
|
729
|
-
fields: {
|
|
730
|
-
id: Field3.text({
|
|
731
|
-
label: "Account ID",
|
|
732
|
-
required: true,
|
|
733
|
-
readonly: true
|
|
734
|
-
}),
|
|
735
|
-
created_at: Field3.datetime({
|
|
736
|
-
label: "Created At",
|
|
737
|
-
defaultValue: "NOW()",
|
|
738
|
-
readonly: true
|
|
739
|
-
}),
|
|
740
|
-
updated_at: Field3.datetime({
|
|
741
|
-
label: "Updated At",
|
|
742
|
-
defaultValue: "NOW()",
|
|
743
|
-
readonly: true
|
|
744
|
-
}),
|
|
745
|
-
provider_id: Field3.text({
|
|
746
|
-
label: "Provider ID",
|
|
747
|
-
required: true,
|
|
748
|
-
description: "OAuth provider identifier (google, github, etc.)"
|
|
749
|
-
}),
|
|
750
|
-
account_id: Field3.text({
|
|
751
|
-
label: "Provider Account ID",
|
|
752
|
-
required: true,
|
|
753
|
-
description: "User's ID in the provider's system"
|
|
754
|
-
}),
|
|
755
|
-
user_id: Field3.text({
|
|
756
|
-
label: "User ID",
|
|
757
|
-
required: true,
|
|
758
|
-
description: "Link to user table"
|
|
759
|
-
}),
|
|
760
|
-
access_token: Field3.textarea({
|
|
761
|
-
label: "Access Token",
|
|
762
|
-
required: false
|
|
763
|
-
}),
|
|
764
|
-
refresh_token: Field3.textarea({
|
|
765
|
-
label: "Refresh Token",
|
|
766
|
-
required: false
|
|
767
|
-
}),
|
|
768
|
-
id_token: Field3.textarea({
|
|
769
|
-
label: "ID Token",
|
|
770
|
-
required: false
|
|
771
|
-
}),
|
|
772
|
-
access_token_expires_at: Field3.datetime({
|
|
773
|
-
label: "Access Token Expires At",
|
|
774
|
-
required: false
|
|
775
|
-
}),
|
|
776
|
-
refresh_token_expires_at: Field3.datetime({
|
|
777
|
-
label: "Refresh Token Expires At",
|
|
778
|
-
required: false
|
|
779
|
-
}),
|
|
780
|
-
scope: Field3.text({
|
|
781
|
-
label: "OAuth Scope",
|
|
782
|
-
required: false
|
|
783
|
-
}),
|
|
784
|
-
password: Field3.text({
|
|
785
|
-
label: "Password Hash",
|
|
786
|
-
required: false,
|
|
787
|
-
description: "Hashed password for email/password provider"
|
|
788
|
-
})
|
|
789
|
-
},
|
|
790
|
-
indexes: [
|
|
791
|
-
{ fields: ["user_id"], unique: false },
|
|
792
|
-
{ fields: ["provider_id", "account_id"], unique: true }
|
|
793
|
-
],
|
|
794
|
-
enable: {
|
|
795
|
-
trackHistory: false,
|
|
796
|
-
searchable: false,
|
|
797
|
-
apiEnabled: true,
|
|
798
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
799
|
-
trash: true,
|
|
800
|
-
mru: false
|
|
801
|
-
}
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
// src/objects/sys-verification.object.ts
|
|
805
|
-
import { ObjectSchema as ObjectSchema4, Field as Field4 } from "@objectstack/spec/data";
|
|
806
|
-
var SysVerification = ObjectSchema4.create({
|
|
807
|
-
namespace: "sys",
|
|
808
|
-
name: "verification",
|
|
809
|
-
label: "Verification",
|
|
810
|
-
pluralLabel: "Verifications",
|
|
811
|
-
icon: "shield-check",
|
|
812
|
-
isSystem: true,
|
|
813
|
-
description: "Email and phone verification tokens",
|
|
814
|
-
titleFormat: "Verification for {identifier}",
|
|
815
|
-
compactLayout: ["identifier", "expires_at", "created_at"],
|
|
816
|
-
fields: {
|
|
817
|
-
id: Field4.text({
|
|
818
|
-
label: "Verification ID",
|
|
819
|
-
required: true,
|
|
820
|
-
readonly: true
|
|
821
|
-
}),
|
|
822
|
-
created_at: Field4.datetime({
|
|
823
|
-
label: "Created At",
|
|
824
|
-
defaultValue: "NOW()",
|
|
825
|
-
readonly: true
|
|
826
|
-
}),
|
|
827
|
-
updated_at: Field4.datetime({
|
|
828
|
-
label: "Updated At",
|
|
829
|
-
defaultValue: "NOW()",
|
|
830
|
-
readonly: true
|
|
831
|
-
}),
|
|
832
|
-
value: Field4.text({
|
|
833
|
-
label: "Verification Token",
|
|
834
|
-
required: true,
|
|
835
|
-
description: "Token or code for verification"
|
|
836
|
-
}),
|
|
837
|
-
expires_at: Field4.datetime({
|
|
838
|
-
label: "Expires At",
|
|
839
|
-
required: true
|
|
840
|
-
}),
|
|
841
|
-
identifier: Field4.text({
|
|
842
|
-
label: "Identifier",
|
|
843
|
-
required: true,
|
|
844
|
-
description: "Email address or phone number"
|
|
845
|
-
})
|
|
846
|
-
},
|
|
847
|
-
indexes: [
|
|
848
|
-
{ fields: ["value"], unique: true },
|
|
849
|
-
{ fields: ["identifier"], unique: false },
|
|
850
|
-
{ fields: ["expires_at"], unique: false }
|
|
851
|
-
],
|
|
852
|
-
enable: {
|
|
853
|
-
trackHistory: false,
|
|
854
|
-
searchable: false,
|
|
855
|
-
apiEnabled: true,
|
|
856
|
-
apiMethods: ["get", "create", "delete"],
|
|
857
|
-
trash: false,
|
|
858
|
-
mru: false
|
|
859
|
-
}
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
// src/objects/sys-organization.object.ts
|
|
863
|
-
import { ObjectSchema as ObjectSchema5, Field as Field5 } from "@objectstack/spec/data";
|
|
864
|
-
var SysOrganization = ObjectSchema5.create({
|
|
865
|
-
namespace: "sys",
|
|
866
|
-
name: "organization",
|
|
867
|
-
label: "Organization",
|
|
868
|
-
pluralLabel: "Organizations",
|
|
869
|
-
icon: "building-2",
|
|
870
|
-
isSystem: true,
|
|
871
|
-
description: "Organizations for multi-tenant grouping",
|
|
872
|
-
titleFormat: "{name}",
|
|
873
|
-
compactLayout: ["name", "slug", "created_at"],
|
|
874
|
-
fields: {
|
|
875
|
-
id: Field5.text({
|
|
876
|
-
label: "Organization ID",
|
|
877
|
-
required: true,
|
|
878
|
-
readonly: true
|
|
879
|
-
}),
|
|
880
|
-
created_at: Field5.datetime({
|
|
881
|
-
label: "Created At",
|
|
882
|
-
defaultValue: "NOW()",
|
|
883
|
-
readonly: true
|
|
884
|
-
}),
|
|
885
|
-
updated_at: Field5.datetime({
|
|
886
|
-
label: "Updated At",
|
|
887
|
-
defaultValue: "NOW()",
|
|
888
|
-
readonly: true
|
|
889
|
-
}),
|
|
890
|
-
name: Field5.text({
|
|
891
|
-
label: "Name",
|
|
892
|
-
required: true,
|
|
893
|
-
searchable: true,
|
|
894
|
-
maxLength: 255
|
|
895
|
-
}),
|
|
896
|
-
slug: Field5.text({
|
|
897
|
-
label: "Slug",
|
|
898
|
-
required: false,
|
|
899
|
-
maxLength: 255,
|
|
900
|
-
description: "URL-friendly identifier"
|
|
901
|
-
}),
|
|
902
|
-
logo: Field5.url({
|
|
903
|
-
label: "Logo",
|
|
904
|
-
required: false
|
|
905
|
-
}),
|
|
906
|
-
metadata: Field5.textarea({
|
|
907
|
-
label: "Metadata",
|
|
908
|
-
required: false,
|
|
909
|
-
description: "JSON-serialized organization metadata"
|
|
910
|
-
})
|
|
911
|
-
},
|
|
912
|
-
indexes: [
|
|
913
|
-
{ fields: ["slug"], unique: true },
|
|
914
|
-
{ fields: ["name"] }
|
|
915
|
-
],
|
|
916
|
-
enable: {
|
|
917
|
-
trackHistory: true,
|
|
918
|
-
searchable: true,
|
|
919
|
-
apiEnabled: true,
|
|
920
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
921
|
-
trash: true,
|
|
922
|
-
mru: true
|
|
923
|
-
}
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
// src/objects/sys-member.object.ts
|
|
927
|
-
import { ObjectSchema as ObjectSchema6, Field as Field6 } from "@objectstack/spec/data";
|
|
928
|
-
var SysMember = ObjectSchema6.create({
|
|
929
|
-
namespace: "sys",
|
|
930
|
-
name: "member",
|
|
931
|
-
label: "Member",
|
|
932
|
-
pluralLabel: "Members",
|
|
933
|
-
icon: "user-check",
|
|
934
|
-
isSystem: true,
|
|
935
|
-
description: "Organization membership records",
|
|
936
|
-
titleFormat: "{user_id} in {organization_id}",
|
|
937
|
-
compactLayout: ["user_id", "organization_id", "role"],
|
|
938
|
-
fields: {
|
|
939
|
-
id: Field6.text({
|
|
940
|
-
label: "Member ID",
|
|
941
|
-
required: true,
|
|
942
|
-
readonly: true
|
|
943
|
-
}),
|
|
944
|
-
created_at: Field6.datetime({
|
|
945
|
-
label: "Created At",
|
|
946
|
-
defaultValue: "NOW()",
|
|
947
|
-
readonly: true
|
|
948
|
-
}),
|
|
949
|
-
organization_id: Field6.text({
|
|
950
|
-
label: "Organization ID",
|
|
951
|
-
required: true
|
|
952
|
-
}),
|
|
953
|
-
user_id: Field6.text({
|
|
954
|
-
label: "User ID",
|
|
955
|
-
required: true
|
|
956
|
-
}),
|
|
957
|
-
role: Field6.text({
|
|
958
|
-
label: "Role",
|
|
959
|
-
required: false,
|
|
960
|
-
description: "Member role within the organization (e.g. admin, member)",
|
|
961
|
-
maxLength: 100
|
|
962
|
-
})
|
|
963
|
-
},
|
|
964
|
-
indexes: [
|
|
965
|
-
{ fields: ["organization_id", "user_id"], unique: true },
|
|
966
|
-
{ fields: ["user_id"] }
|
|
967
|
-
],
|
|
968
|
-
enable: {
|
|
969
|
-
trackHistory: true,
|
|
970
|
-
searchable: false,
|
|
971
|
-
apiEnabled: true,
|
|
972
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
973
|
-
trash: false,
|
|
974
|
-
mru: false
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
// src/objects/sys-invitation.object.ts
|
|
979
|
-
import { ObjectSchema as ObjectSchema7, Field as Field7 } from "@objectstack/spec/data";
|
|
980
|
-
var SysInvitation = ObjectSchema7.create({
|
|
981
|
-
namespace: "sys",
|
|
982
|
-
name: "invitation",
|
|
983
|
-
label: "Invitation",
|
|
984
|
-
pluralLabel: "Invitations",
|
|
985
|
-
icon: "mail",
|
|
986
|
-
isSystem: true,
|
|
987
|
-
description: "Organization invitations for user onboarding",
|
|
988
|
-
titleFormat: "Invitation to {organization_id}",
|
|
989
|
-
compactLayout: ["email", "organization_id", "status"],
|
|
990
|
-
fields: {
|
|
991
|
-
id: Field7.text({
|
|
992
|
-
label: "Invitation ID",
|
|
993
|
-
required: true,
|
|
994
|
-
readonly: true
|
|
995
|
-
}),
|
|
996
|
-
created_at: Field7.datetime({
|
|
997
|
-
label: "Created At",
|
|
998
|
-
defaultValue: "NOW()",
|
|
999
|
-
readonly: true
|
|
1000
|
-
}),
|
|
1001
|
-
organization_id: Field7.text({
|
|
1002
|
-
label: "Organization ID",
|
|
1003
|
-
required: true
|
|
1004
|
-
}),
|
|
1005
|
-
email: Field7.email({
|
|
1006
|
-
label: "Email",
|
|
1007
|
-
required: true,
|
|
1008
|
-
description: "Email address of the invited user"
|
|
1009
|
-
}),
|
|
1010
|
-
role: Field7.text({
|
|
1011
|
-
label: "Role",
|
|
1012
|
-
required: false,
|
|
1013
|
-
maxLength: 100,
|
|
1014
|
-
description: "Role to assign upon acceptance"
|
|
1015
|
-
}),
|
|
1016
|
-
status: Field7.select(["pending", "accepted", "rejected", "expired", "canceled"], {
|
|
1017
|
-
label: "Status",
|
|
1018
|
-
required: true,
|
|
1019
|
-
defaultValue: "pending"
|
|
1020
|
-
}),
|
|
1021
|
-
inviter_id: Field7.text({
|
|
1022
|
-
label: "Inviter ID",
|
|
1023
|
-
required: true,
|
|
1024
|
-
description: "User ID of the person who sent the invitation"
|
|
1025
|
-
}),
|
|
1026
|
-
expires_at: Field7.datetime({
|
|
1027
|
-
label: "Expires At",
|
|
1028
|
-
required: true
|
|
1029
|
-
}),
|
|
1030
|
-
team_id: Field7.text({
|
|
1031
|
-
label: "Team ID",
|
|
1032
|
-
required: false,
|
|
1033
|
-
description: "Optional team to assign upon acceptance"
|
|
1034
|
-
})
|
|
1035
|
-
},
|
|
1036
|
-
indexes: [
|
|
1037
|
-
{ fields: ["organization_id"] },
|
|
1038
|
-
{ fields: ["email"] },
|
|
1039
|
-
{ fields: ["expires_at"] }
|
|
1040
|
-
],
|
|
1041
|
-
enable: {
|
|
1042
|
-
trackHistory: true,
|
|
1043
|
-
searchable: false,
|
|
1044
|
-
apiEnabled: true,
|
|
1045
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1046
|
-
trash: false,
|
|
1047
|
-
mru: false
|
|
1048
|
-
}
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
// src/objects/sys-team.object.ts
|
|
1052
|
-
import { ObjectSchema as ObjectSchema8, Field as Field8 } from "@objectstack/spec/data";
|
|
1053
|
-
var SysTeam = ObjectSchema8.create({
|
|
1054
|
-
namespace: "sys",
|
|
1055
|
-
name: "team",
|
|
1056
|
-
label: "Team",
|
|
1057
|
-
pluralLabel: "Teams",
|
|
1058
|
-
icon: "users",
|
|
1059
|
-
isSystem: true,
|
|
1060
|
-
description: "Teams within organizations for fine-grained grouping",
|
|
1061
|
-
titleFormat: "{name}",
|
|
1062
|
-
compactLayout: ["name", "organization_id", "created_at"],
|
|
1063
|
-
fields: {
|
|
1064
|
-
id: Field8.text({
|
|
1065
|
-
label: "Team ID",
|
|
1066
|
-
required: true,
|
|
1067
|
-
readonly: true
|
|
1068
|
-
}),
|
|
1069
|
-
created_at: Field8.datetime({
|
|
1070
|
-
label: "Created At",
|
|
1071
|
-
defaultValue: "NOW()",
|
|
1072
|
-
readonly: true
|
|
1073
|
-
}),
|
|
1074
|
-
updated_at: Field8.datetime({
|
|
1075
|
-
label: "Updated At",
|
|
1076
|
-
defaultValue: "NOW()",
|
|
1077
|
-
readonly: true
|
|
1078
|
-
}),
|
|
1079
|
-
name: Field8.text({
|
|
1080
|
-
label: "Name",
|
|
1081
|
-
required: true,
|
|
1082
|
-
searchable: true,
|
|
1083
|
-
maxLength: 255
|
|
1084
|
-
}),
|
|
1085
|
-
organization_id: Field8.text({
|
|
1086
|
-
label: "Organization ID",
|
|
1087
|
-
required: true
|
|
1088
|
-
})
|
|
1089
|
-
},
|
|
1090
|
-
indexes: [
|
|
1091
|
-
{ fields: ["organization_id"] },
|
|
1092
|
-
{ fields: ["name", "organization_id"], unique: true }
|
|
1093
|
-
],
|
|
1094
|
-
enable: {
|
|
1095
|
-
trackHistory: true,
|
|
1096
|
-
searchable: true,
|
|
1097
|
-
apiEnabled: true,
|
|
1098
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1099
|
-
trash: true,
|
|
1100
|
-
mru: false
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
// src/objects/sys-team-member.object.ts
|
|
1105
|
-
import { ObjectSchema as ObjectSchema9, Field as Field9 } from "@objectstack/spec/data";
|
|
1106
|
-
var SysTeamMember = ObjectSchema9.create({
|
|
1107
|
-
namespace: "sys",
|
|
1108
|
-
name: "team_member",
|
|
1109
|
-
label: "Team Member",
|
|
1110
|
-
pluralLabel: "Team Members",
|
|
1111
|
-
icon: "user-plus",
|
|
1112
|
-
isSystem: true,
|
|
1113
|
-
description: "Team membership records linking users to teams",
|
|
1114
|
-
titleFormat: "{user_id} in {team_id}",
|
|
1115
|
-
compactLayout: ["user_id", "team_id", "created_at"],
|
|
1116
|
-
fields: {
|
|
1117
|
-
id: Field9.text({
|
|
1118
|
-
label: "Team Member ID",
|
|
1119
|
-
required: true,
|
|
1120
|
-
readonly: true
|
|
1121
|
-
}),
|
|
1122
|
-
created_at: Field9.datetime({
|
|
1123
|
-
label: "Created At",
|
|
1124
|
-
defaultValue: "NOW()",
|
|
1125
|
-
readonly: true
|
|
1126
|
-
}),
|
|
1127
|
-
team_id: Field9.text({
|
|
1128
|
-
label: "Team ID",
|
|
1129
|
-
required: true
|
|
1130
|
-
}),
|
|
1131
|
-
user_id: Field9.text({
|
|
1132
|
-
label: "User ID",
|
|
1133
|
-
required: true
|
|
1134
|
-
})
|
|
1135
|
-
},
|
|
1136
|
-
indexes: [
|
|
1137
|
-
{ fields: ["team_id", "user_id"], unique: true },
|
|
1138
|
-
{ fields: ["user_id"] }
|
|
1139
|
-
],
|
|
1140
|
-
enable: {
|
|
1141
|
-
trackHistory: true,
|
|
1142
|
-
searchable: false,
|
|
1143
|
-
apiEnabled: true,
|
|
1144
|
-
apiMethods: ["get", "list", "create", "delete"],
|
|
1145
|
-
trash: false,
|
|
1146
|
-
mru: false
|
|
1147
|
-
}
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
// src/objects/sys-api-key.object.ts
|
|
1151
|
-
import { ObjectSchema as ObjectSchema10, Field as Field10 } from "@objectstack/spec/data";
|
|
1152
|
-
var SysApiKey = ObjectSchema10.create({
|
|
1153
|
-
namespace: "sys",
|
|
1154
|
-
name: "api_key",
|
|
1155
|
-
label: "API Key",
|
|
1156
|
-
pluralLabel: "API Keys",
|
|
1157
|
-
icon: "key-round",
|
|
1158
|
-
isSystem: true,
|
|
1159
|
-
description: "API keys for programmatic access",
|
|
1160
|
-
titleFormat: "{name}",
|
|
1161
|
-
compactLayout: ["name", "user_id", "expires_at"],
|
|
1162
|
-
fields: {
|
|
1163
|
-
id: Field10.text({
|
|
1164
|
-
label: "API Key ID",
|
|
1165
|
-
required: true,
|
|
1166
|
-
readonly: true
|
|
1167
|
-
}),
|
|
1168
|
-
created_at: Field10.datetime({
|
|
1169
|
-
label: "Created At",
|
|
1170
|
-
defaultValue: "NOW()",
|
|
1171
|
-
readonly: true
|
|
1172
|
-
}),
|
|
1173
|
-
updated_at: Field10.datetime({
|
|
1174
|
-
label: "Updated At",
|
|
1175
|
-
defaultValue: "NOW()",
|
|
1176
|
-
readonly: true
|
|
1177
|
-
}),
|
|
1178
|
-
name: Field10.text({
|
|
1179
|
-
label: "Name",
|
|
1180
|
-
required: true,
|
|
1181
|
-
maxLength: 255,
|
|
1182
|
-
description: "Human-readable label for the API key"
|
|
1183
|
-
}),
|
|
1184
|
-
key: Field10.text({
|
|
1185
|
-
label: "Key",
|
|
1186
|
-
required: true,
|
|
1187
|
-
description: "Hashed API key value"
|
|
1188
|
-
}),
|
|
1189
|
-
prefix: Field10.text({
|
|
1190
|
-
label: "Prefix",
|
|
1191
|
-
required: false,
|
|
1192
|
-
maxLength: 16,
|
|
1193
|
-
description: 'Visible prefix for identifying the key (e.g., "osk_")'
|
|
1194
|
-
}),
|
|
1195
|
-
user_id: Field10.text({
|
|
1196
|
-
label: "User ID",
|
|
1197
|
-
required: true,
|
|
1198
|
-
description: "Owner user of this API key"
|
|
1199
|
-
}),
|
|
1200
|
-
scopes: Field10.textarea({
|
|
1201
|
-
label: "Scopes",
|
|
1202
|
-
required: false,
|
|
1203
|
-
description: "JSON array of permission scopes"
|
|
1204
|
-
}),
|
|
1205
|
-
expires_at: Field10.datetime({
|
|
1206
|
-
label: "Expires At",
|
|
1207
|
-
required: false
|
|
1208
|
-
}),
|
|
1209
|
-
last_used_at: Field10.datetime({
|
|
1210
|
-
label: "Last Used At",
|
|
1211
|
-
required: false
|
|
1212
|
-
}),
|
|
1213
|
-
revoked: Field10.boolean({
|
|
1214
|
-
label: "Revoked",
|
|
1215
|
-
defaultValue: false
|
|
1216
|
-
})
|
|
1217
|
-
},
|
|
1218
|
-
indexes: [
|
|
1219
|
-
{ fields: ["key"], unique: true },
|
|
1220
|
-
{ fields: ["user_id"] },
|
|
1221
|
-
{ fields: ["prefix"] }
|
|
1222
|
-
],
|
|
1223
|
-
enable: {
|
|
1224
|
-
trackHistory: true,
|
|
1225
|
-
searchable: false,
|
|
1226
|
-
apiEnabled: true,
|
|
1227
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1228
|
-
trash: false,
|
|
1229
|
-
mru: false
|
|
1230
|
-
}
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
// src/objects/sys-two-factor.object.ts
|
|
1234
|
-
import { ObjectSchema as ObjectSchema11, Field as Field11 } from "@objectstack/spec/data";
|
|
1235
|
-
var SysTwoFactor = ObjectSchema11.create({
|
|
1236
|
-
namespace: "sys",
|
|
1237
|
-
name: "two_factor",
|
|
1238
|
-
label: "Two Factor",
|
|
1239
|
-
pluralLabel: "Two Factor Credentials",
|
|
1240
|
-
icon: "smartphone",
|
|
1241
|
-
isSystem: true,
|
|
1242
|
-
description: "Two-factor authentication credentials",
|
|
1243
|
-
titleFormat: "Two-factor for {user_id}",
|
|
1244
|
-
compactLayout: ["user_id", "created_at"],
|
|
1245
|
-
fields: {
|
|
1246
|
-
id: Field11.text({
|
|
1247
|
-
label: "Two Factor ID",
|
|
1248
|
-
required: true,
|
|
1249
|
-
readonly: true
|
|
1250
|
-
}),
|
|
1251
|
-
created_at: Field11.datetime({
|
|
1252
|
-
label: "Created At",
|
|
1253
|
-
defaultValue: "NOW()",
|
|
1254
|
-
readonly: true
|
|
1255
|
-
}),
|
|
1256
|
-
updated_at: Field11.datetime({
|
|
1257
|
-
label: "Updated At",
|
|
1258
|
-
defaultValue: "NOW()",
|
|
1259
|
-
readonly: true
|
|
1260
|
-
}),
|
|
1261
|
-
user_id: Field11.text({
|
|
1262
|
-
label: "User ID",
|
|
1263
|
-
required: true
|
|
1264
|
-
}),
|
|
1265
|
-
secret: Field11.text({
|
|
1266
|
-
label: "Secret",
|
|
1267
|
-
required: true,
|
|
1268
|
-
description: "TOTP secret key"
|
|
1269
|
-
}),
|
|
1270
|
-
backup_codes: Field11.textarea({
|
|
1271
|
-
label: "Backup Codes",
|
|
1272
|
-
required: false,
|
|
1273
|
-
description: "JSON-serialized backup recovery codes"
|
|
1274
|
-
})
|
|
1275
|
-
},
|
|
1276
|
-
indexes: [
|
|
1277
|
-
{ fields: ["user_id"], unique: true }
|
|
1278
|
-
],
|
|
1279
|
-
enable: {
|
|
1280
|
-
trackHistory: false,
|
|
1281
|
-
searchable: false,
|
|
1282
|
-
apiEnabled: true,
|
|
1283
|
-
apiMethods: ["get", "create", "update", "delete"],
|
|
1284
|
-
trash: false,
|
|
1285
|
-
mru: false
|
|
1062
|
+
/**
|
|
1063
|
+
* Returns the data engine wired into this auth manager. Used by route
|
|
1064
|
+
* handlers (e.g. bootstrap-status) that need to query identity tables
|
|
1065
|
+
* directly without going through better-auth.
|
|
1066
|
+
*/
|
|
1067
|
+
getDataEngine() {
|
|
1068
|
+
return this.config.dataEngine;
|
|
1286
1069
|
}
|
|
1287
|
-
}
|
|
1070
|
+
};
|
|
1288
1071
|
|
|
1289
|
-
// src/
|
|
1290
|
-
import {
|
|
1291
|
-
|
|
1072
|
+
// src/manifest.ts
|
|
1073
|
+
import {
|
|
1074
|
+
SysAccount,
|
|
1075
|
+
SysApiKey,
|
|
1076
|
+
SysDeviceCode,
|
|
1077
|
+
SysInvitation,
|
|
1078
|
+
SysMember,
|
|
1079
|
+
SysJwks,
|
|
1080
|
+
SysOauthAccessToken,
|
|
1081
|
+
SysOauthApplication,
|
|
1082
|
+
SysOauthConsent,
|
|
1083
|
+
SysOauthRefreshToken,
|
|
1084
|
+
SysOrganization,
|
|
1085
|
+
SysSession,
|
|
1086
|
+
SysTeam,
|
|
1087
|
+
SysTeamMember,
|
|
1088
|
+
SysTwoFactor,
|
|
1089
|
+
SysUser,
|
|
1090
|
+
SysUserPreference,
|
|
1091
|
+
SysVerification
|
|
1092
|
+
} from "@objectstack/platform-objects/identity";
|
|
1093
|
+
var AUTH_PLUGIN_ID = "com.objectstack.plugin-auth";
|
|
1094
|
+
var AUTH_PLUGIN_VERSION = "3.0.1";
|
|
1095
|
+
var authIdentityObjects = [
|
|
1096
|
+
SysUser,
|
|
1097
|
+
SysSession,
|
|
1098
|
+
SysAccount,
|
|
1099
|
+
SysVerification,
|
|
1100
|
+
SysOrganization,
|
|
1101
|
+
SysMember,
|
|
1102
|
+
SysInvitation,
|
|
1103
|
+
SysTeam,
|
|
1104
|
+
SysTeamMember,
|
|
1105
|
+
SysApiKey,
|
|
1106
|
+
SysTwoFactor,
|
|
1107
|
+
SysUserPreference,
|
|
1108
|
+
SysOauthApplication,
|
|
1109
|
+
SysOauthAccessToken,
|
|
1110
|
+
SysOauthRefreshToken,
|
|
1111
|
+
SysOauthConsent,
|
|
1112
|
+
SysJwks,
|
|
1113
|
+
SysDeviceCode
|
|
1114
|
+
];
|
|
1115
|
+
var authPluginManifestHeader = {
|
|
1116
|
+
id: AUTH_PLUGIN_ID,
|
|
1292
1117
|
namespace: "sys",
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
description: "
|
|
1299
|
-
|
|
1300
|
-
compactLayout: ["user_id", "key"],
|
|
1301
|
-
fields: {
|
|
1302
|
-
id: Field12.text({
|
|
1303
|
-
label: "Preference ID",
|
|
1304
|
-
required: true,
|
|
1305
|
-
readonly: true
|
|
1306
|
-
}),
|
|
1307
|
-
created_at: Field12.datetime({
|
|
1308
|
-
label: "Created At",
|
|
1309
|
-
defaultValue: "NOW()",
|
|
1310
|
-
readonly: true
|
|
1311
|
-
}),
|
|
1312
|
-
updated_at: Field12.datetime({
|
|
1313
|
-
label: "Updated At",
|
|
1314
|
-
defaultValue: "NOW()",
|
|
1315
|
-
readonly: true
|
|
1316
|
-
}),
|
|
1317
|
-
user_id: Field12.text({
|
|
1318
|
-
label: "User ID",
|
|
1319
|
-
required: true,
|
|
1320
|
-
maxLength: 255,
|
|
1321
|
-
description: "Owner user of this preference"
|
|
1322
|
-
}),
|
|
1323
|
-
key: Field12.text({
|
|
1324
|
-
label: "Key",
|
|
1325
|
-
required: true,
|
|
1326
|
-
maxLength: 255,
|
|
1327
|
-
description: "Preference key (e.g., theme, locale, plugin.ai.auto_save)"
|
|
1328
|
-
}),
|
|
1329
|
-
value: Field12.json({
|
|
1330
|
-
label: "Value",
|
|
1331
|
-
description: "Preference value (any JSON-serializable type)"
|
|
1332
|
-
})
|
|
1333
|
-
},
|
|
1334
|
-
indexes: [
|
|
1335
|
-
{ fields: ["user_id", "key"], unique: true },
|
|
1336
|
-
{ fields: ["user_id"], unique: false }
|
|
1337
|
-
],
|
|
1338
|
-
enable: {
|
|
1339
|
-
trackHistory: false,
|
|
1340
|
-
searchable: false,
|
|
1341
|
-
apiEnabled: true,
|
|
1342
|
-
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1343
|
-
trash: false,
|
|
1344
|
-
mru: false
|
|
1345
|
-
}
|
|
1346
|
-
});
|
|
1118
|
+
version: AUTH_PLUGIN_VERSION,
|
|
1119
|
+
type: "plugin",
|
|
1120
|
+
scope: "system",
|
|
1121
|
+
defaultDatasource: "cloud",
|
|
1122
|
+
name: "Authentication & Identity Plugin",
|
|
1123
|
+
description: "Core authentication objects for ObjectStack (User, Session, Account, Verification)"
|
|
1124
|
+
};
|
|
1347
1125
|
|
|
1348
1126
|
// src/auth-plugin.ts
|
|
1349
1127
|
var AuthPlugin = class {
|
|
@@ -1374,43 +1152,25 @@ var AuthPlugin = class {
|
|
|
1374
1152
|
});
|
|
1375
1153
|
ctx.registerService("auth", this.authManager);
|
|
1376
1154
|
ctx.getService("manifest").register({
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
]
|
|
1155
|
+
...authPluginManifestHeader,
|
|
1156
|
+
...this.options.manifestDatasource ? { defaultDatasource: this.options.manifestDatasource } : {},
|
|
1157
|
+
objects: authIdentityObjects,
|
|
1158
|
+
// The platform Setup App is a static metadata artifact (lives in
|
|
1159
|
+
// @objectstack/platform-objects/apps). plugin-auth is the natural
|
|
1160
|
+
// owner of its registration since it loads first among the trio
|
|
1161
|
+
// (auth + security + audit) that supplies the underlying objects.
|
|
1162
|
+
apps: [SETUP_APP],
|
|
1163
|
+
// List views for each Setup-nav object are defined on the schema
|
|
1164
|
+
// itself via the canonical `listViews` map (e.g.
|
|
1165
|
+
// sys_user.listViews.{all_users,unverified,two_factor}). Registering
|
|
1166
|
+
// top-level views here is the legacy pre-M10.30c pattern — it caused
|
|
1167
|
+
// duplicate "Users"/"Roles"/"Sessions" tabs to appear alongside the
|
|
1168
|
+
// schema-derived ones, sometimes referencing nonexistent fields
|
|
1169
|
+
// (e.g. legacy `users.view` had phone/status/active columns that do
|
|
1170
|
+
// not exist on sys_user). Schema-embedded listViews is the single
|
|
1171
|
+
// source of truth.
|
|
1172
|
+
dashboards: [SystemOverviewDashboard, SecurityOverviewDashboard]
|
|
1396
1173
|
});
|
|
1397
|
-
try {
|
|
1398
|
-
const setupNav = ctx.getService("setupNav");
|
|
1399
|
-
if (setupNav) {
|
|
1400
|
-
setupNav.contribute({
|
|
1401
|
-
areaId: "area_administration",
|
|
1402
|
-
items: [
|
|
1403
|
-
{ id: "nav_users", type: "object", label: "Users", objectName: "user", icon: "users", order: 10 },
|
|
1404
|
-
{ id: "nav_organizations", type: "object", label: "Organizations", objectName: "organization", icon: "building-2", order: 20 },
|
|
1405
|
-
{ id: "nav_teams", type: "object", label: "Teams", objectName: "team", icon: "users-round", order: 30 },
|
|
1406
|
-
{ id: "nav_api_keys", type: "object", label: "API Keys", objectName: "api_key", icon: "key", order: 40 },
|
|
1407
|
-
{ id: "nav_sessions", type: "object", label: "Sessions", objectName: "session", icon: "monitor", order: 50 }
|
|
1408
|
-
]
|
|
1409
|
-
});
|
|
1410
|
-
ctx.logger.info("Auth navigation items contributed to Setup App");
|
|
1411
|
-
}
|
|
1412
|
-
} catch {
|
|
1413
|
-
}
|
|
1414
1174
|
ctx.logger.info("Auth Plugin initialized successfully");
|
|
1415
1175
|
}
|
|
1416
1176
|
async start(ctx) {
|
|
@@ -1420,6 +1180,17 @@ var AuthPlugin = class {
|
|
|
1420
1180
|
}
|
|
1421
1181
|
if (this.options.registerRoutes) {
|
|
1422
1182
|
ctx.hook("kernel:ready", async () => {
|
|
1183
|
+
if (this.authManager) {
|
|
1184
|
+
try {
|
|
1185
|
+
const emailSvc = ctx.getService("email");
|
|
1186
|
+
if (emailSvc) {
|
|
1187
|
+
this.authManager.setEmailService(emailSvc);
|
|
1188
|
+
ctx.logger.info("Auth: email service wired (transactional mail enabled)");
|
|
1189
|
+
}
|
|
1190
|
+
} catch {
|
|
1191
|
+
ctx.logger.info("Auth: no email service registered \u2014 auth callbacks will log instead of sending");
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1423
1194
|
let httpServer = null;
|
|
1424
1195
|
try {
|
|
1425
1196
|
httpServer = ctx.getService("http-server");
|
|
@@ -1433,7 +1204,8 @@ var AuthPlugin = class {
|
|
|
1433
1204
|
const configuredUrl = this.options.baseUrl || "http://localhost:3000";
|
|
1434
1205
|
const configuredOrigin = new URL(configuredUrl).origin;
|
|
1435
1206
|
const actualUrl = `http://localhost:${actualPort}`;
|
|
1436
|
-
|
|
1207
|
+
const configuredIsLocalhost = configuredOrigin.startsWith("http://localhost");
|
|
1208
|
+
if (configuredIsLocalhost && configuredOrigin !== actualUrl) {
|
|
1437
1209
|
this.authManager.setRuntimeBaseUrl(actualUrl);
|
|
1438
1210
|
ctx.logger.info(
|
|
1439
1211
|
`Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
|
|
@@ -1491,23 +1263,26 @@ var AuthPlugin = class {
|
|
|
1491
1263
|
);
|
|
1492
1264
|
}
|
|
1493
1265
|
const rawApp = httpServer.getRawApp();
|
|
1494
|
-
rawApp.get(`${basePath}/config`,
|
|
1266
|
+
rawApp.get(`${basePath}/config`, (c) => {
|
|
1495
1267
|
try {
|
|
1496
1268
|
const config = this.authManager.getPublicConfig();
|
|
1497
|
-
return c.json({
|
|
1498
|
-
success: true,
|
|
1499
|
-
data: config
|
|
1500
|
-
});
|
|
1269
|
+
return c.json({ success: true, data: config });
|
|
1501
1270
|
} catch (error) {
|
|
1502
1271
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1272
|
+
return c.json({ success: false, error: { code: "auth_config_error", message: err.message } }, 500);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
rawApp.get(`${basePath}/bootstrap-status`, async (c) => {
|
|
1276
|
+
try {
|
|
1277
|
+
const dataEngine = this.authManager.getDataEngine();
|
|
1278
|
+
if (!dataEngine) {
|
|
1279
|
+
return c.json({ hasOwner: true });
|
|
1280
|
+
}
|
|
1281
|
+
const count = await dataEngine.count("sys_user", {});
|
|
1282
|
+
return c.json({ hasOwner: (count ?? 0) > 0 });
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
ctx.logger.warn("[AuthPlugin] bootstrap-status check failed; assuming bootstrapped", error);
|
|
1285
|
+
return c.json({ hasOwner: true });
|
|
1511
1286
|
}
|
|
1512
1287
|
});
|
|
1513
1288
|
rawApp.all(`${basePath}/*`, async (c) => {
|
|
@@ -1521,6 +1296,19 @@ var AuthPlugin = class {
|
|
|
1521
1296
|
ctx.logger.error("[AuthPlugin] better-auth returned server error", new Error(`HTTP ${response.status}: (unable to read body)`));
|
|
1522
1297
|
}
|
|
1523
1298
|
}
|
|
1299
|
+
try {
|
|
1300
|
+
const url = c.req.url;
|
|
1301
|
+
if (response.ok && /\/jwks(\?|$)/.test(url)) {
|
|
1302
|
+
const existing = response.headers.get("cache-control");
|
|
1303
|
+
if (!existing) {
|
|
1304
|
+
response.headers.set(
|
|
1305
|
+
"cache-control",
|
|
1306
|
+
"public, max-age=300, stale-while-revalidate=86400"
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
} catch {
|
|
1311
|
+
}
|
|
1524
1312
|
return response;
|
|
1525
1313
|
} catch (error) {
|
|
1526
1314
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -1537,14 +1325,56 @@ var AuthPlugin = class {
|
|
|
1537
1325
|
);
|
|
1538
1326
|
}
|
|
1539
1327
|
});
|
|
1328
|
+
if (this.options.plugins?.oidcProvider) {
|
|
1329
|
+
void this.registerOidcDiscoveryRoutes(rawApp, ctx).catch((error) => {
|
|
1330
|
+
ctx.logger.error("Failed to register OIDC discovery routes", error);
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1540
1333
|
ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
|
|
1541
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Mount the OIDC / OAuth 2.0 well-known discovery documents at the root
|
|
1337
|
+
* URL. Required by RFC 8414 §3 and OpenID Connect Discovery 1.0 §4 — the
|
|
1338
|
+
* documents must live at `/.well-known/{oauth-authorization-server,openid-configuration}`
|
|
1339
|
+
* relative to the issuer, not under the auth basePath.
|
|
1340
|
+
*/
|
|
1341
|
+
async registerOidcDiscoveryRoutes(rawApp, ctx) {
|
|
1342
|
+
const auth = await this.authManager.getAuthInstance();
|
|
1343
|
+
const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = await import("@better-auth/oauth-provider");
|
|
1344
|
+
const authServerHandler = oauthProviderAuthServerMetadata(auth);
|
|
1345
|
+
const openidConfigHandler = oauthProviderOpenIdConfigMetadata(auth);
|
|
1346
|
+
const DISCOVERY_CACHE = "public, max-age=300, stale-while-revalidate=86400";
|
|
1347
|
+
const withDiscoveryCache = async (handler, req) => {
|
|
1348
|
+
const resp = await handler(req);
|
|
1349
|
+
try {
|
|
1350
|
+
if (resp.ok && !resp.headers.get("cache-control")) {
|
|
1351
|
+
resp.headers.set("cache-control", DISCOVERY_CACHE);
|
|
1352
|
+
}
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
return resp;
|
|
1356
|
+
};
|
|
1357
|
+
rawApp.get("/.well-known/oauth-authorization-server", (c) => withDiscoveryCache(authServerHandler, c.req.raw));
|
|
1358
|
+
rawApp.get("/.well-known/openid-configuration", (c) => withDiscoveryCache(openidConfigHandler, c.req.raw));
|
|
1359
|
+
ctx.logger.info(
|
|
1360
|
+
"OIDC discovery endpoints mounted at /.well-known/{oauth-authorization-server,openid-configuration}"
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1542
1363
|
};
|
|
1543
1364
|
export {
|
|
1544
1365
|
AUTH_ACCOUNT_CONFIG,
|
|
1366
|
+
AUTH_ADMIN_SESSION_FIELDS,
|
|
1367
|
+
AUTH_ADMIN_USER_FIELDS,
|
|
1368
|
+
AUTH_DEVICE_CODE_SCHEMA,
|
|
1545
1369
|
AUTH_INVITATION_SCHEMA,
|
|
1370
|
+
AUTH_JWKS_SCHEMA,
|
|
1546
1371
|
AUTH_MEMBER_SCHEMA,
|
|
1547
1372
|
AUTH_MODEL_TO_PROTOCOL,
|
|
1373
|
+
AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
|
|
1374
|
+
AUTH_OAUTH_APPLICATION_SCHEMA,
|
|
1375
|
+
AUTH_OAUTH_CLIENT_SCHEMA,
|
|
1376
|
+
AUTH_OAUTH_CONSENT_SCHEMA,
|
|
1377
|
+
AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
|
|
1548
1378
|
AUTH_ORGANIZATION_SCHEMA,
|
|
1549
1379
|
AUTH_ORG_SESSION_FIELDS,
|
|
1550
1380
|
AUTH_SESSION_CONFIG,
|
|
@@ -1554,24 +1384,13 @@ export {
|
|
|
1554
1384
|
AUTH_TWO_FACTOR_USER_FIELDS,
|
|
1555
1385
|
AUTH_USER_CONFIG,
|
|
1556
1386
|
AUTH_VERIFICATION_CONFIG,
|
|
1557
|
-
SysAccount as AuthAccount,
|
|
1558
1387
|
AuthManager,
|
|
1559
1388
|
AuthPlugin,
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
SysInvitation,
|
|
1566
|
-
SysMember,
|
|
1567
|
-
SysOrganization,
|
|
1568
|
-
SysSession,
|
|
1569
|
-
SysTeam,
|
|
1570
|
-
SysTeamMember,
|
|
1571
|
-
SysTwoFactor,
|
|
1572
|
-
SysUser,
|
|
1573
|
-
SysUserPreference,
|
|
1574
|
-
SysVerification,
|
|
1389
|
+
buildAdminPluginSchema,
|
|
1390
|
+
buildDeviceAuthorizationPluginSchema,
|
|
1391
|
+
buildJwtPluginSchema,
|
|
1392
|
+
buildOauthProviderPluginSchema,
|
|
1393
|
+
buildOidcProviderPluginSchema,
|
|
1575
1394
|
buildOrganizationPluginSchema,
|
|
1576
1395
|
buildTwoFactorPluginSchema,
|
|
1577
1396
|
createObjectQLAdapter,
|