@spfn/auth 0.3.0-beta.23 → 0.3.0-beta.25

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.
Files changed (35) hide show
  1. package/README.md +328 -7
  2. package/dist/client-proof.d.ts +10 -1
  3. package/dist/client-proof.js +136 -11
  4. package/dist/client-proof.js.map +1 -1
  5. package/dist/client.d.ts +101 -1
  6. package/dist/client.js +65 -0
  7. package/dist/client.js.map +1 -1
  8. package/dist/config.d.ts +122 -0
  9. package/dist/config.js +53 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/crypto.d.ts +1 -1
  12. package/dist/errors.d.ts +159 -3
  13. package/dist/errors.js +95 -2
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.d.ts +52 -12
  16. package/dist/index.js +104 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/{machine-principals-CaEFq61K.d.ts → machine-principals-CdEgxOB1.d.ts} +2049 -771
  19. package/dist/nextjs/api.js +329 -37
  20. package/dist/nextjs/api.js.map +1 -1
  21. package/dist/nextjs/server.d.ts +59 -24
  22. package/dist/nextjs/server.js +105 -11
  23. package/dist/nextjs/server.js.map +1 -1
  24. package/dist/server.d.ts +415 -332
  25. package/dist/server.js +2933 -1545
  26. package/dist/server.js.map +1 -1
  27. package/dist/{session-Dfwu5g2W.d.ts → session-BbhAGZtA.d.ts} +57 -1
  28. package/dist/{types-DYyhze28.d.ts → types-CTdoTOxM.d.ts} +24 -1
  29. package/migrations/20260918184037_happy_mordo/migration.sql +4 -0
  30. package/migrations/20260918184037_happy_mordo/snapshot.json +6000 -0
  31. package/migrations/20260918184152_dear_rictor/migration.sql +3 -0
  32. package/migrations/20260918184152_dear_rictor/snapshot.json +6039 -0
  33. package/migrations/20260919023107_even_mikhail_rasputin/migration.sql +20 -0
  34. package/migrations/20260919023107_even_mikhail_rasputin/snapshot.json +6300 -0
  35. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { K as KeyAlgorithmType } from './types-DYyhze28.js';
1
+ import { K as KeyAlgorithmType, S as SessionBindingType } from './types-CTdoTOxM.js';
2
2
 
3
3
  /**
4
4
  * @spfn/auth - Return-path validation
@@ -27,6 +27,35 @@ import { K as KeyAlgorithmType } from './types-DYyhze28.js';
27
27
  */
28
28
  declare function isSafeReturnPath(returnPath: string): boolean;
29
29
 
30
+ /**
31
+ * The browser family a `user-agent` names — five badges, and nothing else.
32
+ *
33
+ * A bound session records the family it was sealed from and the Next.js proxy
34
+ * compares every later request against it (#97). The comparison has to be coarse
35
+ * on purpose: a version bump, a minor-version reduction, a platform token that
36
+ * changes when someone taps "Request desktop site" must all still be the same
37
+ * browser, or the check signs people out for ordinary acts instead of catching
38
+ * the cookie that moved to another machine.
39
+ *
40
+ * A fixed table rather than a parsing dependency. `package.json` carries no
41
+ * user-agent parser and adding one to answer a five-valued question would be out
42
+ * of proportion; the table below is the whole of what this package needs to know
43
+ * about user-agent strings.
44
+ *
45
+ * @module server/lib/ua-family
46
+ */
47
+ /**
48
+ * The five answers, and deliberately no sixth.
49
+ *
50
+ * There is no desktop/mobile axis. Android's "Request desktop site" flips the
51
+ * platform token on the same browser in the same cookie jar, and a session that
52
+ * refused after that would be a support ticket for a thing the user did on
53
+ * purpose. What this check is looking for is a cookie that moved to a *different*
54
+ * browser, and the browser is what the badge names.
55
+ */
56
+ declare const UA_FAMILIES: readonly ["edge", "chrome", "firefox", "safari", "other"];
57
+ type UaFamily = typeof UA_FAMILIES[number];
58
+
30
59
  /**
31
60
  * @spfn/auth - Client Session Management
32
61
  *
@@ -34,11 +63,38 @@ declare function isSafeReturnPath(returnPath: string): boolean;
34
63
  * More efficient than Iron Session with better Edge Runtime support
35
64
  */
36
65
 
