@voltro/plugin-auth 0.81.0 → 0.83.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/dist/plugin.d.ts CHANGED
@@ -1,12 +1,31 @@
1
1
  import { AuthStrategy } from '@voltro/protocol';
2
2
  import { ConnectionCredential } from '@voltro/protocol';
3
+ import { DataStore } from '@voltro/database';
3
4
  import { Effect } from 'effect';
4
5
  import { SessionSecrets } from '@voltro/protocol/session';
6
+ import { SqlClient } from '@effect/sql/SqlClient';
5
7
  import { Subject } from '@voltro/protocol';
6
8
  import { VoidIfEmpty } from 'effect/Types';
7
9
  import { VoltroPlugin } from '@voltro/protocol';
8
10
  import { YieldableError } from 'effect/Cause';
9
11
 
12
+ /** What the decision can read: the app's database the plugin is bound to
13
+ * (undefined before the bind, and for a handler called without the plugin). */
14
+ declare interface AdmissionContext {
15
+ readonly store: DataStore | undefined;
16
+ }
17
+
18
+ /** Why a join is refused. */
19
+ declare interface AdmissionRefusal {
20
+ /** A stable code the client can branch on, e.g. `seats`. */
21
+ readonly reason: string;
22
+ /** A sentence for the person who was refused. */
23
+ readonly message: string;
24
+ }
25
+
26
+ /** Decide whether a person may join a tenant — `null` to let them in. */
27
+ declare type AdmitJoining = (joining: Joining, context: AdmissionContext) => Effect.Effect<AdmissionRefusal | null> | Promise<AdmissionRefusal | null>;
28
+
10
29
  declare interface AuthConfig {
11
30
  /**
12
31
  * The session-signing key. OMIT IT and the value is read from
@@ -50,6 +69,28 @@ declare interface AuthConfig {
50
69
  * but any concern (unverified email, suspended tenant, …) can hook here.
51
70
  * Absent / empty ⇒ every authenticated user proceeds unchanged. */
52
71
  readonly subjectGuards?: ReadonlyArray<SubjectGuard>;
72
+ /** Admission: asked before anyone JOINS a tenant — a sign-up, an invitation
73
+ * accepted with a new account or an existing one — and before anything is
74
+ * written (the account, the membership, the redeemed invitation). A refusal
75
+ * answers 403 `not_admitted` with its `reason` and `message`. The seam for a
76
+ * seat limit, an invite-only tenant, an address allow-list. Absent ⇒ every
77
+ * join proceeds. Signing in is not joining: subject guards govern that. */
78
+ readonly admit?: AdmitJoining;
79
+ /**
80
+ * Tenants a public sign-up may name (`tenantId` in the sign-up body) besides
81
+ * `defaultTenantId` — a readers' tenant beside the staff one. Anything else
82
+ * named is refused (400 `tenant_not_open`) before an account exists: the
83
+ * request must not choose the tenant it joins, or anyone could sign up into
84
+ * a stranger's. Joining a particular organisation goes through an invitation.
85
+ */
86
+ readonly signUpTenants?: ReadonlyArray<string>;
87
+ /** Make sure the user has a row in the app's `actors` table before their
88
+ * session exists. The `audit()` mixin stamps `createdBy` / `updatedBy`
89
+ * with the user id, and those columns reference `actors`, so a user with no
90
+ * row there cannot write to any audited table on an FK-enforcing database.
91
+ * `authRoutesPlugin` wires this to the app store it is bound to; called by
92
+ * `issueUserSession`, so every session-granting path covers it. */
93
+ readonly ensureActor?: (user: UserRecord) => Effect.Effect<void>;
53
94
  /** Brute-force lockout. After `maxAttempts` failed credential attempts
54
95
  * (wrong password OR wrong MFA code) within `windowSeconds`, the account —
55
96
  * keyed by EMAIL, so an unknown address locks exactly like a real one and
@@ -94,8 +135,12 @@ export declare interface AuthPlugin extends VoltroPlugin {
94
135
  export declare const authRoutesPlugin: (options: AuthRoutesPluginOptions) => AuthPlugin;
95
136
 
96
137
  export declare interface AuthRoutesPluginOptions extends AuthConfig {
97
- /** The user store backing every handler. */
98
- readonly store: UserStore;
138
+ /**
139
+ * The user store backing every handler — a `UserStore`, or a
140
+ * `UserStoreResolver` (e.g. `appUserStore()`) that the plugin calls once the
141
+ * framework has opened the app's database, so users live in that database.
142
+ */
143
+ readonly store: UserStore | UserStoreResolver;
99
144
  /** Passkey config — when omitted, the four passkey routes return 501. */
100
145
  readonly passkey?: PasskeyConfig;
101
146
  /** Challenge store for the passkey ceremonies. Defaults to an in-memory
@@ -139,6 +184,13 @@ export declare interface AuthRoutesPluginOptions extends AuthConfig {
139
184
  * on while leaving either unanswered. See `ImpersonationConfig`.
140
185
  */
141
186
  readonly impersonation?: ImpersonationConfig;
187
+ /**
188
+ * Native login for cookie-less clients (React Native, desktop shells).
189
+ * PRESENT mounts `/auth/native/authorize`, `/native/complete` and
190
+ * `/native/exchange`; ABSENT mounts nothing. `redirectUris` is exact-match —
191
+ * an app names the custom-scheme or universal-link URL it listens on.
192
+ */
193
+ readonly native?: NativeLoginConfig;
142
194
  /** Disambiguates multiple instances of this plugin in one app. */
143
195
  readonly name?: string;
144
196
  }
