@breadstone/archipel-platform-authentication 0.0.13 → 0.0.14
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/README.md +27 -0
- package/package.json +4 -3
- package/src/AuthModule.d.ts +17 -1
- package/src/AuthModule.js +32 -5
- package/src/connectors/AnonymousConnector.d.ts +1 -1
- package/src/connectors/AnonymousConnector.js +2 -2
- package/src/connectors/AppleConnector.d.ts +1 -1
- package/src/connectors/AppleConnector.js +2 -2
- package/src/connectors/GithubConnector.d.ts +1 -1
- package/src/connectors/GithubConnector.js +3 -2
- package/src/connectors/GoogleConnector.d.ts +1 -1
- package/src/connectors/GoogleConnector.js +2 -2
- package/src/connectors/JwtConnector.d.ts +1 -1
- package/src/connectors/JwtConnector.js +2 -2
- package/src/connectors/MicrosoftConnector.d.ts +1 -1
- package/src/connectors/MicrosoftConnector.js +2 -2
- package/src/connectors/apple/env.d.ts +1 -1
- package/src/connectors/github/env.d.ts +1 -1
- package/src/connectors/google/env.d.ts +1 -1
- package/src/connectors/microsoft/env.d.ts +1 -1
- package/src/contracts/ChallengeStorePort.d.ts +32 -0
- package/src/contracts/ChallengeStorePort.js +16 -0
- package/src/contracts/index.d.ts +1 -0
- package/src/contracts/index.js +3 -1
- package/src/env.d.ts +34 -34
- package/src/env.js +34 -34
- package/src/guards/index.js +0 -2
- package/src/mfa/MfaEncryptionService.d.ts +1 -1
- package/src/mfa/MfaEncryptionService.js +2 -2
- package/src/mfa/MfaService.js +0 -3
- package/src/mfa/channels/EmailOtpMfaChannel.d.ts +10 -1
- package/src/mfa/channels/EmailOtpMfaChannel.js +12 -2
- package/src/mfa/channels/PushMfaChannel.d.ts +8 -3
- package/src/mfa/channels/PushMfaChannel.js +10 -4
- package/src/mfa/channels/SmsMfaChannel.d.ts +10 -1
- package/src/mfa/channels/SmsMfaChannel.js +12 -2
- package/src/mfa/channels/TotpMfaChannel.d.ts +1 -1
- package/src/mfa/channels/TotpMfaChannel.js +2 -2
- package/src/services/ChallengeService.d.ts +7 -3
- package/src/services/ChallengeService.js +14 -9
- package/src/services/InMemoryChallengeStore.d.ts +16 -0
- package/src/services/InMemoryChallengeStore.js +40 -0
- package/src/services/SessionService.d.ts +8 -0
- package/src/services/SessionService.js +15 -0
- package/src/services/VerificationService.d.ts +10 -1
- package/src/services/VerificationService.js +31 -9
- package/src/strategies/AnonymousStrategy.d.ts +0 -32
- package/src/strategies/AnonymousStrategy.js +0 -54
package/src/env.js
CHANGED
|
@@ -2,74 +2,74 @@
|
|
|
2
2
|
// #region Imports
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.PLATFORM_AUTHENTICATION_CONFIG_ENTRIES = exports.SEED_ANONYMOUS_USERNAME = exports.AUTH_GITHUB_SCOPE = exports.AUTH_GITHUB_CALLBACK_URL = exports.AUTH_GITHUB_CLIENT_SECRET = exports.AUTH_GITHUB_CLIENT_ID = exports.AUTH_APPLE_SCOPE = exports.AUTH_APPLE_CALLBACK_URL = exports.AUTH_APPLE_KEY_ID = exports.AUTH_APPLE_TEAM_ID = exports.AUTH_APPLE_CLIENT_ID = exports.AUTH_APPLE_PRIVATE_KEY = exports.AUTH_MICROSOFT_SCOPE = exports.AUTH_MICROSOFT_TENANT = exports.AUTH_MICROSOFT_CALLBACK_URL = exports.AUTH_MICROSOFT_CLIENT_SECRET = exports.AUTH_MICROSOFT_CLIENT_ID = exports.AUTH_GOOGLE_SCOPE = exports.AUTH_GOOGLE_CALLBACK_URL = exports.AUTH_GOOGLE_CLIENT_SECRET = exports.AUTH_GOOGLE_CLIENT_ID = exports.AUTH_MFA_PUSH_MIN_RESEND = exports.AUTH_MFA_PUSH_CODE_TTL = exports.AUTH_MFA_EMAIL_MIN_RESEND = exports.AUTH_MFA_EMAIL_CODE_TTL = exports.AUTH_MFA_SMS_MIN_RESEND = exports.AUTH_MFA_SMS_CODE_TTL = exports.AUTH_MFA_ISSUER = exports.AUTH_MFA_MAX_ATTEMPTS = exports.AUTH_MFA_CHALLENGE_EXPIRES_IN = exports.AUTH_MFA_ENCRYPTION_KEY = exports.AUTH_VERIFY_JWT_EXPIRES_IN = exports.AUTH_JWT_EXPIRES_IN = exports.AUTH_JWT_SECRET = void 0;
|
|
5
|
-
const
|
|
5
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
6
6
|
// #endregion
|
|
7
7
|
/** Secret used to sign JWTs. */
|
|
8
|
-
exports.AUTH_JWT_SECRET = (0,
|
|
8
|
+
exports.AUTH_JWT_SECRET = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_JWT_SECRET');
|
|
9
9
|
/** Lifetime of access tokens (e.g. `'15m'`, `'1h'`). */
|
|
10
|
-
exports.AUTH_JWT_EXPIRES_IN = (0,
|
|
10
|
+
exports.AUTH_JWT_EXPIRES_IN = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_JWT_EXPIRES_IN');
|
|
11
11
|
/** Lifetime of email-verification JWTs. */
|
|
12
|
-
exports.AUTH_VERIFY_JWT_EXPIRES_IN = (0,
|
|
12
|
+
exports.AUTH_VERIFY_JWT_EXPIRES_IN = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_VERIFY_JWT_EXPIRES_IN');
|
|
13
13
|
/** AES encryption key used to encrypt MFA secrets at rest. */
|
|
14
|
-
exports.AUTH_MFA_ENCRYPTION_KEY = (0,
|
|
14
|
+
exports.AUTH_MFA_ENCRYPTION_KEY = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_ENCRYPTION_KEY');
|
|
15
15
|
/** How long an MFA challenge stays valid (e.g. `'5m'`). */
|
|
16
|
-
exports.AUTH_MFA_CHALLENGE_EXPIRES_IN = (0,
|
|
16
|
+
exports.AUTH_MFA_CHALLENGE_EXPIRES_IN = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_CHALLENGE_EXPIRES_IN');
|
|
17
17
|
/** Maximum number of failed MFA attempts before lockout. */
|
|
18
|
-
exports.AUTH_MFA_MAX_ATTEMPTS = (0,
|
|
18
|
+
exports.AUTH_MFA_MAX_ATTEMPTS = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_MAX_ATTEMPTS');
|
|
19
19
|
/** Issuer name shown in authenticator apps for TOTP MFA. */
|
|
20
|
-
exports.AUTH_MFA_ISSUER = (0,
|
|
20
|
+
exports.AUTH_MFA_ISSUER = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_ISSUER');
|
|
21
21
|
/** Time-to-live for SMS OTP codes (e.g. `'10m'`). */
|
|
22
|
-
exports.AUTH_MFA_SMS_CODE_TTL = (0,
|
|
22
|
+
exports.AUTH_MFA_SMS_CODE_TTL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_SMS_CODE_TTL');
|
|
23
23
|
/** Minimum interval between SMS resend requests (e.g. `'30s'`). */
|
|
24
|
-
exports.AUTH_MFA_SMS_MIN_RESEND = (0,
|
|
24
|
+
exports.AUTH_MFA_SMS_MIN_RESEND = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_SMS_MIN_RESEND');
|
|
25
25
|
/** Time-to-live for email OTP codes (e.g. `'10m'`). */
|
|
26
|
-
exports.AUTH_MFA_EMAIL_CODE_TTL = (0,
|
|
26
|
+
exports.AUTH_MFA_EMAIL_CODE_TTL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_EMAIL_CODE_TTL');
|
|
27
27
|
/** Minimum interval between email OTP resend requests (e.g. `'30s'`). */
|
|
28
|
-
exports.AUTH_MFA_EMAIL_MIN_RESEND = (0,
|
|
28
|
+
exports.AUTH_MFA_EMAIL_MIN_RESEND = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_EMAIL_MIN_RESEND');
|
|
29
29
|
/** Time-to-live for push OTP codes (e.g. `'10m'`). */
|
|
30
|
-
exports.AUTH_MFA_PUSH_CODE_TTL = (0,
|
|
30
|
+
exports.AUTH_MFA_PUSH_CODE_TTL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_PUSH_CODE_TTL');
|
|
31
31
|
/** Minimum interval between push OTP resend requests (e.g. `'30s'`). */
|
|
32
|
-
exports.AUTH_MFA_PUSH_MIN_RESEND = (0,
|
|
32
|
+
exports.AUTH_MFA_PUSH_MIN_RESEND = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MFA_PUSH_MIN_RESEND');
|
|
33
33
|
/** Google OAuth2 client ID. */
|
|
34
|
-
exports.AUTH_GOOGLE_CLIENT_ID = (0,
|
|
34
|
+
exports.AUTH_GOOGLE_CLIENT_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GOOGLE_CLIENT_ID');
|
|
35
35
|
/** Google OAuth2 client secret. */
|
|
36
|
-
exports.AUTH_GOOGLE_CLIENT_SECRET = (0,
|
|
36
|
+
exports.AUTH_GOOGLE_CLIENT_SECRET = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GOOGLE_CLIENT_SECRET');
|
|
37
37
|
/** Google OAuth2 callback URL. */
|
|
38
|
-
exports.AUTH_GOOGLE_CALLBACK_URL = (0,
|
|
38
|
+
exports.AUTH_GOOGLE_CALLBACK_URL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GOOGLE_CALLBACK_URL');
|
|
39
39
|
/** Comma-separated list of Google OAuth scopes (e.g. `'email,profile'`). */
|
|
40
|
-
exports.AUTH_GOOGLE_SCOPE = (0,
|
|
40
|
+
exports.AUTH_GOOGLE_SCOPE = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GOOGLE_SCOPE');
|
|
41
41
|
/** Microsoft OAuth2 client ID. */
|
|
42
|
-
exports.AUTH_MICROSOFT_CLIENT_ID = (0,
|
|
42
|
+
exports.AUTH_MICROSOFT_CLIENT_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MICROSOFT_CLIENT_ID');
|
|
43
43
|
/** Microsoft OAuth2 client secret. */
|
|
44
|
-
exports.AUTH_MICROSOFT_CLIENT_SECRET = (0,
|
|
44
|
+
exports.AUTH_MICROSOFT_CLIENT_SECRET = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MICROSOFT_CLIENT_SECRET');
|
|
45
45
|
/** Microsoft OAuth2 callback URL. */
|
|
46
|
-
exports.AUTH_MICROSOFT_CALLBACK_URL = (0,
|
|
46
|
+
exports.AUTH_MICROSOFT_CALLBACK_URL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MICROSOFT_CALLBACK_URL');
|
|
47
47
|
/** Microsoft tenant ID or `'common'` for multi-tenant. */
|
|
48
|
-
exports.AUTH_MICROSOFT_TENANT = (0,
|
|
48
|
+
exports.AUTH_MICROSOFT_TENANT = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MICROSOFT_TENANT');
|
|
49
49
|
/** Comma-separated list of Microsoft OAuth scopes (e.g. `'user.read,mail.read'`). */
|
|
50
|
-
exports.AUTH_MICROSOFT_SCOPE = (0,
|
|
50
|
+
exports.AUTH_MICROSOFT_SCOPE = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_MICROSOFT_SCOPE');
|
|
51
51
|
/** Apple Sign-In private key (PEM string). */
|
|
52
|
-
exports.AUTH_APPLE_PRIVATE_KEY = (0,
|
|
52
|
+
exports.AUTH_APPLE_PRIVATE_KEY = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_PRIVATE_KEY');
|
|
53
53
|
/** Apple Sign-In client (service) ID. */
|
|
54
|
-
exports.AUTH_APPLE_CLIENT_ID = (0,
|
|
54
|
+
exports.AUTH_APPLE_CLIENT_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_CLIENT_ID');
|
|
55
55
|
/** Apple developer team ID. */
|
|
56
|
-
exports.AUTH_APPLE_TEAM_ID = (0,
|
|
56
|
+
exports.AUTH_APPLE_TEAM_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_TEAM_ID');
|
|
57
57
|
/** Apple Sign-In key ID. */
|
|
58
|
-
exports.AUTH_APPLE_KEY_ID = (0,
|
|
58
|
+
exports.AUTH_APPLE_KEY_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_KEY_ID');
|
|
59
59
|
/** Apple Sign-In callback URL. */
|
|
60
|
-
exports.AUTH_APPLE_CALLBACK_URL = (0,
|
|
60
|
+
exports.AUTH_APPLE_CALLBACK_URL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_CALLBACK_URL');
|
|
61
61
|
/** Comma-separated list of Apple Sign-In scopes (e.g. `'name,email'`). */
|
|
62
|
-
exports.AUTH_APPLE_SCOPE = (0,
|
|
62
|
+
exports.AUTH_APPLE_SCOPE = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_APPLE_SCOPE');
|
|
63
63
|
/** GitHub OAuth client ID. */
|
|
64
|
-
exports.AUTH_GITHUB_CLIENT_ID = (0,
|
|
64
|
+
exports.AUTH_GITHUB_CLIENT_ID = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GITHUB_CLIENT_ID');
|
|
65
65
|
/** GitHub OAuth client secret. */
|
|
66
|
-
exports.AUTH_GITHUB_CLIENT_SECRET = (0,
|
|
66
|
+
exports.AUTH_GITHUB_CLIENT_SECRET = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GITHUB_CLIENT_SECRET');
|
|
67
67
|
/** GitHub OAuth callback URL. */
|
|
68
|
-
exports.AUTH_GITHUB_CALLBACK_URL = (0,
|
|
68
|
+
exports.AUTH_GITHUB_CALLBACK_URL = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GITHUB_CALLBACK_URL');
|
|
69
69
|
/** Comma-separated list of GitHub OAuth scopes (e.g. `'user:email,read:user'`). */
|
|
70
|
-
exports.AUTH_GITHUB_SCOPE = (0,
|
|
70
|
+
exports.AUTH_GITHUB_SCOPE = (0, archipel_platform_configuration_1.createConfigKey)('AUTH_GITHUB_SCOPE');
|
|
71
71
|
/** Username used for the anonymous seed user. */
|
|
72
|
-
exports.SEED_ANONYMOUS_USERNAME = (0,
|
|
72
|
+
exports.SEED_ANONYMOUS_USERNAME = (0, archipel_platform_configuration_1.createConfigKey)('SEED_ANONYMOUS_USERNAME');
|
|
73
73
|
/** Core configuration entries required by `platform-authentication`. */
|
|
74
74
|
exports.PLATFORM_AUTHENTICATION_CONFIG_ENTRIES = [
|
|
75
75
|
// JWT & Session
|
package/src/guards/index.js
CHANGED
|
@@ -7,6 +7,4 @@ tslib_1.__exportStar(require("./JwtAuthGuard"), exports);
|
|
|
7
7
|
tslib_1.__exportStar(require("./LocalAuthGuard"), exports);
|
|
8
8
|
tslib_1.__exportStar(require("./RolesGuard"), exports);
|
|
9
9
|
tslib_1.__exportStar(require("./SocialAuthGuard"), exports);
|
|
10
|
-
// export * from './_RefreshTokenAuthGuard';
|
|
11
|
-
// export * from './_TwoFactorAuthGuard';
|
|
12
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.MfaEncryptionService = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
|
-
const
|
|
6
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
7
7
|
const common_1 = require("@nestjs/common");
|
|
8
8
|
const node_crypto_1 = require("node:crypto");
|
|
9
9
|
const env_1 = require("../env");
|
|
@@ -54,6 +54,6 @@ let MfaEncryptionService = class MfaEncryptionService {
|
|
|
54
54
|
exports.MfaEncryptionService = MfaEncryptionService;
|
|
55
55
|
exports.MfaEncryptionService = MfaEncryptionService = tslib_1.__decorate([
|
|
56
56
|
(0, common_1.Injectable)(),
|
|
57
|
-
tslib_1.__metadata("design:paramtypes", [
|
|
57
|
+
tslib_1.__metadata("design:paramtypes", [archipel_platform_configuration_1.ConfigService])
|
|
58
58
|
], MfaEncryptionService);
|
|
59
59
|
//# sourceMappingURL=MfaEncryptionService.js.map
|
package/src/mfa/MfaService.js
CHANGED
|
@@ -38,9 +38,6 @@ let MfaService = class MfaService {
|
|
|
38
38
|
if (state.channels[method]?.enabled) {
|
|
39
39
|
throw new common_1.BadRequestException('Selected MFA method is already enabled');
|
|
40
40
|
}
|
|
41
|
-
if (state.channels[method]?.enabled) {
|
|
42
|
-
throw new common_1.BadRequestException('Selected MFA method is already enabled');
|
|
43
|
-
}
|
|
44
41
|
return channel.initSetup(userId, context);
|
|
45
42
|
}
|
|
46
43
|
/**
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { ConfigService } from '@breadstone/archipel-platform-
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
2
|
import { BcryptService } from '@breadstone/archipel-platform-cryptography';
|
|
3
3
|
import { MfaSubjectPort } from '../../contracts/MfaSubjectPort';
|
|
4
4
|
import { IMfaSetup } from '../../models';
|
|
5
5
|
import { IMfaChannel } from '../IMfaChannel';
|
|
6
6
|
import { IMfaSetupContext } from '../MfaState';
|
|
7
|
+
/**
|
|
8
|
+
* Email-based OTP MFA channel.
|
|
9
|
+
*
|
|
10
|
+
* **Stub implementation:** OTP delivery is logged but not actually sent.
|
|
11
|
+
* Replace the `_logger.log` calls with a real email service integration
|
|
12
|
+
* (e.g. platform-mailing) before production use.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
7
16
|
export declare class EmailOtpMfaChannel implements IMfaChannel {
|
|
8
17
|
readonly method = "email";
|
|
9
18
|
private readonly _mfaSubject;
|
|
@@ -4,6 +4,7 @@ var EmailOtpMfaChannel_1;
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.EmailOtpMfaChannel = void 0;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
7
8
|
const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
|
|
8
9
|
const archipel_platform_cryptography_1 = require("@breadstone/archipel-platform-cryptography");
|
|
9
10
|
const common_1 = require("@nestjs/common");
|
|
@@ -12,6 +13,15 @@ const MfaSubjectPort_1 = require("../../contracts/MfaSubjectPort");
|
|
|
12
13
|
const env_1 = require("../../env");
|
|
13
14
|
const MfaState_1 = require("../MfaState");
|
|
14
15
|
// #endregion
|
|
16
|
+
/**
|
|
17
|
+
* Email-based OTP MFA channel.
|
|
18
|
+
*
|
|
19
|
+
* **Stub implementation:** OTP delivery is logged but not actually sent.
|
|
20
|
+
* Replace the `_logger.log` calls with a real email service integration
|
|
21
|
+
* (e.g. platform-mailing) before production use.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
15
25
|
let EmailOtpMfaChannel = EmailOtpMfaChannel_1 = class EmailOtpMfaChannel {
|
|
16
26
|
constructor(mfaSubject, configService, bcryptService) {
|
|
17
27
|
this.method = 'email';
|
|
@@ -143,12 +153,12 @@ let EmailOtpMfaChannel = EmailOtpMfaChannel_1 = class EmailOtpMfaChannel {
|
|
|
143
153
|
return true;
|
|
144
154
|
}
|
|
145
155
|
generateCode() {
|
|
146
|
-
return (0, node_crypto_1.randomInt)(
|
|
156
|
+
return (0, node_crypto_1.randomInt)(10000000, 99999999).toString();
|
|
147
157
|
}
|
|
148
158
|
};
|
|
149
159
|
exports.EmailOtpMfaChannel = EmailOtpMfaChannel;
|
|
150
160
|
exports.EmailOtpMfaChannel = EmailOtpMfaChannel = EmailOtpMfaChannel_1 = tslib_1.__decorate([
|
|
151
161
|
(0, common_1.Injectable)(),
|
|
152
|
-
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort,
|
|
162
|
+
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort, archipel_platform_configuration_1.ConfigService, archipel_platform_cryptography_1.BcryptService])
|
|
153
163
|
], EmailOtpMfaChannel);
|
|
154
164
|
//# sourceMappingURL=EmailOtpMfaChannel.js.map
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { ConfigService } from '@breadstone/archipel-platform-
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
2
|
import { BcryptService } from '@breadstone/archipel-platform-cryptography';
|
|
3
3
|
import { MfaSubjectPort } from '../../contracts/MfaSubjectPort';
|
|
4
4
|
import { IMfaSetup } from '../../models';
|
|
5
5
|
import { IMfaChannel } from '../IMfaChannel';
|
|
6
6
|
import { IMfaSetupContext } from '../MfaState';
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Push-style MFA channel that simulates a device approval code.
|
|
9
|
+
*
|
|
10
|
+
* **Stub implementation:** Approval delivery is logged but not actually sent.
|
|
11
|
+
* Replace the `_logger.log` calls with a real push notification provider
|
|
12
|
+
* before production use.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
10
15
|
*/
|
|
11
16
|
export declare class PushMfaChannel implements IMfaChannel {
|
|
12
17
|
readonly method = "push";
|
|
@@ -4,6 +4,7 @@ var PushMfaChannel_1;
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.PushMfaChannel = void 0;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
7
8
|
const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
|
|
8
9
|
const archipel_platform_cryptography_1 = require("@breadstone/archipel-platform-cryptography");
|
|
9
10
|
const common_1 = require("@nestjs/common");
|
|
@@ -13,8 +14,13 @@ const env_1 = require("../../env");
|
|
|
13
14
|
const MfaState_1 = require("../MfaState");
|
|
14
15
|
// #endregion
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Push-style MFA channel that simulates a device approval code.
|
|
18
|
+
*
|
|
19
|
+
* **Stub implementation:** Approval delivery is logged but not actually sent.
|
|
20
|
+
* Replace the `_logger.log` calls with a real push notification provider
|
|
21
|
+
* before production use.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
18
24
|
*/
|
|
19
25
|
let PushMfaChannel = PushMfaChannel_1 = class PushMfaChannel {
|
|
20
26
|
// #endregion
|
|
@@ -141,12 +147,12 @@ let PushMfaChannel = PushMfaChannel_1 = class PushMfaChannel {
|
|
|
141
147
|
return true;
|
|
142
148
|
}
|
|
143
149
|
generateCode() {
|
|
144
|
-
return (0, node_crypto_1.randomInt)(
|
|
150
|
+
return (0, node_crypto_1.randomInt)(10000000, 99999999).toString();
|
|
145
151
|
}
|
|
146
152
|
};
|
|
147
153
|
exports.PushMfaChannel = PushMfaChannel;
|
|
148
154
|
exports.PushMfaChannel = PushMfaChannel = PushMfaChannel_1 = tslib_1.__decorate([
|
|
149
155
|
(0, common_1.Injectable)(),
|
|
150
|
-
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort,
|
|
156
|
+
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort, archipel_platform_configuration_1.ConfigService, archipel_platform_cryptography_1.BcryptService])
|
|
151
157
|
], PushMfaChannel);
|
|
152
158
|
//# sourceMappingURL=PushMfaChannel.js.map
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { ConfigService } from '@breadstone/archipel-platform-
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
2
|
import { BcryptService } from '@breadstone/archipel-platform-cryptography';
|
|
3
3
|
import { MfaSubjectPort } from '../../contracts/MfaSubjectPort';
|
|
4
4
|
import { IMfaSetup } from '../../models';
|
|
5
5
|
import { IMfaChannel } from '../IMfaChannel';
|
|
6
6
|
import { IMfaSetupContext } from '../MfaState';
|
|
7
|
+
/**
|
|
8
|
+
* SMS-based MFA channel.
|
|
9
|
+
*
|
|
10
|
+
* **Stub implementation:** OTP delivery is logged but not actually sent.
|
|
11
|
+
* Replace the `_logger.log` calls with a real SMS gateway integration
|
|
12
|
+
* (e.g. Twilio, Azure Communication Services) before production use.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
7
16
|
export declare class SmsMfaChannel implements IMfaChannel {
|
|
8
17
|
readonly method = "sms";
|
|
9
18
|
private readonly _mfaSubject;
|
|
@@ -4,6 +4,7 @@ var SmsMfaChannel_1;
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.SmsMfaChannel = void 0;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
7
8
|
const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
|
|
8
9
|
const archipel_platform_cryptography_1 = require("@breadstone/archipel-platform-cryptography");
|
|
9
10
|
const common_1 = require("@nestjs/common");
|
|
@@ -12,6 +13,15 @@ const MfaSubjectPort_1 = require("../../contracts/MfaSubjectPort");
|
|
|
12
13
|
const env_1 = require("../../env");
|
|
13
14
|
const MfaState_1 = require("../MfaState");
|
|
14
15
|
// #endregion
|
|
16
|
+
/**
|
|
17
|
+
* SMS-based MFA channel.
|
|
18
|
+
*
|
|
19
|
+
* **Stub implementation:** OTP delivery is logged but not actually sent.
|
|
20
|
+
* Replace the `_logger.log` calls with a real SMS gateway integration
|
|
21
|
+
* (e.g. Twilio, Azure Communication Services) before production use.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
15
25
|
let SmsMfaChannel = SmsMfaChannel_1 = class SmsMfaChannel {
|
|
16
26
|
// #endregion
|
|
17
27
|
// #region Ctor
|
|
@@ -149,12 +159,12 @@ let SmsMfaChannel = SmsMfaChannel_1 = class SmsMfaChannel {
|
|
|
149
159
|
return true;
|
|
150
160
|
}
|
|
151
161
|
generateCode() {
|
|
152
|
-
return (0, node_crypto_1.randomInt)(
|
|
162
|
+
return (0, node_crypto_1.randomInt)(10000000, 99999999).toString();
|
|
153
163
|
}
|
|
154
164
|
};
|
|
155
165
|
exports.SmsMfaChannel = SmsMfaChannel;
|
|
156
166
|
exports.SmsMfaChannel = SmsMfaChannel = SmsMfaChannel_1 = tslib_1.__decorate([
|
|
157
167
|
(0, common_1.Injectable)(),
|
|
158
|
-
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort,
|
|
168
|
+
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort, archipel_platform_configuration_1.ConfigService, archipel_platform_cryptography_1.BcryptService])
|
|
159
169
|
], SmsMfaChannel);
|
|
160
170
|
//# sourceMappingURL=SmsMfaChannel.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigService } from '@breadstone/archipel-platform-
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
2
|
import { IOtpService } from '@breadstone/archipel-platform-cryptography/otp';
|
|
3
3
|
import { MfaSubjectPort } from '../../contracts/MfaSubjectPort';
|
|
4
4
|
import { IMfaSetup } from '../../models';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.TotpMfaChannel = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
|
-
const
|
|
6
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
7
7
|
const otp_1 = require("@breadstone/archipel-platform-cryptography/otp");
|
|
8
8
|
const common_1 = require("@nestjs/common");
|
|
9
9
|
const MfaSubjectPort_1 = require("../../contracts/MfaSubjectPort");
|
|
@@ -96,7 +96,7 @@ exports.TotpMfaChannel = TotpMfaChannel = tslib_1.__decorate([
|
|
|
96
96
|
(0, common_1.Injectable)(),
|
|
97
97
|
tslib_1.__param(3, (0, common_1.Inject)(otp_1.OTP_SERVICE_TOKEN)),
|
|
98
98
|
tslib_1.__metadata("design:paramtypes", [MfaSubjectPort_1.MfaSubjectPort,
|
|
99
|
-
|
|
99
|
+
archipel_platform_configuration_1.ConfigService,
|
|
100
100
|
MfaEncryptionService_1.MfaEncryptionService, Object])
|
|
101
101
|
], TotpMfaChannel);
|
|
102
102
|
//# sourceMappingURL=TotpMfaChannel.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ConfigService } from '@breadstone/archipel-platform-
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
|
+
import { ChallengeStorePort } from '../contracts/ChallengeStorePort';
|
|
2
3
|
export interface IMfaChallengeState {
|
|
3
4
|
id: string;
|
|
4
5
|
userId: string;
|
|
@@ -15,13 +16,16 @@ export interface IMfaChallengeState {
|
|
|
15
16
|
/**
|
|
16
17
|
* Stores and validates MFA login challenges.
|
|
17
18
|
*
|
|
19
|
+
* Delegates persistence to {@link ChallengeStorePort} so the backing store
|
|
20
|
+
* (in-memory, Redis, etc.) can be swapped without changing business logic.
|
|
21
|
+
*
|
|
18
22
|
* @public
|
|
19
23
|
*/
|
|
20
24
|
export declare class ChallengeService {
|
|
21
25
|
private readonly _ttlMilliseconds;
|
|
22
26
|
private readonly _maxAttempts;
|
|
23
|
-
private readonly
|
|
24
|
-
constructor(configService: ConfigService);
|
|
27
|
+
private readonly _store;
|
|
28
|
+
constructor(configService: ConfigService, store: ChallengeStorePort);
|
|
25
29
|
/**
|
|
26
30
|
* Issues a new MFA challenge for a user.
|
|
27
31
|
*/
|
|
@@ -3,22 +3,27 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.ChallengeService = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
|
+
const archipel_platform_configuration_1 = require("@breadstone/archipel-platform-configuration");
|
|
6
7
|
const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
|
|
7
8
|
const common_1 = require("@nestjs/common");
|
|
8
9
|
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const ChallengeStorePort_1 = require("../contracts/ChallengeStorePort");
|
|
9
11
|
const env_1 = require("../env");
|
|
10
12
|
/**
|
|
11
13
|
* Stores and validates MFA login challenges.
|
|
12
14
|
*
|
|
15
|
+
* Delegates persistence to {@link ChallengeStorePort} so the backing store
|
|
16
|
+
* (in-memory, Redis, etc.) can be swapped without changing business logic.
|
|
17
|
+
*
|
|
13
18
|
* @public
|
|
14
19
|
*/
|
|
15
20
|
let ChallengeService = class ChallengeService {
|
|
16
21
|
// #endregion
|
|
17
22
|
// #region Ctor
|
|
18
|
-
constructor(configService) {
|
|
23
|
+
constructor(configService, store) {
|
|
19
24
|
this._ttlMilliseconds = archipel_platform_core_1.DateTimeUtils.parse(configService.tryGet(env_1.AUTH_MFA_CHALLENGE_EXPIRES_IN.key, '5m'));
|
|
20
25
|
this._maxAttempts = Number(configService.tryGet(env_1.AUTH_MFA_MAX_ATTEMPTS.key, '5'));
|
|
21
|
-
this.
|
|
26
|
+
this._store = store;
|
|
22
27
|
}
|
|
23
28
|
// #endregion
|
|
24
29
|
// #region Methods
|
|
@@ -41,19 +46,19 @@ let ChallengeService = class ChallengeService {
|
|
|
41
46
|
expiresAt: new Date(now.getTime() + this._ttlMilliseconds),
|
|
42
47
|
attempts: 0,
|
|
43
48
|
};
|
|
44
|
-
this.
|
|
49
|
+
await this._store.set(id, challenge);
|
|
45
50
|
return challenge;
|
|
46
51
|
}
|
|
47
52
|
/**
|
|
48
53
|
* Retrieves an active challenge, throwing if invalid or expired.
|
|
49
54
|
*/
|
|
50
55
|
async get(challengeId) {
|
|
51
|
-
const challenge = this.
|
|
56
|
+
const challenge = await this._store.get(challengeId);
|
|
52
57
|
if (!challenge) {
|
|
53
58
|
throw new common_1.UnauthorizedException('Invalid MFA challenge');
|
|
54
59
|
}
|
|
55
60
|
if (challenge.expiresAt.getTime() <= Date.now()) {
|
|
56
|
-
this.
|
|
61
|
+
await this._store.delete(challengeId);
|
|
57
62
|
throw new common_1.UnauthorizedException('MFA challenge expired');
|
|
58
63
|
}
|
|
59
64
|
return challenge;
|
|
@@ -69,22 +74,22 @@ let ChallengeService = class ChallengeService {
|
|
|
69
74
|
attempts: attempts,
|
|
70
75
|
};
|
|
71
76
|
if (attempts >= this._maxAttempts) {
|
|
72
|
-
this.
|
|
77
|
+
await this._store.delete(challengeId);
|
|
73
78
|
throw new common_1.UnauthorizedException('MFA challenge locked after too many attempts');
|
|
74
79
|
}
|
|
75
|
-
this.
|
|
80
|
+
await this._store.set(challengeId, updated);
|
|
76
81
|
return updated;
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
79
84
|
* Invalidates a challenge after successful verification.
|
|
80
85
|
*/
|
|
81
86
|
async invalidate(challengeId) {
|
|
82
|
-
this.
|
|
87
|
+
await this._store.delete(challengeId);
|
|
83
88
|
}
|
|
84
89
|
};
|
|
85
90
|
exports.ChallengeService = ChallengeService;
|
|
86
91
|
exports.ChallengeService = ChallengeService = tslib_1.__decorate([
|
|
87
92
|
(0, common_1.Injectable)(),
|
|
88
|
-
tslib_1.__metadata("design:paramtypes", [
|
|
93
|
+
tslib_1.__metadata("design:paramtypes", [archipel_platform_configuration_1.ConfigService, ChallengeStorePort_1.ChallengeStorePort])
|
|
89
94
|
], ChallengeService);
|
|
90
95
|
//# sourceMappingURL=ChallengeService.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChallengeStorePort } from '../contracts/ChallengeStorePort';
|
|
2
|
+
import type { IMfaChallengeState } from './ChallengeService';
|
|
3
|
+
/**
|
|
4
|
+
* Default in-memory implementation of the {@link ChallengeStorePort}.
|
|
5
|
+
*
|
|
6
|
+
* Suitable for single-instance deployments only. For production multi-instance
|
|
7
|
+
* setups, replace with a Redis-backed implementation via `IAuthModuleOptions.challengeStore`.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export declare class InMemoryChallengeStore extends ChallengeStorePort {
|
|
12
|
+
private readonly _store;
|
|
13
|
+
set(id: string, challenge: IMfaChallengeState): Promise<void>;
|
|
14
|
+
get(id: string): Promise<IMfaChallengeState | undefined>;
|
|
15
|
+
delete(id: string): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// #region Imports
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InMemoryChallengeStore = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const ChallengeStorePort_1 = require("../contracts/ChallengeStorePort");
|
|
8
|
+
// #endregion
|
|
9
|
+
/**
|
|
10
|
+
* Default in-memory implementation of the {@link ChallengeStorePort}.
|
|
11
|
+
*
|
|
12
|
+
* Suitable for single-instance deployments only. For production multi-instance
|
|
13
|
+
* setups, replace with a Redis-backed implementation via `IAuthModuleOptions.challengeStore`.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
let InMemoryChallengeStore = class InMemoryChallengeStore extends ChallengeStorePort_1.ChallengeStorePort {
|
|
18
|
+
constructor() {
|
|
19
|
+
// #region Fields
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this._store = new Map();
|
|
22
|
+
// #endregion
|
|
23
|
+
}
|
|
24
|
+
// #endregion
|
|
25
|
+
// #region Methods
|
|
26
|
+
async set(id, challenge) {
|
|
27
|
+
this._store.set(id, challenge);
|
|
28
|
+
}
|
|
29
|
+
async get(id) {
|
|
30
|
+
return this._store.get(id);
|
|
31
|
+
}
|
|
32
|
+
async delete(id) {
|
|
33
|
+
this._store.delete(id);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.InMemoryChallengeStore = InMemoryChallengeStore;
|
|
37
|
+
exports.InMemoryChallengeStore = InMemoryChallengeStore = tslib_1.__decorate([
|
|
38
|
+
(0, common_1.Injectable)()
|
|
39
|
+
], InMemoryChallengeStore);
|
|
40
|
+
//# sourceMappingURL=InMemoryChallengeStore.js.map
|
|
@@ -63,4 +63,12 @@ export declare class SessionService {
|
|
|
63
63
|
* @returns `true` if a session was invalidated.
|
|
64
64
|
*/
|
|
65
65
|
invalidate(token: string): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Validates that a session token has an acceptable format.
|
|
68
|
+
* Rejects empty, too-short, or obviously malformed tokens.
|
|
69
|
+
*
|
|
70
|
+
* @param token - The session token to validate.
|
|
71
|
+
* @throws BadRequestException if the token is invalid.
|
|
72
|
+
*/
|
|
73
|
+
private validateToken;
|
|
66
74
|
}
|
|
@@ -41,6 +41,7 @@ let SessionService = class SessionService {
|
|
|
41
41
|
* @param clientName - Optional client application name.
|
|
42
42
|
*/
|
|
43
43
|
async store(token, userId, ipAddress, deviceInfo, location, clientName) {
|
|
44
|
+
this.validateToken(token);
|
|
44
45
|
await this._sessionPersistence.store({
|
|
45
46
|
token,
|
|
46
47
|
userId,
|
|
@@ -99,6 +100,20 @@ let SessionService = class SessionService {
|
|
|
99
100
|
async invalidate(token) {
|
|
100
101
|
return this._sessionPersistence.invalidate(token);
|
|
101
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Validates that a session token has an acceptable format.
|
|
105
|
+
* Rejects empty, too-short, or obviously malformed tokens.
|
|
106
|
+
*
|
|
107
|
+
* @param token - The session token to validate.
|
|
108
|
+
* @throws BadRequestException if the token is invalid.
|
|
109
|
+
*/
|
|
110
|
+
validateToken(token) {
|
|
111
|
+
const JWT_MIN_LENGTH = 32;
|
|
112
|
+
const JWT_PATTERN = /^[\w\-._~+/]+=*$/;
|
|
113
|
+
if (!token || token.length < JWT_MIN_LENGTH || !JWT_PATTERN.test(token)) {
|
|
114
|
+
throw new common_1.BadRequestException('Invalid session token format');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
102
117
|
};
|
|
103
118
|
exports.SessionService = SessionService;
|
|
104
119
|
exports.SessionService = SessionService = tslib_1.__decorate([
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ConfigService
|
|
1
|
+
import { ConfigService } from '@breadstone/archipel-platform-configuration';
|
|
2
|
+
import { HostService } from '@breadstone/archipel-platform-core';
|
|
2
3
|
import { JwtService } from '@nestjs/jwt';
|
|
3
4
|
import { VerificationSubjectPort } from '../contracts/VerificationSubjectPort';
|
|
4
5
|
/**
|
|
@@ -53,4 +54,12 @@ export declare class VerificationService {
|
|
|
53
54
|
* @returns The URL to verify the email.
|
|
54
55
|
*/
|
|
55
56
|
buildVerificationLink(token: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Performs a constant-time string comparison to prevent timing attacks.
|
|
59
|
+
*
|
|
60
|
+
* @param a - First string.
|
|
61
|
+
* @param b - Second string.
|
|
62
|
+
* @returns Whether the strings are equal.
|
|
63
|
+
*/
|
|
64
|
+
private safeEqual;
|
|
56
65
|
}
|