66
+ /**
67
+ * What the sealed cookie carries.
68
+ *
69
+ * The first four fields are the session itself and have always been here. The
70
+ * last three are what #97 added, and all three are optional together: a cookie
71
+ * without them is an unbound session, which is every session an account that did
72
+ * not opt in gets and every session an app seals by hand with `saveSession()`.
73
+ * The proxy reads their absence as "behave exactly as before".
74
+ */
37
75
  interface SessionData {
38
76
  userId: string;
39
77
  privateKey: string;
40
78
  keyId: string;
41
79
  algorithm: KeyAlgorithmType;
80
+ /**
81
+ * `'passkey'` when the key sealed here is bound.
82
+ *
83
+ * The backend is the only party that knows an account opted in — the proxy
84
+ * generated the key but never saw the setting — so this is copied out of the
85
+ * `LoginResult` the sign-in answered with. Absent means unbound.
86
+ */
87
+ binding?: SessionBindingType;
88
+ /** Epoch milliseconds the bound key expires at. Only set alongside `binding`. */
89
+ keyExpiresAt?: number;
90
+ /**
91
+ * Browser family the session was sealed from, per `uaFamily`.
92
+ *
93
+ * Recorded here rather than read off the key row because the comparison
94
+ * happens in the proxy: it is the only hop that sees the browser's own
95
+ * `user-agent`, and a server component's call to the RPC proxy carries none.
96
+ */
97
+ uaFamily?: UaFamily;
42
98
  }
43
99
  /**
44
100
  * Seal session data into encrypted JWT (JWE)
@@ -28,6 +28,29 @@ declare const KEY_PLATFORM: readonly ["ios", "android", "web", "desktop"];
28
28
  type KeyPlatformType = typeof KEY_PLATFORM[number];
29
29
  /** Longest device label accepted at registration, and what the list returns. */
30
30
  declare const KEY_DEVICE_NAME_MAX_LENGTH = 64;
31
+ /**
32
+ * Whether a session's device key is tied to something that cannot be copied out
33
+ * of the browser.
34
+ *
35
+ * `'none'` is every session this package issued before #97: a key that lives
36
+ * ninety days, sealed inside the cookie, so a copy of that cookie signs exactly
37
+ * as the original does for as long as the key lasts. `'passkey'` is the opt-in —
38
+ * a key that lives hours instead, which only a fresh WebAuthn assertion may
39
+ * replace, so the copy stops working at the first renewal.
40
+ *
41
+ * The same two values name the account setting (`users.session_binding`) and the
42
+ * fact recorded on one key row (`user_public_keys.binding`). They are one list
43
+ * because a key is bound exactly when the account that owns it asked for it, and
44
+ * a second vocabulary would only invite the two to drift.
45
+ *
46
+ * Server-decided, always. Nothing on a request body chooses it — see
47
+ * `registerPublicKeyService`.
48
+ */
49
+ declare const SESSION_BINDINGS: readonly ["none", "passkey"];
50
+ /**
51
+ * Session binding type derived from the const array
52
+ */
53
+ type SessionBindingType = typeof SESSION_BINDINGS[number];
31
54
  /**
32
55
  * Invitation status enum values
33
56
  * Single source of truth for all invitation statuses
@@ -95,4 +118,4 @@ declare const PURGE_STRATEGIES: readonly ["anonymize", "hard-delete"];
95
118
  */
96
119
  type PurgeStrategy = typeof PURGE_STRATEGIES[number];
97
120
 
98
- export { ACCOUNT_DELETION_REQUESTED_BY as A, INVITATION_STATUSES as I, type KeyAlgorithmType as K, PURGE_STRATEGIES as P, SOCIAL_PROVIDERS as S, USER_STATUSES as U, ACCOUNT_DELETION_REQUEST_STATUSES as a, type AccountDeletionRequestStatus as b, type AccountDeletionRequestedBy as c, type InvitationStatus as d, KEY_ALGORITHM as e, KEY_DEVICE_NAME_MAX_LENGTH as f, KEY_PLATFORM as g, type KeyPlatformType as h, type PurgeStrategy as i, type SocialProvider as j, type UserStatus as k };
121
+ export { ACCOUNT_DELETION_REQUESTED_BY as A, INVITATION_STATUSES as I, type KeyAlgorithmType as K, PURGE_STRATEGIES as P, type SessionBindingType as S, USER_STATUSES as U, ACCOUNT_DELETION_REQUEST_STATUSES as a, type AccountDeletionRequestStatus as b, type AccountDeletionRequestedBy as c, type InvitationStatus as d, KEY_ALGORITHM as e, KEY_DEVICE_NAME_MAX_LENGTH as f, KEY_PLATFORM as g, type KeyPlatformType as h, type PurgeStrategy as i, SESSION_BINDINGS as j, SOCIAL_PROVIDERS as k, type SocialProvider as l, type UserStatus as m };
@@ -0,0 +1,4 @@
1
+ ALTER TABLE "spfn_auth"."users" ADD COLUMN "session_binding" text DEFAULT 'none' NOT NULL;--> statement-breakpoint
2
+ ALTER TABLE "spfn_auth"."users" ADD COLUMN "session_binding_changed_at" timestamp with time zone;--> statement-breakpoint
3
+ ALTER TABLE "spfn_auth"."user_public_keys" ADD COLUMN "registered_ua_family" text;--> statement-breakpoint
4
+ ALTER TABLE "spfn_auth"."user_public_keys" ADD COLUMN "binding" text DEFAULT 'none' NOT NULL;