@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
@@ -1,126 +0,0 @@
1
- import { CreateEntityProps, Entity } from '@rineex/ddd';
2
-
3
- import { IdentityId } from '../value-objects/identity-id.vo';
4
-
5
- /**
6
- * Properties owned by the Identity entity.
7
- *
8
- * IMPORTANT:
9
- * - This is NOT a user profile
10
- * - This is NOT authorization data
11
- * - This represents a stable authentication identity
12
- *
13
- * Examples:
14
- * - Email-based identity
15
- * - External IdP subject
16
- * - Service or machine identity
17
- */
18
- export interface IdentityCreateProps extends CreateEntityProps<IdentityId> {
19
- readonly isActive: boolean;
20
- }
21
-
22
- /**
23
- * Identity entity.
24
- *
25
- * Represents "who is authenticating" in the system.
26
- *
27
- * This entity is intentionally minimal and stable.
28
- * Any additional concerns (profile, roles, permissions)
29
- * MUST live in other bounded contexts.
30
- */
31
- export class Identity extends Entity<IdentityId> {
32
- // We keep the state private to the class
33
- private _isActive: boolean;
34
-
35
- /**
36
- * Private constructor forces usage of factory methods.
37
- */
38
- private constructor({ isActive, ...props }: IdentityCreateProps) {
39
- super(props);
40
- this._isActive = isActive;
41
- }
42
-
43
- /**
44
- * Factory method to create a new identity.
45
- */
46
- static create(id: IdentityId, isActive: boolean = true): Identity {
47
- return new Identity({
48
- isActive,
49
- id,
50
- });
51
- }
52
-
53
- /**
54
- * Getter for the active state
55
- */
56
- public get isActive(): boolean {
57
- return this._isActive;
58
- }
59
-
60
- /**
61
- * Disables this identity.
62
- */
63
- public disable(): void {
64
- if (!this._isActive) return;
65
-
66
- this.mutate(draft => {
67
- draft._isActive = false;
68
- });
69
- }
70
-
71
- /**
72
- * Enables this identity.
73
- */
74
- public enable(): void {
75
- if (this._isActive) return;
76
-
77
- this.mutate(draft => {
78
- draft._isActive = true;
79
- });
80
- }
81
-
82
- /**
83
- * Domain invariant validation.
84
- */
85
- public validate(): void {
86
- if (this.id == null) {
87
- throw new Error('Identity must have a valid IdentityId');
88
- }
89
- if (typeof this._isActive !== 'boolean') {
90
- throw new Error('Identity.isActive must be a boolean');
91
- }
92
- }
93
-
94
- /**
95
- * Serialization to plain object.
96
- */
97
- public toObject(): Record<string, unknown> {
98
- return {
99
- createdAt: this.createdAt.toISOString(),
100
- isActive: this._isActive,
101
- id: this.id.toString(),
102
- };
103
- }
104
-
105
- /**
106
- * Creates a snapshot of mutable state.
107
- * Identity fields MUST NOT be included.
108
- *
109
- * Used internally for rollback.
110
- */
111
- protected snapshot(): Record<string, unknown> {
112
- return {
113
- isActive: this._isActive,
114
- };
115
- }
116
-
117
- /**
118
- * Restores mutable state from a snapshot.
119
- * Identity fields MUST NOT be modified.
120
- *
121
- * @param snapshot - Previously captured state.
122
- */
123
- protected restore(snapshot: Record<string, unknown>): void {
124
- this._isActive = snapshot.isActive as boolean;
125
- }
126
- }
@@ -1 +0,0 @@
1
- export * from './identity.entity';
@@ -1,24 +0,0 @@
1
- import { DomainEvent } from '@rineex/ddd';
2
-
3
- import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
-
5
- /**
6
- * Emitted when an authentication attempt failed.
7
- */
8
- export class AuthenticationFailedEvent extends DomainEvent {
9
- public get eventName(): string {
10
- return 'authentication.auth_attempt.failed';
11
- }
12
-
13
- constructor(public readonly attemptId: AuthAttemptId) {
14
- super({
15
- payload: {
16
- attemptId: attemptId.toString(),
17
- },
18
- aggregateId: attemptId.toString(),
19
- id: crypto.randomUUID(),
20
- occurredAt: Date.now(),
21
- schemaVersion: 1,
22
- });
23
- }
24
- }
@@ -1,29 +0,0 @@
1
- import { DomainEvent } from '@rineex/ddd';
2
-
3
- import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
- import { AuthMethod } from '../value-objects/auth-method.vo';
5
-
6
- /**
7
- * Emitted when an authentication attempt begins.
8
- */
9
- export class AuthenticationStartedEvent extends DomainEvent {
10
- public get eventName(): string {
11
- return 'authentication.authentication_started';
12
- }
13
-
14
- constructor(
15
- public readonly attemptId: AuthAttemptId,
16
- public readonly method: AuthMethod,
17
- ) {
18
- super({
19
- payload: {
20
- attemptId: attemptId.toString(),
21
- method: method.toString(),
22
- },
23
- aggregateId: attemptId.toString(),
24
- id: crypto.randomUUID(),
25
- occurredAt: Date.now(),
26
- schemaVersion: 1,
27
- });
28
- }
29
- }
@@ -1,24 +0,0 @@
1
- import { DomainEvent } from '@rineex/ddd';
2
-
3
- import { AuthAttemptId } from '../value-objects/auth-attempt-id.vo';
4
-
5
- /**
6
- * Emitted when an authentication attempt succeeds.
7
- */
8
- export class AuthenticationSucceededEvent extends DomainEvent {
9
- public get eventName(): string {
10
- return 'authentication.auth_attempt.succeeded';
11
- }
12
-
13
- constructor(public readonly attemptId: AuthAttemptId) {
14
- super({
15
- payload: {
16
- attemptId: attemptId.toString(),
17
- },
18
- aggregateId: attemptId.toString(),
19
- id: crypto.randomUUID(),
20
- occurredAt: Date.now(),
21
- schemaVersion: 1,
22
- });
23
- }
24
- }
@@ -1,3 +0,0 @@
1
- export * from './authentication-failed.event';
2
- export * from './authentication-started.event';
3
- export * from './authentication-succeeded.event';
@@ -1,4 +0,0 @@
1
- export * from './aggregates';
2
- export * from './entities';
3
- export * from './events';
4
- export * from './value-objects';
@@ -1,42 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { AuthAttemptId } from '../auth-attempt-id.vo';
4
-
5
- describe('authAttemptId', () => {
6
- const VALID_UUID = '550e8400-e29b-41d4-a716-446655440000';
7
-
8
- it('should create a valid AuthAttemptId with a valid UUID string', () => {
9
- const authAttemptId = AuthAttemptId.create(VALID_UUID);
10
-
11
- expect(authAttemptId).toBeInstanceOf(AuthAttemptId);
12
- expect(authAttemptId.getValue()).toBe(VALID_UUID);
13
- });
14
-
15
- it('should throw an error when creating with an empty string', () => {
16
- expect(() => AuthAttemptId.create('')).toThrow(
17
- 'AuthAttemptId must be a valid non-empty identifier',
18
- );
19
- });
20
-
21
- it('should throw an error when creating with a string shorter than 16 characters', () => {
22
- expect(() => AuthAttemptId.create('short')).toThrow(
23
- 'AuthAttemptId must be a valid non-empty identifier',
24
- );
25
- });
26
-
27
- it('should throw an error when creating with null or undefined', () => {
28
- expect(() => AuthAttemptId.create(null as any)).toThrow(
29
- 'AuthAttemptId must be a valid non-empty identifier',
30
- );
31
- expect(() => AuthAttemptId.create(undefined as any)).toThrow(
32
- 'AuthAttemptId must be a valid non-empty identifier',
33
- );
34
- });
35
-
36
- it('should accept a valid string with 16 or more characters', () => {
37
- const validId = '1234567890123456';
38
- const authAttemptId = AuthAttemptId.create(validId);
39
-
40
- expect(authAttemptId.getValue()).toBe(validId);
41
- });
42
- });
@@ -1,39 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { AuthFactor } from '../auth-factor.vo';
4
-
5
- describe('authFactor', () => {
6
- it('should create a valid AuthFactor with a valid identifier', () => {
7
- const factor = AuthFactor.create('password');
8
-
9
- expect(factor.toString()).toBe('password');
10
- });
11
-
12
- it('should create a valid AuthFactor with different factor types', () => {
13
- const factors = ['mfa', 'biometric', 'email', 'sms'];
14
-
15
- factors.forEach(factorType => {
16
- const factor = AuthFactor.create(factorType as any);
17
-
18
- expect(factor.toString()).toBe(factorType);
19
- });
20
- });
21
-
22
- it('should throw an error when created with empty value', () => {
23
- expect(() => AuthFactor.create('' as any)).toThrow(
24
- 'AuthFactor must be a valid identifier',
25
- );
26
- });
27
-
28
- it('should throw an error when created with null value', () => {
29
- expect(() => AuthFactor.create(null as any)).toThrow(
30
- 'AuthFactor must be a valid identifier',
31
- );
32
- });
33
-
34
- it('should throw an error when created with undefined value', () => {
35
- expect(() => AuthFactor.create(undefined as any)).toThrow(
36
- 'AuthFactor must be a valid identifier',
37
- );
38
- });
39
- });
@@ -1,23 +0,0 @@
1
- import { UUID } from '@rineex/ddd';
2
-
3
- /**
4
- * Represents a globally unique identifier for an authentication attempt.
5
- *
6
- * This ID is used for:
7
- * - Correlation across logs
8
- * - Event causation
9
- * - Security auditing
10
- *
11
- * Must be generated externally (UUIDv7 recommended).
12
- */
13
- export class AuthAttemptId extends UUID {
14
- protected validate(value: string): void {
15
- if (!value || value.length < 16) {
16
- throw new Error('AuthAttemptId must be a valid non-empty identifier');
17
- }
18
- }
19
-
20
- public static create(value: string): AuthAttemptId {
21
- return new AuthAttemptId(value);
22
- }
23
- }
@@ -1,17 +0,0 @@
1
- import { AuthFactorName } from '@/types/auth-factor.type';
2
- import { PrimitiveValueObject } from '@rineex/ddd';
3
-
4
- /**
5
- * Represents an authentication factor.
6
- */
7
- export class AuthFactor extends PrimitiveValueObject<AuthFactorName> {
8
- protected validate(value: AuthFactorName): void {
9
- if (!value) {
10
- throw new Error('AuthFactor must be a valid identifier');
11
- }
12
- }
13
-
14
- public static create(value: AuthFactorName): AuthFactor {
15
- return new AuthFactor(value);
16
- }
17
- }
@@ -1,34 +0,0 @@
1
- import { AuthMethodName } from '@/types/auth-method.type';
2
- import { PrimitiveValueObject } from '@rineex/ddd';
3
-
4
- /**
5
- * Represents the authentication method requested or used.
6
- *
7
- * Examples:
8
- * - passwordless
9
- * - otp
10
- * - oauth
11
- * - oidc
12
- * - passkey
13
- *
14
- * This is a value object, NOT a strategy.
15
- */
16
- export class AuthMethod extends PrimitiveValueObject<AuthMethodName> {
17
- protected validate(value: AuthMethodName): void {
18
- if (!value) {
19
- throw new Error('AuthMethod cannot be empty');
20
- }
21
- }
22
-
23
- public static create(value: AuthMethodName): AuthMethod {
24
- return new AuthMethod(value);
25
- }
26
-
27
- public is(method: AuthMethodName): boolean {
28
- return this.value === method;
29
- }
30
-
31
- public isNot(method: AuthMethodName): boolean {
32
- return this.value !== method;
33
- }
34
- }
@@ -1,19 +0,0 @@
1
- import { AuthPolicyName } from '@/types/auth-policy.type';
2
- import { PrimitiveValueObject } from '@rineex/ddd';
3
-
4
- /**
5
- * Represents a policy applied during authentication orchestration.
6
- *
7
- * Policies influence decisions but do not authenticate users.
8
- */
9
- export class AuthPolicy extends PrimitiveValueObject<AuthPolicyName> {
10
- protected validate(value: AuthPolicyName): void {
11
- if (!value) {
12
- throw new Error('AuthPolicy must be a valid identifier');
13
- }
14
- }
15
-
16
- public static create(value: AuthPolicyName): AuthPolicy {
17
- return new AuthPolicy(value);
18
- }
19
- }
@@ -1,38 +0,0 @@
1
- import { PrimitiveValueObject } from '@rineex/ddd';
2
-
3
- type AuthStatusType = 'FAILED' | 'PENDING' | 'SUCCEEDED';
4
-
5
- /**
6
- * Represents the lifecycle state of an authentication attempt.
7
- *
8
- * This is intentionally restrictive to avoid invalid transitions.
9
- */
10
- export class AuthStatus extends PrimitiveValueObject<AuthStatusType> {
11
- protected validate(value: AuthStatusType): void {
12
- if (!['FAILED', 'PENDING', 'SUCCEEDED'].includes(value)) {
13
- throw new Error(`Invalid AuthStatus: ${value}`);
14
- }
15
- }
16
-
17
- /**
18
- * Checks if the current auth status matches the provided value.
19
- * @param value - The auth status type to compare against
20
- * @returns True if the current status matches the provided value, false otherwise
21
- */
22
- public is(value: AuthStatusType): boolean {
23
- return this.value === value;
24
- }
25
-
26
- /**
27
- * Checks if the current authentication status is not equal to the provided value.
28
- * @param value - The authentication status type to compare against
29
- * @returns True if the current status differs from the provided value, false otherwise
30
- */
31
- public isNot(value: AuthStatusType): boolean {
32
- return this.value !== value;
33
- }
34
-
35
- static create(value: AuthStatusType): AuthStatus {
36
- return new AuthStatus(value);
37
- }
38
- }
@@ -1,26 +0,0 @@
1
- import { PrimitiveValueObject } from '@rineex/ddd';
2
-
3
- /**
4
- * Represents the canonical identity identifier in the auth domain.
5
- *
6
- * This does NOT imply:
7
- * - user
8
- * - account
9
- * - profile
10
- *
11
- * It is intentionally abstract to support:
12
- * - enterprise SSO
13
- * - external IdPs
14
- * - service identities
15
- */
16
- export class IdentityId extends PrimitiveValueObject<string> {
17
- protected validate(value: string): void {
18
- if (!value || value.length < 8) {
19
- throw new Error('IdentityId must be a valid identifier');
20
- }
21
- }
22
-
23
- public static create(value: string): IdentityId {
24
- return new IdentityId(value);
25
- }
26
- }
@@ -1,13 +0,0 @@
1
- import { IdentityProviderName } from '@/types/identity-provider.type';
2
- import { PrimitiveValueObject } from '@rineex/ddd';
3
-
4
- /**
5
- * Represents an external or internal identity provider.
6
- */
7
- export class IdentityProvider extends PrimitiveValueObject<IdentityProviderName> {
8
- protected validate(value: IdentityProviderName): void {
9
- if (!value) {
10
- throw new Error('IdentityProvider must be a valid identifier');
11
- }
12
- }
13
- }
@@ -1,8 +0,0 @@
1
- export * from './auth-attempt-id.vo';
2
- export * from './auth-factor.vo';
3
- export * from './auth-method.vo';
4
- export * from './auth-policy.vo';
5
- export * from './auth-status.vo';
6
- export * from './identity-id.vo';
7
- export * from './identity-provider.vo';
8
- export * from './risk-signal.vo';
@@ -1,17 +0,0 @@
1
- import { RiskSignalName } from '@/types/risk-signal.type';
2
- import { PrimitiveValueObject } from '@rineex/ddd';
3
-
4
- /**
5
- * Represents a detected risk signal during authentication.
6
- */
7
- export class RiskSignal extends PrimitiveValueObject<RiskSignalName> {
8
- protected validate(value: RiskSignalName): void {
9
- if (!value) {
10
- throw new Error('RiskSignal must be a valid identifier');
11
- }
12
- }
13
-
14
- public static create(value: RiskSignalName): RiskSignal {
15
- return new RiskSignal(value);
16
- }
17
- }
@@ -1,5 +0,0 @@
1
- export * from './identity/aggregates';
2
- export * from './identity/entities';
3
- export * from './identity/events';
4
- export * from './identity/value-objects';
5
- export * from './policy';
@@ -1,84 +0,0 @@
1
- import { AggregateRoot, CreateEntityProps } from '@rineex/ddd';
2
- import { defaultIfBlank } from '@/utils/default-if-blank.util';
3
- import { IdentityId } from '@/index';
4
-
5
- import { MfaActiveChallengeExistsViolation } from '../violations/mfa-active-challenge-exists.violation';
6
- import { MfaAttemptsExceededViolation } from '../violations/mfa-attempts-exceeded.violation';
7
- import { MfaAlreadyVerifiedViolation } from '../violations/mfa-already-verified.violation';
8
- import { MfaSessionId } from '../value-objects/mfa-session-id.vo';
9
- import { MFAChallenge } from '../entities/mfa-challenge.entity';
10
-
11
- export interface MfaSessionProps extends CreateEntityProps<MfaSessionId> {
12
- readonly identityId: IdentityId;
13
- challenges: MFAChallenge[];
14
- maxAttempts: number;
15
- attemptsUsed: number;
16
- verifiedAt?: Date;
17
- }
18
-
19
- export class MFASession extends AggregateRoot<MfaSessionId> {
20
- constructor(public readonly props: MfaSessionProps) {
21
- super(props);
22
- }
23
-
24
- toObject(): Record<string, unknown> {
25
- return {
26
- verifiedAt: defaultIfBlank(this.props.verifiedAt?.toISOString(), null),
27
- challenges: this.props.challenges.map(c => c.toObject()),
28
- identityId: this.props.identityId.toString(),
29
- attemptsUsed: this.props.attemptsUsed,
30
- maxAttempts: this.props.maxAttempts,
31
- id: this.id.toString(),
32
- };
33
- }
34
-
35
- validate(): void {
36
- if (this.props.attemptsUsed > this.props.maxAttempts) {
37
- throw MfaAttemptsExceededViolation.create(
38
- this.props.attemptsUsed,
39
- this.props.maxAttempts,
40
- );
41
- }
42
-
43
- if (this.props.verifiedAt && this.props.challenges.length > 0) {
44
- throw new Error('Verified MFA session cannot have active challenges');
45
- }
46
- }
47
-
48
- get isVerified(): boolean {
49
- return this.props.verifiedAt !== undefined;
50
- }
51
-
52
- issueChallenge(challenge: MFAChallenge, now: Date): void {
53
- if (this.isVerified) throw MfaAlreadyVerifiedViolation.create();
54
-
55
- const hasActive = this.props.challenges.some(c => !c.isExpired(now));
56
-
57
- if (hasActive) {
58
- throw MfaActiveChallengeExistsViolation.create();
59
- }
60
-
61
- this.props.challenges.push(challenge);
62
- }
63
-
64
- markAttempt(): void {
65
- this.props.attemptsUsed += 1;
66
- this.validate();
67
- }
68
-
69
- verify(now: Date): void {
70
- if (this.isVerified) throw MfaAlreadyVerifiedViolation.create();
71
-
72
- this.mutate(draft => {
73
- draft.props.challenges = draft.props.challenges.filter(
74
- c => !c.isExpired(now),
75
- );
76
-
77
- if (draft.props.challenges.length === 0) {
78
- throw new Error('No valid MFA challenges to verify');
79
- }
80
-
81
- draft.props.verifiedAt = now;
82
- });
83
- }
84
- }
@@ -1,70 +0,0 @@
1
- import { AuthDomainViolation } from '@/domain/violations/auth-domain.violation';
2
- import { CreateEntityProps, Entity } from '@rineex/ddd';
3
- import { IdentityId } from '@/index';
4
-
5
- import { MfaChallengeId } from '../value-objects/mfa-challenge-id.vo';
6
- import { MfaChallengeType } from '../types/mfa-challenge-registry';
7
-
8
- export interface Props extends CreateEntityProps<MfaChallengeId> {
9
- /**
10
- * Authentication identity this challenge is bound to.
11
- * This is NOT an application user.
12
- */
13
- readonly identityId: IdentityId;
14
-
15
- readonly challengeType: MfaChallengeType;
16
-
17
- /**
18
- * Challenge issue time.
19
- */
20
- readonly issuedAt: Date;
21
-
22
- /**
23
- * Absolute expiration timestamp.
24
- */
25
- readonly expiresAt: Date;
26
- }
27
-
28
- class MfaChallengeExpiredViolation extends AuthDomainViolation {
29
- readonly code = 'MFA_CHALLENGE_EXPIRED';
30
- readonly message = 'MFA challenge has expired';
31
-
32
- static create(reason: string) {
33
- return new MfaChallengeExpiredViolation({ reason });
34
- }
35
- }
36
-
37
- export class MFAChallenge extends Entity<MfaChallengeId> {
38
- constructor(public readonly props: Props) {
39
- super(props);
40
- }
41
-
42
- toObject(): Record<string, unknown> {
43
- return {
44
- identityId: this.props.identityId.toString(),
45
- challengeType: this.props.challengeType,
46
- expiresAt: this.props.expiresAt,
47
- issuedAt: this.props.issuedAt,
48
- id: this.id.toString(),
49
- };
50
- }
51
-
52
- validate(): void {
53
- if (this.props.expiresAt <= this.props.issuedAt) {
54
- throw MfaChallengeExpiredViolation.create(
55
- 'MFA challenge expiration must be after issue time',
56
- );
57
- }
58
- }
59
-
60
- public static create(props: Props): MFAChallenge {
61
- return new MFAChallenge(props);
62
- }
63
-
64
- /**
65
- * Checks whether the challenge is expired.
66
- */
67
- isExpired(now: Date): boolean {
68
- return now > this.props.expiresAt;
69
- }
70
- }