@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.
Files changed (184) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +30 -0
  3. package/package.json +7 -2
  4. package/jest.config.js +0 -15
  5. package/jest.setup.ts +0 -6
  6. package/src/adapters/database-columns.ts +0 -165
  7. package/src/adapters/express.adapter.ts +0 -385
  8. package/src/adapters/fastify.adapter.ts +0 -416
  9. package/src/adapters/index.ts +0 -16
  10. package/src/adapters/storage.factory.ts +0 -143
  11. package/src/bootstrap.ts +0 -374
  12. package/src/dto/auth-challenge.dto.ts +0 -231
  13. package/src/dto/auth-response.dto.ts +0 -253
  14. package/src/dto/challenge-response.dto.ts +0 -234
  15. package/src/dto/change-password-request.dto.ts +0 -50
  16. package/src/dto/change-password-response.dto.ts +0 -29
  17. package/src/dto/change-password.dto.ts +0 -57
  18. package/src/dto/error-response.dto.ts +0 -136
  19. package/src/dto/get-available-methods.dto.ts +0 -55
  20. package/src/dto/get-challenge-data-response.dto.ts +0 -28
  21. package/src/dto/get-challenge-data.dto.ts +0 -69
  22. package/src/dto/get-client-info.dto.ts +0 -104
  23. package/src/dto/get-device-token-response.dto.ts +0 -25
  24. package/src/dto/get-events-by-type.dto.ts +0 -76
  25. package/src/dto/get-ip-address-response.dto.ts +0 -24
  26. package/src/dto/get-mfa-status.dto.ts +0 -94
  27. package/src/dto/get-risk-assessment-history.dto.ts +0 -39
  28. package/src/dto/get-session-id-response.dto.ts +0 -25
  29. package/src/dto/get-setup-data-response.dto.ts +0 -31
  30. package/src/dto/get-setup-data.dto.ts +0 -75
  31. package/src/dto/get-suspicious-activity.dto.ts +0 -42
  32. package/src/dto/get-user-agent-response.dto.ts +0 -23
  33. package/src/dto/get-user-auth-history.dto.ts +0 -95
  34. package/src/dto/get-user-by-email.dto.ts +0 -61
  35. package/src/dto/get-user-by-id.dto.ts +0 -46
  36. package/src/dto/get-user-devices.dto.ts +0 -53
  37. package/src/dto/get-user-response.dto.ts +0 -17
  38. package/src/dto/has-provider.dto.ts +0 -56
  39. package/src/dto/index.ts +0 -57
  40. package/src/dto/is-trusted-device-response.dto.ts +0 -34
  41. package/src/dto/list-providers-response.dto.ts +0 -23
  42. package/src/dto/login.dto.ts +0 -95
  43. package/src/dto/logout-all-response.dto.ts +0 -24
  44. package/src/dto/logout-all.dto.ts +0 -65
  45. package/src/dto/logout-response.dto.ts +0 -25
  46. package/src/dto/logout.dto.ts +0 -64
  47. package/src/dto/refresh-token.dto.ts +0 -36
  48. package/src/dto/remove-devices.dto.ts +0 -85
  49. package/src/dto/resend-code-response.dto.ts +0 -32
  50. package/src/dto/resend-code.dto.ts +0 -51
  51. package/src/dto/reset-password.dto.ts +0 -115
  52. package/src/dto/respond-challenge.dto.ts +0 -272
  53. package/src/dto/set-mfa-exemption.dto.ts +0 -112
  54. package/src/dto/set-must-change-password-response.dto.ts +0 -27
  55. package/src/dto/set-must-change-password.dto.ts +0 -46
  56. package/src/dto/set-preferred-method.dto.ts +0 -80
  57. package/src/dto/setup-mfa.dto.ts +0 -98
  58. package/src/dto/signup.dto.ts +0 -174
  59. package/src/dto/social-auth.dto.ts +0 -422
  60. package/src/dto/trust-device-response.dto.ts +0 -30
  61. package/src/dto/trust-device.dto.ts +0 -9
  62. package/src/dto/update-user-attributes-request.dto.ts +0 -51
  63. package/src/dto/user-response.dto.ts +0 -138
  64. package/src/dto/user-update.dto.ts +0 -222
  65. package/src/dto/verify-email.dto.ts +0 -313
  66. package/src/dto/verify-mfa-code.dto.ts +0 -103
  67. package/src/dto/verify-phone-by-sub.dto.ts +0 -78
  68. package/src/dto/verify-phone.dto.ts +0 -245
  69. package/src/entities/auth-audit.entity.ts +0 -232
  70. package/src/entities/challenge-session.entity.ts +0 -116
  71. package/src/entities/index.ts +0 -29
  72. package/src/entities/login-attempt.entity.ts +0 -64
  73. package/src/entities/mfa-device.entity.ts +0 -151
  74. package/src/entities/rate-limit.entity.ts +0 -44
  75. package/src/entities/session.entity.ts +0 -180
  76. package/src/entities/social-account.entity.ts +0 -96
  77. package/src/entities/storage-lock.entity.ts +0 -39
  78. package/src/entities/trusted-device.entity.ts +0 -112
  79. package/src/entities/user.entity.ts +0 -243
  80. package/src/entities/verification-token.entity.ts +0 -141
  81. package/src/enums/auth-audit-event-type.enum.ts +0 -360
  82. package/src/enums/error-codes.enum.ts +0 -420
  83. package/src/enums/mfa-method.enum.ts +0 -97
  84. package/src/enums/risk-factor.enum.ts +0 -111
  85. package/src/exceptions/nauth.exception.ts +0 -231
  86. package/src/handlers/auth.handler.ts +0 -260
  87. package/src/handlers/client-info.handler.ts +0 -101
  88. package/src/handlers/csrf.handler.ts +0 -156
  89. package/src/handlers/token-delivery.handler.ts +0 -118
  90. package/src/index.ts +0 -118
  91. package/src/interfaces/client-info.interface.ts +0 -85
  92. package/src/interfaces/config.interface.ts +0 -2135
  93. package/src/interfaces/entities.interface.ts +0 -226
  94. package/src/interfaces/index.ts +0 -15
  95. package/src/interfaces/logger.interface.ts +0 -283
  96. package/src/interfaces/mfa-provider.interface.ts +0 -154
  97. package/src/interfaces/oauth.interface.ts +0 -148
  98. package/src/interfaces/provider.interface.ts +0 -47
  99. package/src/interfaces/social-auth-provider.interface.ts +0 -131
  100. package/src/interfaces/storage-adapter.interface.ts +0 -82
  101. package/src/interfaces/template.interface.ts +0 -510
  102. package/src/interfaces/token-verifier.interface.ts +0 -110
  103. package/src/internal.ts +0 -178
  104. package/src/platform/interfaces.ts +0 -299
  105. package/src/schemas/auth-config.schema.ts +0 -646
  106. package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
  107. package/src/services/adaptive-mfa-decision.service.ts +0 -457
  108. package/src/services/auth-audit.service.spec.ts +0 -675
  109. package/src/services/auth-audit.service.ts +0 -558
  110. package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
  111. package/src/services/auth-challenge-helper.service.ts +0 -825
  112. package/src/services/auth-flow-context-builder.service.ts +0 -520
  113. package/src/services/auth-flow-rules.ts +0 -202
  114. package/src/services/auth-flow-state-definitions.ts +0 -190
  115. package/src/services/auth-flow-state-machine.service.ts +0 -207
  116. package/src/services/auth-flow-state-machine.types.ts +0 -316
  117. package/src/services/auth.service.spec.ts +0 -4195
  118. package/src/services/auth.service.ts +0 -3727
  119. package/src/services/challenge.service.spec.ts +0 -1363
  120. package/src/services/challenge.service.ts +0 -696
  121. package/src/services/client-info.service.spec.ts +0 -572
  122. package/src/services/client-info.service.ts +0 -374
  123. package/src/services/csrf.service.ts +0 -54
  124. package/src/services/email-verification.service.spec.ts +0 -1229
  125. package/src/services/email-verification.service.ts +0 -578
  126. package/src/services/geo-location.service.spec.ts +0 -603
  127. package/src/services/geo-location.service.ts +0 -599
  128. package/src/services/index.ts +0 -13
  129. package/src/services/jwt.service.spec.ts +0 -882
  130. package/src/services/jwt.service.ts +0 -621
  131. package/src/services/mfa-base.service.spec.ts +0 -246
  132. package/src/services/mfa-base.service.ts +0 -611
  133. package/src/services/mfa.service.spec.ts +0 -693
  134. package/src/services/mfa.service.ts +0 -960
  135. package/src/services/password.service.spec.ts +0 -166
  136. package/src/services/password.service.ts +0 -309
  137. package/src/services/phone-verification.service.spec.ts +0 -1120
  138. package/src/services/phone-verification.service.ts +0 -751
  139. package/src/services/risk-detection.service.spec.ts +0 -1292
  140. package/src/services/risk-detection.service.ts +0 -1012
  141. package/src/services/risk-scoring.service.spec.ts +0 -204
  142. package/src/services/risk-scoring.service.ts +0 -131
  143. package/src/services/session.service.spec.ts +0 -1293
  144. package/src/services/session.service.ts +0 -803
  145. package/src/services/social-account.service.spec.ts +0 -725
  146. package/src/services/social-auth-base.service.spec.ts +0 -418
  147. package/src/services/social-auth-base.service.ts +0 -581
  148. package/src/services/social-auth.service.spec.ts +0 -238
  149. package/src/services/social-auth.service.ts +0 -436
  150. package/src/services/social-provider-registry.service.spec.ts +0 -238
  151. package/src/services/social-provider-registry.service.ts +0 -122
  152. package/src/services/trusted-device.service.spec.ts +0 -505
  153. package/src/services/trusted-device.service.ts +0 -339
  154. package/src/storage/account-lockout-storage.service.spec.ts +0 -310
  155. package/src/storage/account-lockout-storage.service.ts +0 -89
  156. package/src/storage/index.ts +0 -3
  157. package/src/storage/memory-storage.adapter.ts +0 -443
  158. package/src/storage/rate-limit-storage.service.spec.ts +0 -247
  159. package/src/storage/rate-limit-storage.service.ts +0 -38
  160. package/src/templates/html-template.engine.spec.ts +0 -161
  161. package/src/templates/html-template.engine.ts +0 -688
  162. package/src/templates/index.ts +0 -7
  163. package/src/utils/common-passwords.spec.ts +0 -230
  164. package/src/utils/common-passwords.ts +0 -170
  165. package/src/utils/context-storage.ts +0 -188
  166. package/src/utils/cookie-names.util.ts +0 -67
  167. package/src/utils/cookies.util.ts +0 -94
  168. package/src/utils/index.ts +0 -12
  169. package/src/utils/ip-extractor.spec.ts +0 -330
  170. package/src/utils/ip-extractor.ts +0 -220
  171. package/src/utils/nauth-logger.spec.ts +0 -388
  172. package/src/utils/nauth-logger.ts +0 -215
  173. package/src/utils/pii-redactor.spec.ts +0 -130
  174. package/src/utils/pii-redactor.ts +0 -288
  175. package/src/utils/setup/get-repositories.ts +0 -140
  176. package/src/utils/setup/init-services.ts +0 -422
  177. package/src/utils/setup/init-social.ts +0 -189
  178. package/src/utils/setup/init-storage.ts +0 -94
  179. package/src/utils/setup/register-mfa.ts +0 -165
  180. package/src/utils/setup/run-nauth-migrations.ts +0 -61
  181. package/src/utils/token-delivery-policy.ts +0 -38
  182. package/src/validators/template.validator.ts +0 -219
  183. package/tsconfig.json +0 -37
  184. 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
- }