@breadstone/archipel-platform-authentication 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +47 -0
  2. package/package.json +67 -0
  3. package/src/AuthModule.d.ts +57 -0
  4. package/src/AuthModule.js +158 -0
  5. package/src/contracts/AuthSubjectPort.d.ts +33 -0
  6. package/src/contracts/AuthSubjectPort.js +15 -0
  7. package/src/contracts/IAuthSubject.d.ts +40 -0
  8. package/src/contracts/IAuthSubject.js +3 -0
  9. package/src/contracts/IMfaSubject.d.ts +48 -0
  10. package/src/contracts/IMfaSubject.js +3 -0
  11. package/src/contracts/IMfaSubjectUpdate.d.ts +16 -0
  12. package/src/contracts/IMfaSubjectUpdate.js +3 -0
  13. package/src/contracts/ISessionRecord.d.ts +22 -0
  14. package/src/contracts/ISessionRecord.js +3 -0
  15. package/src/contracts/ISocialProfile.d.ts +23 -0
  16. package/src/contracts/ISocialProfile.js +3 -0
  17. package/src/contracts/IStoreSessionArgs.d.ts +14 -0
  18. package/src/contracts/IStoreSessionArgs.js +4 -0
  19. package/src/contracts/IVerifiableSubject.d.ts +28 -0
  20. package/src/contracts/IVerifiableSubject.js +3 -0
  21. package/src/contracts/MfaSubjectPort.d.ts +26 -0
  22. package/src/contracts/MfaSubjectPort.js +15 -0
  23. package/src/contracts/SessionPersistencePort.d.ts +58 -0
  24. package/src/contracts/SessionPersistencePort.js +15 -0
  25. package/src/contracts/SocialAuthPort.d.ts +19 -0
  26. package/src/contracts/SocialAuthPort.js +15 -0
  27. package/src/contracts/TokenEnricherPort.d.ts +17 -0
  28. package/src/contracts/TokenEnricherPort.js +15 -0
  29. package/src/contracts/VerificationSubjectPort.d.ts +40 -0
  30. package/src/contracts/VerificationSubjectPort.js +15 -0
  31. package/src/contracts/index.d.ts +13 -0
  32. package/src/contracts/index.js +16 -0
  33. package/src/decorators/RolesDecorator.d.ts +12 -0
  34. package/src/decorators/RolesDecorator.js +23 -0
  35. package/src/decorators/TokenDecorator.d.ts +6 -0
  36. package/src/decorators/TokenDecorator.js +20 -0
  37. package/src/decorators/UserDecorator.d.ts +6 -0
  38. package/src/decorators/UserDecorator.js +16 -0
  39. package/src/decorators/index.d.ts +3 -0
  40. package/src/decorators/index.js +7 -0
  41. package/src/env.d.ts +57 -0
  42. package/src/env.js +179 -0
  43. package/src/guards/AnonymousAuthGuard.d.ts +9 -0
  44. package/src/guards/AnonymousAuthGuard.js +20 -0
  45. package/src/guards/GithubAuthGuard.d.ts +9 -0
  46. package/src/guards/GithubAuthGuard.js +20 -0
  47. package/src/guards/JwtAuthGuard.d.ts +9 -0
  48. package/src/guards/JwtAuthGuard.js +20 -0
  49. package/src/guards/LocalAuthGuard.d.ts +9 -0
  50. package/src/guards/LocalAuthGuard.js +20 -0
  51. package/src/guards/RolesGuard.d.ts +16 -0
  52. package/src/guards/RolesGuard.js +43 -0
  53. package/src/guards/SocialAuthGuard.d.ts +9 -0
  54. package/src/guards/SocialAuthGuard.js +35 -0
  55. package/src/guards/_RefreshTokenAuthGuard.d.ts +0 -0
  56. package/src/guards/_RefreshTokenAuthGuard.js +2 -0
  57. package/src/guards/_TwoFactorAuthGuard.d.ts +0 -0
  58. package/src/guards/_TwoFactorAuthGuard.js +2 -0
  59. package/src/guards/index.d.ts +6 -0
  60. package/src/guards/index.js +12 -0
  61. package/src/index.d.ts +9 -0
  62. package/src/index.js +13 -0
  63. package/src/mappers/SessionMappingProfile.d.ts +42 -0
  64. package/src/mappers/SessionMappingProfile.js +68 -0
  65. package/src/middlewares/LastActiveMiddleware.d.ts +29 -0
  66. package/src/middlewares/LastActiveMiddleware.js +55 -0
  67. package/src/middlewares/index.d.ts +1 -0
  68. package/src/middlewares/index.js +5 -0
  69. package/src/models/IMfaChallengePayload.d.ts +31 -0
  70. package/src/models/IMfaChallengePayload.js +3 -0
  71. package/src/models/IMfaConfirmation.d.ts +11 -0
  72. package/src/models/IMfaConfirmation.js +3 -0
  73. package/src/models/IMfaSetup.d.ts +23 -0
  74. package/src/models/IMfaSetup.js +3 -0
  75. package/src/models/ISignInResult.d.ts +31 -0
  76. package/src/models/ISignInResult.js +3 -0
  77. package/src/models/JwtPayloadBase.d.ts +44 -0
  78. package/src/models/JwtPayloadBase.js +55 -0
  79. package/src/models/index.d.ts +6 -0
  80. package/src/models/index.js +10 -0
  81. package/src/models/responses/SessionResponse.d.ts +20 -0
  82. package/src/models/responses/SessionResponse.js +164 -0
  83. package/src/models/responses/index.d.ts +1 -0
  84. package/src/models/responses/index.js +5 -0
  85. package/src/serializer/SessionSerializer.d.ts +5 -0
  86. package/src/serializer/SessionSerializer.js +23 -0
  87. package/src/services/AuthTokenService.d.ts +30 -0
  88. package/src/services/AuthTokenService.js +58 -0
  89. package/src/services/ChallengeService.d.ts +41 -0
  90. package/src/services/ChallengeService.js +89 -0
  91. package/src/services/MfaService.d.ts +60 -0
  92. package/src/services/MfaService.js +251 -0
  93. package/src/services/SessionService.d.ts +66 -0
  94. package/src/services/SessionService.js +108 -0
  95. package/src/services/VerificationService.d.ts +56 -0
  96. package/src/services/VerificationService.js +124 -0
  97. package/src/services/index.d.ts +5 -0
  98. package/src/services/index.js +9 -0
  99. package/src/services/mfa/IMfaChannel.d.ts +40 -0
  100. package/src/services/mfa/IMfaChannel.js +4 -0
  101. package/src/services/mfa/MfaEncryptionService.d.ts +23 -0
  102. package/src/services/mfa/MfaEncryptionService.js +59 -0
  103. package/src/services/mfa/MfaState.d.ts +38 -0
  104. package/src/services/mfa/MfaState.js +23 -0
  105. package/src/services/mfa/MfaTokens.d.ts +4 -0
  106. package/src/services/mfa/MfaTokens.js +8 -0
  107. package/src/services/mfa/channels/EmailOtpMfaChannel.d.ts +21 -0
  108. package/src/services/mfa/channels/EmailOtpMfaChannel.js +152 -0
  109. package/src/services/mfa/channels/PushMfaChannel.d.ts +25 -0
  110. package/src/services/mfa/channels/PushMfaChannel.js +150 -0
  111. package/src/services/mfa/channels/SmsMfaChannel.d.ts +21 -0
  112. package/src/services/mfa/channels/SmsMfaChannel.js +158 -0
  113. package/src/services/mfa/channels/TotpMfaChannel.d.ts +19 -0
  114. package/src/services/mfa/channels/TotpMfaChannel.js +100 -0
  115. package/src/strategies/AnonymousStrategy.d.ts +32 -0
  116. package/src/strategies/AnonymousStrategy.js +53 -0
  117. package/src/strategies/AppleStrategy.d.ts +38 -0
  118. package/src/strategies/AppleStrategy.js +79 -0
  119. package/src/strategies/GithubStrategy.d.ts +24 -0
  120. package/src/strategies/GithubStrategy.js +65 -0
  121. package/src/strategies/GoogleStrategy.d.ts +34 -0
  122. package/src/strategies/GoogleStrategy.js +74 -0
  123. package/src/strategies/JwtStrategy.d.ts +31 -0
  124. package/src/strategies/JwtStrategy.js +55 -0
  125. package/src/strategies/LocalStrategy.d.ts +31 -0
  126. package/src/strategies/LocalStrategy.js +56 -0
  127. package/src/strategies/MicrosoftStrategy.d.ts +35 -0
  128. package/src/strategies/MicrosoftStrategy.js +75 -0
