@nauth-toolkit/core 0.1.0 → 0.1.5
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 +9 -0
- package/package.json +8 -3
- 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,247 +0,0 @@
|
|
|
1
|
-
import { RateLimitStorageService } from './rate-limit-storage.service';
|
|
2
|
-
import { StorageAdapter } from '../interfaces/storage-adapter.interface';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Rate Limit Storage Service Unit Tests
|
|
6
|
-
*
|
|
7
|
-
* Tests rate limiting storage operations.
|
|
8
|
-
* Covers counter increment, window management, and expiration handling.
|
|
9
|
-
*
|
|
10
|
-
* Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
|
|
11
|
-
*/
|
|
12
|
-
describe('RateLimitStorageService', () => {
|
|
13
|
-
let service: RateLimitStorageService;
|
|
14
|
-
let mockStorageAdapter: jest.Mocked<StorageAdapter>;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create mock storage adapter
|
|
18
|
-
mockStorageAdapter = {
|
|
19
|
-
initialize: jest.fn(),
|
|
20
|
-
isHealthy: jest.fn(),
|
|
21
|
-
get: jest.fn(),
|
|
22
|
-
set: jest.fn(),
|
|
23
|
-
del: jest.fn(),
|
|
24
|
-
exists: jest.fn(),
|
|
25
|
-
incr: jest.fn(),
|
|
26
|
-
decr: jest.fn(),
|
|
27
|
-
expire: jest.fn(),
|
|
28
|
-
ttl: jest.fn(),
|
|
29
|
-
hget: jest.fn(),
|
|
30
|
-
hset: jest.fn(),
|
|
31
|
-
hgetall: jest.fn(),
|
|
32
|
-
hdel: jest.fn(),
|
|
33
|
-
lpush: jest.fn(),
|
|
34
|
-
lrange: jest.fn(),
|
|
35
|
-
llen: jest.fn(),
|
|
36
|
-
keys: jest.fn(),
|
|
37
|
-
scan: jest.fn(),
|
|
38
|
-
cleanup: jest.fn(),
|
|
39
|
-
disconnect: jest.fn(),
|
|
40
|
-
} as any;
|
|
41
|
-
|
|
42
|
-
// Instantiate service directly
|
|
43
|
-
service = new RateLimitStorageService(mockStorageAdapter);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
afterEach(() => {
|
|
47
|
-
jest.clearAllMocks();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// Service Initialization
|
|
52
|
-
// ============================================================================
|
|
53
|
-
|
|
54
|
-
it('should be defined', () => {
|
|
55
|
-
expect(service).toBeDefined();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// incrementRateLimit() Method
|
|
60
|
-
// ============================================================================
|
|
61
|
-
|
|
62
|
-
describe('incrementRateLimit', () => {
|
|
63
|
-
it('should increment counter and return count', async () => {
|
|
64
|
-
mockStorageAdapter.incr.mockResolvedValue(1);
|
|
65
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
66
|
-
|
|
67
|
-
const result = await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
68
|
-
|
|
69
|
-
expect(result).toBe(1);
|
|
70
|
-
expect(mockStorageAdapter.incr).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should set expiry on first request in window', async () => {
|
|
74
|
-
mockStorageAdapter.incr.mockResolvedValue(1);
|
|
75
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
76
|
-
|
|
77
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
78
|
-
|
|
79
|
-
expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
|
|
80
|
-
'nauth:ratelimit:user-123:/api/login',
|
|
81
|
-
60, // 60000ms = 60 seconds
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should not set expiry on subsequent requests', async () => {
|
|
86
|
-
mockStorageAdapter.incr.mockResolvedValue(2);
|
|
87
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
88
|
-
|
|
89
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
90
|
-
|
|
91
|
-
expect(mockStorageAdapter.expire).not.toHaveBeenCalled();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should convert windowMs to seconds for TTL', async () => {
|
|
95
|
-
mockStorageAdapter.incr.mockResolvedValue(1);
|
|
96
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
97
|
-
|
|
98
|
-
await service.incrementRateLimit('user-123', '/api/login', 120000); // 2 minutes
|
|
99
|
-
|
|
100
|
-
expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
|
|
101
|
-
'nauth:ratelimit:user-123:/api/login',
|
|
102
|
-
120, // 120000ms = 120 seconds
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should round up windowMs to seconds', async () => {
|
|
107
|
-
mockStorageAdapter.incr.mockResolvedValue(1);
|
|
108
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
109
|
-
|
|
110
|
-
await service.incrementRateLimit('user-123', '/api/login', 1500); // 1.5 seconds
|
|
111
|
-
|
|
112
|
-
expect(mockStorageAdapter.expire).toHaveBeenCalledWith(
|
|
113
|
-
'nauth:ratelimit:user-123:/api/login',
|
|
114
|
-
2, // Math.ceil(1500 / 1000) = 2
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should use correct key format', async () => {
|
|
119
|
-
mockStorageAdapter.incr.mockResolvedValue(1);
|
|
120
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
121
|
-
|
|
122
|
-
await service.incrementRateLimit('ip-192.168.1.1', '/api/signup', 30000);
|
|
123
|
-
|
|
124
|
-
expect(mockStorageAdapter.incr).toHaveBeenCalledWith('nauth:ratelimit:ip-192.168.1.1:/api/signup');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ============================================================================
|
|
129
|
-
// getRateLimit() Method
|
|
130
|
-
// ============================================================================
|
|
131
|
-
|
|
132
|
-
describe('getRateLimit', () => {
|
|
133
|
-
it('should return current rate limit count', async () => {
|
|
134
|
-
mockStorageAdapter.get.mockResolvedValue('5');
|
|
135
|
-
|
|
136
|
-
const result = await service.getRateLimit('user-123', '/api/login');
|
|
137
|
-
|
|
138
|
-
expect(result).toBe(5);
|
|
139
|
-
expect(mockStorageAdapter.get).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should return 0 when no limit recorded', async () => {
|
|
143
|
-
mockStorageAdapter.get.mockResolvedValue(null);
|
|
144
|
-
|
|
145
|
-
const result = await service.getRateLimit('user-123', '/api/login');
|
|
146
|
-
|
|
147
|
-
expect(result).toBe(0);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should parse string value to number', async () => {
|
|
151
|
-
mockStorageAdapter.get.mockResolvedValue('10');
|
|
152
|
-
|
|
153
|
-
const result = await service.getRateLimit('user-123', '/api/login');
|
|
154
|
-
|
|
155
|
-
expect(result).toBe(10);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should handle empty string', async () => {
|
|
159
|
-
mockStorageAdapter.get.mockResolvedValue('');
|
|
160
|
-
|
|
161
|
-
const result = await service.getRateLimit('user-123', '/api/login');
|
|
162
|
-
|
|
163
|
-
expect(result).toBe(0);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should use correct key format', async () => {
|
|
167
|
-
mockStorageAdapter.get.mockResolvedValue('3');
|
|
168
|
-
|
|
169
|
-
await service.getRateLimit('ip-10.0.0.1', '/api/reset-password');
|
|
170
|
-
|
|
171
|
-
expect(mockStorageAdapter.get).toHaveBeenCalledWith('nauth:ratelimit:ip-10.0.0.1:/api/reset-password');
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// ============================================================================
|
|
176
|
-
// resetRateLimit() Method
|
|
177
|
-
// ============================================================================
|
|
178
|
-
|
|
179
|
-
describe('resetRateLimit', () => {
|
|
180
|
-
it('should delete rate limit counter', async () => {
|
|
181
|
-
mockStorageAdapter.del.mockResolvedValue();
|
|
182
|
-
|
|
183
|
-
await service.resetRateLimit('user-123', '/api/login');
|
|
184
|
-
|
|
185
|
-
expect(mockStorageAdapter.del).toHaveBeenCalledWith('nauth:ratelimit:user-123:/api/login');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should reset limit for correct identifier and endpoint', async () => {
|
|
189
|
-
mockStorageAdapter.del.mockResolvedValue();
|
|
190
|
-
|
|
191
|
-
await service.resetRateLimit('ip-192.168.1.1', '/api/signup');
|
|
192
|
-
|
|
193
|
-
expect(mockStorageAdapter.del).toHaveBeenCalledWith('nauth:ratelimit:ip-192.168.1.1:/api/signup');
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// ============================================================================
|
|
198
|
-
// Integration Tests
|
|
199
|
-
// ============================================================================
|
|
200
|
-
|
|
201
|
-
describe('Integration', () => {
|
|
202
|
-
it('should track rate limit across multiple requests', async () => {
|
|
203
|
-
mockStorageAdapter.incr.mockResolvedValueOnce(1).mockResolvedValueOnce(2).mockResolvedValueOnce(3);
|
|
204
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
205
|
-
mockStorageAdapter.get.mockResolvedValue('3');
|
|
206
|
-
|
|
207
|
-
// Make 3 requests
|
|
208
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
209
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
210
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
211
|
-
|
|
212
|
-
const count = await service.getRateLimit('user-123', '/api/login');
|
|
213
|
-
|
|
214
|
-
expect(count).toBe(3);
|
|
215
|
-
expect(mockStorageAdapter.expire).toHaveBeenCalledTimes(1); // Only on first request
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should reset rate limit correctly', async () => {
|
|
219
|
-
mockStorageAdapter.incr.mockResolvedValue(5);
|
|
220
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
221
|
-
mockStorageAdapter.del.mockResolvedValue();
|
|
222
|
-
mockStorageAdapter.get.mockResolvedValue(null);
|
|
223
|
-
|
|
224
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
225
|
-
await service.resetRateLimit('user-123', '/api/login');
|
|
226
|
-
|
|
227
|
-
const count = await service.getRateLimit('user-123', '/api/login');
|
|
228
|
-
|
|
229
|
-
expect(count).toBe(0);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should handle different endpoints independently', async () => {
|
|
233
|
-
mockStorageAdapter.incr.mockResolvedValueOnce(1).mockResolvedValueOnce(1);
|
|
234
|
-
mockStorageAdapter.expire.mockResolvedValue();
|
|
235
|
-
mockStorageAdapter.get.mockResolvedValueOnce('1').mockResolvedValueOnce('1');
|
|
236
|
-
|
|
237
|
-
await service.incrementRateLimit('user-123', '/api/login', 60000);
|
|
238
|
-
await service.incrementRateLimit('user-123', '/api/signup', 60000);
|
|
239
|
-
|
|
240
|
-
const loginCount = await service.getRateLimit('user-123', '/api/login');
|
|
241
|
-
const signupCount = await service.getRateLimit('user-123', '/api/signup');
|
|
242
|
-
|
|
243
|
-
expect(loginCount).toBe(1);
|
|
244
|
-
expect(signupCount).toBe(1);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { RateLimitStorage, StorageAdapter } from '../interfaces/storage-adapter.interface';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Rate limit storage implementation using StorageAdapter (Platform-Agnostic)
|
|
5
|
-
*/
|
|
6
|
-
export class RateLimitStorageService implements RateLimitStorage {
|
|
7
|
-
private readonly keyPrefix = 'nauth:ratelimit:';
|
|
8
|
-
|
|
9
|
-
constructor(private readonly storageAdapter: StorageAdapter) {}
|
|
10
|
-
|
|
11
|
-
async incrementRateLimit(identifier: string, endpoint: string, windowMs: number): Promise<number> {
|
|
12
|
-
const key = this.getKey(identifier, endpoint);
|
|
13
|
-
const count = await this.storageAdapter.incr(key);
|
|
14
|
-
|
|
15
|
-
if (count === 1) {
|
|
16
|
-
// First request in this window, set expiry
|
|
17
|
-
const ttl = Math.ceil(windowMs / 1000);
|
|
18
|
-
await this.storageAdapter.expire(key, ttl);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return count;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async getRateLimit(identifier: string, endpoint: string): Promise<number> {
|
|
25
|
-
const key = this.getKey(identifier, endpoint);
|
|
26
|
-
const value = await this.storageAdapter.get(key);
|
|
27
|
-
return value ? parseInt(value, 10) : 0;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async resetRateLimit(identifier: string, endpoint: string): Promise<void> {
|
|
31
|
-
const key = this.getKey(identifier, endpoint);
|
|
32
|
-
await this.storageAdapter.del(key);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private getKey(identifier: string, endpoint: string): string {
|
|
36
|
-
return `${this.keyPrefix}${identifier}:${endpoint}`;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { HtmlTemplateEngine } from './html-template.engine';
|
|
2
|
-
import { TemplateType } from '../interfaces/template.interface';
|
|
3
|
-
|
|
4
|
-
describe('HtmlTemplateEngine', () => {
|
|
5
|
-
let engine: HtmlTemplateEngine;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
engine = new HtmlTemplateEngine();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should be defined', () => {
|
|
12
|
-
expect(engine).toBeDefined();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('render', () => {
|
|
16
|
-
it('should render verification email template', async () => {
|
|
17
|
-
const variables = {
|
|
18
|
-
appName: 'Test App',
|
|
19
|
-
userName: 'John Doe',
|
|
20
|
-
code: '123456',
|
|
21
|
-
link: 'https://example.com/verify',
|
|
22
|
-
expiryMinutes: 60,
|
|
23
|
-
companyName: 'Test Company',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const email = await engine.render(TemplateType.VERIFICATION, variables);
|
|
27
|
-
|
|
28
|
-
expect(email.subject).toContain('Test App');
|
|
29
|
-
expect(email.html).toContain('John Doe');
|
|
30
|
-
expect(email.html).toContain('123456');
|
|
31
|
-
expect(email.html).toContain('https://example.com/verify');
|
|
32
|
-
expect(email.text).toContain('123456');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should render password reset template', async () => {
|
|
36
|
-
const variables = {
|
|
37
|
-
appName: 'Test App',
|
|
38
|
-
userName: 'Jane Doe',
|
|
39
|
-
link: 'https://example.com/reset',
|
|
40
|
-
expiryMinutes: 30,
|
|
41
|
-
companyName: 'Test Company',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const email = await engine.render(TemplateType.PASSWORD_RESET, variables);
|
|
45
|
-
|
|
46
|
-
expect(email.subject).toContain('Reset'); // Capitalized in actual template
|
|
47
|
-
expect(email.html).toContain('Jane Doe');
|
|
48
|
-
expect(email.html).toContain('https://example.com/reset');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should render welcome email template', async () => {
|
|
52
|
-
const variables = {
|
|
53
|
-
appName: 'Test App',
|
|
54
|
-
userName: 'Bob Smith',
|
|
55
|
-
dashboardUrl: 'https://example.com/dashboard',
|
|
56
|
-
supportEmail: 'support@example.com',
|
|
57
|
-
companyName: 'Test Company',
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const email = await engine.render(TemplateType.WELCOME, variables);
|
|
61
|
-
|
|
62
|
-
expect(email.subject).toContain('Welcome');
|
|
63
|
-
expect(email.html).toContain('Bob Smith');
|
|
64
|
-
expect(email.html).toContain('https://example.com/dashboard');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should render account lockout template', async () => {
|
|
68
|
-
const variables = {
|
|
69
|
-
appName: 'Test App',
|
|
70
|
-
userName: 'Alice Johnson',
|
|
71
|
-
reason: 'Too many failed login attempts',
|
|
72
|
-
durationMinutes: 15,
|
|
73
|
-
supportEmail: 'support@example.com',
|
|
74
|
-
companyName: 'Test Company',
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const email = await engine.render(TemplateType.ACCOUNT_LOCKOUT, variables);
|
|
78
|
-
|
|
79
|
-
expect(email.subject).toContain('Account Locked');
|
|
80
|
-
expect(email.html).toContain('Alice Johnson');
|
|
81
|
-
expect(email.html).toContain('Too many failed login attempts');
|
|
82
|
-
expect(email.html).toContain('15');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should render new device login template', async () => {
|
|
86
|
-
const variables = {
|
|
87
|
-
appName: 'Test App',
|
|
88
|
-
userName: 'Charlie Brown',
|
|
89
|
-
deviceName: 'iPhone 13',
|
|
90
|
-
deviceType: 'mobile',
|
|
91
|
-
ipAddress: '192.168.1.100',
|
|
92
|
-
location: 'New York, US',
|
|
93
|
-
timestamp: '2025-10-22T12:00:00Z',
|
|
94
|
-
supportEmail: 'support@example.com',
|
|
95
|
-
companyName: 'Test Company',
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const email = await engine.render(TemplateType.NEW_DEVICE, variables);
|
|
99
|
-
|
|
100
|
-
expect(email.subject).toContain('New Device Login');
|
|
101
|
-
expect(email.html).toContain('Charlie Brown');
|
|
102
|
-
expect(email.html).toContain('iPhone 13');
|
|
103
|
-
expect(email.html).toContain('192.168.1.100');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should throw error for unknown template type', async () => {
|
|
107
|
-
let error: Error | undefined;
|
|
108
|
-
try {
|
|
109
|
-
await engine.render('unknown' as any, {});
|
|
110
|
-
} catch (e) {
|
|
111
|
-
error = e as Error;
|
|
112
|
-
}
|
|
113
|
-
expect(error).toBeDefined();
|
|
114
|
-
expect(error?.message).toContain('Template "unknown" not found');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should handle missing variables gracefully', async () => {
|
|
118
|
-
const variables = {
|
|
119
|
-
appName: 'Test App',
|
|
120
|
-
// Missing other required variables
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const email = await engine.render(TemplateType.VERIFICATION, variables);
|
|
124
|
-
|
|
125
|
-
expect(email.subject).toContain('Test App');
|
|
126
|
-
expect(email.html).toBeDefined();
|
|
127
|
-
expect(email.text).toBeDefined();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should replace all occurrences of a variable', async () => {
|
|
131
|
-
const variables = {
|
|
132
|
-
appName: 'Test App',
|
|
133
|
-
userName: 'Test User',
|
|
134
|
-
companyName: 'Test Company',
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const email = await engine.render(TemplateType.WELCOME, variables);
|
|
138
|
-
|
|
139
|
-
// userName should appear in the greeting
|
|
140
|
-
expect(email.html).toContain('Test User');
|
|
141
|
-
expect(email.text).toContain('Test User');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should handle special characters in variables', async () => {
|
|
145
|
-
const variables = {
|
|
146
|
-
appName: 'Test & Company <Special>',
|
|
147
|
-
userName: 'John "Doe"',
|
|
148
|
-
code: '123456',
|
|
149
|
-
link: 'https://example.com/verify',
|
|
150
|
-
expiryMinutes: 60,
|
|
151
|
-
companyName: 'Test & Co.',
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const email = await engine.render(TemplateType.VERIFICATION, variables);
|
|
155
|
-
|
|
156
|
-
// HTML escapes special characters in userName
|
|
157
|
-
expect(email.html).toContain('John "Doe"');
|
|
158
|
-
expect(email.text).toContain('John "Doe"');
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
});
|