@nauth-toolkit/core 0.1.0 → 0.1.3
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/LICENSE +90 -0
- package/README.md +30 -0
- package/package.json +7 -2
- package/jest.config.js +0 -15
- package/jest.setup.ts +0 -6
- package/src/adapters/database-columns.ts +0 -165
- package/src/adapters/express.adapter.ts +0 -385
- package/src/adapters/fastify.adapter.ts +0 -416
- package/src/adapters/index.ts +0 -16
- package/src/adapters/storage.factory.ts +0 -143
- package/src/bootstrap.ts +0 -374
- package/src/dto/auth-challenge.dto.ts +0 -231
- package/src/dto/auth-response.dto.ts +0 -253
- package/src/dto/challenge-response.dto.ts +0 -234
- package/src/dto/change-password-request.dto.ts +0 -50
- package/src/dto/change-password-response.dto.ts +0 -29
- package/src/dto/change-password.dto.ts +0 -57
- package/src/dto/error-response.dto.ts +0 -136
- package/src/dto/get-available-methods.dto.ts +0 -55
- package/src/dto/get-challenge-data-response.dto.ts +0 -28
- package/src/dto/get-challenge-data.dto.ts +0 -69
- package/src/dto/get-client-info.dto.ts +0 -104
- package/src/dto/get-device-token-response.dto.ts +0 -25
- package/src/dto/get-events-by-type.dto.ts +0 -76
- package/src/dto/get-ip-address-response.dto.ts +0 -24
- package/src/dto/get-mfa-status.dto.ts +0 -94
- package/src/dto/get-risk-assessment-history.dto.ts +0 -39
- package/src/dto/get-session-id-response.dto.ts +0 -25
- package/src/dto/get-setup-data-response.dto.ts +0 -31
- package/src/dto/get-setup-data.dto.ts +0 -75
- package/src/dto/get-suspicious-activity.dto.ts +0 -42
- package/src/dto/get-user-agent-response.dto.ts +0 -23
- package/src/dto/get-user-auth-history.dto.ts +0 -95
- package/src/dto/get-user-by-email.dto.ts +0 -61
- package/src/dto/get-user-by-id.dto.ts +0 -46
- package/src/dto/get-user-devices.dto.ts +0 -53
- package/src/dto/get-user-response.dto.ts +0 -17
- package/src/dto/has-provider.dto.ts +0 -56
- package/src/dto/index.ts +0 -57
- package/src/dto/is-trusted-device-response.dto.ts +0 -34
- package/src/dto/list-providers-response.dto.ts +0 -23
- package/src/dto/login.dto.ts +0 -95
- package/src/dto/logout-all-response.dto.ts +0 -24
- package/src/dto/logout-all.dto.ts +0 -65
- package/src/dto/logout-response.dto.ts +0 -25
- package/src/dto/logout.dto.ts +0 -64
- package/src/dto/refresh-token.dto.ts +0 -36
- package/src/dto/remove-devices.dto.ts +0 -85
- package/src/dto/resend-code-response.dto.ts +0 -32
- package/src/dto/resend-code.dto.ts +0 -51
- package/src/dto/reset-password.dto.ts +0 -115
- package/src/dto/respond-challenge.dto.ts +0 -272
- package/src/dto/set-mfa-exemption.dto.ts +0 -112
- package/src/dto/set-must-change-password-response.dto.ts +0 -27
- package/src/dto/set-must-change-password.dto.ts +0 -46
- package/src/dto/set-preferred-method.dto.ts +0 -80
- package/src/dto/setup-mfa.dto.ts +0 -98
- package/src/dto/signup.dto.ts +0 -174
- package/src/dto/social-auth.dto.ts +0 -422
- package/src/dto/trust-device-response.dto.ts +0 -30
- package/src/dto/trust-device.dto.ts +0 -9
- package/src/dto/update-user-attributes-request.dto.ts +0 -51
- package/src/dto/user-response.dto.ts +0 -138
- package/src/dto/user-update.dto.ts +0 -222
- package/src/dto/verify-email.dto.ts +0 -313
- package/src/dto/verify-mfa-code.dto.ts +0 -103
- package/src/dto/verify-phone-by-sub.dto.ts +0 -78
- package/src/dto/verify-phone.dto.ts +0 -245
- package/src/entities/auth-audit.entity.ts +0 -232
- package/src/entities/challenge-session.entity.ts +0 -116
- package/src/entities/index.ts +0 -29
- package/src/entities/login-attempt.entity.ts +0 -64
- package/src/entities/mfa-device.entity.ts +0 -151
- package/src/entities/rate-limit.entity.ts +0 -44
- package/src/entities/session.entity.ts +0 -180
- package/src/entities/social-account.entity.ts +0 -96
- package/src/entities/storage-lock.entity.ts +0 -39
- package/src/entities/trusted-device.entity.ts +0 -112
- package/src/entities/user.entity.ts +0 -243
- package/src/entities/verification-token.entity.ts +0 -141
- package/src/enums/auth-audit-event-type.enum.ts +0 -360
- package/src/enums/error-codes.enum.ts +0 -420
- package/src/enums/mfa-method.enum.ts +0 -97
- package/src/enums/risk-factor.enum.ts +0 -111
- package/src/exceptions/nauth.exception.ts +0 -231
- package/src/handlers/auth.handler.ts +0 -260
- package/src/handlers/client-info.handler.ts +0 -101
- package/src/handlers/csrf.handler.ts +0 -156
- package/src/handlers/token-delivery.handler.ts +0 -118
- package/src/index.ts +0 -118
- package/src/interfaces/client-info.interface.ts +0 -85
- package/src/interfaces/config.interface.ts +0 -2135
- package/src/interfaces/entities.interface.ts +0 -226
- package/src/interfaces/index.ts +0 -15
- package/src/interfaces/logger.interface.ts +0 -283
- package/src/interfaces/mfa-provider.interface.ts +0 -154
- package/src/interfaces/oauth.interface.ts +0 -148
- package/src/interfaces/provider.interface.ts +0 -47
- package/src/interfaces/social-auth-provider.interface.ts +0 -131
- package/src/interfaces/storage-adapter.interface.ts +0 -82
- package/src/interfaces/template.interface.ts +0 -510
- package/src/interfaces/token-verifier.interface.ts +0 -110
- package/src/internal.ts +0 -178
- package/src/platform/interfaces.ts +0 -299
- package/src/schemas/auth-config.schema.ts +0 -646
- package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
- package/src/services/adaptive-mfa-decision.service.ts +0 -457
- package/src/services/auth-audit.service.spec.ts +0 -675
- package/src/services/auth-audit.service.ts +0 -558
- package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
- package/src/services/auth-challenge-helper.service.ts +0 -825
- package/src/services/auth-flow-context-builder.service.ts +0 -520
- package/src/services/auth-flow-rules.ts +0 -202
- package/src/services/auth-flow-state-definitions.ts +0 -190
- package/src/services/auth-flow-state-machine.service.ts +0 -207
- package/src/services/auth-flow-state-machine.types.ts +0 -316
- package/src/services/auth.service.spec.ts +0 -4195
- package/src/services/auth.service.ts +0 -3727
- package/src/services/challenge.service.spec.ts +0 -1363
- package/src/services/challenge.service.ts +0 -696
- package/src/services/client-info.service.spec.ts +0 -572
- package/src/services/client-info.service.ts +0 -374
- package/src/services/csrf.service.ts +0 -54
- package/src/services/email-verification.service.spec.ts +0 -1229
- package/src/services/email-verification.service.ts +0 -578
- package/src/services/geo-location.service.spec.ts +0 -603
- package/src/services/geo-location.service.ts +0 -599
- package/src/services/index.ts +0 -13
- package/src/services/jwt.service.spec.ts +0 -882
- package/src/services/jwt.service.ts +0 -621
- package/src/services/mfa-base.service.spec.ts +0 -246
- package/src/services/mfa-base.service.ts +0 -611
- package/src/services/mfa.service.spec.ts +0 -693
- package/src/services/mfa.service.ts +0 -960
- package/src/services/password.service.spec.ts +0 -166
- package/src/services/password.service.ts +0 -309
- package/src/services/phone-verification.service.spec.ts +0 -1120
- package/src/services/phone-verification.service.ts +0 -751
- package/src/services/risk-detection.service.spec.ts +0 -1292
- package/src/services/risk-detection.service.ts +0 -1012
- package/src/services/risk-scoring.service.spec.ts +0 -204
- package/src/services/risk-scoring.service.ts +0 -131
- package/src/services/session.service.spec.ts +0 -1293
- package/src/services/session.service.ts +0 -803
- package/src/services/social-account.service.spec.ts +0 -725
- package/src/services/social-auth-base.service.spec.ts +0 -418
- package/src/services/social-auth-base.service.ts +0 -581
- package/src/services/social-auth.service.spec.ts +0 -238
- package/src/services/social-auth.service.ts +0 -436
- package/src/services/social-provider-registry.service.spec.ts +0 -238
- package/src/services/social-provider-registry.service.ts +0 -122
- package/src/services/trusted-device.service.spec.ts +0 -505
- package/src/services/trusted-device.service.ts +0 -339
- package/src/storage/account-lockout-storage.service.spec.ts +0 -310
- package/src/storage/account-lockout-storage.service.ts +0 -89
- package/src/storage/index.ts +0 -3
- package/src/storage/memory-storage.adapter.ts +0 -443
- package/src/storage/rate-limit-storage.service.spec.ts +0 -247
- package/src/storage/rate-limit-storage.service.ts +0 -38
- package/src/templates/html-template.engine.spec.ts +0 -161
- package/src/templates/html-template.engine.ts +0 -688
- package/src/templates/index.ts +0 -7
- package/src/utils/common-passwords.spec.ts +0 -230
- package/src/utils/common-passwords.ts +0 -170
- package/src/utils/context-storage.ts +0 -188
- package/src/utils/cookie-names.util.ts +0 -67
- package/src/utils/cookies.util.ts +0 -94
- package/src/utils/index.ts +0 -12
- package/src/utils/ip-extractor.spec.ts +0 -330
- package/src/utils/ip-extractor.ts +0 -220
- package/src/utils/nauth-logger.spec.ts +0 -388
- package/src/utils/nauth-logger.ts +0 -215
- package/src/utils/pii-redactor.spec.ts +0 -130
- package/src/utils/pii-redactor.ts +0 -288
- package/src/utils/setup/get-repositories.ts +0 -140
- package/src/utils/setup/init-services.ts +0 -422
- package/src/utils/setup/init-social.ts +0 -189
- package/src/utils/setup/init-storage.ts +0 -94
- package/src/utils/setup/register-mfa.ts +0 -165
- package/src/utils/setup/run-nauth-migrations.ts +0 -61
- package/src/utils/token-delivery-policy.ts +0 -38
- package/src/validators/template.validator.ts +0 -219
- package/tsconfig.json +0 -37
- package/tsconfig.lint.json +0 -6
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { RiskScoringService } from './risk-scoring.service';
|
|
2
|
-
import { NAuthConfig } from '../interfaces/config.interface';
|
|
3
|
-
import { NAuthLogger } from '../utils/nauth-logger';
|
|
4
|
-
import { RiskFactor } from '../enums/risk-factor.enum';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Risk Scoring Service Unit Tests
|
|
8
|
-
*
|
|
9
|
-
* Covers:
|
|
10
|
-
* - Risk score calculation with default weights
|
|
11
|
-
* - Risk score calculation with custom weights
|
|
12
|
-
* - Risk level classification
|
|
13
|
-
* - Score capping at 100
|
|
14
|
-
* - Unknown risk factor handling
|
|
15
|
-
*/
|
|
16
|
-
describe('RiskScoringService', () => {
|
|
17
|
-
let service: RiskScoringService;
|
|
18
|
-
let mockConfig: NAuthConfig;
|
|
19
|
-
let mockLogger: NAuthLogger;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
mockLogger = {
|
|
23
|
-
log: jest.fn(),
|
|
24
|
-
error: jest.fn(),
|
|
25
|
-
warn: jest.fn(),
|
|
26
|
-
debug: jest.fn(),
|
|
27
|
-
verbose: jest.fn(),
|
|
28
|
-
} as any;
|
|
29
|
-
|
|
30
|
-
mockConfig = {
|
|
31
|
-
jwt: {
|
|
32
|
-
accessToken: { secret: 'test-secret', expiresIn: '15m' },
|
|
33
|
-
refreshToken: { secret: 'test-refresh-secret', expiresIn: '7d' },
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
service = new RiskScoringService(mockConfig, mockLogger);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should be defined', () => {
|
|
41
|
-
expect(service).toBeDefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('calculateRiskScore()', () => {
|
|
45
|
-
it('should calculate score with default weights', () => {
|
|
46
|
-
const factors = [RiskFactor.NEW_DEVICE, RiskFactor.NEW_COUNTRY];
|
|
47
|
-
const score = service.calculateRiskScore(factors);
|
|
48
|
-
|
|
49
|
-
// new_device: 25 + new_country: 25 = 50
|
|
50
|
-
expect(score).toBe(50);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should calculate score with single factor', () => {
|
|
54
|
-
const factors = [RiskFactor.NEW_DEVICE];
|
|
55
|
-
const score = service.calculateRiskScore(factors);
|
|
56
|
-
|
|
57
|
-
expect(score).toBe(25);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should calculate score with all factors', () => {
|
|
61
|
-
const factors = [
|
|
62
|
-
RiskFactor.NEW_DEVICE,
|
|
63
|
-
RiskFactor.NEW_IP,
|
|
64
|
-
RiskFactor.NEW_COUNTRY,
|
|
65
|
-
RiskFactor.IMPOSSIBLE_TRAVEL,
|
|
66
|
-
RiskFactor.SUSPICIOUS_ACTIVITY,
|
|
67
|
-
];
|
|
68
|
-
const score = service.calculateRiskScore(factors);
|
|
69
|
-
|
|
70
|
-
// 25 + 15 + 25 + 40 + 30 = 135, capped at 100
|
|
71
|
-
expect(score).toBe(100);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should cap score at 100', () => {
|
|
75
|
-
const factors = [RiskFactor.IMPOSSIBLE_TRAVEL, RiskFactor.IMPOSSIBLE_TRAVEL, RiskFactor.IMPOSSIBLE_TRAVEL];
|
|
76
|
-
// This won't happen in practice (factors are unique), but tests cap behavior
|
|
77
|
-
// Simulate by using custom weights
|
|
78
|
-
mockConfig.mfa = {
|
|
79
|
-
adaptive: {
|
|
80
|
-
riskWeights: {
|
|
81
|
-
impossible_travel: 50, // High weight to test capping
|
|
82
|
-
suspicious_activity: 60,
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
service = new RiskScoringService(mockConfig, mockLogger);
|
|
87
|
-
|
|
88
|
-
const factors2 = [RiskFactor.IMPOSSIBLE_TRAVEL, RiskFactor.SUSPICIOUS_ACTIVITY];
|
|
89
|
-
const score = service.calculateRiskScore(factors2);
|
|
90
|
-
|
|
91
|
-
// 50 + 60 = 110, should cap at 100
|
|
92
|
-
expect(score).toBe(100);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should use custom weights from config', () => {
|
|
96
|
-
mockConfig.mfa = {
|
|
97
|
-
adaptive: {
|
|
98
|
-
riskWeights: {
|
|
99
|
-
new_device: 30,
|
|
100
|
-
new_country: 35,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
service = new RiskScoringService(mockConfig, mockLogger);
|
|
105
|
-
|
|
106
|
-
const factors = [RiskFactor.NEW_DEVICE, RiskFactor.NEW_COUNTRY];
|
|
107
|
-
const score = service.calculateRiskScore(factors);
|
|
108
|
-
|
|
109
|
-
// Custom weights: 30 + 35 = 65
|
|
110
|
-
expect(score).toBe(65);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should handle unknown risk factors gracefully', () => {
|
|
114
|
-
const factors = [RiskFactor.NEW_DEVICE, 'unknown_factor' as any];
|
|
115
|
-
const score = service.calculateRiskScore(factors);
|
|
116
|
-
|
|
117
|
-
// Only new_device counted: 25
|
|
118
|
-
expect(score).toBe(25);
|
|
119
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
120
|
-
'Unknown risk factor: unknown_factor, ignoring in score calculation',
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should return 0 for empty factors array', () => {
|
|
125
|
-
const score = service.calculateRiskScore([]);
|
|
126
|
-
expect(score).toBe(0);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should handle mixed known and unknown factors', () => {
|
|
130
|
-
const factors = [RiskFactor.NEW_DEVICE, 'unknown1' as any, RiskFactor.NEW_COUNTRY, 'unknown2' as any];
|
|
131
|
-
const score = service.calculateRiskScore(factors);
|
|
132
|
-
|
|
133
|
-
// Only known factors counted: 25 + 25 = 50
|
|
134
|
-
expect(score).toBe(50);
|
|
135
|
-
expect(mockLogger.warn).toHaveBeenCalledTimes(2);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
describe('getRiskLevel()', () => {
|
|
140
|
-
it('should classify score as low (0-20)', () => {
|
|
141
|
-
expect(service.getRiskLevel(0)).toBe('low');
|
|
142
|
-
expect(service.getRiskLevel(10)).toBe('low');
|
|
143
|
-
expect(service.getRiskLevel(20)).toBe('low');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should classify score as medium (21-50)', () => {
|
|
147
|
-
expect(service.getRiskLevel(21)).toBe('medium');
|
|
148
|
-
expect(service.getRiskLevel(35)).toBe('medium');
|
|
149
|
-
expect(service.getRiskLevel(50)).toBe('medium');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should classify score as high (51-100)', () => {
|
|
153
|
-
expect(service.getRiskLevel(51)).toBe('high');
|
|
154
|
-
expect(service.getRiskLevel(75)).toBe('high');
|
|
155
|
-
expect(service.getRiskLevel(100)).toBe('high');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should handle edge cases correctly', () => {
|
|
159
|
-
expect(service.getRiskLevel(20)).toBe('low');
|
|
160
|
-
expect(service.getRiskLevel(21)).toBe('medium');
|
|
161
|
-
expect(service.getRiskLevel(50)).toBe('medium');
|
|
162
|
-
expect(service.getRiskLevel(51)).toBe('high');
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe('Integration scenarios', () => {
|
|
167
|
-
it('should correctly score typical new device scenario', () => {
|
|
168
|
-
const factors = [RiskFactor.NEW_DEVICE];
|
|
169
|
-
const score = service.calculateRiskScore(factors);
|
|
170
|
-
const level = service.getRiskLevel(score);
|
|
171
|
-
|
|
172
|
-
expect(score).toBe(25);
|
|
173
|
-
expect(level).toBe('medium');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should correctly score travel scenario', () => {
|
|
177
|
-
const factors = [RiskFactor.NEW_COUNTRY];
|
|
178
|
-
const score = service.calculateRiskScore(factors);
|
|
179
|
-
const level = service.getRiskLevel(score);
|
|
180
|
-
|
|
181
|
-
expect(score).toBe(25);
|
|
182
|
-
expect(level).toBe('medium');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should correctly score high-risk scenario', () => {
|
|
186
|
-
const factors = [RiskFactor.NEW_DEVICE, RiskFactor.NEW_COUNTRY, RiskFactor.IMPOSSIBLE_TRAVEL];
|
|
187
|
-
const score = service.calculateRiskScore(factors);
|
|
188
|
-
const level = service.getRiskLevel(score);
|
|
189
|
-
|
|
190
|
-
// 25 + 25 + 40 = 90
|
|
191
|
-
expect(score).toBe(90);
|
|
192
|
-
expect(level).toBe('high');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should correctly score suspicious activity scenario', () => {
|
|
196
|
-
const factors = [RiskFactor.SUSPICIOUS_ACTIVITY];
|
|
197
|
-
const score = service.calculateRiskScore(factors);
|
|
198
|
-
const level = service.getRiskLevel(score);
|
|
199
|
-
|
|
200
|
-
expect(score).toBe(30);
|
|
201
|
-
expect(level).toBe('medium');
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { NAuthConfig } from '../interfaces/config.interface';
|
|
2
|
-
import { NAuthLogger } from '../utils/nauth-logger';
|
|
3
|
-
import { RiskFactor } from '../enums/risk-factor.enum';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Risk Scoring Service
|
|
7
|
-
*
|
|
8
|
-
* Calculates risk scores (0-100) based on detected risk factors.
|
|
9
|
-
* Uses configurable weights for each risk factor to determine overall risk.
|
|
10
|
-
*
|
|
11
|
-
* **Default Weights (aligned with NIST 800-63B recommendations):**
|
|
12
|
-
* - new_device: 25 points (medium risk - ensures MFA on first login)
|
|
13
|
-
* - new_ip: 15 points (only used if country/city unchanged - see RiskDetectionService)
|
|
14
|
-
* - new_country: 25 points (higher - significant geographic change)
|
|
15
|
-
* - impossible_travel: 40 points (critical - strong indicator of account compromise)
|
|
16
|
-
* - suspicious_activity: 30 points (high - recent security events)
|
|
17
|
-
* - incomplete_location_data: 20 points (medium-high - reduced confidence in risk assessment)
|
|
18
|
-
*
|
|
19
|
-
* **Note:** `new_ip` is automatically excluded when `new_country` or `impossible_travel`
|
|
20
|
-
* is detected to prevent double-counting (IP is the source of location data).
|
|
21
|
-
*
|
|
22
|
-
* **Risk Levels:**
|
|
23
|
-
* - 0-20: Low (no MFA required for ADAPTIVE)
|
|
24
|
-
* - 21-50: Medium (MFA recommended)
|
|
25
|
-
* - 51-100: High (MFA required)
|
|
26
|
-
*
|
|
27
|
-
* **Design Notes:**
|
|
28
|
-
* - Weights are additive (sum all factor weights)
|
|
29
|
-
* - Score is capped at 100 (maximum risk)
|
|
30
|
-
* - Default weights are conservative and security-focused
|
|
31
|
-
* - Configurable per installation via adaptive.riskWeights
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```typescript
|
|
35
|
-
* const score = riskScoringService.calculateRiskScore(['new_device', 'new_country']);
|
|
36
|
-
* // Returns: 45 (20 + 25)
|
|
37
|
-
*
|
|
38
|
-
* const level = riskScoringService.getRiskLevel(45);
|
|
39
|
-
* // Returns: 'medium'
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export class RiskScoringService {
|
|
43
|
-
/**
|
|
44
|
-
* Default risk factor weights
|
|
45
|
-
*
|
|
46
|
-
* Conservative defaults aligned with security best practices:
|
|
47
|
-
* - `new_device`: 25 points (medium risk - always requires MFA on first login)
|
|
48
|
-
* - `new_ip`: 15 points (lower - common occurrence)
|
|
49
|
-
* - `new_country`: 25 points (higher - significant geographic change)
|
|
50
|
-
* - `impossible_travel`: 40 points (critical - strong indicator of account compromise)
|
|
51
|
-
* - `suspicious_activity`: 30 points (high - recent security events)
|
|
52
|
-
* - `incomplete_location_data`: 20 points (medium-high - reduced confidence in risk assessment)
|
|
53
|
-
*
|
|
54
|
-
* **Note:** `new_device` weight is set to 25 to ensure it always triggers medium risk
|
|
55
|
-
* (21-50 range) and requires MFA, which is important for first-time logins.
|
|
56
|
-
*/
|
|
57
|
-
private readonly defaultWeights: Record<string, number> = {
|
|
58
|
-
new_device: 25,
|
|
59
|
-
new_ip: 15,
|
|
60
|
-
new_country: 25,
|
|
61
|
-
impossible_travel: 40,
|
|
62
|
-
suspicious_activity: 30,
|
|
63
|
-
incomplete_location_data: 20,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
constructor(
|
|
67
|
-
private readonly config: NAuthConfig,
|
|
68
|
-
private readonly logger: NAuthLogger,
|
|
69
|
-
) {}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Calculate risk score from detected factors
|
|
73
|
-
*
|
|
74
|
-
* Sums the weights of all detected risk factors and caps at 100.
|
|
75
|
-
* Uses configured risk weights if available, otherwise uses defaults.
|
|
76
|
-
*
|
|
77
|
-
* @param riskFactors - Array of detected risk factor strings
|
|
78
|
-
* @returns Risk score (0-100, capped at 100)
|
|
79
|
-
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```typescript
|
|
82
|
-
* const score = riskScoringService.calculateRiskScore(['new_device', 'new_country']);
|
|
83
|
-
* // Returns: 45 (20 + 25)
|
|
84
|
-
* ```
|
|
85
|
-
*/
|
|
86
|
-
calculateRiskScore(riskFactors: RiskFactor[]): number {
|
|
87
|
-
// Get weights from config or use defaults
|
|
88
|
-
const weights = this.config.mfa?.adaptive?.riskWeights || this.defaultWeights;
|
|
89
|
-
|
|
90
|
-
let score = 0;
|
|
91
|
-
for (const factor of riskFactors) {
|
|
92
|
-
const weight = weights[factor];
|
|
93
|
-
if (weight !== undefined) {
|
|
94
|
-
score += weight;
|
|
95
|
-
} else {
|
|
96
|
-
// Unknown factor - log warning but don't fail
|
|
97
|
-
this.logger?.warn?.(`Unknown risk factor: ${factor}, ignoring in score calculation`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Cap at 100 (maximum risk)
|
|
102
|
-
return Math.min(score, 100);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get risk level classification
|
|
107
|
-
*
|
|
108
|
-
* Classifies risk score into one of three levels:
|
|
109
|
-
* - low: 0-20 (no MFA required for ADAPTIVE)
|
|
110
|
-
* - medium: 21-50 (MFA recommended)
|
|
111
|
-
* - high: 51-100 (MFA required)
|
|
112
|
-
*
|
|
113
|
-
* @param score - Risk score (0-100)
|
|
114
|
-
* @returns Risk level classification
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```typescript
|
|
118
|
-
* const level = riskScoringService.getRiskLevel(45);
|
|
119
|
-
* // Returns: 'medium'
|
|
120
|
-
* ```
|
|
121
|
-
*/
|
|
122
|
-
getRiskLevel(score: number): 'low' | 'medium' | 'high' {
|
|
123
|
-
if (score <= 20) {
|
|
124
|
-
return 'low';
|
|
125
|
-
}
|
|
126
|
-
if (score <= 50) {
|
|
127
|
-
return 'medium';
|
|
128
|
-
}
|
|
129
|
-
return 'high';
|
|
130
|
-
}
|
|
131
|
-
}
|