@objectstack/plugin-auth 4.0.4 → 4.0.5
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 +332 -19942
- package/dist/index.d.ts +332 -19942
- package/dist/index.js +351 -882
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +355 -862
- 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,15 @@
|
|
|
1
|
-
// src/auth-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/auth-plugin.ts
|
|
2
|
+
import {
|
|
3
|
+
SETUP_APP,
|
|
4
|
+
SystemOverviewDashboard,
|
|
5
|
+
SecurityOverviewDashboard,
|
|
6
|
+
UsersView,
|
|
7
|
+
OrganizationsView,
|
|
8
|
+
RolesView,
|
|
9
|
+
SessionsView,
|
|
10
|
+
AuditLogsView,
|
|
11
|
+
PackageInstallationsView
|
|
12
|
+
} from "@objectstack/platform-objects/apps";
|
|
6
13
|
|
|
7
14
|
// src/objectql-adapter.ts
|
|
8
15
|
import { createAdapterFactory } from "better-auth/adapters";
|
|
@@ -276,6 +283,81 @@ var AUTH_TWO_FACTOR_SCHEMA = {
|
|
|
276
283
|
var AUTH_TWO_FACTOR_USER_FIELDS = {
|
|
277
284
|
twoFactorEnabled: "two_factor_enabled"
|
|
278
285
|
};
|
|
286
|
+
var AUTH_OAUTH_CLIENT_SCHEMA = {
|
|
287
|
+
modelName: SystemObjectName2.OAUTH_APPLICATION,
|
|
288
|
+
// 'sys_oauth_application'
|
|
289
|
+
fields: {
|
|
290
|
+
clientId: "client_id",
|
|
291
|
+
clientSecret: "client_secret",
|
|
292
|
+
skipConsent: "skip_consent",
|
|
293
|
+
enableEndSession: "enable_end_session",
|
|
294
|
+
subjectType: "subject_type",
|
|
295
|
+
userId: "user_id",
|
|
296
|
+
createdAt: "created_at",
|
|
297
|
+
updatedAt: "updated_at",
|
|
298
|
+
redirectUris: "redirect_uris",
|
|
299
|
+
postLogoutRedirectUris: "post_logout_redirect_uris",
|
|
300
|
+
tokenEndpointAuthMethod: "token_endpoint_auth_method",
|
|
301
|
+
grantTypes: "grant_types",
|
|
302
|
+
responseTypes: "response_types",
|
|
303
|
+
requirePKCE: "require_pkce",
|
|
304
|
+
softwareId: "software_id",
|
|
305
|
+
softwareVersion: "software_version",
|
|
306
|
+
softwareStatement: "software_statement",
|
|
307
|
+
referenceId: "reference_id"
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var AUTH_OAUTH_APPLICATION_SCHEMA = AUTH_OAUTH_CLIENT_SCHEMA;
|
|
311
|
+
var AUTH_OAUTH_ACCESS_TOKEN_SCHEMA = {
|
|
312
|
+
modelName: SystemObjectName2.OAUTH_ACCESS_TOKEN,
|
|
313
|
+
// 'sys_oauth_access_token'
|
|
314
|
+
fields: {
|
|
315
|
+
clientId: "client_id",
|
|
316
|
+
sessionId: "session_id",
|
|
317
|
+
userId: "user_id",
|
|
318
|
+
referenceId: "reference_id",
|
|
319
|
+
refreshId: "refresh_id",
|
|
320
|
+
expiresAt: "expires_at",
|
|
321
|
+
createdAt: "created_at"
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var AUTH_OAUTH_REFRESH_TOKEN_SCHEMA = {
|
|
325
|
+
modelName: SystemObjectName2.OAUTH_REFRESH_TOKEN,
|
|
326
|
+
// 'sys_oauth_refresh_token'
|
|
327
|
+
fields: {
|
|
328
|
+
clientId: "client_id",
|
|
329
|
+
sessionId: "session_id",
|
|
330
|
+
userId: "user_id",
|
|
331
|
+
referenceId: "reference_id",
|
|
332
|
+
expiresAt: "expires_at",
|
|
333
|
+
createdAt: "created_at",
|
|
334
|
+
authTime: "auth_time"
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var AUTH_OAUTH_CONSENT_SCHEMA = {
|
|
338
|
+
modelName: SystemObjectName2.OAUTH_CONSENT,
|
|
339
|
+
// 'sys_oauth_consent'
|
|
340
|
+
fields: {
|
|
341
|
+
clientId: "client_id",
|
|
342
|
+
userId: "user_id",
|
|
343
|
+
referenceId: "reference_id",
|
|
344
|
+
createdAt: "created_at",
|
|
345
|
+
updatedAt: "updated_at"
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var AUTH_DEVICE_CODE_SCHEMA = {
|
|
349
|
+
modelName: SystemObjectName2.DEVICE_CODE,
|
|
350
|
+
// 'sys_device_code'
|
|
351
|
+
fields: {
|
|
352
|
+
deviceCode: "device_code",
|
|
353
|
+
userCode: "user_code",
|
|
354
|
+
userId: "user_id",
|
|
355
|
+
expiresAt: "expires_at",
|
|
356
|
+
lastPolledAt: "last_polled_at",
|
|
357
|
+
pollingInterval: "polling_interval",
|
|
358
|
+
clientId: "client_id"
|
|
359
|
+
}
|
|
360
|
+
};
|
|
279
361
|
function buildTwoFactorPluginSchema() {
|
|
280
362
|
return {
|
|
281
363
|
twoFactor: AUTH_TWO_FACTOR_SCHEMA,
|
|
@@ -296,6 +378,35 @@ function buildOrganizationPluginSchema() {
|
|
|
296
378
|
}
|
|
297
379
|
};
|
|
298
380
|
}
|
|
381
|
+
var AUTH_JWKS_SCHEMA = {
|
|
382
|
+
modelName: SystemObjectName2.JWKS,
|
|
383
|
+
// 'sys_jwks'
|
|
384
|
+
fields: {
|
|
385
|
+
publicKey: "public_key",
|
|
386
|
+
privateKey: "private_key",
|
|
387
|
+
createdAt: "created_at",
|
|
388
|
+
expiresAt: "expires_at"
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
function buildJwtPluginSchema() {
|
|
392
|
+
return {
|
|
393
|
+
jwks: AUTH_JWKS_SCHEMA
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function buildOauthProviderPluginSchema() {
|
|
397
|
+
return {
|
|
398
|
+
oauthClient: AUTH_OAUTH_CLIENT_SCHEMA,
|
|
399
|
+
oauthAccessToken: AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
|
|
400
|
+
oauthRefreshToken: AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
|
|
401
|
+
oauthConsent: AUTH_OAUTH_CONSENT_SCHEMA
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
var buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
|
|
405
|
+
function buildDeviceAuthorizationPluginSchema() {
|
|
406
|
+
return {
|
|
407
|
+
deviceCode: AUTH_DEVICE_CODE_SCHEMA
|
|
408
|
+
};
|
|
409
|
+
}
|
|
299
410
|
|
|
300
411
|
// src/auth-manager.ts
|
|
301
412
|
var AuthManager = class {
|
|
@@ -309,16 +420,18 @@ var AuthManager = class {
|
|
|
309
420
|
/**
|
|
310
421
|
* Get or create the better-auth instance (lazy initialization)
|
|
311
422
|
*/
|
|
312
|
-
getOrCreateAuth() {
|
|
423
|
+
async getOrCreateAuth() {
|
|
313
424
|
if (!this.auth) {
|
|
314
|
-
this.auth = this.createAuthInstance();
|
|
425
|
+
this.auth = await this.createAuthInstance();
|
|
315
426
|
}
|
|
316
427
|
return this.auth;
|
|
317
428
|
}
|
|
318
429
|
/**
|
|
319
430
|
* Create a better-auth instance from configuration
|
|
320
431
|
*/
|
|
321
|
-
createAuthInstance() {
|
|
432
|
+
async createAuthInstance() {
|
|
433
|
+
const { betterAuth } = await import("better-auth");
|
|
434
|
+
const plugins = await this.buildPluginList();
|
|
322
435
|
const betterAuthConfig = {
|
|
323
436
|
// Base configuration
|
|
324
437
|
secret: this.config.secret || this.generateSecret(),
|
|
@@ -370,7 +483,7 @@ var AuthManager = class {
|
|
|
370
483
|
// 1 day default
|
|
371
484
|
},
|
|
372
485
|
// better-auth plugins — registered based on AuthPluginConfig flags
|
|
373
|
-
plugins
|
|
486
|
+
plugins,
|
|
374
487
|
// Trusted origins for CSRF protection (supports wildcards like "https://*.example.com")
|
|
375
488
|
// Auto-includes origins from CORS_ORIGIN env var so CORS and CSRF stay in sync.
|
|
376
489
|
...(() => {
|
|
@@ -405,20 +518,51 @@ var AuthManager = class {
|
|
|
405
518
|
* a `schema` option containing the appropriate snake_case field mappings,
|
|
406
519
|
* so that `createAdapterFactory` transforms them automatically.
|
|
407
520
|
*/
|
|
408
|
-
buildPluginList() {
|
|
409
|
-
const pluginConfig = this.config.plugins;
|
|
521
|
+
async buildPluginList() {
|
|
522
|
+
const pluginConfig = this.config.plugins ?? {};
|
|
410
523
|
const plugins = [];
|
|
411
|
-
|
|
524
|
+
const enabled = {
|
|
525
|
+
organization: pluginConfig.organization ?? true,
|
|
526
|
+
twoFactor: pluginConfig.twoFactor ?? false,
|
|
527
|
+
passkeys: pluginConfig.passkeys ?? false,
|
|
528
|
+
magicLink: pluginConfig.magicLink ?? false,
|
|
529
|
+
oidcProvider: pluginConfig.oidcProvider ?? false,
|
|
530
|
+
deviceAuthorization: pluginConfig.deviceAuthorization ?? false
|
|
531
|
+
};
|
|
532
|
+
const { bearer } = await import("better-auth/plugins/bearer");
|
|
533
|
+
plugins.push(bearer());
|
|
534
|
+
if (enabled.organization) {
|
|
535
|
+
const { organization } = await import("better-auth/plugins/organization");
|
|
412
536
|
plugins.push(organization({
|
|
413
|
-
schema: buildOrganizationPluginSchema()
|
|
537
|
+
schema: buildOrganizationPluginSchema(),
|
|
538
|
+
// Enable the team sub-feature so the framework's `sys_team` /
|
|
539
|
+
// `sys_team_member` tables (already declared in platform-objects)
|
|
540
|
+
// are actually wired up to better-auth's CRUD endpoints
|
|
541
|
+
// (`/organization/{create,update,remove,list}-team[s]` and
|
|
542
|
+
// `/organization/{add,remove,list}-team-member[s]`). The Account
|
|
543
|
+
// portal exposes a Teams page; without this flag those endpoints
|
|
544
|
+
// 404 and the section silently breaks.
|
|
545
|
+
teams: { enabled: true },
|
|
546
|
+
// No mailer is wired in framework yet — log the accept URL so
|
|
547
|
+
// operators / UI can fall back to copy-paste flows. Replace this
|
|
548
|
+
// with a real mail integration when available.
|
|
549
|
+
sendInvitationEmail: async ({ email, invitation, organization: org, inviter }) => {
|
|
550
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
551
|
+
const acceptUrl = `${baseUrl}/accept-invitation/${invitation.id}`;
|
|
552
|
+
console.warn(
|
|
553
|
+
`[AuthManager] Invitation email not configured. To: ${email} (org: ${org?.name ?? invitation.organizationId}, role: ${invitation.role}, inviter: ${inviter?.user?.email ?? "unknown"}) URL: ${acceptUrl}`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
414
556
|
}));
|
|
415
557
|
}
|
|
416
|
-
if (
|
|
558
|
+
if (enabled.twoFactor) {
|
|
559
|
+
const { twoFactor } = await import("better-auth/plugins/two-factor");
|
|
417
560
|
plugins.push(twoFactor({
|
|
418
561
|
schema: buildTwoFactorPluginSchema()
|
|
419
562
|
}));
|
|
420
563
|
}
|
|
421
|
-
if (
|
|
564
|
+
if (enabled.magicLink) {
|
|
565
|
+
const { magicLink } = await import("better-auth/plugins/magic-link");
|
|
422
566
|
plugins.push(magicLink({
|
|
423
567
|
sendMagicLink: async ({ email, url }) => {
|
|
424
568
|
console.warn(
|
|
@@ -427,6 +571,43 @@ var AuthManager = class {
|
|
|
427
571
|
}
|
|
428
572
|
}));
|
|
429
573
|
}
|
|
574
|
+
if (this.config.oidcProviders?.length) {
|
|
575
|
+
const { genericOAuth } = await import("better-auth/plugins/generic-oauth");
|
|
576
|
+
plugins.push(genericOAuth({
|
|
577
|
+
config: this.config.oidcProviders.map((p) => ({
|
|
578
|
+
providerId: p.providerId,
|
|
579
|
+
...p.discoveryUrl ? { discoveryUrl: p.discoveryUrl } : {},
|
|
580
|
+
...p.issuer ? { issuer: p.issuer } : {},
|
|
581
|
+
...p.authorizationUrl ? { authorizationUrl: p.authorizationUrl } : {},
|
|
582
|
+
...p.tokenUrl ? { tokenUrl: p.tokenUrl } : {},
|
|
583
|
+
...p.userInfoUrl ? { userInfoUrl: p.userInfoUrl } : {},
|
|
584
|
+
clientId: p.clientId,
|
|
585
|
+
clientSecret: p.clientSecret,
|
|
586
|
+
...p.scopes ? { scopes: p.scopes } : {},
|
|
587
|
+
...p.pkce != null ? { pkce: p.pkce } : {}
|
|
588
|
+
}))
|
|
589
|
+
}));
|
|
590
|
+
}
|
|
591
|
+
if (enabled.oidcProvider) {
|
|
592
|
+
const { jwt } = await import("better-auth/plugins");
|
|
593
|
+
plugins.push(jwt({ schema: buildJwtPluginSchema() }));
|
|
594
|
+
const { oauthProvider } = await import("@better-auth/oauth-provider");
|
|
595
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
596
|
+
plugins.push(oauthProvider({
|
|
597
|
+
// Account SPA renders both pages — see apps/account.
|
|
598
|
+
loginPage: `${baseUrl}/_account/login`,
|
|
599
|
+
consentPage: `${baseUrl}/_account/oauth/consent`,
|
|
600
|
+
schema: buildOauthProviderPluginSchema()
|
|
601
|
+
}));
|
|
602
|
+
}
|
|
603
|
+
if (enabled.deviceAuthorization) {
|
|
604
|
+
const { deviceAuthorization } = await import("better-auth/plugins/device-authorization");
|
|
605
|
+
const baseUrl = (this.config.baseUrl ?? "").replace(/\/$/, "");
|
|
606
|
+
plugins.push(deviceAuthorization({
|
|
607
|
+
verificationUri: `${baseUrl}/_account/auth/device`,
|
|
608
|
+
schema: buildDeviceAuthorizationPluginSchema()
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
430
611
|
return plugins;
|
|
431
612
|
}
|
|
432
613
|
/**
|
|
@@ -502,7 +683,7 @@ var AuthManager = class {
|
|
|
502
683
|
* @returns Web standard Response object
|
|
503
684
|
*/
|
|
504
685
|
async handleRequest(request) {
|
|
505
|
-
const auth = this.getOrCreateAuth();
|
|
686
|
+
const auth = await this.getOrCreateAuth();
|
|
506
687
|
const response = await auth.handler(request);
|
|
507
688
|
if (response.status >= 500) {
|
|
508
689
|
try {
|
|
@@ -518,18 +699,19 @@ var AuthManager = class {
|
|
|
518
699
|
* Get the better-auth API for programmatic access
|
|
519
700
|
* Use this for server-side operations (e.g., creating users, checking sessions)
|
|
520
701
|
*/
|
|
521
|
-
|
|
522
|
-
|
|
702
|
+
async getApi() {
|
|
703
|
+
const auth = await this.getOrCreateAuth();
|
|
704
|
+
return auth.api;
|
|
523
705
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
706
|
+
// ---------------------------------------------------------------------------
|
|
707
|
+
// Device Flow (CLI browser-based login)
|
|
708
|
+
//
|
|
709
|
+
// The device authorization flow (RFC 8628) is now handled entirely by
|
|
710
|
+
// better-auth's `device-authorization` plugin. Endpoints are exposed at
|
|
711
|
+
// `${basePath}/device/{code,token,approve,deny}` and persisted in
|
|
712
|
+
// `sys_device_code`. Enable via `plugins.deviceAuthorization: true` in
|
|
713
|
+
// AuthPluginConfig.
|
|
714
|
+
// ---------------------------------------------------------------------------
|
|
533
715
|
getPublicConfig() {
|
|
534
716
|
const socialProviders = [];
|
|
535
717
|
if (this.config.socialProviders) {
|
|
@@ -549,11 +731,22 @@ var AuthManager = class {
|
|
|
549
731
|
socialProviders.push({
|
|
550
732
|
id,
|
|
551
733
|
name: nameMap[id] || id.charAt(0).toUpperCase() + id.slice(1),
|
|
552
|
-
enabled: true
|
|
734
|
+
enabled: true,
|
|
735
|
+
type: "social"
|
|
553
736
|
});
|
|
554
737
|
}
|
|
555
738
|
}
|
|
556
739
|
}
|
|
740
|
+
if (this.config.oidcProviders?.length) {
|
|
741
|
+
for (const p of this.config.oidcProviders) {
|
|
742
|
+
socialProviders.push({
|
|
743
|
+
id: p.providerId,
|
|
744
|
+
name: p.name ?? p.providerId.charAt(0).toUpperCase() + p.providerId.slice(1),
|
|
745
|
+
enabled: true,
|
|
746
|
+
type: "oidc"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
}
|
|
557
750
|
const emailPasswordConfig = this.config.emailAndPassword ?? {};
|
|
558
751
|
const emailPassword = {
|
|
559
752
|
enabled: emailPasswordConfig.enabled !== false,
|
|
@@ -566,7 +759,9 @@ var AuthManager = class {
|
|
|
566
759
|
twoFactor: pluginConfig.twoFactor ?? false,
|
|
567
760
|
passkeys: pluginConfig.passkeys ?? false,
|
|
568
761
|
magicLink: pluginConfig.magicLink ?? false,
|
|
569
|
-
organization: pluginConfig.organization ??
|
|
762
|
+
organization: pluginConfig.organization ?? true,
|
|
763
|
+
oidcProvider: pluginConfig.oidcProvider ?? false,
|
|
764
|
+
deviceAuthorization: pluginConfig.deviceAuthorization ?? false
|
|
570
765
|
};
|
|
571
766
|
return {
|
|
572
767
|
emailPassword,
|
|
@@ -574,776 +769,69 @@ var AuthManager = class {
|
|
|
574
769
|
features
|
|
575
770
|
};
|
|
576
771
|
}
|
|
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
|
|
772
|
+
/**
|
|
773
|
+
* Returns the data engine wired into this auth manager. Used by route
|
|
774
|
+
* handlers (e.g. bootstrap-status) that need to query identity tables
|
|
775
|
+
* directly without going through better-auth.
|
|
776
|
+
*/
|
|
777
|
+
getDataEngine() {
|
|
778
|
+
return this.config.dataEngine;
|
|
1286
779
|
}
|
|
1287
|
-
}
|
|
780
|
+
};
|
|
1288
781
|
|
|
1289
|
-
// src/
|
|
1290
|
-
import {
|
|
1291
|
-
|
|
782
|
+
// src/manifest.ts
|
|
783
|
+
import {
|
|
784
|
+
SysAccount,
|
|
785
|
+
SysApiKey,
|
|
786
|
+
SysDeviceCode,
|
|
787
|
+
SysInvitation,
|
|
788
|
+
SysMember,
|
|
789
|
+
SysJwks,
|
|
790
|
+
SysOauthAccessToken,
|
|
791
|
+
SysOauthApplication,
|
|
792
|
+
SysOauthConsent,
|
|
793
|
+
SysOauthRefreshToken,
|
|
794
|
+
SysOrganization,
|
|
795
|
+
SysSession,
|
|
796
|
+
SysTeam,
|
|
797
|
+
SysTeamMember,
|
|
798
|
+
SysTwoFactor,
|
|
799
|
+
SysUser,
|
|
800
|
+
SysUserPreference,
|
|
801
|
+
SysVerification
|
|
802
|
+
} from "@objectstack/platform-objects/identity";
|
|
803
|
+
var AUTH_PLUGIN_ID = "com.objectstack.plugin-auth";
|
|
804
|
+
var AUTH_PLUGIN_VERSION = "3.0.1";
|
|
805
|
+
var authIdentityObjects = [
|
|
806
|
+
SysUser,
|
|
807
|
+
SysSession,
|
|
808
|
+
SysAccount,
|
|
809
|
+
SysVerification,
|
|
810
|
+
SysOrganization,
|
|
811
|
+
SysMember,
|
|
812
|
+
SysInvitation,
|
|
813
|
+
SysTeam,
|
|
814
|
+
SysTeamMember,
|
|
815
|
+
SysApiKey,
|
|
816
|
+
SysTwoFactor,
|
|
817
|
+
SysUserPreference,
|
|
818
|
+
SysOauthApplication,
|
|
819
|
+
SysOauthAccessToken,
|
|
820
|
+
SysOauthRefreshToken,
|
|
821
|
+
SysOauthConsent,
|
|
822
|
+
SysJwks,
|
|
823
|
+
SysDeviceCode
|
|
824
|
+
];
|
|
825
|
+
var authPluginManifestHeader = {
|
|
826
|
+
id: AUTH_PLUGIN_ID,
|
|
1292
827
|
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
|
-
});
|
|
828
|
+
version: AUTH_PLUGIN_VERSION,
|
|
829
|
+
type: "plugin",
|
|
830
|
+
scope: "system",
|
|
831
|
+
defaultDatasource: "cloud",
|
|
832
|
+
name: "Authentication & Identity Plugin",
|
|
833
|
+
description: "Core authentication objects for ObjectStack (User, Session, Account, Verification)"
|
|
834
|
+
};
|
|
1347
835
|
|
|
1348
836
|
// src/auth-plugin.ts
|
|
1349
837
|
var AuthPlugin = class {
|
|
@@ -1374,43 +862,27 @@ var AuthPlugin = class {
|
|
|
1374
862
|
});
|
|
1375
863
|
ctx.registerService("auth", this.authManager);
|
|
1376
864
|
ctx.getService("manifest").register({
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
objects
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
]
|
|
865
|
+
...authPluginManifestHeader,
|
|
866
|
+
objects: authIdentityObjects,
|
|
867
|
+
// The platform Setup App is a static metadata artifact (lives in
|
|
868
|
+
// @objectstack/platform-objects/apps). plugin-auth is the natural
|
|
869
|
+
// owner of its registration since it loads first among the trio
|
|
870
|
+
// (auth + security + audit) that supplies the underlying objects.
|
|
871
|
+
apps: [SETUP_APP],
|
|
872
|
+
// Curated list views and dashboards consumed by the Setup App's
|
|
873
|
+
// navigation entries. The manifest service does NOT auto-discover
|
|
874
|
+
// these from the app definition — they must be registered as
|
|
875
|
+
// explicit top-level arrays per ObjectStackDefinitionSchema.
|
|
876
|
+
views: [
|
|
877
|
+
UsersView,
|
|
878
|
+
OrganizationsView,
|
|
879
|
+
RolesView,
|
|
880
|
+
SessionsView,
|
|
881
|
+
AuditLogsView,
|
|
882
|
+
PackageInstallationsView
|
|
883
|
+
],
|
|
884
|
+
dashboards: [SystemOverviewDashboard, SecurityOverviewDashboard]
|
|
1396
885
|
});
|
|
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
886
|
ctx.logger.info("Auth Plugin initialized successfully");
|
|
1415
887
|
}
|
|
1416
888
|
async start(ctx) {
|
|
@@ -1433,7 +905,8 @@ var AuthPlugin = class {
|
|
|
1433
905
|
const configuredUrl = this.options.baseUrl || "http://localhost:3000";
|
|
1434
906
|
const configuredOrigin = new URL(configuredUrl).origin;
|
|
1435
907
|
const actualUrl = `http://localhost:${actualPort}`;
|
|
1436
|
-
|
|
908
|
+
const configuredIsLocalhost = configuredOrigin.startsWith("http://localhost");
|
|
909
|
+
if (configuredIsLocalhost && configuredOrigin !== actualUrl) {
|
|
1437
910
|
this.authManager.setRuntimeBaseUrl(actualUrl);
|
|
1438
911
|
ctx.logger.info(
|
|
1439
912
|
`Auth baseUrl auto-updated to ${actualUrl} (configured: ${configuredUrl})`
|
|
@@ -1491,23 +964,26 @@ var AuthPlugin = class {
|
|
|
1491
964
|
);
|
|
1492
965
|
}
|
|
1493
966
|
const rawApp = httpServer.getRawApp();
|
|
1494
|
-
rawApp.get(`${basePath}/config`,
|
|
967
|
+
rawApp.get(`${basePath}/config`, (c) => {
|
|
1495
968
|
try {
|
|
1496
969
|
const config = this.authManager.getPublicConfig();
|
|
1497
|
-
return c.json({
|
|
1498
|
-
success: true,
|
|
1499
|
-
data: config
|
|
1500
|
-
});
|
|
970
|
+
return c.json({ success: true, data: config });
|
|
1501
971
|
} catch (error) {
|
|
1502
972
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
973
|
+
return c.json({ success: false, error: { code: "auth_config_error", message: err.message } }, 500);
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
rawApp.get(`${basePath}/bootstrap-status`, async (c) => {
|
|
977
|
+
try {
|
|
978
|
+
const dataEngine = this.authManager.getDataEngine();
|
|
979
|
+
if (!dataEngine) {
|
|
980
|
+
return c.json({ hasOwner: true });
|
|
981
|
+
}
|
|
982
|
+
const count = await dataEngine.count("sys_user", {});
|
|
983
|
+
return c.json({ hasOwner: (count ?? 0) > 0 });
|
|
984
|
+
} catch (error) {
|
|
985
|
+
ctx.logger.warn("[AuthPlugin] bootstrap-status check failed; assuming bootstrapped", error);
|
|
986
|
+
return c.json({ hasOwner: true });
|
|
1511
987
|
}
|
|
1512
988
|
});
|
|
1513
989
|
rawApp.all(`${basePath}/*`, async (c) => {
|
|
@@ -1537,14 +1013,43 @@ var AuthPlugin = class {
|
|
|
1537
1013
|
);
|
|
1538
1014
|
}
|
|
1539
1015
|
});
|
|
1016
|
+
if (this.options.plugins?.oidcProvider) {
|
|
1017
|
+
void this.registerOidcDiscoveryRoutes(rawApp, ctx).catch((error) => {
|
|
1018
|
+
ctx.logger.error("Failed to register OIDC discovery routes", error);
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1540
1021
|
ctx.logger.info(`Auth routes registered: All requests under ${basePath}/* forwarded to better-auth`);
|
|
1541
1022
|
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Mount the OIDC / OAuth 2.0 well-known discovery documents at the root
|
|
1025
|
+
* URL. Required by RFC 8414 §3 and OpenID Connect Discovery 1.0 §4 — the
|
|
1026
|
+
* documents must live at `/.well-known/{oauth-authorization-server,openid-configuration}`
|
|
1027
|
+
* relative to the issuer, not under the auth basePath.
|
|
1028
|
+
*/
|
|
1029
|
+
async registerOidcDiscoveryRoutes(rawApp, ctx) {
|
|
1030
|
+
const auth = await this.authManager.getAuthInstance();
|
|
1031
|
+
const { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } = await import("@better-auth/oauth-provider");
|
|
1032
|
+
const authServerHandler = oauthProviderAuthServerMetadata(auth);
|
|
1033
|
+
const openidConfigHandler = oauthProviderOpenIdConfigMetadata(auth);
|
|
1034
|
+
rawApp.get("/.well-known/oauth-authorization-server", (c) => authServerHandler(c.req.raw));
|
|
1035
|
+
rawApp.get("/.well-known/openid-configuration", (c) => openidConfigHandler(c.req.raw));
|
|
1036
|
+
ctx.logger.info(
|
|
1037
|
+
"OIDC discovery endpoints mounted at /.well-known/{oauth-authorization-server,openid-configuration}"
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1542
1040
|
};
|
|
1543
1041
|
export {
|
|
1544
1042
|
AUTH_ACCOUNT_CONFIG,
|
|
1043
|
+
AUTH_DEVICE_CODE_SCHEMA,
|
|
1545
1044
|
AUTH_INVITATION_SCHEMA,
|
|
1045
|
+
AUTH_JWKS_SCHEMA,
|
|
1546
1046
|
AUTH_MEMBER_SCHEMA,
|
|
1547
1047
|
AUTH_MODEL_TO_PROTOCOL,
|
|
1048
|
+
AUTH_OAUTH_ACCESS_TOKEN_SCHEMA,
|
|
1049
|
+
AUTH_OAUTH_APPLICATION_SCHEMA,
|
|
1050
|
+
AUTH_OAUTH_CLIENT_SCHEMA,
|
|
1051
|
+
AUTH_OAUTH_CONSENT_SCHEMA,
|
|
1052
|
+
AUTH_OAUTH_REFRESH_TOKEN_SCHEMA,
|
|
1548
1053
|
AUTH_ORGANIZATION_SCHEMA,
|
|
1549
1054
|
AUTH_ORG_SESSION_FIELDS,
|
|
1550
1055
|
AUTH_SESSION_CONFIG,
|
|
@@ -1554,24 +1059,12 @@ export {
|
|
|
1554
1059
|
AUTH_TWO_FACTOR_USER_FIELDS,
|
|
1555
1060
|
AUTH_USER_CONFIG,
|
|
1556
1061
|
AUTH_VERIFICATION_CONFIG,
|
|
1557
|
-
SysAccount as AuthAccount,
|
|
1558
1062
|
AuthManager,
|
|
1559
1063
|
AuthPlugin,
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
SysApiKey,
|
|
1565
|
-
SysInvitation,
|
|
1566
|
-
SysMember,
|
|
1567
|
-
SysOrganization,
|
|
1568
|
-
SysSession,
|
|
1569
|
-
SysTeam,
|
|
1570
|
-
SysTeamMember,
|
|
1571
|
-
SysTwoFactor,
|
|
1572
|
-
SysUser,
|
|
1573
|
-
SysUserPreference,
|
|
1574
|
-
SysVerification,
|
|
1064
|
+
buildDeviceAuthorizationPluginSchema,
|
|
1065
|
+
buildJwtPluginSchema,
|
|
1066
|
+
buildOauthProviderPluginSchema,
|
|
1067
|
+
buildOidcProviderPluginSchema,
|
|
1575
1068
|
buildOrganizationPluginSchema,
|
|
1576
1069
|
buildTwoFactorPluginSchema,
|
|
1577
1070
|
createObjectQLAdapter,
|