@objectstack/plugin-auth 10.3.0 → 11.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/LICENSE +202 -93
- package/dist/index.d.mts +476 -15
- package/dist/index.d.ts +476 -15
- package/dist/index.js +1768 -108
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1762 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
package/dist/index.d.mts
CHANGED
|
@@ -169,6 +169,34 @@ interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
|
169
169
|
* Required for database operations using ObjectQL instead of third-party ORMs
|
|
170
170
|
*/
|
|
171
171
|
dataEngine?: IDataEngine;
|
|
172
|
+
/**
|
|
173
|
+
* Optional callback invoked AFTER an organization is created via better-auth's
|
|
174
|
+
* `createOrganization` (the org-plugin `afterCreateOrganization` hook). Lets a
|
|
175
|
+
* host stack run org-creation side effects that core `databaseHooks` can't —
|
|
176
|
+
* better-auth's org-plugin models (`organization`/`member`) do NOT fire those.
|
|
177
|
+
* The cloud control plane uses it to provision an org's born-with production
|
|
178
|
+
* environment. Failure-isolated: org creation is never rolled back.
|
|
179
|
+
*/
|
|
180
|
+
onOrganizationCreated?: (data: {
|
|
181
|
+
organizationId: string;
|
|
182
|
+
userId?: string;
|
|
183
|
+
name?: string;
|
|
184
|
+
slug?: string;
|
|
185
|
+
}) => void | Promise<void>;
|
|
186
|
+
/**
|
|
187
|
+
* D5.1 — OIDC OP authorization gate (cloud-as-IdP app-assignment).
|
|
188
|
+
* When set, it is called for an AUTHENTICATED subject on
|
|
189
|
+
* `/oauth2/authorize` before an authorization code is issued, with the
|
|
190
|
+
* subject + the requesting `clientId`. Return `false` to DENY (no code).
|
|
191
|
+
* The cloud control plane uses it to require org-membership: a cloud user
|
|
192
|
+
* may only obtain a code for an env client (`project_<envId>`) of an org
|
|
193
|
+
* they belong to. Unset (open editions / self-host, where the OP is not a
|
|
194
|
+
* multi-tenant issuer) = allow. Host is expected to fail CLOSED on error.
|
|
195
|
+
*/
|
|
196
|
+
oidcAuthorizeGate?: (params: {
|
|
197
|
+
userId: string;
|
|
198
|
+
clientId: string;
|
|
199
|
+
}) => boolean | Promise<boolean>;
|
|
172
200
|
/**
|
|
173
201
|
* Base path for auth routes
|
|
174
202
|
* Forwarded to better-auth's basePath option so it can match incoming
|
|
@@ -231,22 +259,82 @@ interface AuthManagerOptions extends Partial<AuthConfig> {
|
|
|
231
259
|
* "create organization" screen.
|
|
232
260
|
*/
|
|
233
261
|
databaseHooks?: BetterAuthOptions['databaseHooks'];
|
|
262
|
+
/**
|
|
263
|
+
* ADR-0069 D2 — account lockout (anti-brute-force). After this many
|
|
264
|
+
* consecutive failed sign-ins the account is locked for
|
|
265
|
+
* {@link lockoutDurationMinutes}. `0` (default) disables lockout.
|
|
266
|
+
* Enforced per-identity in the `/sign-in/email` before/after hooks
|
|
267
|
+
* (survives IP rotation, unlike the per-IP {@link rateLimit}).
|
|
268
|
+
*/
|
|
269
|
+
lockoutThreshold?: number;
|
|
270
|
+
/** Minutes an account stays locked once the threshold is crossed. Default 15. */
|
|
271
|
+
lockoutDurationMinutes?: number;
|
|
272
|
+
/**
|
|
273
|
+
* ADR-0069 D1 — password complexity. When `passwordRequireComplexity` is on,
|
|
274
|
+
* a new password must contain at least `passwordMinClasses` (1-4) of the
|
|
275
|
+
* character classes upper / lower / digit / symbol. Enforced by a validator
|
|
276
|
+
* in the `/sign-up/email`, `/reset-password`, `/change-password` before hook
|
|
277
|
+
* (better-auth only enforces min/max length natively).
|
|
278
|
+
*/
|
|
279
|
+
passwordRequireComplexity?: boolean;
|
|
280
|
+
/** Minimum distinct character classes required (1-4). Default 3. */
|
|
281
|
+
passwordMinClasses?: number;
|
|
282
|
+
/**
|
|
283
|
+
* ADR-0069 D1 — password history depth. When > 0, a password change/reset is
|
|
284
|
+
* rejected if the new password matches the current or any of the last
|
|
285
|
+
* `passwordHistoryCount` hashes (`sys_account.previous_password_hashes`).
|
|
286
|
+
* Reuses better-auth's native hash/verify — no bespoke crypto.
|
|
287
|
+
*/
|
|
288
|
+
passwordHistoryCount?: number;
|
|
289
|
+
/**
|
|
290
|
+
* ADR-0069 D1 — password expiry (days). When > 0, an authenticated user whose
|
|
291
|
+
* `sys_user.password_changed_at` is older than this is gated out of protected
|
|
292
|
+
* resources (`PASSWORD_EXPIRED`) until they change their password. Computed in
|
|
293
|
+
* `customSession` (→ `user.authGate`) and enforced at the transport seam. 0 =
|
|
294
|
+
* off. A null `password_changed_at` never expires (existing users on upgrade).
|
|
295
|
+
*/
|
|
296
|
+
passwordExpiryDays?: number;
|
|
297
|
+
/**
|
|
298
|
+
* ADR-0069 D3 — enforced MFA. When true, an authenticated user without TOTP
|
|
299
|
+
* enrolled (`sys_user.two_factor_enabled`) is gated out of protected resources
|
|
300
|
+
* (`MFA_REQUIRED`) once their grace window elapses, until they enroll. Shares
|
|
301
|
+
* the `customSession` → `user.authGate` seam with password expiry.
|
|
302
|
+
*/
|
|
303
|
+
mfaRequired?: boolean;
|
|
304
|
+
/** Days a user may defer MFA enrollment before the hard block. Default 7. */
|
|
305
|
+
mfaGracePeriodDays?: number;
|
|
306
|
+
/**
|
|
307
|
+
* ADR-0069 D4 — session controls. Enforced in `customSession` (idle/absolute)
|
|
308
|
+
* and the sign-in hook (concurrent). 0 = off for each. A revoked session is
|
|
309
|
+
* expired in place (`sys_session.expires_at` past + `revoked_at`/`revoke_reason`)
|
|
310
|
+
* so better-auth returns no session on the next request (→ 401 → re-login).
|
|
311
|
+
*/
|
|
312
|
+
sessionIdleTimeoutMinutes?: number;
|
|
313
|
+
sessionAbsoluteMaxHours?: number;
|
|
314
|
+
maxConcurrentSessions?: number;
|
|
315
|
+
/**
|
|
316
|
+
* ADR-0069 D5 — network gating. When non-empty, auth requests (sign-in,
|
|
317
|
+
* session) from a client IP outside these CIDR / exact ranges are rejected
|
|
318
|
+
* with `IP_NOT_ALLOWED` at the auth-route middleware. Requires a trusted proxy
|
|
319
|
+
* to set `x-forwarded-for` / `cf-connecting-ip`; fails OPEN when the client IP
|
|
320
|
+
* can't be determined (so a missing proxy header is a no-op, not a lockout).
|
|
321
|
+
*/
|
|
322
|
+
allowedIpRanges?: string[];
|
|
323
|
+
/**
|
|
324
|
+
* ADR-0069 D2 — better-auth-native per-IP rate limiting, passed through to
|
|
325
|
+
* better-auth's core `rateLimit`. The settings bind tightens `customRules`
|
|
326
|
+
* for the auth endpoints (`/sign-in/email`, `/sign-up/email`,
|
|
327
|
+
* `/reset-password`). Multi-node deployments need a shared `storage`.
|
|
328
|
+
*/
|
|
329
|
+
rateLimit?: BetterAuthOptions['rateLimit'];
|
|
234
330
|
}
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
* Wraps better-auth and provides authentication services for ObjectStack.
|
|
239
|
-
* Supports multiple authentication methods:
|
|
240
|
-
* - Email/password
|
|
241
|
-
* - OAuth providers (Google, GitHub, etc.)
|
|
242
|
-
* - Magic links
|
|
243
|
-
* - Two-factor authentication
|
|
244
|
-
* - Passkeys
|
|
245
|
-
* - Organization/teams
|
|
246
|
-
*/
|
|
331
|
+
/** ADR-0069 D5 — does `ip` match `range` (IPv4 CIDR `a.b.c.d/n`, or exact IP)? */
|
|
332
|
+
declare function ipMatchesRange(ip: string, range: string): boolean;
|
|
247
333
|
declare class AuthManager {
|
|
248
334
|
private auth;
|
|
249
335
|
private config;
|
|
336
|
+
private _orgMfaCache;
|
|
337
|
+
private _orgMfaRefreshing;
|
|
250
338
|
/**
|
|
251
339
|
* Result of the dev-only admin seed (set by `AuthPlugin.maybeSeedDevAdmin`
|
|
252
340
|
* when it provisions the well-known admin on an empty DB). The `serve`
|
|
@@ -391,6 +479,16 @@ declare class AuthManager {
|
|
|
391
479
|
* sign in with email/password going forward.
|
|
392
480
|
*/
|
|
393
481
|
getAuthContext(): Promise<any>;
|
|
482
|
+
/**
|
|
483
|
+
* SSO-only ("enforced") login mode: the login UI hides the local password
|
|
484
|
+
* form + self-registration so the team signs in via the IdP only.
|
|
485
|
+
* `OS_AUTH_SSO_ONLY` (when set) wins over the `ssoOnlyMode` config knob —
|
|
486
|
+
* parity with the `disableSignUp` env override — so a deployment can force
|
|
487
|
+
* it regardless of the per-env/config value. Break-glass is preserved: this
|
|
488
|
+
* NEVER disables `emailAndPassword.enabled`; it only forces `disableSignUp`
|
|
489
|
+
* and signals the UI to hide the password form. Generic over the IdP.
|
|
490
|
+
*/
|
|
491
|
+
private resolveSsoOnly;
|
|
394
492
|
getPublicConfig(): {
|
|
395
493
|
emailPassword: {
|
|
396
494
|
enabled: boolean;
|
|
@@ -417,10 +515,215 @@ declare class AuthManager {
|
|
|
417
515
|
organization: boolean;
|
|
418
516
|
multiOrgEnabled: boolean;
|
|
419
517
|
oidcProvider: boolean;
|
|
518
|
+
sso: boolean;
|
|
519
|
+
ssoEnforced: boolean;
|
|
420
520
|
deviceAuthorization: boolean;
|
|
421
521
|
admin: boolean;
|
|
422
522
|
};
|
|
423
523
|
};
|
|
524
|
+
/**
|
|
525
|
+
* Coarse "is the domain-routed `@better-auth/sso` plugin wired" flag.
|
|
526
|
+
* Resolved with the EXACT logic that decides whether the plugin is mounted
|
|
527
|
+
* in `buildPlugins()` (`ssoFromEnv ?? pluginConfig.sso ?? false`) so the
|
|
528
|
+
* advertised capability can never disagree with the actual `/sign-in/sso`
|
|
529
|
+
* route. `OS_SSO_ENABLED` (when set) wins over the config-file setting.
|
|
530
|
+
* Public so `AuthPlugin` can gate the Setup-nav "SSO Providers" entry on it
|
|
531
|
+
* (captures both self-host `OS_SSO_ENABLED` and the cloud per-env
|
|
532
|
+
* `planAllowsSso` config, since that arrives via `plugins.sso`).
|
|
533
|
+
*/
|
|
534
|
+
isSsoWired(): boolean;
|
|
535
|
+
/**
|
|
536
|
+
* Whether opt-in DNS domain-verification (ADR-0024 ②) is wired — i.e. the
|
|
537
|
+
* `/sso/request-domain-verification` + `/sso/verify-domain` endpoints are
|
|
538
|
+
* mounted (and the hard "domain must be verified to log in" gate is active).
|
|
539
|
+
* Resolved with the EXACT logic `buildPluginList` uses for the `sso()`
|
|
540
|
+
* `domainVerification.enabled` option, so the bridge can return a clear
|
|
541
|
+
* "not enabled for this environment" instead of a bare 404 when off.
|
|
542
|
+
* Implies `isSsoWired()` (the sso plugin must be loaded to honor it).
|
|
543
|
+
*/
|
|
544
|
+
isSsoDomainVerificationEnabled(): boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Whether enterprise SSO is actually *usable*, not merely wired: the plugin
|
|
547
|
+
* is on AND at least one `sys_sso_provider` row exists. Per-email domain→IdP
|
|
548
|
+
* matching still happens at `/sign-in/sso`; this answers the coarser "is
|
|
549
|
+
* there any point showing the SSO button at all", so a freshly-enabled but
|
|
550
|
+
* unconfigured SSO setup doesn't advertise a button that errors for everyone.
|
|
551
|
+
*
|
|
552
|
+
* Fails OPEN to the wired flag when providers can't be counted (no data
|
|
553
|
+
* engine, query error) — a config-introspection hiccup must never make the
|
|
554
|
+
* login page hide a button that genuinely works.
|
|
555
|
+
*/
|
|
556
|
+
isSsoUsable(): Promise<boolean>;
|
|
557
|
+
/**
|
|
558
|
+
* Extra `trustedOrigins` entries derived from an external-SSO registration
|
|
559
|
+
* request. For a `POST /sso/register` | `/sso/update-provider`, parse the
|
|
560
|
+
* (cloned) body and return the PUBLIC-ROUTABLE origins of the declared
|
|
561
|
+
* `issuer` / `oidcConfig` endpoints so `@better-auth/sso`'s discovery
|
|
562
|
+
* validation accepts a customer IdP registered at runtime (ADR-0024) without
|
|
563
|
+
* the operator pre-listing it in boot config. Only public-routable hosts are
|
|
564
|
+
* returned — private / internal / loopback hosts are never auto-trusted
|
|
565
|
+
* (better-auth's `isPublicRoutableHost`, the same predicate its own
|
|
566
|
+
* sub-endpoint check uses). Best-effort: any parse error yields `[]`.
|
|
567
|
+
*/
|
|
568
|
+
private ssoDiscoveryTrustedOrigins;
|
|
569
|
+
/**
|
|
570
|
+
* Resolve the acting user (+ their active org) for a before-hook gate,
|
|
571
|
+
* hook-order-independent. Tries the standard cookie session first, then falls
|
|
572
|
+
* back to explicit token resolution (bearer or the session cookie's token
|
|
573
|
+
* part) — the bearer plugin may convert `Authorization: Bearer` to a session
|
|
574
|
+
* AFTER this global before-hook runs. Returns `null` when no valid session
|
|
575
|
+
* can be resolved (→ caller lets `sessionMiddleware` issue the 401).
|
|
576
|
+
*/
|
|
577
|
+
private resolveActor;
|
|
578
|
+
/**
|
|
579
|
+
* True when `userId` is a platform admin (a `sys_user_permission_set` row
|
|
580
|
+
* pointing at `admin_full_access` with `organization_id = null`) OR an
|
|
581
|
+
* owner/admin member of `activeOrgId` (any org membership with role
|
|
582
|
+
* owner/admin when no active org is set). Mirrors the role-derivation in
|
|
583
|
+
* `customSession`; reads through `withSystemReadContext` so the lookups are
|
|
584
|
+
* not themselves RLS-scoped to the acting (possibly non-privileged) user.
|
|
585
|
+
* Fails CLOSED (returns false) on any lookup error — this backs a security
|
|
586
|
+
* gate, so an unverifiable actor must never pass.
|
|
587
|
+
*/
|
|
588
|
+
private isOrgOrPlatformAdmin;
|
|
589
|
+
/**
|
|
590
|
+
* Compose the framework's identity-source stamp (`account.create.after`)
|
|
591
|
+
* with any host-supplied `databaseHooks`, preserving BOTH. The cloud passes
|
|
592
|
+
* `user.create.after` (personal-org provisioning) + `session.create.before`
|
|
593
|
+
* (active-org) — different model/op, so no collision — but if a host ever
|
|
594
|
+
* adds its own `account.create.after` we chain it after the stamp rather
|
|
595
|
+
* than silently dropping one.
|
|
596
|
+
*/
|
|
597
|
+
private composeDatabaseHooks;
|
|
598
|
+
/**
|
|
599
|
+
* Maintain `sys_user.source` (ADR-0024 D4 provenance) as accounts are linked.
|
|
600
|
+
* Drives the managed-vs-native user-mgmt gating: a managed (`idp-provisioned`)
|
|
601
|
+
* user holds no local credential, so the password / identity-edit actions
|
|
602
|
+
* hide for them — preventing a managed user from self-minting a local
|
|
603
|
+
* password that would bypass enforced SSO.
|
|
604
|
+
*
|
|
605
|
+
* Two cases, both break-glass safe and idempotent (only writes on a real
|
|
606
|
+
* change, so trackHistory stays quiet):
|
|
607
|
+
*
|
|
608
|
+
* • A **federated** account (any non-`credential` provider — the cloud-as-IdP
|
|
609
|
+
* `objectstack-cloud` provider OR a customer's own OIDC/SAML IdP) is
|
|
610
|
+
* linked AND the user holds NO local credential → mark `idp-provisioned`.
|
|
611
|
+
* A user who already has a `credential` account (an env-native user who
|
|
612
|
+
* linked SSO) is left `env-native` — they keep a usable password.
|
|
613
|
+
*
|
|
614
|
+
* • A **credential** account is created (local signup, or the break-glass
|
|
615
|
+
* owner's password set via set-initial-password — which can land AFTER the
|
|
616
|
+
* first SSO link) → ensure `env-native`. This flips a previously-stamped
|
|
617
|
+
* owner back, so the break-glass admin never loses self-service password
|
|
618
|
+
* management.
|
|
619
|
+
*
|
|
620
|
+
* Best-effort: any failure leaves the prior value (the gate fails open — a
|
|
621
|
+
* managed user might transiently show a password action that simply errors —
|
|
622
|
+
* never a hard login failure).
|
|
623
|
+
*/
|
|
624
|
+
private stampIdentitySource;
|
|
625
|
+
/**
|
|
626
|
+
* ADR-0069 D1 — reject a password that doesn't meet the configured character-
|
|
627
|
+
* class complexity. No-op when `passwordRequireComplexity` is off. Counts the
|
|
628
|
+
* four classes (upper / lower / digit / symbol) present and throws
|
|
629
|
+
* `PASSWORD_POLICY_VIOLATION` when fewer than `passwordMinClasses` are used.
|
|
630
|
+
*/
|
|
631
|
+
private assertPasswordComplexity;
|
|
632
|
+
/**
|
|
633
|
+
* ADR-0069 — is any authentication-policy gate enabled? Cheap, synchronous;
|
|
634
|
+
* lets the transport seams skip session lookups entirely when off (the
|
|
635
|
+
* default), keeping the gate zero-overhead until an admin opts in.
|
|
636
|
+
*/
|
|
637
|
+
isAuthGateActive(): boolean;
|
|
638
|
+
/**
|
|
639
|
+
* ADR-0069 — refresh the "any org requires MFA" cache in the background when
|
|
640
|
+
* stale (60s TTL). Fire-and-forget: a brand-new per-org requirement activates
|
|
641
|
+
* the gate on the next request, never blocking this one. No-op when global MFA
|
|
642
|
+
* is already on (the gate is active regardless).
|
|
643
|
+
*/
|
|
644
|
+
private refreshOrgMfaCacheIfStale;
|
|
645
|
+
/**
|
|
646
|
+
* ADR-0069 — compute the auth-policy gate posture for a session. Returns an
|
|
647
|
+
* `{ code, message }` when the user is currently blocked (e.g. password
|
|
648
|
+
* expired), else undefined. No-op (and no DB read) when no gate feature is
|
|
649
|
+
* enabled. Fails OPEN on any lookup error — a transient hiccup must never lock
|
|
650
|
+
* a compliant user out.
|
|
651
|
+
*/
|
|
652
|
+
private computeAuthGate;
|
|
653
|
+
/**
|
|
654
|
+
* ADR-0069 D1 — stamp `sys_user.password_changed_at = now` after a password is
|
|
655
|
+
* set (sign-up / change / reset). Best-effort; never throws. Written as a Date
|
|
656
|
+
* (never epoch-ms) per ADR-0074.
|
|
657
|
+
*/
|
|
658
|
+
private stampPasswordChangedAt;
|
|
659
|
+
/**
|
|
660
|
+
* ADR-0069 D1 — parse the bounded `previous_password_hashes` JSON column into
|
|
661
|
+
* a string[] of hashes, tolerating null / malformed values.
|
|
662
|
+
*/
|
|
663
|
+
private parseHashes;
|
|
664
|
+
/**
|
|
665
|
+
* ADR-0069 D1 — resolve the user whose password is being changed. For
|
|
666
|
+
* `/change-password` the caller is authenticated (session); for
|
|
667
|
+
* `/reset-password` the user is carried by the reset token's verification
|
|
668
|
+
* value (the same lookup better-auth's own handler uses).
|
|
669
|
+
*/
|
|
670
|
+
private resolvePasswordChangeUserId;
|
|
671
|
+
/**
|
|
672
|
+
* ADR-0069 D1 — throw `PASSWORD_REUSE` when `candidate` matches the user's
|
|
673
|
+
* current password or any hash in the bounded history. Reuses better-auth's
|
|
674
|
+
* native `password.verify` (passed in) rather than re-hashing. Returns the
|
|
675
|
+
* current hash (for the after-hook to append) when the candidate is fresh, or
|
|
676
|
+
* undefined when the feature is off / nothing to compare.
|
|
677
|
+
*/
|
|
678
|
+
private assertPasswordNotReused;
|
|
679
|
+
/**
|
|
680
|
+
* ADR-0069 D1 — append `oldHash` to the bounded password-history ring after a
|
|
681
|
+
* successful change/reset. Best-effort; never throws.
|
|
682
|
+
*/
|
|
683
|
+
private recordPasswordHistory;
|
|
684
|
+
/**
|
|
685
|
+
* ADR-0069 D2 — throw `ACCOUNT_LOCKED` when the identity is currently locked
|
|
686
|
+
* out (brute-force protection). No-op when lockout is disabled
|
|
687
|
+
* (`lockoutThreshold <= 0`) or no data engine is wired. Fails OPEN on a
|
|
688
|
+
* lookup error: an infra hiccup must never block every login.
|
|
689
|
+
*/
|
|
690
|
+
private assertAccountNotLocked;
|
|
691
|
+
/**
|
|
692
|
+
* ADR-0069 D2 — record a sign-in outcome for lockout accounting. On failure
|
|
693
|
+
* increments `failed_login_count` and, once it reaches `lockoutThreshold`,
|
|
694
|
+
* stamps `locked_until = now + lockoutDurationMinutes`. On success resets
|
|
695
|
+
* both (only writing when there is something to clear, to avoid a no-op
|
|
696
|
+
* history row on every login). No-op when lockout is disabled. Never throws —
|
|
697
|
+
* a counter write must not turn a valid login into an error.
|
|
698
|
+
*/
|
|
699
|
+
private recordSignInOutcome;
|
|
700
|
+
/**
|
|
701
|
+
* ADR-0069 D2 — clear a user's lockout state (admin "Unlock" action).
|
|
702
|
+
* Resets `failed_login_count` and `locked_until`. Returns false when no data
|
|
703
|
+
* engine is wired or the user does not exist.
|
|
704
|
+
*/
|
|
705
|
+
unlockUser(userId: string): Promise<boolean>;
|
|
706
|
+
/**
|
|
707
|
+
* ADR-0069 D4 — idle / absolute session enforcement, run per request from
|
|
708
|
+
* `customSession`. No-op when both are off. Revokes (expires in place +
|
|
709
|
+
* stamps revoked_at/revoke_reason) when a limit is exceeded so better-auth
|
|
710
|
+
* returns no session on the NEXT request; otherwise touches `last_activity_at`
|
|
711
|
+
* (throttled to once a minute). Best-effort — never throws.
|
|
712
|
+
*/
|
|
713
|
+
private enforceSessionControls;
|
|
714
|
+
/**
|
|
715
|
+
* ADR-0069 D4 — concurrent-session cap, run from the sign-in after-hook.
|
|
716
|
+
* Keeps the newest `maxConcurrentSessions` live sessions for the user and
|
|
717
|
+
* revokes the rest (oldest first). No-op when off. Best-effort.
|
|
718
|
+
*/
|
|
719
|
+
private enforceConcurrentCap;
|
|
720
|
+
/**
|
|
721
|
+
* ADR-0069 D5 — is `ip` within the configured allow-list? True (allow) when no
|
|
722
|
+
* ranges are configured, OR when the IP can't be determined (fail-open so a
|
|
723
|
+
* misconfigured proxy never locks everyone out — an admin enabling this must
|
|
724
|
+
* ensure forwarded headers are trusted). Supports IPv4 CIDR + exact IPv4/IPv6.
|
|
725
|
+
*/
|
|
726
|
+
isClientIpAllowed(ip: string | undefined): boolean;
|
|
424
727
|
/**
|
|
425
728
|
* Returns the data engine wired into this auth manager. Used by route
|
|
426
729
|
* handlers (e.g. bootstrap-status) that need to query identity tables
|
|
@@ -480,6 +783,98 @@ interface SetInitialPasswordResult {
|
|
|
480
783
|
*/
|
|
481
784
|
declare function runSetInitialPassword(authApi: SetPasswordCapableApi, request: Request): Promise<SetInitialPasswordResult>;
|
|
482
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Shared `register-sso-provider` (form) handler.
|
|
788
|
+
*
|
|
789
|
+
* `@better-auth/sso`'s `POST /sso/register` expects the OIDC protocol fields
|
|
790
|
+
* NESTED under `oidcConfig` ({ clientId, clientSecret, discoveryEndpoint,
|
|
791
|
+
* scopes, mapping }). The `sys_sso_provider` `register_sso_provider` UI action
|
|
792
|
+
* collects FLAT form fields (the action param schema has no nested-path
|
|
793
|
+
* support), so posting them straight to `/sso/register` drops
|
|
794
|
+
* clientId/clientSecret at the top level (Zod-stripped) and persists an
|
|
795
|
+
* unusable `oidc_config = null` provider that can never complete a login
|
|
796
|
+
* (ADR-0024).
|
|
797
|
+
*
|
|
798
|
+
* This helper reshapes the flat form body into the nested shape and
|
|
799
|
+
* RE-DISPATCHES it through the real `/sso/register` endpoint (via the
|
|
800
|
+
* better-auth universal handler passed in) so the admin gate, the
|
|
801
|
+
* public-routable `trustedOrigins` allowance, discovery hydration, and secret
|
|
802
|
+
* handling all still run — no logic is duplicated. It is the single source of
|
|
803
|
+
* truth for the two mount points that must stay in lockstep: the full
|
|
804
|
+
* `AuthPlugin` (self-host / OSS host kernel) and the cloud `AuthProxyPlugin`
|
|
805
|
+
* (per-environment runtime) — mirroring `runSetInitialPassword`.
|
|
806
|
+
*/
|
|
807
|
+
interface RegisterSsoFormResult {
|
|
808
|
+
/** HTTP status to return to the caller. */
|
|
809
|
+
status: number;
|
|
810
|
+
/** JSON body; mirrors the `{ success, data?, error? }` envelope the client parses. */
|
|
811
|
+
body: {
|
|
812
|
+
success: boolean;
|
|
813
|
+
data?: {
|
|
814
|
+
providerId: string;
|
|
815
|
+
};
|
|
816
|
+
error?: {
|
|
817
|
+
code: string;
|
|
818
|
+
message: string;
|
|
819
|
+
};
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
/** A better-auth universal handler: `(request) => Response`. */
|
|
823
|
+
type AuthRequestHandler = (request: Request) => Promise<Response>;
|
|
824
|
+
/**
|
|
825
|
+
* Reshape a flat SSO-provider registration form body and register it.
|
|
826
|
+
*
|
|
827
|
+
* @param handle the better-auth universal handler (`AuthManager.handleRequest`
|
|
828
|
+
* on the host kernel, or the resolved per-env handler in the
|
|
829
|
+
* cloud proxy). Used to re-dispatch the nested body to the real
|
|
830
|
+
* `/sso/register` route so all of its gates run.
|
|
831
|
+
* @param request the raw Web `Request` — its headers carry the caller's session
|
|
832
|
+
* cookie / bearer + Origin; its body carries the flat form
|
|
833
|
+
* fields ({ providerId, issuer, domain, clientId, clientSecret,
|
|
834
|
+
* discoveryEndpoint?, scopes?, mapId?, mapEmail?, mapName? }).
|
|
835
|
+
*/
|
|
836
|
+
declare function runRegisterSsoProviderFromForm(handle: AuthRequestHandler, request: Request): Promise<RegisterSsoFormResult>;
|
|
837
|
+
/**
|
|
838
|
+
* ADR-0069 P3 — SAML 2.0 sibling of {@link runRegisterSsoProviderFromForm}.
|
|
839
|
+
*
|
|
840
|
+
* `@better-auth/sso` (samlify-backed) registers a SAML IdP via the SAME
|
|
841
|
+
* `/sso/register` endpoint, with the protocol fields nested under `samlConfig`
|
|
842
|
+
* ({ entryPoint, cert, callbackUrl, identifierFormat? }) instead of `oidcConfig`.
|
|
843
|
+
* The UI action collects FLAT fields; this helper reshapes them, derives the
|
|
844
|
+
* per-provider ACS callback URL (`/sso/saml2/sp/acs/<providerId>`), and
|
|
845
|
+
* re-dispatches through `/sso/register` so the admin gate + provisioning run.
|
|
846
|
+
* Returns the SP ACS + metadata URLs the admin must configure on the IdP.
|
|
847
|
+
*/
|
|
848
|
+
declare function runRegisterSamlProviderFromForm(handle: AuthRequestHandler, request: Request): Promise<RegisterSsoFormResult & {
|
|
849
|
+
body: RegisterSsoFormResult['body'] & {
|
|
850
|
+
acsUrl?: string;
|
|
851
|
+
spMetadataUrl?: string;
|
|
852
|
+
};
|
|
853
|
+
}>;
|
|
854
|
+
/**
|
|
855
|
+
* Request a DNS-TXT domain-verification challenge for a registered provider and
|
|
856
|
+
* return the ready-to-paste DNS record (for a one-shot `resultDialog`).
|
|
857
|
+
*
|
|
858
|
+
* Body: `{ providerId, domain? }` (domain only shapes the displayed record name).
|
|
859
|
+
*/
|
|
860
|
+
declare function runRequestDomainVerification(handle: AuthRequestHandler, request: Request): Promise<RegisterSsoFormResult & {
|
|
861
|
+
body: RegisterSsoFormResult['body'] & {
|
|
862
|
+
data?: any;
|
|
863
|
+
};
|
|
864
|
+
}>;
|
|
865
|
+
/**
|
|
866
|
+
* Verify a provider's domain ownership (re-checks the DNS-TXT record). Reshapes
|
|
867
|
+
* @better-auth/sso's empty `204` / `502` into a `{ success, data:{ message } }`
|
|
868
|
+
* envelope so the action surfaces a clear toast.
|
|
869
|
+
*
|
|
870
|
+
* Body: `{ providerId }`.
|
|
871
|
+
*/
|
|
872
|
+
declare function runVerifyDomain(handle: AuthRequestHandler, request: Request): Promise<RegisterSsoFormResult & {
|
|
873
|
+
body: RegisterSsoFormResult['body'] & {
|
|
874
|
+
data?: any;
|
|
875
|
+
};
|
|
876
|
+
}>;
|
|
877
|
+
|
|
483
878
|
/**
|
|
484
879
|
* Mapping from better-auth model names to ObjectStack protocol object names.
|
|
485
880
|
*
|
|
@@ -492,6 +887,18 @@ declare const AUTH_MODEL_TO_PROTOCOL: Record<string, string>;
|
|
|
492
887
|
* Falls back to the original model name for custom / non-core models.
|
|
493
888
|
*/
|
|
494
889
|
declare function resolveProtocolName(model: string): string;
|
|
890
|
+
/**
|
|
891
|
+
* Wrap a data engine so its READ operations (find / findOne / count) run as
|
|
892
|
+
* SYSTEM reads — injecting `context.isSystem: true` (merged; any caller-supplied
|
|
893
|
+
* context still wins on other keys). better-auth has already authenticated the
|
|
894
|
+
* session and scopes every query by its OWN where-clauses (e.g. member.userId =
|
|
895
|
+
* session.user). A deployment's control-plane org-scope read hook, however, keys
|
|
896
|
+
* off the CALLER's user id, and these adapter reads carry no caller context — so
|
|
897
|
+
* without isSystem that hook filters sys_member / sys_organization reads down to
|
|
898
|
+
* zero and `organization.list()` returns no orgs for a real member. Writes pass
|
|
899
|
+
* through untouched (org-scope is a read-only hook).
|
|
900
|
+
*/
|
|
901
|
+
declare function withSystemReadContext(engine: IDataEngine): IDataEngine;
|
|
495
902
|
/**
|
|
496
903
|
* Create an ObjectQL adapter **factory** for better-auth.
|
|
497
904
|
*
|
|
@@ -508,7 +915,7 @@ declare function resolveProtocolName(model: string): string;
|
|
|
508
915
|
* @param dataEngine - ObjectQL data engine instance
|
|
509
916
|
* @returns better-auth AdapterFactory
|
|
510
917
|
*/
|
|
511
|
-
declare function createObjectQLAdapterFactory(
|
|
918
|
+
declare function createObjectQLAdapterFactory(rawDataEngine: IDataEngine): better_auth_adapters.AdapterFactory<better_auth.BetterAuthOptions>;
|
|
512
919
|
/**
|
|
513
920
|
* Create a raw ObjectQL adapter for better-auth (without factory wrapping).
|
|
514
921
|
*
|
|
@@ -523,7 +930,7 @@ declare function createObjectQLAdapterFactory(dataEngine: IDataEngine): better_a
|
|
|
523
930
|
* @param dataEngine - ObjectQL data engine instance
|
|
524
931
|
* @returns better-auth CustomAdapter (raw, without factory wrapping)
|
|
525
932
|
*/
|
|
526
|
-
declare function createObjectQLAdapter(
|
|
933
|
+
declare function createObjectQLAdapter(rawDataEngine: IDataEngine): {
|
|
527
934
|
create: <T extends Record<string, any>>({ model, data, select: _select }: {
|
|
528
935
|
model: string;
|
|
529
936
|
data: T;
|
|
@@ -1225,6 +1632,60 @@ declare function buildOauthProviderPluginSchema(): {
|
|
|
1225
1632
|
* from the deprecated `better-auth/plugins/oidc-provider` plugin.
|
|
1226
1633
|
*/
|
|
1227
1634
|
declare const buildOidcProviderPluginSchema: typeof buildOauthProviderPluginSchema;
|
|
1635
|
+
/**
|
|
1636
|
+
* `@better-auth/sso` plugin `ssoProvider` model mapping.
|
|
1637
|
+
*
|
|
1638
|
+
* Each row is an external OIDC/SAML IdP this environment federates login to
|
|
1639
|
+
* (the relying-party side — ADR-0024's OPEN per-env SSO mechanism). The
|
|
1640
|
+
* protocol detail lives in JSON blobs (`oidcConfig` / `samlConfig`); the model
|
|
1641
|
+
* itself is thin. Mirrors @better-auth/sso@1.6.20's `BaseSSOProvider`.
|
|
1642
|
+
*
|
|
1643
|
+
* | camelCase (better-auth) | snake_case (ObjectStack) |
|
|
1644
|
+
* |:------------------------|:-------------------------|
|
|
1645
|
+
* | providerId | provider_id |
|
|
1646
|
+
* | oidcConfig | oidc_config |
|
|
1647
|
+
* | samlConfig | saml_config |
|
|
1648
|
+
* | userId | user_id |
|
|
1649
|
+
* | organizationId | organization_id |
|
|
1650
|
+
* | issuer / domain | (same name — no remap) |
|
|
1651
|
+
*/
|
|
1652
|
+
declare const AUTH_SSO_PROVIDER_SCHEMA: {
|
|
1653
|
+
readonly modelName: "sys_sso_provider";
|
|
1654
|
+
readonly fields: {
|
|
1655
|
+
readonly providerId: "provider_id";
|
|
1656
|
+
readonly oidcConfig: "oidc_config";
|
|
1657
|
+
readonly samlConfig: "saml_config";
|
|
1658
|
+
readonly userId: "user_id";
|
|
1659
|
+
readonly organizationId: "organization_id";
|
|
1660
|
+
readonly domainVerified: "domain_verified";
|
|
1661
|
+
};
|
|
1662
|
+
};
|
|
1663
|
+
/**
|
|
1664
|
+
* `@better-auth/scim` plugin `scimProvider` model mapping.
|
|
1665
|
+
*
|
|
1666
|
+
* Each row is a SCIM connection: a bearer token an external IdP (Okta / Entra)
|
|
1667
|
+
* uses to auto-provision / deprovision THIS environment's users — the env is
|
|
1668
|
+
* the SCIM Service Provider (ADR-0071). Like `@better-auth/sso`, the plugin
|
|
1669
|
+
* hardcodes its model and exposes NO `schema` option, so the mapping is
|
|
1670
|
+
* consumed at the ADAPTER layer (AUTH_MODEL_TO_PROTOCOL + field resolution in
|
|
1671
|
+
* objectql-adapter.ts), NOT handed to the plugin.
|
|
1672
|
+
*
|
|
1673
|
+
* | camelCase (better-auth) | snake_case (ObjectStack) |
|
|
1674
|
+
* |:------------------------|:-------------------------|
|
|
1675
|
+
* | providerId | provider_id |
|
|
1676
|
+
* | scimToken | scim_token |
|
|
1677
|
+
* | organizationId | organization_id |
|
|
1678
|
+
* | userId | user_id |
|
|
1679
|
+
*/
|
|
1680
|
+
declare const AUTH_SCIM_PROVIDER_SCHEMA: {
|
|
1681
|
+
readonly modelName: "sys_scim_provider";
|
|
1682
|
+
readonly fields: {
|
|
1683
|
+
readonly providerId: "provider_id";
|
|
1684
|
+
readonly scimToken: "scim_token";
|
|
1685
|
+
readonly organizationId: "organization_id";
|
|
1686
|
+
readonly userId: "user_id";
|
|
1687
|
+
};
|
|
1688
|
+
};
|
|
1228
1689
|
/**
|
|
1229
1690
|
* Builds the `schema` option for better-auth's `deviceAuthorization()` plugin.
|
|
1230
1691
|
*
|
|
@@ -1249,4 +1710,4 @@ declare function buildDeviceAuthorizationPluginSchema(): {
|
|
|
1249
1710
|
};
|
|
1250
1711
|
};
|
|
1251
1712
|
|
|
1252
|
-
export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SESSION_CONFIG, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, type SetInitialPasswordResult, type SetPasswordCapableApi, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, resolveProtocolName, runSetInitialPassword };
|
|
1713
|
+
export { AUTH_ACCOUNT_CONFIG, AUTH_ADMIN_SESSION_FIELDS, AUTH_ADMIN_USER_FIELDS, AUTH_DEVICE_CODE_SCHEMA, AUTH_INVITATION_SCHEMA, AUTH_JWKS_SCHEMA, AUTH_MEMBER_SCHEMA, AUTH_MODEL_TO_PROTOCOL, AUTH_OAUTH_ACCESS_TOKEN_SCHEMA, AUTH_OAUTH_APPLICATION_SCHEMA, AUTH_OAUTH_CLIENT_SCHEMA, AUTH_OAUTH_CONSENT_SCHEMA, AUTH_OAUTH_REFRESH_TOKEN_SCHEMA, AUTH_ORGANIZATION_SCHEMA, AUTH_ORG_SESSION_FIELDS, AUTH_SCIM_PROVIDER_SCHEMA, AUTH_SESSION_CONFIG, AUTH_SSO_PROVIDER_SCHEMA, AUTH_TEAM_MEMBER_SCHEMA, AUTH_TEAM_SCHEMA, AUTH_TWO_FACTOR_SCHEMA, AUTH_TWO_FACTOR_USER_FIELDS, AUTH_USER_CONFIG, AUTH_VERIFICATION_CONFIG, AuthManager, type AuthManagerOptions, AuthPlugin, type AuthPluginOptions, type AuthRequestHandler, type RegisterSsoFormResult, type SetInitialPasswordResult, type SetPasswordCapableApi, buildAdminPluginSchema, buildDeviceAuthorizationPluginSchema, buildJwtPluginSchema, buildOauthProviderPluginSchema, buildOidcProviderPluginSchema, buildOrganizationPluginSchema, buildTwoFactorPluginSchema, createObjectQLAdapter, createObjectQLAdapterFactory, ipMatchesRange, resolveProtocolName, runRegisterSamlProviderFromForm, runRegisterSsoProviderFromForm, runRequestDomainVerification, runSetInitialPassword, runVerifyDomain, withSystemReadContext };
|