@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.
- package/dist/cjs/auth-errors.d.ts +26 -0
- package/dist/cjs/auth-errors.js +58 -1
- package/dist/cjs/auth.d.ts +41 -0
- package/dist/cjs/auth.js +114 -0
- package/dist/esm/auth-errors.d.ts +26 -0
- package/dist/esm/auth-errors.js +51 -0
- package/dist/esm/auth.d.ts +41 -0
- package/dist/esm/auth.js +115 -1
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/cjs/auth-errors.js
CHANGED
|
@@ -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;
|
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/esm/auth-errors.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/esm/auth.d.ts
CHANGED
|
@@ -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.
|
|
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",
|