@@ -0,0 +1,6 @@
1
+ export * from './IMfaChallengePayload';
2
+ export * from './IMfaConfirmation';
3
+ export * from './IMfaSetup';
4
+ export * from './ISignInResult';
5
+ export * from './JwtPayloadBase';
6
+ export * from './responses';
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./IMfaChallengePayload"), exports);
5
+ tslib_1.__exportStar(require("./IMfaConfirmation"), exports);
6
+ tslib_1.__exportStar(require("./IMfaSetup"), exports);
7
+ tslib_1.__exportStar(require("./ISignInResult"), exports);
8
+ tslib_1.__exportStar(require("./JwtPayloadBase"), exports);
9
+ tslib_1.__exportStar(require("./responses"), exports);
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The `SessionResponse` class.
3
+ *
4
+ * @public
5
+ */
6
+ export declare class SessionResponse {
7
+ id: string;
8
+ deviceType: string | null;
9
+ deviceName: string | null;
10
+ browserName: string | null;
11
+ browserVersion: string | null;
12
+ osName: string | null;
13
+ osVersion: string | null;
14
+ location: string | null;
15
+ clientName: string | null;
16
+ ipAddress: string;
17
+ lastActive: Date;
18
+ createdAt: Date;
19
+ isCurrent: boolean;
20
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ // #region Imports
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SessionResponse = void 0;
5
+ const tslib_1 = require("tslib");
6
+ const swagger_1 = require("@nestjs/swagger");
7
+ const class_transformer_1 = require("class-transformer");
8
+ const class_validator_1 = require("class-validator");
9
+ // #endregion
10
+ /**
11
+ * The `SessionResponse` class.
12
+ *
13
+ * @public
14
+ */
15
+ class SessionResponse {
16
+ }
17
+ exports.SessionResponse = SessionResponse;
18
+ tslib_1.__decorate([
19
+ (0, class_transformer_1.Expose)(),
20
+ (0, class_validator_1.IsString)(),
21
+ (0, swagger_1.ApiProperty)({
22
+ description: 'Unique session identifier',
23
+ type: String,
24
+ example: '507f1f77bcf86cd799439011'
25
+ }),
26
+ tslib_1.__metadata("design:type", String)
27
+ ], SessionResponse.prototype, "id", void 0);
28
+ tslib_1.__decorate([
29
+ (0, class_transformer_1.Expose)(),
30
+ (0, class_validator_1.IsOptional)(),
31
+ (0, class_validator_1.IsString)(),
32
+ (0, swagger_1.ApiProperty)({
33
+ description: 'Device type',
34
+ type: String,
35
+ example: 'desktop',
36
+ nullable: true
37
+ }),
38
+ tslib_1.__metadata("design:type", Object)
39
+ ], SessionResponse.prototype, "deviceType", void 0);
40
+ tslib_1.__decorate([
41
+ (0, class_transformer_1.Expose)(),
42
+ (0, class_validator_1.IsOptional)(),
43
+ (0, class_validator_1.IsString)(),
44
+ (0, swagger_1.ApiProperty)({
45
+ description: 'Human-readable device name',
46
+ type: String,
47
+ example: 'macOS Chrome',
48
+ nullable: true
49
+ }),
50
+ tslib_1.__metadata("design:type", Object)
51
+ ], SessionResponse.prototype, "deviceName", void 0);
52
+ tslib_1.__decorate([
53
+ (0, class_transformer_1.Expose)(),
54
+ (0, class_validator_1.IsOptional)(),
55
+ (0, class_validator_1.IsString)(),
56
+ (0, swagger_1.ApiProperty)({
57
+ description: 'Browser name',
58
+ type: String,
59
+ example: 'Chrome',
60
+ nullable: true
61
+ }),
62
+ tslib_1.__metadata("design:type", Object)
63
+ ], SessionResponse.prototype, "browserName", void 0);
64
+ tslib_1.__decorate([
65
+ (0, class_transformer_1.Expose)(),
66
+ (0, class_validator_1.IsOptional)(),
67
+ (0, class_validator_1.IsString)(),
68
+ (0, swagger_1.ApiProperty)({
69
+ description: 'Browser version',
70
+ type: String,
71
+ example: '120.0.0',
72
+ nullable: true
73
+ }),
74
+ tslib_1.__metadata("design:type", Object)
75
+ ], SessionResponse.prototype, "browserVersion", void 0);
76
+ tslib_1.__decorate([
77
+ (0, class_transformer_1.Expose)(),
78
+ (0, class_validator_1.IsOptional)(),
79
+ (0, class_validator_1.IsString)(),
80
+ (0, swagger_1.ApiProperty)({
81
+ description: 'Operating system name',
82
+ type: String,
83
+ example: 'macOS',
84
+ nullable: true
85
+ }),
86
+ tslib_1.__metadata("design:type", Object)
87
+ ], SessionResponse.prototype, "osName", void 0);
88
+ tslib_1.__decorate([
89
+ (0, class_transformer_1.Expose)(),
90
+ (0, class_validator_1.IsOptional)(),
91
+ (0, class_validator_1.IsString)(),
92
+ (0, swagger_1.ApiProperty)({
93
+ description: 'Operating system version',
94
+ type: String,
95
+ example: '14.0',
96
+ nullable: true
97
+ }),
98
+ tslib_1.__metadata("design:type", Object)
99
+ ], SessionResponse.prototype, "osVersion", void 0);
100
+ tslib_1.__decorate([
101
+ (0, class_transformer_1.Expose)(),
102
+ (0, class_validator_1.IsOptional)(),
103
+ (0, class_validator_1.IsString)(),
104
+ (0, swagger_1.ApiProperty)({
105
+ description: 'Geographic location',
106
+ type: String,
107
+ example: 'Berlin Germany',
108
+ nullable: true
109
+ }),
110
+ tslib_1.__metadata("design:type", Object)
111
+ ], SessionResponse.prototype, "location", void 0);
112
+ tslib_1.__decorate([
113
+ (0, class_transformer_1.Expose)(),
114
+ (0, class_validator_1.IsOptional)(),
115
+ (0, class_validator_1.IsString)(),
116
+ (0, swagger_1.ApiProperty)({
117
+ description: 'Client application name',
118
+ type: String,
119
+ example: 'My Awesome App',
120
+ nullable: true
121
+ }),
122
+ tslib_1.__metadata("design:type", Object)
123
+ ], SessionResponse.prototype, "clientName", void 0);
124
+ tslib_1.__decorate([
125
+ (0, class_transformer_1.Expose)(),
126
+ (0, class_validator_1.IsString)(),
127
+ (0, swagger_1.ApiProperty)({
128
+ description: 'IP address',
129
+ type: String,
130
+ example: '192.168.1.1'
131
+ }),
132
+ tslib_1.__metadata("design:type", String)
133
+ ], SessionResponse.prototype, "ipAddress", void 0);
134
+ tslib_1.__decorate([
135
+ (0, class_transformer_1.Expose)(),
136
+ (0, class_validator_1.IsDate)(),
137
+ (0, swagger_1.ApiProperty)({
138
+ description: 'Last activity timestamp',
139
+ type: Date,
140
+ example: '2025-11-05T10:30:00Z'
141
+ }),
142
+ tslib_1.__metadata("design:type", Date)
143
+ ], SessionResponse.prototype, "lastActive", void 0);
144
+ tslib_1.__decorate([
145
+ (0, class_transformer_1.Expose)(),
146
+ (0, class_validator_1.IsDate)(),
147
+ (0, swagger_1.ApiProperty)({
148
+ description: 'Session creation timestamp',
149
+ type: Date,
150
+ example: '2025-11-01T08:00:00Z'
151
+ }),
152
+ tslib_1.__metadata("design:type", Date)
153
+ ], SessionResponse.prototype, "createdAt", void 0);
154
+ tslib_1.__decorate([
155
+ (0, class_transformer_1.Expose)(),
156
+ (0, class_validator_1.IsBoolean)(),
157
+ (0, swagger_1.ApiProperty)({
158
+ description: 'Indicates if this is the current active session',
159
+ type: Boolean,
160
+ example: true
161
+ }),
162
+ tslib_1.__metadata("design:type", Boolean)
163
+ ], SessionResponse.prototype, "isCurrent", void 0);
164
+ //# sourceMappingURL=SessionResponse.js.map
@@ -0,0 +1 @@
1
+ export * from './SessionResponse';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./SessionResponse"), exports);
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ import { PassportSerializer } from '@nestjs/passport';
2
+ export declare class SessionSerializer extends PassportSerializer {
3
+ serializeUser(user: unknown, done: (err: Error | null, user: unknown) => void): void;
4
+ deserializeUser(payload: string, done: (err: Error | null, payload: string) => void): void;
5
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /* eslint-disable no-unused-vars */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SessionSerializer = void 0;
5
+ const tslib_1 = require("tslib");
6
+ // #region Imports
7
+ const common_1 = require("@nestjs/common");
8
+ const passport_1 = require("@nestjs/passport");
9
+ // #endregion
10
+ let SessionSerializer = class SessionSerializer extends passport_1.PassportSerializer {
11
+ // #region Methods
12
+ serializeUser(user, done) {
13
+ done(null, user);
14
+ }
15
+ deserializeUser(payload, done) {
16
+ done(null, payload);
17
+ }
18
+ };
19
+ exports.SessionSerializer = SessionSerializer;
20
+ exports.SessionSerializer = SessionSerializer = tslib_1.__decorate([
21
+ (0, common_1.Injectable)()
22
+ ], SessionSerializer);
23
+ //# sourceMappingURL=SessionSerializer.js.map
@@ -0,0 +1,30 @@
1
+ import { JwtService } from '@nestjs/jwt';
2
+ import type { IAuthSubject } from '../contracts/IAuthSubject';
3
+ import { TokenEnricherPort } from '../contracts/TokenEnricherPort';
4
+ /**
5
+ * Generic service responsible for creating JWT access tokens.
6
+ * Builds a base payload from {@link IAuthSubject} and optionally enriches it
7
+ * via the {@link TokenEnricherPort} with product-specific claims.
8
+ *
9
+ * @public
10
+ */
11
+ export declare class AuthTokenService {
12
+ private readonly _jwtService;
13
+ private readonly _tokenEnricher;
14
+ /**
15
+ * Constructs a new instance of the `AuthTokenService` class.
16
+ *
17
+ * @public
18
+ * @param jwtService - The NestJS JWT service.
19
+ * @param tokenEnricher - Optional token enricher port.
20
+ */
21
+ constructor(jwtService: JwtService, tokenEnricher: TokenEnricherPort | null);
22
+ /**
23
+ * Creates a signed JWT for the given subject.
24
+ *
25
+ * @public
26
+ * @param subject - The authenticated subject.
27
+ * @returns The signed JWT access token.
28
+ */
29
+ createAccessToken(subject: IAuthSubject): Promise<string>;
30
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ // #region Imports
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AuthTokenService = void 0;
5
+ const tslib_1 = require("tslib");
6
+ const common_1 = require("@nestjs/common");
7
+ const jwt_1 = require("@nestjs/jwt");
8
+ // #endregion
9
+ /**
10
+ * Generic service responsible for creating JWT access tokens.
11
+ * Builds a base payload from {@link IAuthSubject} and optionally enriches it
12
+ * via the {@link TokenEnricherPort} with product-specific claims.
13
+ *
14
+ * @public
15
+ */
16
+ let AuthTokenService = class AuthTokenService {
17
+ // #endregion
18
+ // #region Ctor
19
+ /**
20
+ * Constructs a new instance of the `AuthTokenService` class.
21
+ *
22
+ * @public
23
+ * @param jwtService - The NestJS JWT service.
24
+ * @param tokenEnricher - Optional token enricher port.
25
+ */
26
+ constructor(jwtService, tokenEnricher) {
27
+ this._jwtService = jwtService;
28
+ this._tokenEnricher = tokenEnricher ?? null;
29
+ }
30
+ // #endregion
31
+ // #region Methods
32
+ /**
33
+ * Creates a signed JWT for the given subject.
34
+ *
35
+ * @public
36
+ * @param subject - The authenticated subject.
37
+ * @returns The signed JWT access token.
38
+ */
39
+ async createAccessToken(subject) {
40
+ const baseClaims = {
41
+ id: subject.id,
42
+ email: subject.email,
43
+ roles: [...subject.roles],
44
+ };
45
+ if (this._tokenEnricher) {
46
+ const extra = await this._tokenEnricher.enrichClaims(subject);
47
+ Object.assign(baseClaims, extra);
48
+ }
49
+ return this._jwtService.signAsync(baseClaims);
50
+ }
51
+ };
52
+ exports.AuthTokenService = AuthTokenService;
53
+ exports.AuthTokenService = AuthTokenService = tslib_1.__decorate([
54
+ (0, common_1.Injectable)(),
55
+ tslib_1.__param(1, (0, common_1.Optional)()),
56
+ tslib_1.__metadata("design:paramtypes", [jwt_1.JwtService, Object])
57
+ ], AuthTokenService);
58
+ //# sourceMappingURL=AuthTokenService.js.map
@@ -0,0 +1,41 @@
1
+ import { ConfigService } from '@breadstone/archipel-platform-core';
2
+ export interface IMfaChallengeState {
3
+ id: string;
4
+ userId: string;
5
+ allowedMethods: string[];
6
+ preferredMethod?: string;
7
+ ipAddress?: string;
8
+ userAgent?: string;
9
+ location?: string;
10
+ clientName?: string;
11
+ createdAt: Date;
12
+ expiresAt: Date;
13
+ attempts: number;
14
+ }
15
+ /**
16
+ * Stores and validates MFA login challenges.
17
+ *
18
+ * @public
19
+ */
20
+ export declare class ChallengeService {
21
+ private readonly _ttlMilliseconds;
22
+ private readonly _maxAttempts;
23
+ private readonly _challenges;
24
+ constructor(configService: ConfigService);
25
+ /**
26
+ * Issues a new MFA challenge for a user.
27
+ */
28
+ create(userId: string, methods: string[], preferredMethod?: string, ipAddress?: string, userAgent?: string, location?: string, clientName?: string): Promise<IMfaChallengeState>;
29
+ /**
30
+ * Retrieves an active challenge, throwing if invalid or expired.
31
+ */
32
+ get(challengeId: string): Promise<IMfaChallengeState>;
33
+ /**
34
+ * Increments failed attempts and optionally invalidates the challenge.
35
+ */
36
+ incrementAttempts(challengeId: string): Promise<IMfaChallengeState>;
37
+ /**
38
+ * Invalidates a challenge after successful verification.
39
+ */
40
+ invalidate(challengeId: string): Promise<void>;
41
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ // #region Imports
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ChallengeService = void 0;
5
+ const tslib_1 = require("tslib");
6
+ const common_1 = require("@nestjs/common");
7
+ const archipel_platform_core_1 = require("@breadstone/archipel-platform-core");
8
+ const node_crypto_1 = require("node:crypto");
9
+ /**
10
+ * Stores and validates MFA login challenges.
11
+ *
12
+ * @public
13
+ */
14
+ let ChallengeService = class ChallengeService {
15
+ // #endregion
16
+ // #region Ctor
17
+ constructor(configService) {
18
+ this._ttlMilliseconds = archipel_platform_core_1.DateTimeUtils.parse(configService.tryGet('AUTH_MFA_CHALLENGE_EXPIRES_IN', '5m'));
19
+ this._maxAttempts = Number(configService.tryGet('AUTH_MFA_MAX_ATTEMPTS', '5'));
20
+ this._challenges = new Map();
21
+ }
22
+ // #endregion
23
+ // #region Methods
24
+ /**
25
+ * Issues a new MFA challenge for a user.
26
+ */
27
+ async create(userId, methods, preferredMethod, ipAddress, userAgent, location, clientName) {
28
+ const id = (0, node_crypto_1.randomUUID)();
29
+ const now = new Date();
30
+ const challenge = {
31
+ id: id,
32
+ userId: userId,
33
+ allowedMethods: methods,
34
+ preferredMethod: preferredMethod && methods.includes(preferredMethod) ? preferredMethod : methods[0],
35
+ ipAddress,
36
+ userAgent,
37
+ location,
38
+ clientName,
39
+ createdAt: now,
40
+ expiresAt: new Date(now.getTime() + this._ttlMilliseconds),
41
+ attempts: 0,
42
+ };
43
+ this._challenges.set(id, challenge);
44
+ return challenge;
45
+ }
46
+ /**
47
+ * Retrieves an active challenge, throwing if invalid or expired.
48
+ */
49
+ async get(challengeId) {
50
+ const challenge = this._challenges.get(challengeId);
51
+ if (!challenge) {
52
+ throw new common_1.UnauthorizedException('Invalid MFA challenge');
53
+ }
54
+ if (challenge.expiresAt.getTime() <= Date.now()) {
55
+ this._challenges.delete(challengeId);
56
+ throw new common_1.UnauthorizedException('MFA challenge expired');
57
+ }
58
+ return challenge;
59
+ }
60
+ /**
61
+ * Increments failed attempts and optionally invalidates the challenge.
62
+ */
63
+ async incrementAttempts(challengeId) {
64
+ const challenge = await this.get(challengeId);
65
+ const attempts = challenge.attempts + 1;
66
+ const updated = {
67
+ ...challenge,
68
+ attempts: attempts,
69
+ };
70
+ if (attempts >= this._maxAttempts) {
71
+ this._challenges.delete(challengeId);
72
+ throw new common_1.UnauthorizedException('MFA challenge locked after too many attempts');
73
+ }
74
+ this._challenges.set(challengeId, updated);
75
+ return updated;
76
+ }
77
+ /**
78
+ * Invalidates a challenge after successful verification.
79
+ */
80
+ async invalidate(challengeId) {
81
+ this._challenges.delete(challengeId);
82
+ }
83
+ };
84
+ exports.ChallengeService = ChallengeService;
85
+ exports.ChallengeService = ChallengeService = tslib_1.__decorate([
86
+ (0, common_1.Injectable)(),
87
+ tslib_1.__metadata("design:paramtypes", [archipel_platform_core_1.ConfigService])
88
+ ], ChallengeService);
89
+ //# sourceMappingURL=ChallengeService.js.map
@@ -0,0 +1,60 @@
1
+ import { BcryptService } from '@breadstone/archipel-platform-core';
2
+ import { MfaSubjectPort } from '../contracts/MfaSubjectPort';
3
+ import { IMfaConfirmation, IMfaSetup } from '../models';
4
+ import { IMfaChannel } from './mfa/IMfaChannel';
5
+ import { MfaEncryptionService } from './mfa/MfaEncryptionService';
6
+ import { IMfaSetupContext } from './mfa/MfaState';
7
+ /**
8
+ * Orchestrates MFA channels, backup codes, and user preferences.
9
+ *
10
+ * @public
11
+ */
12
+ export declare class MfaService {
13
+ private readonly _bcryptService;
14
+ private readonly _mfaSubject;
15
+ private readonly _channels;
16
+ private readonly _backupCodeCount;
17
+ private readonly _encryptionService;
18
+ constructor(bcryptService: BcryptService, mfaSubject: MfaSubjectPort, encryptionService: MfaEncryptionService, channels: IMfaChannel[]);
19
+ /**
20
+ * Initializes setup for a specific MFA method.
21
+ */
22
+ initSetup(userId: string, method: string, context?: IMfaSetupContext): Promise<IMfaSetup>;
23
+ /**
24
+ * Verifies the initial code for a method and issues backup codes.
25
+ */
26
+ verifySetup(userId: string, method: string, code: string): Promise<IMfaConfirmation>;
27
+ /**
28
+ * Verifies a login challenge using the selected method or backup code.
29
+ */
30
+ verifyChallenge(userId: string, method: string, code: string): Promise<boolean>;
31
+ /**
32
+ * Returns enabled methods and preferred method for a user.
33
+ */
34
+ getEnabledMethods(userId: string): Promise<{
35
+ methods: string[];
36
+ preferredMethod?: string;
37
+ }>;
38
+ /**
39
+ * Issues out-of-band challenge codes for login flows for applicable channels.
40
+ */
41
+ issueChallenges(userId: string, methods: ReadonlyArray<string>): Promise<void>;
42
+ /**
43
+ * Updates the preferred MFA method.
44
+ */
45
+ setPreferredMethod(userId: string, method: string): Promise<void>;
46
+ /**
47
+ * Regenerates backup codes.
48
+ */
49
+ regenerateBackupCodes(userId: string): Promise<ReadonlyArray<string>>;
50
+ /**
51
+ * Disables MFA for a user and clears channel secrets.
52
+ */
53
+ disable(userId: string): Promise<void>;
54
+ get registeredMethods(): string[];
55
+ private requireChannel;
56
+ private generateBackupCodes;
57
+ private tryConsumeBackupCode;
58
+ private getUser;
59
+ private touchVerification;
60
+ }