@koolbase/core 12.1.0 → 12.2.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.
@@ -257,3 +257,29 @@ export declare class InvalidGoogleTokenError extends KoolbaseAuthError {
257
257
  export declare class GoogleEmailRequiredError extends KoolbaseAuthError {
258
258
  constructor();
259
259
  }
260
+ /**
261
+ * The first factor passed and this account needs a second: sign-in is not
262
+ * finished. Pass `challengeToken` to `verifyMfa` with a code from the
263
+ * person's authenticator app, or to `verifyRecoveryCode`. Apps that never
264
+ * enable MFA never see this.
265
+ */
266
+ export declare class MfaRequiredError extends KoolbaseAuthError {
267
+ readonly challengeToken: string;
268
+ readonly expiresAt?: string;
269
+ constructor(challengeToken: string, expiresAt?: string);
270
+ }
271
+ export declare class RecentAuthRequiredError extends KoolbaseAuthError {
272
+ constructor();
273
+ }
274
+ export declare class RecentMfaRequiredError extends KoolbaseAuthError {
275
+ constructor();
276
+ }
277
+ export declare class MfaAlreadyEnabledError extends KoolbaseAuthError {
278
+ constructor();
279
+ }
280
+ export declare class MfaEnrollmentNotFoundError extends KoolbaseAuthError {
281
+ constructor();
282
+ }
283
+ export declare class MfaNotEnabledError extends KoolbaseAuthError {
284
+ constructor();
285
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GoogleEmailRequiredError = exports.InvalidGoogleTokenError = exports.MalformedSessionResponseError = exports.ProviderIdentityAlreadyLinkedError = exports.IdentityNotFoundError = exports.InsufficientScopeError = exports.HideRequiresVerificationError = exports.LastCredentialError = exports.InsufficientAuthorityError = exports.SessionRequiredError = exports.UnsupportedOAuthProviderError = exports.OAuthOnlyAccountError = exports.AccountExistsError = exports.CurrentPasswordIncorrectError = exports.TokenAlreadyUsedError = exports.TokenExpiredError = exports.SignupsDisabledError = exports.ContactNotVerifiedError = exports.GoogleSignInNotConfiguredError = exports.OAuthEmailConflictError = exports.AppleEmailRequiredError = exports.InvalidAppleTokenError = exports.AppleSignInNotConfiguredError = exports.SmsConfigMissingError = exports.PhoneAlreadyLinkedError = exports.OtpRateLimitError = exports.OtpMaxAttemptsError = exports.OtpInvalidError = exports.OtpExpiredError = exports.EmailCodeDisabledError = exports.InvalidPhoneNumberError = exports.NetworkError = exports.VerificationResendDailyCapError = exports.VerificationResendCooldownError = exports.RateLimitError = exports.UnlockTokenInvalidError = exports.AccountLockedError = exports.TokenRevokedError = exports.SessionExpiredError = exports.WeakPasswordError = exports.UserDisabledError = exports.EmailAlreadyInUseError = exports.InvalidCredentialsError = exports.KoolbaseAuthError = void 0;
3
+ exports.MfaNotEnabledError = exports.MfaEnrollmentNotFoundError = exports.MfaAlreadyEnabledError = exports.RecentMfaRequiredError = exports.RecentAuthRequiredError = exports.MfaRequiredError = exports.GoogleEmailRequiredError = exports.InvalidGoogleTokenError = exports.MalformedSessionResponseError = exports.ProviderIdentityAlreadyLinkedError = exports.IdentityNotFoundError = exports.InsufficientScopeError = exports.HideRequiresVerificationError = exports.LastCredentialError = exports.InsufficientAuthorityError = exports.SessionRequiredError = exports.UnsupportedOAuthProviderError = exports.OAuthOnlyAccountError = exports.AccountExistsError = exports.CurrentPasswordIncorrectError = exports.TokenAlreadyUsedError = exports.TokenExpiredError = exports.SignupsDisabledError = exports.ContactNotVerifiedError = exports.GoogleSignInNotConfiguredError = exports.OAuthEmailConflictError = exports.AppleEmailRequiredError = exports.InvalidAppleTokenError = exports.AppleSignInNotConfiguredError = exports.SmsConfigMissingError = exports.PhoneAlreadyLinkedError = exports.OtpRateLimitError = exports.OtpMaxAttemptsError = exports.OtpInvalidError = exports.OtpExpiredError = exports.EmailCodeDisabledError = exports.InvalidPhoneNumberError = exports.NetworkError = exports.VerificationResendDailyCapError = exports.VerificationResendCooldownError = exports.RateLimitError = exports.UnlockTokenInvalidError = exports.AccountLockedError = exports.TokenRevokedError = exports.SessionExpiredError = exports.WeakPasswordError = exports.UserDisabledError = exports.EmailAlreadyInUseError = exports.InvalidCredentialsError = exports.KoolbaseAuthError = void 0;
4
4
  const errors_js_1 = require("./errors.js");
5
5
  /**
6
6
  * Base error type for all Koolbase auth errors. Catchable via
@@ -485,3 +485,60 @@ class GoogleEmailRequiredError extends KoolbaseAuthError {
485
485
  }
486
486
  }
487
487
  exports.GoogleEmailRequiredError = GoogleEmailRequiredError;
488
+ // ─── Two-step sign-in (MFA) ─────────────────────────────────────────────────
489
+ /**
490
+ * The first factor passed and this account needs a second: sign-in is not
491
+ * finished. Pass `challengeToken` to `verifyMfa` with a code from the
492
+ * person's authenticator app, or to `verifyRecoveryCode`. Apps that never
493
+ * enable MFA never see this.
494
+ */
495
+ class MfaRequiredError extends KoolbaseAuthError {
496
+ constructor(challengeToken, expiresAt) {
497
+ super('Two-step sign-in required', 'mfa_required');
498
+ this.name = 'MfaRequiredError';
499
+ this.challengeToken = challengeToken;
500
+ this.expiresAt = expiresAt;
501
+ Object.setPrototypeOf(this, MfaRequiredError.prototype);
502
+ }
503
+ }
504
+ exports.MfaRequiredError = MfaRequiredError;
505
+ class RecentAuthRequiredError extends KoolbaseAuthError {
506
+ constructor() {
507
+ super('Sign in again to change two-step sign-in', 'recent_auth_required');
508
+ this.name = 'RecentAuthRequiredError';
509
+ Object.setPrototypeOf(this, RecentAuthRequiredError.prototype);
510
+ }
511
+ }
512
+ exports.RecentAuthRequiredError = RecentAuthRequiredError;
513
+ class RecentMfaRequiredError extends KoolbaseAuthError {
514
+ constructor() {
515
+ super('Confirm with your authenticator first', 'recent_mfa_required');
516
+ this.name = 'RecentMfaRequiredError';
517
+ Object.setPrototypeOf(this, RecentMfaRequiredError.prototype);
518
+ }
519
+ }
520
+ exports.RecentMfaRequiredError = RecentMfaRequiredError;
521
+ class MfaAlreadyEnabledError extends KoolbaseAuthError {
522
+ constructor() {
523
+ super('Two-step sign-in is already on for this account', 'mfa_already_enabled');
524
+ this.name = 'MfaAlreadyEnabledError';
525
+ Object.setPrototypeOf(this, MfaAlreadyEnabledError.prototype);
526
+ }
527
+ }
528
+ exports.MfaAlreadyEnabledError = MfaAlreadyEnabledError;
529
+ class MfaEnrollmentNotFoundError extends KoolbaseAuthError {
530
+ constructor() {
531
+ super('No enrolment in progress; start again', 'mfa_enrollment_not_found');
532
+ this.name = 'MfaEnrollmentNotFoundError';
533
+ Object.setPrototypeOf(this, MfaEnrollmentNotFoundError.prototype);
534
+ }
535
+ }
536
+ exports.MfaEnrollmentNotFoundError = MfaEnrollmentNotFoundError;
537
+ class MfaNotEnabledError extends KoolbaseAuthError {
538
+ constructor() {
539
+ super('Two-step sign-in is not on for this account', 'mfa_not_enabled');
540
+ this.name = 'MfaNotEnabledError';
541
+ Object.setPrototypeOf(this, MfaNotEnabledError.prototype);
542
+ }
543
+ }
544
+ exports.MfaNotEnabledError = MfaNotEnabledError;
@@ -343,6 +343,47 @@ export declare class KoolbaseAuth {
343
343
  email: string;
344
344
  code: string;
345
345
  }): Promise<KoolbaseSession>;
346
+ private mfaRequired;
347
+ /** Finishes sign-in with a code from the person's authenticator app. */
348
+ verifyMfa(params: {
349
+ challengeToken: string;
350
+ code: string;
351
+ }): Promise<KoolbaseSession>;
352
+ /**
353
+ * Finishes sign-in with a recovery code. Each works once; prompt the person
354
+ * to regenerate when recoveryCodesRemaining reaches zero.
355
+ */
356
+ verifyRecoveryCode(params: {
357
+ challengeToken: string;
358
+ code: string;
359
+ }): Promise<{
360
+ session: KoolbaseSession;
361
+ recoveryCodesRemaining: number;
362
+ }>;
363
+ /**
364
+ * Starts adding an authenticator; needs a sign-in within ten minutes. Show
365
+ * otpauthUri as a QR code, then call confirmMfaEnrollment with the first
366
+ * code the app shows.
367
+ */
368
+ enrollMfa(): Promise<{
369
+ otpauthUri: string;
370
+ secret: string;
371
+ }>;
372
+ /** Turns MFA on and returns ten recovery codes, shown this once only. Other devices are signed out. */
373
+ confirmMfaEnrollment(code: string): Promise<string[]>;
374
+ mfaStatus(): Promise<{
375
+ enabled: boolean;
376
+ recoveryCodesRemaining: number;
377
+ }>;
378
+ /** Re-confirms the second factor, opening ten minutes to change MFA. */
379
+ stepUpMfa(params: {
380
+ code?: string;
381
+ recoveryCode?: string;
382
+ }): Promise<void>;
383
+ /** Turns MFA off. Needs stepUpMfa() within the last ten minutes. */
384
+ disableMfa(): Promise<void>;
385
+ /** Replaces all recovery codes; the old ones stop working. Needs stepUpMfa() within ten minutes. */
386
+ regenerateRecoveryCodes(): Promise<string[]>;
346
387
  sendOtp(params: SendOtpParams): Promise<OtpSendResult>;
347
388
  verifyOtp(params: VerifyOtpParams): Promise<PhoneVerifyResult>;
348
389
  linkPhone(params: LinkPhoneParams): Promise<void>;
package/dist/cjs/auth.js CHANGED
@@ -388,6 +388,8 @@ class KoolbaseAuth {
388
388
  const errorMessage = body?.error ?? '';
389
389
  // ─── code-first ───
390
390
  switch (code) {
391
+ case 'mfa_required':
392
+ throw this.mfaRequired(body);
391
393
  case 'oauth_not_configured':
392
394
  throw new auth_errors_js_1.GoogleSignInNotConfiguredError();
393
395
  case 'invalid_oauth_token':
@@ -442,6 +444,8 @@ class KoolbaseAuth {
442
444
  const errorMessage = body?.error ?? '';
443
445
  // ─── code-first ───
444
446
  switch (code) {
447
+ case 'mfa_required':
448
+ throw this.mfaRequired(body);
445
449
  case 'oauth_not_configured':
446
450
  throw new auth_errors_js_1.AppleSignInNotConfiguredError();
447
451
  case 'invalid_oauth_token':
@@ -873,6 +877,102 @@ class KoolbaseAuth {
873
877
  await this.setSessionInternal(session);
874
878
  return session;
875
879
  }
880
+ // ─── Two-step sign-in (MFA) ─────────────────────────────────────────────
881
+ //
882
+ // When an account has MFA on, every sign-in method throws MfaRequiredError.
883
+ // Finish with verifyMfa() or verifyRecoveryCode().
884
+ // One builder for every parser, so the four cannot drift apart in how the
885
+ // challenge reaches the app.
886
+ mfaRequired(body) {
887
+ const d = body?.details ?? {};
888
+ return new auth_errors_js_1.MfaRequiredError(String(d.challenge_token ?? ''), d.expires_at);
889
+ }
890
+ /** Finishes sign-in with a code from the person's authenticator app. */
891
+ async verifyMfa(params) {
892
+ const res = await this.authRequest('/v1/sdk/auth/mfa/verify', {
893
+ method: 'POST',
894
+ body: { challenge_token: params.challengeToken, code: params.code },
895
+ });
896
+ const session = await this.parseSessionResponse(res, false);
897
+ await this.setSessionInternal(session);
898
+ return session;
899
+ }
900
+ /**
901
+ * Finishes sign-in with a recovery code. Each works once; prompt the person
902
+ * to regenerate when recoveryCodesRemaining reaches zero.
903
+ */
904
+ async verifyRecoveryCode(params) {
905
+ const res = await this.authRequest('/v1/sdk/auth/mfa/verify-recovery', {
906
+ method: 'POST',
907
+ body: { challenge_token: params.challengeToken, code: params.code },
908
+ });
909
+ if (!res.ok)
910
+ await this.throwTypedError(res, false);
911
+ // Read once: the session and the remaining count come from one body.
912
+ const data = await res.json();
913
+ if (!data?.access_token)
914
+ throw new auth_errors_js_1.MalformedSessionResponseError('an access token');
915
+ if (!data?.refresh_token)
916
+ throw new auth_errors_js_1.MalformedSessionResponseError('a refresh token');
917
+ const session = {
918
+ accessToken: data.access_token,
919
+ refreshToken: data.refresh_token,
920
+ expiresAt: data.expires_at,
921
+ user: this.mapUser(data.user),
922
+ };
923
+ await this.setSessionInternal(session);
924
+ return { session, recoveryCodesRemaining: Number(data.recovery_codes_remaining ?? 0) };
925
+ }
926
+ /**
927
+ * Starts adding an authenticator; needs a sign-in within ten minutes. Show
928
+ * otpauthUri as a QR code, then call confirmMfaEnrollment with the first
929
+ * code the app shows.
930
+ */
931
+ async enrollMfa() {
932
+ const res = await this.authRequest('/v1/sdk/auth/mfa/enroll', { method: 'POST', includeAuth: true });
933
+ await this.checkResponse(res);
934
+ const d = await res.json();
935
+ return { otpauthUri: d.otpauth_uri, secret: d.secret };
936
+ }
937
+ /** Turns MFA on and returns ten recovery codes, shown this once only. Other devices are signed out. */
938
+ async confirmMfaEnrollment(code) {
939
+ const res = await this.authRequest('/v1/sdk/auth/mfa/enroll/confirm', {
940
+ method: 'POST',
941
+ body: { code },
942
+ includeAuth: true,
943
+ });
944
+ await this.checkResponse(res);
945
+ return (await res.json()).recovery_codes;
946
+ }
947
+ async mfaStatus() {
948
+ const res = await this.authRequest('/v1/sdk/auth/mfa', { method: 'GET', includeAuth: true });
949
+ await this.checkResponse(res);
950
+ const d = await res.json();
951
+ return { enabled: !!d.enabled, recoveryCodesRemaining: Number(d.recovery_codes_remaining ?? 0) };
952
+ }
953
+ /** Re-confirms the second factor, opening ten minutes to change MFA. */
954
+ async stepUpMfa(params) {
955
+ const res = await this.authRequest('/v1/sdk/auth/mfa/step-up', {
956
+ method: 'POST',
957
+ body: {
958
+ ...(params.code ? { code: params.code } : {}),
959
+ ...(params.recoveryCode ? { recovery_code: params.recoveryCode } : {}),
960
+ },
961
+ includeAuth: true,
962
+ });
963
+ await this.checkResponse(res);
964
+ }
965
+ /** Turns MFA off. Needs stepUpMfa() within the last ten minutes. */
966
+ async disableMfa() {
967
+ const res = await this.authRequest('/v1/sdk/auth/mfa/disable', { method: 'POST', includeAuth: true });
968
+ await this.checkResponse(res);
969
+ }
970
+ /** Replaces all recovery codes; the old ones stop working. Needs stepUpMfa() within ten minutes. */
971
+ async regenerateRecoveryCodes() {
972
+ const res = await this.authRequest('/v1/sdk/auth/mfa/recovery-codes', { method: 'POST', includeAuth: true });
973
+ await this.checkResponse(res);
974
+ return (await res.json()).recovery_codes;
975
+ }
876
976
  // ─── Phone OTP ──────────────────────────────────────────────────────────
877
977
  async sendOtp(params) {
878
978
  this.validatePhone(params.phoneNumber);
@@ -1040,6 +1140,18 @@ class KoolbaseAuth {
1040
1140
  const msg = body.error ?? '';
1041
1141
  // ─── code-first ───
1042
1142
  switch (code) {
1143
+ case 'mfa_required':
1144
+ throw this.mfaRequired(body);
1145
+ case 'recent_auth_required':
1146
+ throw new auth_errors_js_1.RecentAuthRequiredError();
1147
+ case 'recent_mfa_required':
1148
+ throw new auth_errors_js_1.RecentMfaRequiredError();
1149
+ case 'mfa_already_enabled':
1150
+ throw new auth_errors_js_1.MfaAlreadyEnabledError();
1151
+ case 'mfa_enrollment_not_found':
1152
+ throw new auth_errors_js_1.MfaEnrollmentNotFoundError();
1153
+ case 'mfa_not_enabled':
1154
+ throw new auth_errors_js_1.MfaNotEnabledError();
1043
1155
  case 'invalid_credentials':
1044
1156
  throw new auth_errors_js_1.InvalidCredentialsError();
1045
1157
  case 'email_in_use':
@@ -1160,6 +1272,8 @@ class KoolbaseAuth {
1160
1272
  const msg = body.error ?? '';
1161
1273
  // ─── code-first ───
1162
1274
  switch (code) {
1275
+ case 'mfa_required':
1276
+ throw this.mfaRequired(body);
1163
1277
  case 'invalid_phone':
1164
1278
  throw new auth_errors_js_1.InvalidPhoneNumberError();
1165
1279
  case 'otp_expired':
@@ -257,3 +257,29 @@ export declare class InvalidGoogleTokenError extends KoolbaseAuthError {
257
257
  export declare class GoogleEmailRequiredError extends KoolbaseAuthError {
258
258
  constructor();
259
259
  }
260
+ /**
261
+ * The first factor passed and this account needs a second: sign-in is not
262
+ * finished. Pass `challengeToken` to `verifyMfa` with a code from the
263
+ * person's authenticator app, or to `verifyRecoveryCode`. Apps that never
264
+ * enable MFA never see this.
265
+ */
266
+ export declare class MfaRequiredError extends KoolbaseAuthError {
267
+ readonly challengeToken: string;
268
+ readonly expiresAt?: string;
269
+ constructor(challengeToken: string, expiresAt?: string);
270
+ }
271
+ export declare class RecentAuthRequiredError extends KoolbaseAuthError {
272
+ constructor();
273
+ }
274
+ export declare class RecentMfaRequiredError extends KoolbaseAuthError {
275
+ constructor();
276
+ }
277
+ export declare class MfaAlreadyEnabledError extends KoolbaseAuthError {
278
+ constructor();
279
+ }
280
+ export declare class MfaEnrollmentNotFoundError extends KoolbaseAuthError {
281
+ constructor();
282
+ }
283
+ export declare class MfaNotEnabledError extends KoolbaseAuthError {
284
+ constructor();
285
+ }
@@ -438,3 +438,54 @@ export class GoogleEmailRequiredError extends KoolbaseAuthError {
438
438
  Object.setPrototypeOf(this, GoogleEmailRequiredError.prototype);
439
439
  }
440
440
  }
441
+ // ─── Two-step sign-in (MFA) ─────────────────────────────────────────────────
442
+ /**
443
+ * The first factor passed and this account needs a second: sign-in is not
444
+ * finished. Pass `challengeToken` to `verifyMfa` with a code from the
445
+ * person's authenticator app, or to `verifyRecoveryCode`. Apps that never
446
+ * enable MFA never see this.
447
+ */
448
+ export class MfaRequiredError extends KoolbaseAuthError {
449
+ constructor(challengeToken, expiresAt) {
450
+ super('Two-step sign-in required', 'mfa_required');
451
+ this.name = 'MfaRequiredError';
452
+ this.challengeToken = challengeToken;
453
+ this.expiresAt = expiresAt;
454
+ Object.setPrototypeOf(this, MfaRequiredError.prototype);
455
+ }
456
+ }
457
+ export class RecentAuthRequiredError extends KoolbaseAuthError {
458
+ constructor() {
459
+ super('Sign in again to change two-step sign-in', 'recent_auth_required');
460
+ this.name = 'RecentAuthRequiredError';
461
+ Object.setPrototypeOf(this, RecentAuthRequiredError.prototype);
462
+ }
463
+ }
464
+ export class RecentMfaRequiredError extends KoolbaseAuthError {
465
+ constructor() {
466
+ super('Confirm with your authenticator first', 'recent_mfa_required');
467
+ this.name = 'RecentMfaRequiredError';
468
+ Object.setPrototypeOf(this, RecentMfaRequiredError.prototype);
469
+ }
470
+ }
471
+ export class MfaAlreadyEnabledError extends KoolbaseAuthError {
472
+ constructor() {
473
+ super('Two-step sign-in is already on for this account', 'mfa_already_enabled');
474
+ this.name = 'MfaAlreadyEnabledError';
475
+ Object.setPrototypeOf(this, MfaAlreadyEnabledError.prototype);
476
+ }
477
+ }
478
+ export class MfaEnrollmentNotFoundError extends KoolbaseAuthError {
479
+ constructor() {
480
+ super('No enrolment in progress; start again', 'mfa_enrollment_not_found');
481
+ this.name = 'MfaEnrollmentNotFoundError';
482
+ Object.setPrototypeOf(this, MfaEnrollmentNotFoundError.prototype);
483
+ }
484
+ }
485
+ export class MfaNotEnabledError extends KoolbaseAuthError {
486
+ constructor() {
487
+ super('Two-step sign-in is not on for this account', 'mfa_not_enabled');
488
+ this.name = 'MfaNotEnabledError';
489
+ Object.setPrototypeOf(this, MfaNotEnabledError.prototype);
490
+ }
491
+ }
@@ -343,6 +343,47 @@ export declare class KoolbaseAuth {
343
343
  email: string;
344
344
  code: string;
345
345
  }): Promise<KoolbaseSession>;
346
+ private mfaRequired;
347
+ /** Finishes sign-in with a code from the person's authenticator app. */
348
+ verifyMfa(params: {
349
+ challengeToken: string;
350
+ code: string;
351
+ }): Promise<KoolbaseSession>;
352
+ /**
353
+ * Finishes sign-in with a recovery code. Each works once; prompt the person
354
+ * to regenerate when recoveryCodesRemaining reaches zero.
355
+ */
356
+ verifyRecoveryCode(params: {
357
+ challengeToken: string;
358
+ code: string;
359
+ }): Promise<{
360
+ session: KoolbaseSession;
361
+ recoveryCodesRemaining: number;
362
+ }>;
363
+ /**
364
+ * Starts adding an authenticator; needs a sign-in within ten minutes. Show
365
+ * otpauthUri as a QR code, then call confirmMfaEnrollment with the first
366
+ * code the app shows.
367
+ */
368
+ enrollMfa(): Promise<{
369
+ otpauthUri: string;
370
+ secret: string;
371
+ }>;
372
+ /** Turns MFA on and returns ten recovery codes, shown this once only. Other devices are signed out. */
373
+ confirmMfaEnrollment(code: string): Promise<string[]>;
374
+ mfaStatus(): Promise<{
375
+ enabled: boolean;
376
+ recoveryCodesRemaining: number;
377
+ }>;
378
+ /** Re-confirms the second factor, opening ten minutes to change MFA. */
379
+ stepUpMfa(params: {
380
+ code?: string;
381
+ recoveryCode?: string;
382
+ }): Promise<void>;
383
+ /** Turns MFA off. Needs stepUpMfa() within the last ten minutes. */
384
+ disableMfa(): Promise<void>;
385
+ /** Replaces all recovery codes; the old ones stop working. Needs stepUpMfa() within ten minutes. */
386
+ regenerateRecoveryCodes(): Promise<string[]>;
346
387
  sendOtp(params: SendOtpParams): Promise<OtpSendResult>;
347
388
  verifyOtp(params: VerifyOtpParams): Promise<PhoneVerifyResult>;
348
389
  linkPhone(params: LinkPhoneParams): Promise<void>;
package/dist/esm/auth.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RestoreResult, } from './types.js';
2
- import { AccountLockedError, EmailAlreadyInUseError, InvalidCredentialsError, InvalidPhoneNumberError, KoolbaseAuthError, OtpExpiredError, OtpInvalidError, OtpMaxAttemptsError, OtpRateLimitError, PhoneAlreadyLinkedError, IdentityNotFoundError, InsufficientScopeError, MalformedSessionResponseError, ProviderIdentityAlreadyLinkedError, AccountExistsError, ContactNotVerifiedError, CurrentPasswordIncorrectError, HideRequiresVerificationError, InsufficientAuthorityError, LastCredentialError, OAuthOnlyAccountError, SessionRequiredError, SignupsDisabledError, TokenAlreadyUsedError, TokenExpiredError, UnsupportedOAuthProviderError, RateLimitError, VerificationResendCooldownError, VerificationResendDailyCapError, SessionExpiredError, SmsConfigMissingError, UnlockTokenInvalidError, UserDisabledError, WeakPasswordError, AppleEmailRequiredError, AppleSignInNotConfiguredError, InvalidAppleTokenError, OAuthEmailConflictError, GoogleEmailRequiredError, GoogleSignInNotConfiguredError, InvalidGoogleTokenError, EmailCodeDisabledError, } from './auth-errors.js';
2
+ import { AccountLockedError, EmailAlreadyInUseError, InvalidCredentialsError, InvalidPhoneNumberError, KoolbaseAuthError, OtpExpiredError, OtpInvalidError, OtpMaxAttemptsError, OtpRateLimitError, PhoneAlreadyLinkedError, IdentityNotFoundError, InsufficientScopeError, MalformedSessionResponseError, ProviderIdentityAlreadyLinkedError, AccountExistsError, ContactNotVerifiedError, CurrentPasswordIncorrectError, HideRequiresVerificationError, InsufficientAuthorityError, LastCredentialError, OAuthOnlyAccountError, SessionRequiredError, SignupsDisabledError, TokenAlreadyUsedError, TokenExpiredError, UnsupportedOAuthProviderError, RateLimitError, VerificationResendCooldownError, VerificationResendDailyCapError, SessionExpiredError, SmsConfigMissingError, UnlockTokenInvalidError, UserDisabledError, WeakPasswordError, AppleEmailRequiredError, AppleSignInNotConfiguredError, InvalidAppleTokenError, OAuthEmailConflictError, GoogleEmailRequiredError, GoogleSignInNotConfiguredError, InvalidGoogleTokenError, EmailCodeDisabledError, MfaRequiredError, RecentAuthRequiredError, RecentMfaRequiredError, MfaAlreadyEnabledError, MfaEnrollmentNotFoundError, MfaNotEnabledError, } from './auth-errors.js';
3
3
  import { getPlatform } from './platform.js';
4
4
  import { DeviceMetadata } from './device-metadata.js';
5
5
  export class KoolbaseAuth {
@@ -385,6 +385,8 @@ export class KoolbaseAuth {
385
385
  const errorMessage = body?.error ?? '';
386
386
  // ─── code-first ───
387
387
  switch (code) {
388
+ case 'mfa_required':
389
+ throw this.mfaRequired(body);
388
390
  case 'oauth_not_configured':
389
391
  throw new GoogleSignInNotConfiguredError();
390
392
  case 'invalid_oauth_token':
@@ -439,6 +441,8 @@ export class KoolbaseAuth {
439
441
  const errorMessage = body?.error ?? '';
440
442
  // ─── code-first ───
441
443
  switch (code) {
444
+ case 'mfa_required':
445
+ throw this.mfaRequired(body);
442
446
  case 'oauth_not_configured':
443
447
  throw new AppleSignInNotConfiguredError();
444
448
  case 'invalid_oauth_token':
@@ -870,6 +874,102 @@ export class KoolbaseAuth {
870
874
  await this.setSessionInternal(session);
871
875
  return session;
872
876
  }
877
+ // ─── Two-step sign-in (MFA) ─────────────────────────────────────────────
878
+ //
879
+ // When an account has MFA on, every sign-in method throws MfaRequiredError.
880
+ // Finish with verifyMfa() or verifyRecoveryCode().
881
+ // One builder for every parser, so the four cannot drift apart in how the
882
+ // challenge reaches the app.
883
+ mfaRequired(body) {
884
+ const d = body?.details ?? {};
885
+ return new MfaRequiredError(String(d.challenge_token ?? ''), d.expires_at);
886
+ }
887
+ /** Finishes sign-in with a code from the person's authenticator app. */
888
+ async verifyMfa(params) {
889
+ const res = await this.authRequest('/v1/sdk/auth/mfa/verify', {
890
+ method: 'POST',
891
+ body: { challenge_token: params.challengeToken, code: params.code },
892
+ });
893
+ const session = await this.parseSessionResponse(res, false);
894
+ await this.setSessionInternal(session);
895
+ return session;
896
+ }
897
+ /**
898
+ * Finishes sign-in with a recovery code. Each works once; prompt the person
899
+ * to regenerate when recoveryCodesRemaining reaches zero.
900
+ */
901
+ async verifyRecoveryCode(params) {
902
+ const res = await this.authRequest('/v1/sdk/auth/mfa/verify-recovery', {
903
+ method: 'POST',
904
+ body: { challenge_token: params.challengeToken, code: params.code },
905
+ });
906
+ if (!res.ok)
907
+ await this.throwTypedError(res, false);
908
+ // Read once: the session and the remaining count come from one body.
909
+ const data = await res.json();
910
+ if (!data?.access_token)
911
+ throw new MalformedSessionResponseError('an access token');
912
+ if (!data?.refresh_token)
913
+ throw new MalformedSessionResponseError('a refresh token');
914
+ const session = {
915
+ accessToken: data.access_token,
916
+ refreshToken: data.refresh_token,
917
+ expiresAt: data.expires_at,
918
+ user: this.mapUser(data.user),
919
+ };
920
+ await this.setSessionInternal(session);
921
+ return { session, recoveryCodesRemaining: Number(data.recovery_codes_remaining ?? 0) };
922
+ }
923
+ /**
924
+ * Starts adding an authenticator; needs a sign-in within ten minutes. Show
925
+ * otpauthUri as a QR code, then call confirmMfaEnrollment with the first
926
+ * code the app shows.
927
+ */
928
+ async enrollMfa() {
929
+ const res = await this.authRequest('/v1/sdk/auth/mfa/enroll', { method: 'POST', includeAuth: true });
930
+ await this.checkResponse(res);
931
+ const d = await res.json();
932
+ return { otpauthUri: d.otpauth_uri, secret: d.secret };
933
+ }
934
+ /** Turns MFA on and returns ten recovery codes, shown this once only. Other devices are signed out. */
935
+ async confirmMfaEnrollment(code) {
936
+ const res = await this.authRequest('/v1/sdk/auth/mfa/enroll/confirm', {
937
+ method: 'POST',
938
+ body: { code },
939
+ includeAuth: true,
940
+ });
941
+ await this.checkResponse(res);
942
+ return (await res.json()).recovery_codes;
943
+ }
944
+ async mfaStatus() {
945
+ const res = await this.authRequest('/v1/sdk/auth/mfa', { method: 'GET', includeAuth: true });
946
+ await this.checkResponse(res);
947
+ const d = await res.json();
948
+ return { enabled: !!d.enabled, recoveryCodesRemaining: Number(d.recovery_codes_remaining ?? 0) };
949
+ }
950
+ /** Re-confirms the second factor, opening ten minutes to change MFA. */
951
+ async stepUpMfa(params) {
952
+ const res = await this.authRequest('/v1/sdk/auth/mfa/step-up', {
953
+ method: 'POST',
954
+ body: {
955
+ ...(params.code ? { code: params.code } : {}),
956
+ ...(params.recoveryCode ? { recovery_code: params.recoveryCode } : {}),
957
+ },
958
+ includeAuth: true,
959
+ });
960
+ await this.checkResponse(res);
961
+ }
962
+ /** Turns MFA off. Needs stepUpMfa() within the last ten minutes. */
963
+ async disableMfa() {
964
+ const res = await this.authRequest('/v1/sdk/auth/mfa/disable', { method: 'POST', includeAuth: true });
965
+ await this.checkResponse(res);
966
+ }
967
+ /** Replaces all recovery codes; the old ones stop working. Needs stepUpMfa() within ten minutes. */
968
+ async regenerateRecoveryCodes() {
969
+ const res = await this.authRequest('/v1/sdk/auth/mfa/recovery-codes', { method: 'POST', includeAuth: true });
970
+ await this.checkResponse(res);
971
+ return (await res.json()).recovery_codes;
972
+ }
873
973
  // ─── Phone OTP ──────────────────────────────────────────────────────────
874
974
  async sendOtp(params) {
875
975
  this.validatePhone(params.phoneNumber);
@@ -1037,6 +1137,18 @@ export class KoolbaseAuth {
1037
1137
  const msg = body.error ?? '';
1038
1138
  // ─── code-first ───
1039
1139
  switch (code) {
1140
+ case 'mfa_required':
1141
+ throw this.mfaRequired(body);
1142
+ case 'recent_auth_required':
1143
+ throw new RecentAuthRequiredError();
1144
+ case 'recent_mfa_required':
1145
+ throw new RecentMfaRequiredError();
1146
+ case 'mfa_already_enabled':
1147
+ throw new MfaAlreadyEnabledError();
1148
+ case 'mfa_enrollment_not_found':
1149
+ throw new MfaEnrollmentNotFoundError();
1150
+ case 'mfa_not_enabled':
1151
+ throw new MfaNotEnabledError();
1040
1152
  case 'invalid_credentials':
1041
1153
  throw new InvalidCredentialsError();
1042
1154
  case 'email_in_use':
@@ -1157,6 +1269,8 @@ export class KoolbaseAuth {
1157
1269
  const msg = body.error ?? '';
1158
1270
  // ─── code-first ───
1159
1271
  switch (code) {
1272
+ case 'mfa_required':
1273
+ throw this.mfaRequired(body);
1160
1274
  case 'invalid_phone':
1161
1275
  throw new InvalidPhoneNumberError();
1162
1276
  case 'otp_expired':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koolbase/core",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "types": "./dist/esm/index.d.ts",