@rineex/auth-core 0.1.0 → 0.1.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 (112) hide show
  1. package/package.json +6 -3
  2. package/Architecture.md +0 -257
  3. package/CHANGELOG.md +0 -62
  4. package/Definition.md +0 -1490
  5. package/Develop.md +0 -0
  6. package/RULES.md +0 -1470
  7. package/eslint.config.mjs +0 -59
  8. package/src/application/mfa/events/challenge-issue-observability.event.ts +0 -18
  9. package/src/application/mfa/events/session-started-observability.event.ts +0 -18
  10. package/src/application/mfa/events/verification-failed-observability.event.ts +0 -14
  11. package/src/application/mfa/events/verification-succeeded-observibility.event.ts +0 -12
  12. package/src/application/mfa/issue-mfa-challenge.application-service.ts +0 -75
  13. package/src/application/mfa/start-mfa-session.application-service.ts +0 -90
  14. package/src/application/mfa/verify-mfa.application-service.ts +0 -61
  15. package/src/application/services/auth-orchestrator.service.ts +0 -77
  16. package/src/application/services/oauth-authorize.service.ts +0 -12
  17. package/src/domain/identity/aggregates/authentication-attempt.aggregate.ts +0 -136
  18. package/src/domain/identity/aggregates/index.ts +0 -1
  19. package/src/domain/identity/entities/identity.entity.ts +0 -126
  20. package/src/domain/identity/entities/index.ts +0 -1
  21. package/src/domain/identity/events/authentication-failed.event.ts +0 -24
  22. package/src/domain/identity/events/authentication-started.event.ts +0 -29
  23. package/src/domain/identity/events/authentication-succeeded.event.ts +0 -24
  24. package/src/domain/identity/events/index.ts +0 -3
  25. package/src/domain/identity/index.ts +0 -4
  26. package/src/domain/identity/value-objects/__tests__/auth-attempt-id.vo.spec.ts +0 -42
  27. package/src/domain/identity/value-objects/__tests__/auth-factor.vo.spec.ts +0 -39
  28. package/src/domain/identity/value-objects/__tests__/auth-method.vo.spec.ts +0 -0
  29. package/src/domain/identity/value-objects/auth-attempt-id.vo.ts +0 -23
  30. package/src/domain/identity/value-objects/auth-factor.vo.ts +0 -17
  31. package/src/domain/identity/value-objects/auth-method.vo.ts +0 -34
  32. package/src/domain/identity/value-objects/auth-policy.vo.ts +0 -19
  33. package/src/domain/identity/value-objects/auth-status.vo.ts +0 -38
  34. package/src/domain/identity/value-objects/identity-id.vo.ts +0 -26
  35. package/src/domain/identity/value-objects/identity-provider.vo.ts +0 -13
  36. package/src/domain/identity/value-objects/index.ts +0 -8
  37. package/src/domain/identity/value-objects/risk-signal.vo.ts +0 -17
  38. package/src/domain/index.ts +0 -5
  39. package/src/domain/mfa/aggregates/mfa-session.aggregate.ts +0 -84
  40. package/src/domain/mfa/entities/mfa-challenge.entity.ts +0 -70
  41. package/src/domain/mfa/types/mfa-challenge-registry.ts +0 -21
  42. package/src/domain/mfa/value-objects/mfa-challenge-id.vo.ts +0 -19
  43. package/src/domain/mfa/value-objects/mfa-challenge-status.vo.ts +0 -31
  44. package/src/domain/mfa/value-objects/mfa-session-id.vo.ts +0 -19
  45. package/src/domain/mfa/violations/mfa-active-challenge-exists.violation.ts +0 -10
  46. package/src/domain/mfa/violations/mfa-already-verified.violation.ts +0 -10
  47. package/src/domain/mfa/violations/mfa-attempts-exceeded.violation.ts +0 -17
  48. package/src/domain/mfa/violations/mfa-expired.violation.ts +0 -10
  49. package/src/domain/oauth/aggregates/oauth-authorization.aggregate.ts +0 -106
  50. package/src/domain/oauth/aggregates/oauth-authorize.service.ts +0 -0
  51. package/src/domain/oauth/entities/oauth-authorization.entity.ts +0 -50
  52. package/src/domain/oauth/value-objects/authorization-code-id.vo.ts +0 -9
  53. package/src/domain/oauth/value-objects/authorization-code.vo.ts +0 -18
  54. package/src/domain/oauth/value-objects/client-id.vo.ts +0 -9
  55. package/src/domain/oauth/value-objects/code-challenge-method.vo.ts +0 -15
  56. package/src/domain/oauth/value-objects/code-challenge.vo.ts +0 -24
  57. package/src/domain/oauth/value-objects/oauth-authorization-id.vo.ts +0 -19
  58. package/src/domain/oauth/value-objects/oauth-provider.vo.ts +0 -15
  59. package/src/domain/oauth/value-objects/pkce.vo.ts +0 -29
  60. package/src/domain/oauth/value-objects/redirect-uri.vo.ts +0 -19
  61. package/src/domain/oauth/value-objects/scope-set.vo.ts +0 -37
  62. package/src/domain/oauth/violations/authorization-already-used.violation.ts +0 -10
  63. package/src/domain/oauth/violations/authorization-expired.violation.ts +0 -10
  64. package/src/domain/oauth/violations/consent-required.violation.ts +0 -10
  65. package/src/domain/oauth/violations/invalid-authorization-code.violation.ts +0 -12
  66. package/src/domain/oauth/violations/invalid-oauth-provider.violation.ts +0 -13
  67. package/src/domain/oauth/violations/invalid-pkce.violation.ts +0 -12
  68. package/src/domain/oauth/violations/invalid-redirect-uri.violation.ts +0 -10
  69. package/src/domain/policy/contracts/auth-policy-context.ts +0 -27
  70. package/src/domain/policy/contracts/auth-policy-decision.ts +0 -7
  71. package/src/domain/policy/contracts/auth-policy.ts +0 -17
  72. package/src/domain/policy/contracts/index.ts +0 -3
  73. package/src/domain/policy/engine/auth-policy-engine.ts +0 -41
  74. package/src/domain/policy/index.ts +0 -2
  75. package/src/domain/session/entities/session.entity.ts +0 -82
  76. package/src/domain/session/value-objects/session-id.vo.ts +0 -10
  77. package/src/domain/token/aggregates/token.aggregate.ts +0 -34
  78. package/src/domain/token/value-objects/auth-token.vo.ts +0 -29
  79. package/src/domain/token/value-objects/session-token.vo.ts +0 -14
  80. package/src/domain/violations/auth-domain.violation.ts +0 -9
  81. package/src/domain/violations/invalid-auth-token.violation.ts +0 -13
  82. package/src/domain/violations/invalid-scope.violation.ts +0 -10
  83. package/src/domain/violations/invalid-session.violation.ts +0 -13
  84. package/src/index.ts +0 -3
  85. package/src/ports/inbound/auth-method.port.ts +0 -18
  86. package/src/ports/inbound/index.ts +0 -2
  87. package/src/ports/inbound/start-auth.command.ts +0 -28
  88. package/src/ports/index.ts +0 -2
  89. package/src/ports/log/log.port.ts +0 -25
  90. package/src/ports/mfa/mfa-clock.port.ts +0 -11
  91. package/src/ports/mfa/mfa-session-id-generator.port.ts +0 -15
  92. package/src/ports/mfa/mfa-session-repository.port.ts +0 -31
  93. package/src/ports/observability/observability-event.port.ts +0 -16
  94. package/src/ports/outbound/authentication-attempt.repository.port.ts +0 -11
  95. package/src/ports/outbound/domain-event-publisher.port.ts +0 -13
  96. package/src/ports/outbound/index.ts +0 -2
  97. package/src/ports/outbound/session.repository.port.ts +0 -9
  98. package/src/ports/repositories/oauth-authorization.repository.ts +0 -21
  99. package/src/ports/repositories/token.repository.ts +0 -11
  100. package/src/types/auth-context.type.ts +0 -11
  101. package/src/types/auth-factor.type.ts +0 -10
  102. package/src/types/auth-method.type.ts +0 -20
  103. package/src/types/auth-policy.type.ts +0 -16
  104. package/src/types/identity-provider.type.ts +0 -8
  105. package/src/types/index.ts +0 -6
  106. package/src/types/observability-event.ts +0 -33
  107. package/src/types/risk-signal.type.ts +0 -11
  108. package/src/utils/default-if-blank.util.ts +0 -46
  109. package/tsconfig.build.json +0 -6
  110. package/tsconfig.json +0 -30
  111. package/tsup.config.ts +0 -13
  112. package/vitest.config.ts +0 -12
