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

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/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import * as _spfn_core_nextjs from '@spfn/core/nextjs';
2
- import { P as PermissionConfig, R as RoleConfig, m as mainAuthRouter, S as SendVerificationCodeResult, a as RegisterResult, b as RequestSignupLinkResult, C as ConfirmSignupLinkResult, c as RequestPasswordResetResult, d as ConfirmPasswordResetResult, L as LoginResult, e as StartDeviceAuthResult, D as DeviceAuthInfoResult, F as FinishPasskeyEnrollmentResult, f as PasskeySummary, T as TotpEnrolmentResult, g as ConfirmTotpResult, M as MfaStatus, h as RotateKeyResult, K as KeySummary, i as RevokeAllKeysResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, j as OAuthNativeResult, U as UserProfile, k as ProfileInfo, l as OAuth2ConsentView, n as OAuth2AuthorizationCodeIssued, o as OAuth2GrantSummary } from './machine-principals-CaEFq61K.js';
3
- export { A as AuthInitOptions, p as AuthSession, q as PERMISSION_CATEGORIES, r as PermissionCategory, V as VERIFICATION_PURPOSES, s as VERIFICATION_TARGET_TYPES, t as VerificationPurpose, u as VerificationTargetType } from './machine-principals-CaEFq61K.js';
2
+ import { P as PermissionConfig, R as RoleConfig, m as mainAuthRouter, S as SendVerificationCodeResult, a as RegisterResult, b as RequestSignupLinkResult, C as ConfirmSignupLinkResult, c as RequestPasswordResetResult, d as ConfirmPasswordResetResult, L as LoginBindingFields, e as LoginResult, f as StartDeviceAuthResult, D as DeviceAuthInfoResult, F as FinishPasskeyEnrollmentResult, g as PasskeySummary, T as TotpEnrolmentResult, h as ConfirmTotpResult, M as MfaStatus, i as RotateKeyResult, K as KeySummary, j as RevokeAllKeysResult, k as SessionBindingResult, l as SessionRenewResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, n as OAuthNativeResult, U as UserProfile, o as ProfileInfo, p as OAuth2ConsentView, q as OAuth2AuthorizationCodeIssued, r as OAuth2GrantSummary } from './machine-principals-ZJd9anVT.js';
3
+ export { A as AuthInitOptions, s as AuthSession, t as PERMISSION_CATEGORIES, u as PermissionCategory, V as VERIFICATION_PURPOSES, v as VERIFICATION_TARGET_TYPES, w as VerificationPurpose, x as VerificationTargetType } from './machine-principals-ZJd9anVT.js';
4
4
  import * as _simplewebauthn_server from '@simplewebauthn/server';
5
5
  import * as _spfn_core_route from '@spfn/core/route';
6
6
  import { HttpMethod } from '@spfn/core/route';
7
- export { A as ACCOUNT_DELETION_REQUESTED_BY, a as ACCOUNT_DELETION_REQUEST_STATUSES, b as AccountDeletionRequestStatus, c as AccountDeletionRequestedBy, I as INVITATION_STATUSES, d as InvitationStatus, e as KEY_ALGORITHM, f as KEY_DEVICE_NAME_MAX_LENGTH, g as KEY_PLATFORM, K as KeyAlgorithmType, h as KeyPlatformType, P as PURGE_STRATEGIES, i as PurgeStrategy, S as SOCIAL_PROVIDERS, j as SocialProvider, U as USER_STATUSES, k as UserStatus } from './types-DYyhze28.js';
7
+ import { S as SessionBindingType } from './types-CTdoTOxM.js';
8
+ export { A as ACCOUNT_DELETION_REQUESTED_BY, a as ACCOUNT_DELETION_REQUEST_STATUSES, b as AccountDeletionRequestStatus, c as AccountDeletionRequestedBy, I as INVITATION_STATUSES, d as InvitationStatus, e as KEY_ALGORITHM, f as KEY_DEVICE_NAME_MAX_LENGTH, g as KEY_PLATFORM, K as KeyAlgorithmType, h as KeyPlatformType, P as PURGE_STRATEGIES, i as PurgeStrategy, j as SESSION_BINDINGS, k as SOCIAL_PROVIDERS, l as SocialProvider, U as USER_STATUSES, m as UserStatus } from './types-CTdoTOxM.js';
8
9
  import * as _sinclair_typebox from '@sinclair/typebox';