@@ -418,6 +470,13 @@ declare interface InvitationStore {
418
470
  readonly supersedePendingInvitations: (tenantId: string, email: string, revokedByUserId: string, now: Date) => Effect.Effect<number>;
419
471
  }
420
472
 
473
+ /** Someone about to join a tenant. */
474
+ declare interface Joining {
475
+ readonly email: string;
476
+ readonly tenantId: string;
477
+ readonly via: 'sign-up' | 'invitation';
478
+ }
479
+
421
480
  /**
422
481
  * Brute-force lockout policy, resolved from `AuthConfig.lockout` and passed
423
482
  * INTO the store on each failure so the store itself stays policy-free.
@@ -508,6 +567,19 @@ declare interface MfaStore {
508
567
  readonly countRecoveryCodes: (userId: string) => Effect.Effect<number>;
509
568
  }
510
569
 
570
+ declare interface NativeLoginConfig {
571
+ /** Exact redirect URIs the app may name — a custom scheme
572
+ * (`acme://auth/callback`) or an https universal link. No wildcards. */
573
+ readonly redirectUris: ReadonlyArray<string>;
574
+ /** The web app's sign-in page, relative to the app origin. Default `/login`.
575
+ * It must honour `?next=` (the auth template does). */
576
+ readonly loginPath?: string;
577
+ /** Lifetime of the pending request cookie. Default 10 minutes. */
578
+ readonly pendingTtlSeconds?: number;
579
+ /** Cookie name for the pending request. Default `voltro:native`. */
580
+ readonly pendingCookieName?: string;
581
+ }
582
+
511
583
  declare interface PasskeyConfig {
512
584
  /** Relying-party id — the registrable domain (e.g. `example.com`). */
513
585
  readonly rpId: string;
@@ -622,6 +694,10 @@ declare interface SessionStore {
622
694
  /** Revoke every session for the user EXCEPT `keepSessionId` — the
623
695
  * "sign out other devices" action. Returns the count removed. */
624
696
  readonly revokeAllOtherSessions: (userId: string, keepSessionId: string) => Effect.Effect<number>;
697
+ /** Revoke every session for the user — "sign out everywhere": a password
698
+ * reset, a deactivation, an account deletion, a security event. Returns
699
+ * the count removed. */
700
+ readonly revokeAllSessions: (userId: string) => Effect.Effect<number>;
625
701
  }
626
702
 
627
703
  /**
@@ -642,7 +718,7 @@ declare type SubjectGuardVerdict = {
642
718
  readonly message: string;
643
719
  };
644
720
 
645
- declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify';
721
+ declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify' | 'native-code';
646
722
 
647
723
  /** A single-use, hashed, expiring token (magic-link / password-reset). */
648
724
  declare interface TokenRecord {
@@ -750,4 +826,13 @@ declare interface UserRecord {
750
826
  declare interface UserStore extends IdentityStore, MfaStore, MembershipStore, SessionStore, TokenStore, InvitationStore, ImpersonationStore, PasskeyStore, LockoutStore {
751
827
  }
752
828
 
829
+ /** What a resolver is handed once the app's database is open. */
830
+ declare interface UserStoreBindContext {
831
+ /** The framework's open pool — absent when the app runs `store: 'memory'`. */
832
+ readonly sql?: SqlClient;
833
+ }
834
+
835
+ /** Builds the `UserStore` once the framework has opened the app's database. */
836
+ declare type UserStoreResolver = (ctx: UserStoreBindContext) => UserStore;
837
+
753
838
  export { }
package/dist/plugin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./password.js";
2
2
  import "./session.js";
3
- import { n as e, t } from "./plugin-DFQ8N52Q.js";
3
+ import { n as e, t } from "./plugin-CNW-X70p.js";
4
4
  import "./csrf.js";
5
5
  export { t as authRoutesPlugin, e as getAuthSessionStrategy };
package/dist/session.d.ts CHANGED
@@ -36,6 +36,8 @@ export declare interface IssueSessionOptions {
36
36
  readonly ttlSeconds?: number;
37
37
  readonly domain?: string;
38
38
  readonly secure?: boolean;
39
+ /** Clock override (epoch ms) — a testing seam for back-dated sessions. */
40
+ readonly now?: () => number;
39
41
  }
40
42
 
41
43
  export { KeyedSecret }
@@ -53,6 +55,19 @@ export { KeyedSecret }
53
55
  */
54
56
  export declare const readSession: (cookieHeader: string | undefined, secret: SessionSecretInput) => SubjectIdentity | null;
55
57
 
58
+ /**
59
+ * Keyed read over the WHOLE header bag: the `x-voltro-session` header a
60
+ * cookie-less client presents (React Native, a desktop shell — the value it
61
+ * obtained through the native login exchange), or the session cookie a browser
62
+ * carries. Same verify, same keyed rotation, same renewal signal as
63
+ * `readSessionKeyed`; only where the value comes from differs.
64
+ *
65
+ * `readSessionKeyed` stays for the callers that hold a cookie string (an SSR
66
+ * layout reading `request.headers.cookie`); this is what the routes and the
67
+ * rpc strategy use, because they must accept both.
68
+ */
69
+ export declare const readSessionFromHeaders: (headers: Readonly<Record<string, string | undefined>>, secret: SessionSecretInput, options?: VerifyOptions) => VerifyResult | null;
70
+
56
71
  /**
57
72
  * Keyed read: the full `VerifyResult` — subject + the `kid` that
58
73
  * verified + the sliding-window `renew` flag + `exp`/`iat`. The auth
package/dist/session.js CHANGED
@@ -1,20 +1,23 @@
1
- import { DEFAULT_SESSION_KID as e, buildSetCookie as t, readCookie as n, resolveOptionalSessionSecrets as r, resolveSessionSecret as i, resolveSessionSecrets as a, signSession as o, timingSafeStringEqual as s, verifySessionKeyed as c } from "@voltro/protocol/session";
1
+ import { DEFAULT_SESSION_KID as e, buildSetCookie as t, readCookie as n, readSessionValue as r, resolveOptionalSessionSecrets as i, resolveSessionSecret as a, resolveSessionSecrets as o, signSession as s, timingSafeStringEqual as c, verifySessionKeyed as l } from "@voltro/protocol/session";
2
2
  //#region src/session.ts
3
- var l = "voltro:session", u = 604800, d = i, f = a, p = (e) => typeof e == "string" ? e : e.current, m = (t) => {
3
+ var u = "voltro:session", d = 604800, f = a, p = o, m = (e) => typeof e == "string" ? e : e.current, h = (t) => {
4
4
  if (typeof t != "string") return t;
5
- let n = r(), i = {
6
- kid: n && s(n.current.secret, t) ? n.current.kid : e,
5
+ let n = i(), r = {
6
+ kid: n && c(n.current.secret, t) ? n.current.kid : e,
7
7
  secret: t
8
8
  };
9
9
  return n?.previous ? {
10
- current: i,
10
+ current: r,
11
11
  previous: n.previous
12
- } : { current: i };
13
- }, h = (e, n, r = {}) => {
14
- let i = r.ttlSeconds ?? u, a = o(e, p(n), { ttlSeconds: i });
12
+ } : { current: r };
13
+ }, g = (e, n, r = {}) => {
14
+ let i = r.ttlSeconds ?? d, a = s(e, m(n), {
15
+ ttlSeconds: i,
16
+ ...r.now === void 0 ? {} : { now: r.now }
17
+ });
15
18
  return {
16
19
  value: a,
17
- setCookie: t(l, a, {
20
+ setCookie: t(u, a, {
18
21
  maxAgeSeconds: i,
19
22
  httpOnly: !0,
20
23
  sameSite: "lax",
@@ -23,10 +26,13 @@ var l = "voltro:session", u = 604800, d = i, f = a, p = (e) => typeof e == "stri
23
26
  path: "/"
24
27
  })
25
28
  };
26
- }, g = (e, t) => _(e, t)?.subject ?? null, _ = (e, t, r = {}) => {
27
- let i = n(e, l);
28
- return i ? c(i, m(t), r) : null;
29
- }, v = (e = {}) => t(l, "", {
29
+ }, _ = (e, t) => v(e, t)?.subject ?? null, v = (e, t, r = {}) => {
30
+ let i = n(e, u);
31
+ return i ? l(i, h(t), r) : null;
32
+ }, y = (e, t, n = {}) => {
33
+ let i = r(e, u);
34
+ return i ? l(i, h(t), n) : null;
35
+ }, b = (e = {}) => t(u, "", {
30
36
  maxAgeSeconds: 0,
31
37
  httpOnly: !0,
32
38
  sameSite: "lax",
@@ -35,4 +41,4 @@ var l = "voltro:session", u = 604800, d = i, f = a, p = (e) => typeof e == "stri
35
41
  path: "/"
36
42
  });
37
43
  //#endregion
38
- export { l as SESSION_COOKIE_NAME, v as clearSessionCookie, h as issueSession, g as readSession, _ as readSessionKeyed, d as resolveSessionSecret, f as resolveSessionSecrets, m as sessionSecretsOf };
44
+ export { u as SESSION_COOKIE_NAME, b as clearSessionCookie, g as issueSession, _ as readSession, y as readSessionFromHeaders, v as readSessionKeyed, f as resolveSessionSecret, p as resolveSessionSecrets, h as sessionSecretsOf };
@@ -1,6 +1,6 @@
1
1
  import { sessionSecretsOf as e } from "./session.js";
2
2
  import { Data as t, Effect as n } from "effect";
3
- import { readCookie as r, resolveSessionSecret as i, verifySessionKeyed as a } from "@voltro/protocol/session";
3
+ import { readSessionValue as r, resolveSessionSecret as i, verifySessionKeyed as a } from "@voltro/protocol/session";
4
4
  import { generateId as o } from "@voltro/database";
5
5
  //#region src/revocation.ts
6
6
  var s = 3e4, c = 1e4, l = (e, t = {}) => {
@@ -124,6 +124,11 @@ var s = 3e4, c = 1e4, l = (e, t = {}) => {
124
124
  }
125
125
  return n;
126
126
  }),
127
+ revokeAllSessions: (e) => n.sync(() => {
128
+ let t = 0;
129
+ for (let n = a.length - 1; n >= 0; n--) a[n].userId === e && (a.splice(n, 1), t++);
130
+ return t;
131
+ }),
127
132
  insertToken: (e) => n.sync(() => {
128
133
  let t = {
129
134
  ...e,
@@ -288,30 +293,46 @@ var s = 3e4, c = 1e4, l = (e, t = {}) => {
288
293
  let t = e.metadata?.memberships;
289
294
  return Array.isArray(t) ? t.filter((e) => typeof e == "object" && !!e && typeof e.tenantId == "string" && typeof e.role == "string") : [];
290
295
  }, _ = (e) => typeof e.isLive == "function", v = (e) => e.revocation && _(e.revocation) ? e.revocation : e.store ? l(e.store, e.revocation ?? {}) : null, y = (t = {}) => {
291
- let o = t.cookieName ?? "voltro:session", s = v(t);
296
+ let o = t.cookieName ?? "voltro:session", s = v(t), c = t.revocation && !_(t.revocation) ? t.revocation : {}, u = /* @__PURE__ */ new WeakMap(), d = (e) => {
297
+ if (s) return s;
298
+ let n = t.sessionRegistry;
299
+ if (n === void 0) return null;
300
+ if (typeof n != "function") return l(n, c);
301
+ if (e === void 0) return null;
302
+ let r = u.get(e);
303
+ if (r) return r;
304
+ let i = l(n(e), c);
305
+ return u.set(e, i), i;
306
+ };
292
307
  return {
293
308
  id: "voltro-password",
294
- resolve: (c) => {
295
- let l = r(c.headers.cookie, o);
296
- if (!l) return { kind: "skip" };
297
- let u = e(t.secret ?? i()), d = a(l, u);
298
- if (!d) return {
309
+ resolve: (s) => {
310
+ let c = r(s.headers, o);
311
+ if (!c) return { kind: "skip" };
312
+ let l = e(t.secret ?? i()), u = a(c, l);
313
+ if (!u) return {
299
314
  kind: "failed",
300
315
  reason: "voltro session cookie failed verification"
301
316
  };
302
- let f = b(d.subject, "voltro-password");
303
- if (!s) return {
317
+ let f = b(u.subject, "voltro-password"), p = h(f);
318
+ if (!p) return t.requireSessionId === !0 ? {
319
+ kind: "failed",
320
+ reason: "voltro session cookie carries no session id"
321
+ } : {
304
322
  kind: "matched",
305
323
  subject: f
306
324
  };
307
- let p = h(f);
308
- return p ? n.runPromise(s.isLive(p)).then((e) => e ? {
325
+ let m = d(s.store);
326
+ return m ? n.runPromise(m.isLive(p)).then((e) => e ? {
309
327
  kind: "matched",
310
328
  subject: f
311
329
  } : {
312
330
  kind: "failed",
313
331
  reason: "voltro session revoked"
314
- }) : {
332
+ }) : t.requireSessionId === !0 && t.sessionRegistry !== void 0 ? {
333
+ kind: "failed",
334
+ reason: "session registry not available yet"
335
+ } : {
315
336
  kind: "matched",
316
337
  subject: f
317
338
  };
@@ -1,4 +1,5 @@
1
1
  import { AuthStrategy } from '@voltro/protocol';
2
+ import { DataStore } from '@voltro/database';
2
3
  import { Effect } from 'effect';
3
4
  import { SessionSecrets } from '@voltro/protocol/session';
4
5
  import { VoidIfEmpty } from 'effect/Types';
@@ -233,6 +234,10 @@ declare interface SessionRecord {
233
234
  readonly lastSeenAt: Date;
234
235
  }
235
236
 
237
+ /** The session rows an app registers and revokes — the session half of a
238
+ * `UserStore`, usable without the rest of it. */
239
+ declare type SessionRegistry = SessionStore;
240
+
236
241
  declare interface SessionRevocationChecker {
237
242
  /** Is this session still live? `false` means the row was revoked (or
238
243
  * is past its `expiresAt`) — the caller rejects the cookie. Verdicts
@@ -286,9 +291,13 @@ declare interface SessionStore {
286
291
  /** Revoke every session for the user EXCEPT `keepSessionId` — the
287
292
  * "sign out other devices" action. Returns the count removed. */
288
293
  readonly revokeAllOtherSessions: (userId: string, keepSessionId: string) => Effect.Effect<number>;
294
+ /** Revoke every session for the user — "sign out everywhere": a password
295
+ * reset, a deactivation, an account deletion, a security event. Returns
296
+ * the count removed. */
297
+ readonly revokeAllSessions: (userId: string) => Effect.Effect<number>;
289
298
  }
290
299
 
291
- declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify';
300
+ declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify' | 'native-code';
292
301
 
293
302
  /** A single-use, hashed, expiring token (magic-link / password-reset). */
294
303
  declare interface TokenRecord {
@@ -421,6 +430,17 @@ export declare interface VoltroPasswordStrategyOptions {
421
430
  * routes and the rpc auth chain. Ignored without a `store`
422
431
  * (unless a checker is passed directly). */
423
432
  readonly revocation?: SessionRevocationOptions | SessionRevocationChecker;
433
+ /** Session rows to check cookies against when the app mints its own
434
+ * sessions (`issueRegisteredSession`): a registry, or a factory over the
435
+ * app's store (`dataStoreSessionRegistry`) — built once per store, from
436
+ * the `store` the framework hands every strategy. The checker is tuned by
437
+ * `revocation` (options) as for `store`. */
438
+ readonly sessionRegistry?: SessionRegistry | ((store: DataStore) => SessionRegistry);
439
+ /** Refuse a cookie that carries no `metadata.sessionId` — one minted
440
+ * outside the registry. Without this such a cookie is accepted as before
441
+ * (stateless); with it, nothing but a registered session authenticates,
442
+ * and every session can be revoked. */
443
+ readonly requireSessionId?: boolean;
424
444
  }
425
445
 
426
446
  export { }
package/dist/strategy.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./session.js";
2
- import { t as e } from "./strategy-MP_KxNuA.js";
2
+ import { t as e } from "./strategy-XaGjSicX.js";
3
3
  export { e as voltroPasswordStrategy };
package/dist/tokens.d.ts CHANGED
@@ -58,9 +58,13 @@ export declare const mintSecret: (ttlSeconds: number) => Omit<MintedToken, "purp
58
58
  */
59
59
  export declare const mintToken: (purpose: TokenPurpose, ttlSeconds?: number) => MintedToken;
60
60
 
61
+ /** Lifetime of a native-login one-time code: the app redeems it within
62
+ * seconds of the browser redirect; five minutes covers a slow hand-off. */
63
+ export declare const NATIVE_CODE_TTL_S: number;
64
+
61
65
  /** Default password-reset lifetime — a bit longer. */
62
66
  export declare const PASSWORD_RESET_TTL_S: number;
63
67
 
64
- declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify';
68
+ declare type TokenPurpose = 'magic-link' | 'password-reset' | 'mfa-pending' | 'email-verify' | 'native-code';
65
69
 
66
70
  export { }
package/dist/tokens.js CHANGED
@@ -1,20 +1,21 @@
1
1
  import { createHash as e, randomBytes as t } from "node:crypto";
2
2
  //#region src/tokens.ts
3
- var n = 900, r = 3600, i = 86400, a = 300, o = {
3
+ var n = 900, r = 3600, i = 86400, a = 300, o = 300, s = {
4
4
  "magic-link": 900,
5
5
  "password-reset": r,
6
6
  "mfa-pending": 300,
7
- "email-verify": i
8
- }, s = (e) => e.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""), c = (t) => e("sha256").update(t).digest("hex"), l = (e) => {
9
- let n = s(t(32));
7
+ "email-verify": i,
8
+ "native-code": 300
9
+ }, c = (e) => e.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""), l = (t) => e("sha256").update(t).digest("hex"), u = (e) => {
10
+ let n = c(t(32));
10
11
  return {
11
12
  token: n,
12
- tokenHash: c(n),
13
+ tokenHash: l(n),
13
14
  expiresAt: new Date(Date.now() + e * 1e3)
14
15
  };
15
- }, u = (e, t) => ({
16
- ...l(t ?? o[e]),
16
+ }, d = (e, t) => ({
17
+ ...u(t ?? s[e]),
17
18
  purpose: e
18
19
  });
19
20
  //#endregion
20
- export { i as EMAIL_VERIFY_TTL_S, n as MAGIC_LINK_TTL_S, a as MFA_PENDING_TTL_S, r as PASSWORD_RESET_TTL_S, c as hashToken, l as mintSecret, u as mintToken };
21
+ export { i as EMAIL_VERIFY_TTL_S, n as MAGIC_LINK_TTL_S, a as MFA_PENDING_TTL_S, o as NATIVE_CODE_TTL_S, r as PASSWORD_RESET_TTL_S, l as hashToken, u as mintSecret, d as mintToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-auth",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "Authentication primitives: password hashing, session creation, schema mixin + tables. Pairs with the `auth` app template for the UI; both independently usable. Server-side only (uses node:crypto).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -68,6 +68,11 @@
68
68
  "import": "./dist/web.js",
69
69
  "default": "./dist/web.js"
70
70
  },
71
+ "./native": {
72
+ "types": "./dist/native.d.ts",
73
+ "import": "./dist/native.js",
74
+ "default": "./dist/native.js"
75
+ },
71
76
  "./package.json": "./package.json"
72
77
  },
73
78
  "main": "./dist/index.js",
@@ -79,8 +84,8 @@
79
84
  },
80
85
  "dependencies": {
81
86
  "@effect/sql": "^0.52.1",
82
- "@voltro/database": "0.81.0",
83
- "@voltro/protocol": "0.81.0"
87
+ "@voltro/database": "0.83.0",
88
+ "@voltro/protocol": "0.83.0"
84
89
  },
85
90
  "peerDependencies": {
86
91
  "effect": "^3.22.2",