package/eslint.config.mjs DELETED
@@ -1,59 +0,0 @@
1
- import config from '@rineex/eslint-config/base';
2
- import db from '@rineex/eslint-config/db';
3
-
4
- export default [
5
- ...config,
6
- ...db,
7
-
8
- {
9
- rules: {
10
- '@typescript-eslint/class-literal-property-style': ['off'],
11
- '@typescript-eslint/consistent-type-definitions': ['off'],
12
- '@typescript-eslint/consistent-type-imports': 'off',
13
- '@typescript-eslint/no-extraneous-class': 'off',
14
- 'max-params': ['error', 5],
15
- },
16
- },
17
- {
18
- rules: {
19
- 'perfectionist/sort-imports': [
20
- 'error',
21
- {
22
- groups: [
23
- 'builtin', // Node.js built-in modules (e.g., 'fs', 'path')
24
- 'external', // External dependencies (e.g., 'react', 'lodash')
25
- 'nestjs',
26
- 'common', // Your custom group for '@/common/*'
27
- 'internal', // Other internal imports (e.g., '~/components/Button')
28
- 'parent', // Parent directory imports (e.g., '../utils')
29
- 'sibling', // Same directory imports (e.g., './config')
30
- 'index', // Index file imports (e.g., './')
31
- ],
32
- customGroups: {
33
- value: {
34
- nestjs: '@nestjs/*',
35
- common: '@/*', // Matches imports like '@/common/utils', '@/common/helpers', etc.
36
- },
37
- },
38
-
39
- newlinesBetween: 'always',
40
- type: 'line-length',
41
-
42
- order: 'desc',
43
- },
44
- ],
45
- 'perfectionist/sort-objects': [
46
- 'error',
47
- {
48
- type: 'line-length',
49
- order: 'desc',
50
- },
51
- ],
52
-
53
- 'perfectionist/sort-decorators': [
54
- 'error',
55
- { type: 'line-length', order: 'desc' },
56
- ],
57
- },
58
- },
59
- ];
@@ -1,18 +0,0 @@
1
- import { ObservabilityEvent } from '@/types/observability-event';
2
-
3
- export class ChallengeIssueObservabilityEvent extends ObservabilityEvent {
4
- constructor(params: {
5
- sessionId: string;
6
- challengeType: string;
7
- success?: boolean;
8
- }) {
9
- super({
10
- payload: {
11
- challengeType: params.challengeType,
12
- sessionId: params.sessionId,
13
- success: params.success,
14
- },
15
- name: 'authentication.mfa.challenge.issued',
16
- });
17
- }
18
- }
@@ -1,18 +0,0 @@
1
- import { ObservabilityEvent } from '@/types/observability-event';
2
-
3
- export class SessionStartedObservabilityEvent extends ObservabilityEvent {
4
- constructor(params: {
5
- sessionId: string;
6
- identityId: string;
7
- reused?: boolean;
8
- }) {
9
- super({
10
- payload: {
11
- reused: params.reused ?? false,
12
- identityId: params.identityId,
13
- sessionId: params.sessionId,
14
- },
15
- name: 'authentication.mfa.session.started',
16
- });
17
- }
18
- }
@@ -1,14 +0,0 @@
1
- import { ObservabilityEvent } from '@/types/observability-event';
2
-
3
- export class VerificationFailedObservabilityEvent extends ObservabilityEvent {
4
- constructor(sessionId: string, reasonCode: string) {
5
- super({
6
- payload: {
7
- reasonCode,
8
- sessionId,
9
- },
10
- name: 'authentication.mfa.verification_failed',
11
- occurredAt: new Date(),
12
- });
13
- }
14
- }
@@ -1,12 +0,0 @@
1
- import { ObservabilityEvent } from '@/types/observability-event';
2
-
3
- export class VerificationSucceededObservabilityEvent extends ObservabilityEvent {
4
- constructor(params: { sessionId: string }) {
5
- super({
6
- payload: {
7
- sessionId: params.sessionId,
8
- },
9
- name: 'authentication.mfa.verification.succeeded',
10
- });
11
- }
12
- }
@@ -1,75 +0,0 @@
1
- import { ObservabilityEventPort } from '@/ports/observability/observability-event.port';
2
- import { MfaSessionRepository } from '@/ports/mfa/mfa-session-repository.port';
3
- import { MfaSessionId } from '@/domain/mfa/value-objects/mfa-session-id.vo';
4
- import { MFAChallenge } from '@/domain/mfa/entities/mfa-challenge.entity';
5
- import { ApplicationServicePort, Result } from '@rineex/ddd';
6
- import { MfaClock } from '@/ports/mfa/mfa-clock.port';
7
- import { LoggerPort } from '@/ports/log/log.port';
8
-
9
- import { ChallengeIssueObservabilityEvent } from './events/challenge-issue-observability.event';
10
-
11
- type IssueMfaChallengeInput = {
12
- sessionId: MfaSessionId;
13
- challenge: MFAChallenge;
14
- };
15
-
16
- type IssueMfaChallengeOutput = Result<void, never>;
17
-
18
- export class IssueMfaChallengeApplicationService implements ApplicationServicePort<
19
- IssueMfaChallengeInput,
20
- IssueMfaChallengeOutput
21
- > {
22
- constructor(
23
- private readonly repository: MfaSessionRepository,
24
- private readonly clock: MfaClock,
25
- private readonly logger: LoggerPort,
26
- private readonly events: ObservabilityEventPort,
27
- ) {}
28
-
29
- async execute({
30
- challenge,
31
- sessionId,
32
- }: IssueMfaChallengeInput): Promise<IssueMfaChallengeOutput> {
33
- try {
34
- const session = await this.repository.findById(sessionId);
35
-
36
- if (!session) {
37
- this.logger.warn('MFA session not found', { sessionId });
38
- throw new Error('MFA session not found'); // app-layer error
39
- }
40
-
41
- session.issueChallenge(challenge, this.clock.now());
42
-
43
- await this.repository.save(session);
44
-
45
- this.logger.info('MFA challenge issued', {
46
- sessionId: sessionId.toString(),
47
- challengeType: challenge,
48
- });
49
-
50
- this.events.emit(
51
- new ChallengeIssueObservabilityEvent({
52
- challengeType: challenge.props.challengeType,
53
- sessionId: sessionId.toString(),
54
- success: true,
55
- }),
56
- );
57
-
58
- return Result.ok(undefined);
59
- } catch (error) {
60
- this.events.emit(
61
- new ChallengeIssueObservabilityEvent({
62
- challengeType: challenge.props.challengeType,
63
- sessionId: sessionId.toString(),
64
- success: false,
65
- }),
66
- );
67
-
68
- this.logger.error('Error issuing MFA challenge', {
69
- sessionId: sessionId.toString(),
70
- error,
71
- });
72
- return Result.fail(error as never);
73
- }
74
- }
75
- }
@@ -1,90 +0,0 @@
1
- import { ObservabilityEventPort } from '@/ports/observability/observability-event.port';
2
- import { MfaSessionIdGenerator } from '@/ports/mfa/mfa-session-id-generator.port';
3
- import { MfaSessionRepository } from '@/ports/mfa/mfa-session-repository.port';
4
- import { MFASession } from '@/domain/mfa/aggregates/mfa-session.aggregate';
5
- import { ApplicationServicePort, Result } from '@rineex/ddd';
6
- import { LoggerPort } from '@/ports/log/log.port';
7
- import { IdentityId } from '@/index';
8
-
9
- import { VerificationFailedObservabilityEvent } from './events/verification-failed-observability.event';
10
- import { SessionStartedObservabilityEvent } from './events/session-started-observability.event';
11
-
12
- type StartMFASessionInput = {
13
- identityId: IdentityId;
14
- maxAttempts: number;
15
- };
16
-
17
- type StartMFASessionOutput = Result<MFASession, never>;
18
-
19
- export class StartMfaSessionApplicationService implements ApplicationServicePort<
20
- StartMFASessionInput,
21
- StartMFASessionOutput
22
- > {
23
- constructor(
24
- private readonly repository: MfaSessionRepository,
25
- private readonly idGenerator: MfaSessionIdGenerator,
26
- private readonly logger: LoggerPort,
27
- private readonly events: ObservabilityEventPort,
28
- ) {}
29
-
30
- async execute(args: StartMFASessionInput): Promise<StartMFASessionOutput> {
31
- try {
32
- const existing = await this.repository.findActiveByIdentity(
33
- args.identityId,
34
- );
35
-
36
- if (existing) {
37
- this.logger.info('MFA session reused', {
38
- identityId: args.identityId.toString(),
39
- });
40
-
41
- this.events.emit(
42
- new SessionStartedObservabilityEvent({
43
- identityId: args.identityId.toString(),
44
- sessionId: existing.id.toString(),
45
- reused: true,
46
- }),
47
- );
48
-
49
- return Result.ok(existing);
50
- }
51
-
52
- const session = new MFASession({
53
- id: this.idGenerator.generate(),
54
- maxAttempts: args.maxAttempts,
55
- identityId: args.identityId,
56
- attemptsUsed: 0,
57
- challenges: [],
58
- });
59
-
60
- await this.repository.save(session);
61
-
62
- this.logger.info('MFA session started', {
63
- identityId: args.identityId.toString(),
64
- sessionId: session.id.toString(),
65
- });
66
-
67
- this.events.emit(
68
- new SessionStartedObservabilityEvent({
69
- identityId: args.identityId.toString(),
70
- sessionId: session.id.toString(),
71
- reused: false,
72
- }),
73
- );
74
-
75
- return Result.ok(session);
76
- } catch (violation) {
77
- this.events.emit(
78
- new VerificationFailedObservabilityEvent(
79
- args.identityId.toString(),
80
- violation instanceof Error
81
- ? violation.message
82
- : 'START_MFA_SESSION_ERROR',
83
- ),
84
- );
85
-
86
- this.logger.error('Error starting MFA session', { violation, ...args });
87
- return Result.fail(violation as never);
88
- }
89
- }
90
- }
@@ -1,61 +0,0 @@
1
- import { ObservabilityEventPort } from '@/ports/observability/observability-event.port';
2
- import { MfaSessionRepository } from '@/ports/mfa/mfa-session-repository.port';
3
- import { MfaSessionId } from '@/domain/mfa/value-objects/mfa-session-id.vo';
4
- import { ApplicationServicePort, Result } from '@rineex/ddd';
5
- import { MfaClock } from '@/ports/mfa/mfa-clock.port';
6
- import { LoggerPort } from '@/ports/log/log.port';
7
-
8
- import { VerificationSucceededObservabilityEvent } from './events/verification-succeeded-observibility.event';
9
- import { VerificationFailedObservabilityEvent } from './events/verification-failed-observability.event';
10
-
11
- type VerifyMFAInput = {
12
- sessionId: MfaSessionId;
13
- };
14
-
15
- type VerifyMFAOutput = Result<void, never>;
16
-
17
- export class VerifyMfaApplicationService implements ApplicationServicePort<
18
- VerifyMFAInput,
19
- VerifyMFAOutput
20
- > {
21
- constructor(
22
- private readonly repository: MfaSessionRepository,
23
- private readonly clock: MfaClock,
24
- private readonly events: ObservabilityEventPort,
25
-
26
- private readonly logger: LoggerPort,
27
- ) {}
28
-
29
- async execute({ sessionId }: VerifyMFAInput): Promise<VerifyMFAOutput> {
30
- try {
31
- const session = await this.repository.findById(sessionId);
32
-
33
- if (!session) {
34
- throw new Error('MFA session not found');
35
- }
36
-
37
- session.markAttempt();
38
- session.verify(this.clock.now());
39
-
40
- await this.repository.save(session);
41
-
42
- this.events.emit(
43
- new VerificationSucceededObservabilityEvent({
44
- sessionId: session.id.toString(),
45
- }),
46
- );
47
-
48
- return Result.ok(undefined);
49
- } catch (error) {
50
- this.events.emit(
51
- new VerificationFailedObservabilityEvent(
52
- sessionId.toString(),
53
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
54
- (error as any).code ?? 'UNKNOWN',
55
- ),
56
- );
57
- this.logger?.error('Error verifying MFA session', { sessionId, error });
58
- return Result.fail(error as never);
59
- }
60
- }
61
- }
@@ -1,77 +0,0 @@
1
- import { AuthenticationAttemptRepositoryPort } from '@/ports/outbound/authentication-attempt.repository.port';
2
- import { DomainEventPublisherPort } from '@/ports/outbound/domain-event-publisher.port';
3
- import { StartAuthenticationCommand } from '@/ports/inbound/start-auth.command';
4
- import { AuthAttemptId, AuthenticationAttempt, AuthMethod } from '@/domain';
5
- import { AuthMethodPort } from '@/ports/inbound/auth-method.port';
6
- import { AuthMethodName } from '@/types/auth-method.type';
7
- import { ApplicationServicePort } from '@rineex/ddd';
8
-
9
- /**
10
- * Application service responsible for orchestrating authentication flows.
11
- *
12
- * This service:
13
- * - Coordinates domain objects
14
- * - Delegates authentication to method modules
15
- * - Remains stateless and deterministic
16
- *
17
- * It is safe to use in:
18
- * - HTTP servers
19
- * - Workers
20
- * - Serverless
21
- * - CLI tools
22
- */
23
- export class AuthOrchestratorService implements ApplicationServicePort<
24
- StartAuthenticationCommand,
25
- AuthAttemptId
26
- > {
27
- constructor(
28
- private readonly authMethods: readonly AuthMethodPort[],
29
- private readonly attemptRepository: AuthenticationAttemptRepositoryPort,
30
- private readonly eventPublisher: DomainEventPublisherPort,
31
- private readonly idGenerator: () => AuthAttemptId,
32
- ) {}
33
-
34
- /**
35
- * Starts a new authentication flow.
36
- *
37
- * @throws Error if method is not registered
38
- */
39
- async execute({
40
- identityId,
41
- context,
42
- method,
43
- }: StartAuthenticationCommand): Promise<AuthAttemptId> {
44
- const authMethod = this.resolveAuthMethod(method);
45
-
46
- const attemptId = this.idGenerator();
47
-
48
- const attempt = AuthenticationAttempt.start(
49
- attemptId,
50
- AuthMethod.create(method),
51
- identityId,
52
- );
53
-
54
- await this.attemptRepository.save(attempt);
55
-
56
- await this.eventPublisher.publish(attempt.pullDomainEvents());
57
-
58
- await authMethod.authenticate(context);
59
-
60
- return attemptId;
61
- }
62
-
63
- /**
64
- * Resolves an authentication method implementation.
65
- *
66
- * Fail-fast behavior is intentional for security reasons.
67
- */
68
- private resolveAuthMethod(method: AuthMethodName): AuthMethodPort {
69
- const resolved = this.authMethods.find(m => m.method.is(method));
70
-
71
- if (!resolved) {
72
- throw new Error(`Authentication method not registered: ${method}`);
73
- }
74
-
75
- return resolved;
76
- }
77
- }
@@ -1,12 +0,0 @@
1
- import { ApplicationServicePort } from '@rineex/ddd';
2
-
3
- export class OAuthAuthorizeService implements ApplicationServicePort<any, any> {
4
- constructor(
5
- private readonly authorizationRepository: AuthorizationRepository,
6
- private readonly clientRepository: ClientRepository,
7
- ) {}
8
-
9
- async execute(command: any): Promise<any> {
10
- // Implementation goes here
11
- }
12
- }
@@ -1,136 +0,0 @@
1
- import { AggregateRoot, CreateEntityProps } from '@rineex/ddd';
2
-
3
- import { AuthenticationSucceededEvent } from '../events/authentication-succeeded.event';
4
- import { AuthenticationStartedEvent } from '../events/authentication-started.event';
5
- import { AuthenticationFailedEvent } from '../events/authentication-failed.event';
6
- import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
7
- import { AuthStatus } from '../value-objects/auth-status.vo';
8
- import { AuthMethod } from '../value-objects/auth-method.vo';
9
- import { IdentityId } from '../value-objects/identity-id.vo';
10
-
11
- interface AuthenticationAttemptProps extends CreateEntityProps<AuthAttemptId> {
12
- status: AuthStatus;
13
- method: AuthMethod;
14
- identityId?: IdentityId;
15
- }
16
-
17
- /**
18
- * Aggregate Root representing a single authentication attempt.
19
- *
20
- * Responsibilities:
21
- * - Enforce authentication lifecycle invariants
22
- * - Emit auditable domain events
23
- * - Prevent invalid state transitions
24
- *
25
- * This aggregate DOES NOT:
26
- * - Perform authentication
27
- * - Talk to infrastructure
28
- * - Know about HTTP or sessions
29
- */
30
- export class AuthenticationAttempt extends AggregateRoot<AuthAttemptId> {
31
- /**
32
- * Current status of the authentication attempt.
33
- */
34
- private _status: AuthStatus;
35
- /**
36
- * Method used for authentication.
37
- */
38
- private _method: AuthMethod;
39
- /**
40
- * Optional identity being authenticated.
41
- */
42
- private _identityId?: IdentityId;
43
-
44
- private constructor({ createdAt, id, ...props }: AuthenticationAttemptProps) {
45
- super({
46
- createdAt,
47
- id,
48
- });
49
- this._status = props.status;
50
- this._method = props.method;
51
- this._identityId = props.identityId;
52
- }
53
-
54
- toObject(): Record<string, unknown> {
55
- return {
56
- identityId: this._identityId.getValue(),
57
- createdAt: this.createdAt,
58
- id: this.id.getValue(),
59
- status: this._status,
60
- method: this._method,
61
- };
62
- }
63
-
64
- /**
65
- * Factory method to start a new authentication attempt.
66
- */
67
- static start(
68
- id: AuthAttemptId,
69
- method: AuthMethod,
70
- identityId?: IdentityId,
71
- ): AuthenticationAttempt {
72
- const attempt = new AuthenticationAttempt({
73
- status: AuthStatus.create('PENDING'),
74
- identityId,
75
- method,
76
- id,
77
- });
78
-
79
- attempt.addEvent(new AuthenticationStartedEvent(id, method));
80
-
81
- return attempt;
82
- }
83
-
84
- /**
85
- * Marks the authentication attempt as successful.
86
- *
87
- * @throws Error if already completed
88
- */
89
- succeed(): void {
90
- if (this._status.isNot('PENDING')) {
91
- throw new Error('Authentication attempt already completed');
92
- }
93
-
94
- this.mutate(draft => {
95
- draft._status = AuthStatus.create('SUCCEEDED');
96
- });
97
-
98
- this.addEvent(new AuthenticationSucceededEvent(this.id));
99
- }
100
-
101
- /**
102
- * Marks the authentication attempt as failed.
103
- *
104
- * @throws Error if already completed
105
- */
106
- fail(reason: string): void {
107
- if (this._status.isNot('PENDING')) {
108
- throw new Error('Authentication attempt already completed');
109
- }
110
-
111
- if (!reason || reason.length < 3) {
112
- throw new Error('Failure reason must be provided');
113
- }
114
-
115
- this.mutate(draft => {
116
- draft._status = AuthStatus.create('FAILED');
117
- });
118
-
119
- this.addEvent(new AuthenticationFailedEvent(this.id));
120
- }
121
-
122
- /**
123
- * Aggregate invariant validation.
124
- *
125
- * Called automatically before domain events are added.
126
- */
127
- validate(): void {
128
- if (!this._method) {
129
- throw new Error('AuthenticationAttempt must have a method');
130
- }
131
-
132
- if (!this._status) {
133
- throw new Error('AuthenticationAttempt must have a status');
134
- }
135
- }
136
- }
@@ -1 +0,0 @@
1
- export * from './authentication-attempt.aggregate';