@rineex/auth-core 0.0.6 → 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.
- package/package.json +8 -4
- package/Architecture.md +0 -257
- package/CHANGELOG.md +0 -49
- package/Definition.md +0 -1490
- package/Develop.md +0 -0
- package/RULES.md +0 -1470
- package/eslint.config.mjs +0 -59
- package/src/application/mfa/events/challenge-issue-observability.event.ts +0 -18
- package/src/application/mfa/events/session-started-observability.event.ts +0 -18
- package/src/application/mfa/events/verification-failed-observability.event.ts +0 -14
- package/src/application/mfa/events/verification-succeeded-observibility.event.ts +0 -12
- package/src/application/mfa/issue-mfa-challenge.application-service.ts +0 -75
- package/src/application/mfa/start-mfa-session.application-service.ts +0 -90
- package/src/application/mfa/verify-mfa.application-service.ts +0 -61
- package/src/application/services/auth-orchestrator.service.ts +0 -77
- package/src/application/services/oauth-authorize.service.ts +0 -12
- package/src/domain/identity/aggregates/authentication-attempt.aggregate.ts +0 -136
- package/src/domain/identity/aggregates/index.ts +0 -1
- package/src/domain/identity/entities/identity.entity.ts +0 -126
- package/src/domain/identity/entities/index.ts +0 -1
- package/src/domain/identity/events/authentication-failed.event.ts +0 -24
- package/src/domain/identity/events/authentication-started.event.ts +0 -29
- package/src/domain/identity/events/authentication-succeeded.event.ts +0 -24
- package/src/domain/identity/events/index.ts +0 -3
- package/src/domain/identity/index.ts +0 -4
- package/src/domain/identity/value-objects/__tests__/auth-attempt-id.vo.spec.ts +0 -42
- package/src/domain/identity/value-objects/__tests__/auth-factor.vo.spec.ts +0 -39
- package/src/domain/identity/value-objects/__tests__/auth-method.vo.spec.ts +0 -0
- package/src/domain/identity/value-objects/auth-attempt-id.vo.ts +0 -23
- package/src/domain/identity/value-objects/auth-factor.vo.ts +0 -17
- package/src/domain/identity/value-objects/auth-method.vo.ts +0 -34
- package/src/domain/identity/value-objects/auth-policy.vo.ts +0 -19
- package/src/domain/identity/value-objects/auth-status.vo.ts +0 -38
- package/src/domain/identity/value-objects/identity-id.vo.ts +0 -26
- package/src/domain/identity/value-objects/identity-provider.vo.ts +0 -13
- package/src/domain/identity/value-objects/index.ts +0 -8
- package/src/domain/identity/value-objects/risk-signal.vo.ts +0 -17
- package/src/domain/index.ts +0 -5
- package/src/domain/mfa/aggregates/mfa-session.aggregate.ts +0 -84
- package/src/domain/mfa/entities/mfa-challenge.entity.ts +0 -70
- package/src/domain/mfa/types/mfa-challenge-registry.ts +0 -21
- package/src/domain/mfa/value-objects/mfa-challenge-id.vo.ts +0 -19
- package/src/domain/mfa/value-objects/mfa-challenge-status.vo.ts +0 -31
- package/src/domain/mfa/value-objects/mfa-session-id.vo.ts +0 -19
- package/src/domain/mfa/violations/mfa-active-challenge-exists.violation.ts +0 -10
- package/src/domain/mfa/violations/mfa-already-verified.violation.ts +0 -10
- package/src/domain/mfa/violations/mfa-attempts-exceeded.violation.ts +0 -17
- package/src/domain/mfa/violations/mfa-expired.violation.ts +0 -10
- package/src/domain/oauth/aggregates/oauth-authorization.aggregate.ts +0 -106
- package/src/domain/oauth/aggregates/oauth-authorize.service.ts +0 -0
- package/src/domain/oauth/entities/oauth-authorization.entity.ts +0 -50
- package/src/domain/oauth/value-objects/authorization-code-id.vo.ts +0 -9
- package/src/domain/oauth/value-objects/authorization-code.vo.ts +0 -18
- package/src/domain/oauth/value-objects/client-id.vo.ts +0 -9
- package/src/domain/oauth/value-objects/code-challenge-method.vo.ts +0 -15
- package/src/domain/oauth/value-objects/code-challenge.vo.ts +0 -24
- package/src/domain/oauth/value-objects/oauth-authorization-id.vo.ts +0 -19
- package/src/domain/oauth/value-objects/oauth-provider.vo.ts +0 -15
- package/src/domain/oauth/value-objects/pkce.vo.ts +0 -29
- package/src/domain/oauth/value-objects/redirect-uri.vo.ts +0 -19
- package/src/domain/oauth/value-objects/scope-set.vo.ts +0 -37
- package/src/domain/oauth/violations/authorization-already-used.violation.ts +0 -10
- package/src/domain/oauth/violations/authorization-expired.violation.ts +0 -10
- package/src/domain/oauth/violations/consent-required.violation.ts +0 -10
- package/src/domain/oauth/violations/invalid-authorization-code.violation.ts +0 -12
- package/src/domain/oauth/violations/invalid-oauth-provider.violation.ts +0 -13
- package/src/domain/oauth/violations/invalid-pkce.violation.ts +0 -12
- package/src/domain/oauth/violations/invalid-redirect-uri.violation.ts +0 -10
- package/src/domain/policy/contracts/auth-policy-context.ts +0 -27
- package/src/domain/policy/contracts/auth-policy-decision.ts +0 -7
- package/src/domain/policy/contracts/auth-policy.ts +0 -17
- package/src/domain/policy/contracts/index.ts +0 -3
- package/src/domain/policy/engine/auth-policy-engine.ts +0 -41
- package/src/domain/policy/index.ts +0 -2
- package/src/domain/session/entities/session.entity.ts +0 -82
- package/src/domain/session/value-objects/session-id.vo.ts +0 -10
- package/src/domain/token/aggregates/token.aggregate.ts +0 -34
- package/src/domain/token/value-objects/auth-token.vo.ts +0 -29
- package/src/domain/token/value-objects/session-token.vo.ts +0 -14
- package/src/domain/violations/auth-domain.violation.ts +0 -9
- package/src/domain/violations/invalid-auth-token.violation.ts +0 -13
- package/src/domain/violations/invalid-scope.violation.ts +0 -10
- package/src/domain/violations/invalid-session.violation.ts +0 -13
- package/src/index.ts +0 -3
- package/src/ports/inbound/auth-method.port.ts +0 -18
- package/src/ports/inbound/index.ts +0 -2
- package/src/ports/inbound/start-auth.command.ts +0 -28
- package/src/ports/index.ts +0 -2
- package/src/ports/log/log.port.ts +0 -25
- package/src/ports/mfa/mfa-clock.port.ts +0 -11
- package/src/ports/mfa/mfa-session-id-generator.port.ts +0 -15
- package/src/ports/mfa/mfa-session-repository.port.ts +0 -31
- package/src/ports/observability/observability-event.port.ts +0 -16
- package/src/ports/outbound/authentication-attempt.repository.port.ts +0 -11
- package/src/ports/outbound/domain-event-publisher.port.ts +0 -13
- package/src/ports/outbound/index.ts +0 -2
- package/src/ports/outbound/session.repository.port.ts +0 -9
- package/src/ports/repositories/oauth-authorization.repository.ts +0 -21
- package/src/ports/repositories/token.repository.ts +0 -11
- package/src/types/auth-context.type.ts +0 -11
- package/src/types/auth-factor.type.ts +0 -10
- package/src/types/auth-method.type.ts +0 -20
- package/src/types/auth-policy.type.ts +0 -16
- package/src/types/identity-provider.type.ts +0 -8
- package/src/types/index.ts +0 -6
- package/src/types/observability-event.ts +0 -33
- package/src/types/risk-signal.type.ts +0 -11
- package/src/utils/default-if-blank.util.ts +0 -46
- package/tsconfig.build.json +0 -6
- package/tsconfig.json +0 -28
- package/tsup.config.ts +0 -13
- 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';
|