9
10
  import 'drizzle-orm/pg-core';
10
11
  import '@spfn/core/event';
@@ -180,7 +181,7 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
180
181
  deviceName: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
181
182
  platform: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ios" | "android" | "web" | "desktop">[]>>;
182
183
  }>;
183
- }, RegisterResult>;
184
+ }, RegisterResult & LoginBindingFields>;
184
185
  login: _spfn_core_route.RouteDef<{
185
186
  body: _sinclair_typebox.TObject<{
186
187
  email: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
@@ -218,6 +219,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
218
219
  } | {
219
220
  email?: string | undefined;
220
221
  phone?: string | undefined;
222
+ sessionBinding?: "none" | "passkey" | undefined;
223
+ keyExpiresAtMillis?: number | undefined;
221
224
  status: "approved";
222
225
  userId: string;
223
226
  publicId: string;
@@ -394,6 +397,32 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
394
397
  phoneVerified: boolean;
395
398
  hasPassword: boolean;
396
399
  }>;
400
+ setSessionBinding: _spfn_core_route.RouteDef<{
401
+ body: _sinclair_typebox.TObject<{
402
+ mode: _sinclair_typebox.TUnion<[_sinclair_typebox.TLiteral<"passkey">, _sinclair_typebox.TLiteral<"none">]>;
403
+ response: _sinclair_typebox.TOptional<_sinclair_typebox.TUnknown>;
404
+ currentPassword: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
405
+ }>;
406
+ }, {}, SessionBindingResult>;
407
+ getSessionBinding: _spfn_core_route.RouteDef<{}, {}, SessionBindingResult>;
408
+ sessionBindingDisableOptions: _spfn_core_route.RouteDef<{
409
+ body: _sinclair_typebox.TObject<{}>;
410
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
411
+ sessionRenewOptions: _spfn_core_route.RouteDef<{
412
+ body: _sinclair_typebox.TObject<{}>;
413
+ }, {}, _simplewebauthn_server.PublicKeyCredentialRequestOptionsJSON>;
414
+ sessionRenewVerify: _spfn_core_route.RouteDef<{
415
+ body: _sinclair_typebox.TObject<{
416
+ response: _sinclair_typebox.TUnknown;
417
+ }>;
418
+ }, {
419
+ body: _sinclair_typebox.TObject<{
420
+ publicKey: _sinclair_typebox.TString;
421
+ keyId: _sinclair_typebox.TString;
422
+ fingerprint: _sinclair_typebox.TString;
423
+ algorithm: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<_sinclair_typebox.TLiteral<"ES256" | "RS256">[]>>;
424
+ }>;
425
+ }, SessionRenewResult>;
397
426
  issueOneTimeToken: _spfn_core_route.RouteDef<{}, {}, IssueOneTimeTokenResult>;
398
427
  requestAccountDeletion: _spfn_core_route.RouteDef<{
399
428
  body: _sinclair_typebox.TObject<{
@@ -456,6 +485,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
456
485
  returnUrl: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
457
486
  }>;
458
487
  }, {}, {
488
+ sessionBinding?: SessionBindingType;
489
+ keyExpiresAtMillis?: number;
459
490
  success: boolean;
460
491
  userId: string;
461
492
  keyId: string;
@@ -670,6 +701,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
670
701
  emailVerifiedAt: Date | null;
671
702
  phoneVerifiedAt: Date | null;
672
703
  keyEpoch: number;
704
+ sessionBinding: "none" | "passkey";
705
+ sessionBindingChangedAt: Date | null;
673
706
  lastLoginAt: Date | null;
674
707
  }>;
675
708
  updateLocale: _spfn_core_route.RouteDef<{
@@ -843,4 +876,4 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
843
876
  }>>;
844
877
  type AuthRouter = typeof mainAuthRouter;
845
878
 
846
- export { type AuthRouter, BASE64_PATTERN, BUILTIN_PERMISSIONS, BUILTIN_ROLES, BUILTIN_ROLE_PERMISSIONS, type BuiltinPermissionName, type BuiltinRoleName, EMAIL_PATTERN, FINGERPRINT_PATTERN, PHONE_PATTERN, PermissionConfig, ProfileInfo, RoleConfig, UUID_PATTERN, UserProfile, authApi, routeMap as authRouteMap };
879
+ export { type AuthRouter, BASE64_PATTERN, BUILTIN_PERMISSIONS, BUILTIN_ROLES, BUILTIN_ROLE_PERMISSIONS, type BuiltinPermissionName, type BuiltinRoleName, EMAIL_PATTERN, FINGERPRINT_PATTERN, PHONE_PATTERN, PermissionConfig, ProfileInfo, RoleConfig, SessionBindingType, UUID_PATTERN, UserProfile, authApi, routeMap as authRouteMap };
package/dist/index.js CHANGED
@@ -12,12 +12,14 @@ import { ErrorRegistry } from "@spfn/core/errors";
12
12
 
13
13
  // src/errors/auth-errors.ts
14
14
  import {
15
+ BadRequestError,
15
16
  ValidationError,
16
17
  UnauthorizedError,
17
18
  ForbiddenError,
18
19
  ConflictError,
19
20
  NotFoundError,
20
- HttpError
21
+ HttpError,
22
+ InternalServerError
21
23
  } from "@spfn/core/errors";
22
24
  var InvalidCredentialsError = class extends UnauthorizedError {
23
25
  constructor(data = {}) {
@@ -366,6 +368,56 @@ var LastRecoveryCredentialError = class extends ConflictError {
366
368
  this.name = "LastRecoveryCredentialError";
367
369
  }
368
370
  };
371
+ var SessionRenewalRequiredError = class extends UnauthorizedError {
372
+ code = "SESSION_RENEWAL_REQUIRED";
373
+ constructor(data = {}) {
374
+ super({
375
+ message: data.message || "This session needs renewing. Confirm with your passkey to continue.",
376
+ details: data.details
377
+ });
378
+ this.name = "SessionRenewalRequiredError";
379
+ }
380
+ };
381
+ var SessionRenewalRefusedError = class extends UnauthorizedError {
382
+ code = "SESSION_RENEWAL_REFUSED";
383
+ constructor(data = {}) {
384
+ super({
385
+ message: data.message || "This session cannot be renewed. Sign in again.",
386
+ details: data.details
387
+ });
388
+ this.name = "SessionRenewalRefusedError";
389
+ }
390
+ };
391
+ var SessionContextChangedError = class extends UnauthorizedError {
392
+ code = "SESSION_CONTEXT_CHANGED";
393
+ constructor(data = {}) {
394
+ super({
395
+ message: data.message || "This session was started in a different browser. Sign in again.",
396
+ details: data.details
397
+ });
398
+ this.name = "SessionContextChangedError";
399
+ }
400
+ };
401
+ var SessionBindingUnavailableError = class extends BadRequestError {
402
+ code = "SESSION_BINDING_UNAVAILABLE";
403
+ constructor(data = {}) {
404
+ super({
405
+ message: data.message || "Session binding needs a deployment where the backend can recognise the Next.js proxy. Configure proxy-guard (SPFN_PROXY_SIGNATURE_KEYS) and try again.",
406
+ details: data.details
407
+ });
408
+ this.name = "SessionBindingUnavailableError";
409
+ }
410
+ };
411
+ var SessionResealFailedError = class extends InternalServerError {
412
+ code = "SESSION_RESEAL_FAILED";
413
+ constructor(data = {}) {
414
+ super({
415
+ message: data.message || "The setting was changed but this session could not be updated. Sign in again.",
416
+ details: data.details
417
+ });
418
+ this.name = "SessionResealFailedError";
419
+ }
420
+ };
369
421
  var PasskeyConfigError = class extends HttpError {
370
422
  constructor(data) {
371
423
  super({
@@ -508,6 +560,11 @@ authErrorRegistry.append([
508
560
  RecentAuthenticationRequiredError,
509
561
  LastRecoveryCredentialError,
510
562
  PasskeyConfigError,
563
+ SessionBindingUnavailableError,
564
+ SessionRenewalRequiredError,
565
+ SessionRenewalRefusedError,
566
+ SessionContextChangedError,
567
+ SessionResealFailedError,
511
568
  OAuth2UnknownClientError,
512
569
  OAuth2RedirectUriMismatchError,
513
570
  OAuth2AuthorizeRedirectError,
@@ -554,6 +611,11 @@ var routeMap = {
554
611
  mfaStatus: { method: "GET", path: "/_auth/mfa/status" },
555
612
  mfaStepUp: { method: "POST", path: "/_auth/mfa/step-up" },
556
613
  mfaStepUpOptions: { method: "POST", path: "/_auth/mfa/step-up/options" },
614
+ setSessionBinding: { method: "POST", path: "/_auth/session/binding" },
615
+ getSessionBinding: { method: "GET", path: "/_auth/session/binding" },
616
+ sessionBindingDisableOptions: { method: "POST", path: "/_auth/session/binding/disable/options" },
617
+ sessionRenewOptions: { method: "POST", path: "/_auth/session/renew/options" },
618
+ sessionRenewVerify: { method: "POST", path: "/_auth/session/renew/verify" },
557
619
  passkeyRegisterOptions: { method: "POST", path: "/_auth/passkeys/register/options" },
558
620
  passkeyRegisterVerify: { method: "POST", path: "/_auth/passkeys/register/verify" },
559
621
  passkeyLoginOptions: { method: "POST", path: "/_auth/passkeys/login/options" },
@@ -740,6 +802,7 @@ var BUILTIN_ROLE_PERMISSIONS = {
740
802
  var KEY_ALGORITHM = ["ES256", "RS256"];
741
803
  var KEY_PLATFORM = ["ios", "android", "web", "desktop"];
742
804
  var KEY_DEVICE_NAME_MAX_LENGTH = 64;
805
+ var SESSION_BINDINGS = ["none", "passkey"];
743
806
  var INVITATION_STATUSES = ["pending", "accepted", "expired", "cancelled"];
744
807
  var USER_STATUSES = ["active", "inactive", "suspended", "pending_deletion", "deleted"];
745
808
  var SOCIAL_PROVIDERS = ["google", "apple", "github", "kakao", "naver", "superself"];
@@ -3404,7 +3467,15 @@ var DeviceAuthPollResponseSchema = Type.Union([
3404
3467
  publicId: Type.String(),
3405
3468
  email: Type.Optional(Type.String()),
3406
3469
  phone: Type.Optional(Type.String()),
3407
- passwordChangeRequired: Type.Boolean()
3470
+ passwordChangeRequired: Type.Boolean(),
3471
+ // The approved branch is a `LoginResult` spread whole, so it carries the
3472
+ // binding fields a sign-in can carry (#97). In practice it never does: a
3473
+ // waiting device polls the backend directly, and a key is bound only on a
3474
+ // request that reached it through the trusted Next.js proxy. Declared
3475
+ // anyway, because the branch is whatever the login answered and the two
3476
+ // must not be able to drift apart.
3477
+ sessionBinding: Type.Optional(Type.Union(SESSION_BINDINGS.map((mode) => Type.Literal(mode)))),
3478
+ keyExpiresAtMillis: Type.Optional(Type.Integer())
3408
3479
  })
3409
3480
  ], {
3410
3481
  description: "Pending, or the login the approval produced"