@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,505 +0,0 @@
|
|
|
1
|
-
import { Repository } from 'typeorm';
|
|
2
|
-
import { TrustedDeviceService } from './trusted-device.service';
|
|
3
|
-
import { BaseTrustedDevice } from '../entities/trusted-device.entity';
|
|
4
|
-
import { NAuthConfig } from '../interfaces/config.interface';
|
|
5
|
-
import { NAuthLogger } from '../utils/nauth-logger';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Trusted Device Service Unit Tests
|
|
9
|
-
*
|
|
10
|
-
* Tests device trust management for "remember device" feature.
|
|
11
|
-
* Covers device creation, validation, revocation, and expiration handling.
|
|
12
|
-
*
|
|
13
|
-
* Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
|
|
14
|
-
*/
|
|
15
|
-
describe('TrustedDeviceService', () => {
|
|
16
|
-
let service: TrustedDeviceService;
|
|
17
|
-
let mockRepository: jest.Mocked<Repository<BaseTrustedDevice>>;
|
|
18
|
-
let mockLogger: jest.Mocked<NAuthLogger>;
|
|
19
|
-
|
|
20
|
-
const mockConfig: Partial<NAuthConfig> = {
|
|
21
|
-
mfa: {
|
|
22
|
-
enabled: true,
|
|
23
|
-
rememberDevices: 'user_opt_in',
|
|
24
|
-
rememberDeviceDays: 30,
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const mockTrustedDevice: Partial<BaseTrustedDevice> = {
|
|
29
|
-
id: 1,
|
|
30
|
-
userId: 1,
|
|
31
|
-
deviceTokenHash: 'hashed-token-123',
|
|
32
|
-
deviceName: 'My Device',
|
|
33
|
-
deviceType: 'desktop',
|
|
34
|
-
ipAddress: '1.2.3.4',
|
|
35
|
-
userAgent: 'test-user-agent',
|
|
36
|
-
platform: 'Windows',
|
|
37
|
-
browser: 'Chrome',
|
|
38
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days from now
|
|
39
|
-
lastUsedAt: new Date(),
|
|
40
|
-
createdAt: new Date(),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
// Create mock repository
|
|
45
|
-
mockRepository = {
|
|
46
|
-
findOne: jest.fn(),
|
|
47
|
-
create: jest.fn(),
|
|
48
|
-
save: jest.fn(),
|
|
49
|
-
update: jest.fn(),
|
|
50
|
-
delete: jest.fn(),
|
|
51
|
-
find: jest.fn(),
|
|
52
|
-
} as any;
|
|
53
|
-
|
|
54
|
-
// Create mock logger
|
|
55
|
-
mockLogger = {
|
|
56
|
-
log: jest.fn(),
|
|
57
|
-
error: jest.fn(),
|
|
58
|
-
warn: jest.fn(),
|
|
59
|
-
debug: jest.fn(),
|
|
60
|
-
} as any;
|
|
61
|
-
|
|
62
|
-
// Instantiate service directly
|
|
63
|
-
service = new TrustedDeviceService(mockConfig as NAuthConfig, mockLogger, mockRepository);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
afterEach(() => {
|
|
67
|
-
jest.clearAllMocks();
|
|
68
|
-
// Restore crypto.randomUUID if it was mocked
|
|
69
|
-
jest.restoreAllMocks();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// Service Initialization
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
it('should be defined', () => {
|
|
77
|
-
expect(service).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// createTrustedDevice() Method
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
describe('createTrustedDevice', () => {
|
|
85
|
-
it('should create trusted device successfully', async () => {
|
|
86
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
87
|
-
mockRepository.create.mockReturnValue(mockTrustedDevice as any);
|
|
88
|
-
mockRepository.save.mockResolvedValue(mockTrustedDevice as any);
|
|
89
|
-
|
|
90
|
-
const result = await service.createTrustedDevice(
|
|
91
|
-
1,
|
|
92
|
-
'My Device',
|
|
93
|
-
'desktop',
|
|
94
|
-
'1.2.3.4',
|
|
95
|
-
'test-user-agent',
|
|
96
|
-
'Windows',
|
|
97
|
-
'Chrome',
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
// Verify result is a valid UUID format
|
|
101
|
-
expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
102
|
-
expect(mockRepository.create).toHaveBeenCalled();
|
|
103
|
-
expect(mockRepository.save).toHaveBeenCalled();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('should throw error when rememberDevices is not enabled', async () => {
|
|
107
|
-
const configDisabled: Partial<NAuthConfig> = {
|
|
108
|
-
mfa: {
|
|
109
|
-
enabled: true,
|
|
110
|
-
rememberDevices: 'never',
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const serviceDisabled = new TrustedDeviceService(configDisabled as NAuthConfig, mockLogger, mockRepository);
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
await serviceDisabled.createTrustedDevice(1);
|
|
118
|
-
fail('Should have thrown Error');
|
|
119
|
-
} catch (error: any) {
|
|
120
|
-
expect(error.message).toContain('rememberDevices is not enabled');
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should throw error when repository not available', async () => {
|
|
125
|
-
const serviceWithoutRepo = new TrustedDeviceService(mockConfig as NAuthConfig, mockLogger, undefined);
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await serviceWithoutRepo.createTrustedDevice(1);
|
|
129
|
-
fail('Should have thrown Error');
|
|
130
|
-
} catch (error: any) {
|
|
131
|
-
expect(error.message).toContain('TrustedDeviceRepository not available');
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should update existing device if already trusted', async () => {
|
|
136
|
-
const existingDevice = { ...mockTrustedDevice };
|
|
137
|
-
// Calculate hash for the device token that will be generated
|
|
138
|
-
const createHash = (await import('crypto')).createHash;
|
|
139
|
-
// We'll verify the hash was calculated correctly by checking the update was called
|
|
140
|
-
|
|
141
|
-
mockRepository.findOne.mockResolvedValue(existingDevice as any);
|
|
142
|
-
mockRepository.update.mockResolvedValue({ affected: 1 } as any);
|
|
143
|
-
|
|
144
|
-
const result = await service.createTrustedDevice(1, 'Updated Device Name');
|
|
145
|
-
|
|
146
|
-
// Verify result is a valid UUID format
|
|
147
|
-
expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
148
|
-
expect(mockRepository.update).toHaveBeenCalled();
|
|
149
|
-
expect(mockRepository.create).not.toHaveBeenCalled();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should calculate expiry based on rememberDeviceDays', async () => {
|
|
153
|
-
const configWithDays: Partial<NAuthConfig> = {
|
|
154
|
-
mfa: {
|
|
155
|
-
enabled: true,
|
|
156
|
-
rememberDevices: 'user_opt_in',
|
|
157
|
-
rememberDeviceDays: 60,
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const serviceWithDays = new TrustedDeviceService(configWithDays as NAuthConfig, mockLogger, mockRepository);
|
|
162
|
-
|
|
163
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
164
|
-
mockRepository.create.mockReturnValue(mockTrustedDevice as any);
|
|
165
|
-
mockRepository.save.mockResolvedValue(mockTrustedDevice as any);
|
|
166
|
-
|
|
167
|
-
await serviceWithDays.createTrustedDevice(1);
|
|
168
|
-
|
|
169
|
-
// Verify trustedUntil is set correctly (60 days from now)
|
|
170
|
-
const createCall = mockRepository.create.mock.calls[0][0] as any;
|
|
171
|
-
const expectedDate = new Date();
|
|
172
|
-
expectedDate.setDate(expectedDate.getDate() + 60);
|
|
173
|
-
expect(new Date(createCall.trustedUntil).getTime()).toBeCloseTo(expectedDate.getTime(), -3); // Within 1 second
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should store all device information', async () => {
|
|
177
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
178
|
-
mockRepository.create.mockReturnValue(mockTrustedDevice as any);
|
|
179
|
-
mockRepository.save.mockResolvedValue(mockTrustedDevice as any);
|
|
180
|
-
|
|
181
|
-
await service.createTrustedDevice(1, 'My Device', 'desktop', '1.2.3.4', 'test-user-agent', 'Windows', 'Chrome');
|
|
182
|
-
|
|
183
|
-
expect(mockRepository.create).toHaveBeenCalledWith(
|
|
184
|
-
(expect as any).objectContaining({
|
|
185
|
-
userId: 1,
|
|
186
|
-
deviceName: 'My Device',
|
|
187
|
-
deviceType: 'desktop',
|
|
188
|
-
ipAddress: '1.2.3.4',
|
|
189
|
-
userAgent: 'test-user-agent',
|
|
190
|
-
platform: 'Windows',
|
|
191
|
-
browser: 'Chrome',
|
|
192
|
-
}),
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should handle null/undefined device information', async () => {
|
|
197
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
198
|
-
mockRepository.create.mockReturnValue(mockTrustedDevice as any);
|
|
199
|
-
mockRepository.save.mockResolvedValue(mockTrustedDevice as any);
|
|
200
|
-
|
|
201
|
-
await service.createTrustedDevice(1, null, null, null, null, null, null);
|
|
202
|
-
|
|
203
|
-
expect(mockRepository.create).toHaveBeenCalledWith(
|
|
204
|
-
(expect as any).objectContaining({
|
|
205
|
-
deviceName: null,
|
|
206
|
-
deviceType: null,
|
|
207
|
-
ipAddress: null,
|
|
208
|
-
userAgent: null,
|
|
209
|
-
platform: null,
|
|
210
|
-
browser: null,
|
|
211
|
-
}),
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// ============================================================================
|
|
217
|
-
// isDeviceTrusted() Method
|
|
218
|
-
// ============================================================================
|
|
219
|
-
|
|
220
|
-
describe('isDeviceTrusted', () => {
|
|
221
|
-
it('should return false when deviceToken is null', async () => {
|
|
222
|
-
const result = await service.isDeviceTrusted(null, 1);
|
|
223
|
-
|
|
224
|
-
expect(result).toBe(false);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('should return false when deviceToken is undefined', async () => {
|
|
228
|
-
const result = await service.isDeviceTrusted(undefined, 1);
|
|
229
|
-
|
|
230
|
-
expect(result).toBe(false);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should return false when repository not available', async () => {
|
|
234
|
-
const serviceWithoutRepo = new TrustedDeviceService(mockConfig as NAuthConfig, mockLogger, undefined);
|
|
235
|
-
|
|
236
|
-
const result = await serviceWithoutRepo.isDeviceTrusted('token', 1);
|
|
237
|
-
|
|
238
|
-
expect(result).toBe(false);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('should return false when rememberDevices is not enabled', async () => {
|
|
242
|
-
const configDisabled: Partial<NAuthConfig> = {
|
|
243
|
-
mfa: {
|
|
244
|
-
enabled: true,
|
|
245
|
-
rememberDevices: 'never',
|
|
246
|
-
},
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const serviceDisabled = new TrustedDeviceService(configDisabled as NAuthConfig, mockLogger, mockRepository);
|
|
250
|
-
|
|
251
|
-
const result = await serviceDisabled.isDeviceTrusted('token', 1);
|
|
252
|
-
|
|
253
|
-
expect(result).toBe(false);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should return true when device is trusted and not expired', async () => {
|
|
257
|
-
const deviceToken = 'device-token-uuid-123';
|
|
258
|
-
const trustedDevice = {
|
|
259
|
-
...mockTrustedDevice,
|
|
260
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // Future date
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
mockRepository.findOne.mockResolvedValue(trustedDevice as any);
|
|
264
|
-
|
|
265
|
-
const result = await service.isDeviceTrusted(deviceToken, 1);
|
|
266
|
-
|
|
267
|
-
expect(result).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('should return false when device not found', async () => {
|
|
271
|
-
const deviceToken = 'device-token-uuid-123';
|
|
272
|
-
|
|
273
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
274
|
-
|
|
275
|
-
const result = await service.isDeviceTrusted(deviceToken, 1);
|
|
276
|
-
|
|
277
|
-
expect(result).toBe(false);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should return false and delete when device expired', async () => {
|
|
281
|
-
const deviceToken = 'device-token-uuid-123';
|
|
282
|
-
const expiredDevice = {
|
|
283
|
-
...mockTrustedDevice,
|
|
284
|
-
trustedUntil: new Date(Date.now() - 1000), // Past date
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
mockRepository.findOne.mockResolvedValue(expiredDevice as any);
|
|
288
|
-
mockRepository.delete.mockResolvedValue({ affected: 1 } as any);
|
|
289
|
-
|
|
290
|
-
const result = await service.isDeviceTrusted(deviceToken, 1);
|
|
291
|
-
|
|
292
|
-
expect(result).toBe(false);
|
|
293
|
-
expect(mockRepository.delete).toHaveBeenCalled();
|
|
294
|
-
expect(mockLogger.debug).toHaveBeenCalledWith((expect as any).stringContaining('Trusted device expired'));
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should update lastUsedAt when device is trusted', async () => {
|
|
298
|
-
const deviceToken = 'device-token-uuid-123';
|
|
299
|
-
const trustedDevice = {
|
|
300
|
-
...mockTrustedDevice,
|
|
301
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
302
|
-
lastUsedAt: new Date(Date.now() - 20 * 60 * 1000), // 20 minutes ago
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
mockRepository.findOne.mockResolvedValue(trustedDevice as any);
|
|
306
|
-
mockRepository.update.mockResolvedValue({ affected: 1 } as any);
|
|
307
|
-
|
|
308
|
-
await service.isDeviceTrusted(deviceToken, 1);
|
|
309
|
-
|
|
310
|
-
expect(mockRepository.update).toHaveBeenCalled();
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should throttle lastUsedAt updates to once per 15 minutes', async () => {
|
|
314
|
-
const deviceToken = 'device-token-uuid-123';
|
|
315
|
-
const trustedDevice = {
|
|
316
|
-
...mockTrustedDevice,
|
|
317
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
318
|
-
lastUsedAt: new Date(), // Just now
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
mockRepository.findOne.mockResolvedValue(trustedDevice as any);
|
|
322
|
-
|
|
323
|
-
await service.isDeviceTrusted(deviceToken, 1);
|
|
324
|
-
|
|
325
|
-
// Should not update if lastUsedAt is recent
|
|
326
|
-
expect(mockRepository.update).not.toHaveBeenCalled();
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// ============================================================================
|
|
331
|
-
// validateDeviceToken() Method
|
|
332
|
-
// ============================================================================
|
|
333
|
-
|
|
334
|
-
describe('validateDeviceToken', () => {
|
|
335
|
-
it('should return not suspicious when token is null', async () => {
|
|
336
|
-
const result = await service.validateDeviceToken(null, 1);
|
|
337
|
-
|
|
338
|
-
expect(result).toEqual({ isValid: false, isSuspicious: false });
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it('should return not suspicious when token is undefined', async () => {
|
|
342
|
-
const result = await service.validateDeviceToken(undefined, 1);
|
|
343
|
-
|
|
344
|
-
expect(result).toEqual({ isValid: false, isSuspicious: false });
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should return valid and not suspicious when device is trusted', async () => {
|
|
348
|
-
const deviceToken = 'device-token-uuid-123';
|
|
349
|
-
const trustedDevice = {
|
|
350
|
-
...mockTrustedDevice,
|
|
351
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
mockRepository.findOne.mockResolvedValue(trustedDevice as any);
|
|
355
|
-
|
|
356
|
-
const result = await service.validateDeviceToken(deviceToken, 1);
|
|
357
|
-
|
|
358
|
-
expect(result).toEqual({ isValid: true, isSuspicious: false });
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('should return suspicious when token provided but not trusted', async () => {
|
|
362
|
-
const deviceToken = 'device-token-uuid-123';
|
|
363
|
-
|
|
364
|
-
mockRepository.findOne.mockResolvedValue(null);
|
|
365
|
-
|
|
366
|
-
const result = await service.validateDeviceToken(deviceToken, 1);
|
|
367
|
-
|
|
368
|
-
expect(result).toEqual({ isValid: false, isSuspicious: true });
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('should return suspicious when token provided but expired', async () => {
|
|
372
|
-
const deviceToken = 'device-token-uuid-123';
|
|
373
|
-
const expiredDevice = {
|
|
374
|
-
...mockTrustedDevice,
|
|
375
|
-
trustedUntil: new Date(Date.now() - 1000),
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
mockRepository.findOne.mockResolvedValue(expiredDevice as any);
|
|
379
|
-
mockRepository.delete.mockResolvedValue({ affected: 1 } as any);
|
|
380
|
-
|
|
381
|
-
const result = await service.validateDeviceToken(deviceToken, 1);
|
|
382
|
-
|
|
383
|
-
expect(result).toEqual({ isValid: false, isSuspicious: true });
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
// ============================================================================
|
|
388
|
-
// revokeTrustedDevice() Method
|
|
389
|
-
// ============================================================================
|
|
390
|
-
|
|
391
|
-
describe('revokeTrustedDevice', () => {
|
|
392
|
-
it('should revoke trusted device successfully', async () => {
|
|
393
|
-
const deviceToken = 'device-token-uuid-123';
|
|
394
|
-
|
|
395
|
-
mockRepository.delete.mockResolvedValue({ affected: 1 } as any);
|
|
396
|
-
|
|
397
|
-
await service.revokeTrustedDevice(deviceToken, 1);
|
|
398
|
-
|
|
399
|
-
expect(mockRepository.delete).toHaveBeenCalled();
|
|
400
|
-
expect(mockLogger.debug).toHaveBeenCalledWith((expect as any).stringContaining('Revoked trusted device'));
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
it('should return early when repository not available', async () => {
|
|
404
|
-
const serviceWithoutRepo = new TrustedDeviceService(mockConfig as NAuthConfig, mockLogger, undefined);
|
|
405
|
-
|
|
406
|
-
await serviceWithoutRepo.revokeTrustedDevice('token', 1);
|
|
407
|
-
|
|
408
|
-
// Should not throw, just return early
|
|
409
|
-
expect(mockLogger.debug).not.toHaveBeenCalled();
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
it('should delete device by hash', async () => {
|
|
413
|
-
const deviceToken = 'device-token-uuid-123';
|
|
414
|
-
const createHash = (await import('crypto')).createHash;
|
|
415
|
-
const hash = createHash('sha256').update(deviceToken).digest('hex');
|
|
416
|
-
|
|
417
|
-
mockRepository.delete.mockResolvedValue({ affected: 1 } as any);
|
|
418
|
-
|
|
419
|
-
await service.revokeTrustedDevice(deviceToken, 1);
|
|
420
|
-
|
|
421
|
-
expect(mockRepository.delete).toHaveBeenCalledWith({
|
|
422
|
-
userId: 1,
|
|
423
|
-
deviceTokenHash: hash,
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
// ============================================================================
|
|
429
|
-
// getUserTrustedDevices() Method
|
|
430
|
-
// ============================================================================
|
|
431
|
-
|
|
432
|
-
describe('getUserTrustedDevices', () => {
|
|
433
|
-
it('should return user trusted devices', async () => {
|
|
434
|
-
const devices = [
|
|
435
|
-
{ ...mockTrustedDevice, id: 1 },
|
|
436
|
-
{ ...mockTrustedDevice, id: 2 },
|
|
437
|
-
];
|
|
438
|
-
|
|
439
|
-
mockRepository.find.mockResolvedValue(devices as any);
|
|
440
|
-
|
|
441
|
-
const result = await service.getUserTrustedDevices(1);
|
|
442
|
-
|
|
443
|
-
expect(result.length).toBe(2);
|
|
444
|
-
expect('deviceTokenHash' in result[0]).toBe(false);
|
|
445
|
-
expect('deviceTokenHash' in result[1]).toBe(false);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
it('should return empty array when repository not available', async () => {
|
|
449
|
-
const serviceWithoutRepo = new TrustedDeviceService(mockConfig as NAuthConfig, mockLogger, undefined);
|
|
450
|
-
|
|
451
|
-
const result = await serviceWithoutRepo.getUserTrustedDevices(1);
|
|
452
|
-
|
|
453
|
-
expect(result).toEqual([]);
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('should filter out expired devices', async () => {
|
|
457
|
-
const devices = [
|
|
458
|
-
{ ...mockTrustedDevice, id: 1, trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) },
|
|
459
|
-
{ ...mockTrustedDevice, id: 2, trustedUntil: new Date(Date.now() - 1000) }, // Expired
|
|
460
|
-
];
|
|
461
|
-
|
|
462
|
-
mockRepository.find.mockResolvedValue(devices as any);
|
|
463
|
-
|
|
464
|
-
const result = await service.getUserTrustedDevices(1);
|
|
465
|
-
|
|
466
|
-
expect(result.length).toBe(1);
|
|
467
|
-
expect(result[0].id).toBe(1);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
it('should order devices by lastUsedAt DESC', async () => {
|
|
471
|
-
const devices = [
|
|
472
|
-
{ ...mockTrustedDevice, id: 1, lastUsedAt: new Date('2025-01-01') },
|
|
473
|
-
{ ...mockTrustedDevice, id: 2, lastUsedAt: new Date('2025-01-02') },
|
|
474
|
-
];
|
|
475
|
-
|
|
476
|
-
mockRepository.find.mockResolvedValue(devices as any);
|
|
477
|
-
|
|
478
|
-
await service.getUserTrustedDevices(1);
|
|
479
|
-
|
|
480
|
-
expect(mockRepository.find).toHaveBeenCalledWith({
|
|
481
|
-
where: { userId: 1 },
|
|
482
|
-
order: { lastUsedAt: 'DESC' },
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
it('should exclude deviceTokenHash from results', async () => {
|
|
487
|
-
const devices = [
|
|
488
|
-
{
|
|
489
|
-
...mockTrustedDevice,
|
|
490
|
-
id: 1,
|
|
491
|
-
deviceTokenHash: 'hash-123',
|
|
492
|
-
trustedUntil: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
493
|
-
},
|
|
494
|
-
];
|
|
495
|
-
|
|
496
|
-
mockRepository.find.mockResolvedValue(devices as any);
|
|
497
|
-
|
|
498
|
-
const result = await service.getUserTrustedDevices(1);
|
|
499
|
-
|
|
500
|
-
expect('deviceTokenHash' in result[0]).toBe(false);
|
|
501
|
-
expect('id' in result[0]).toBe(true);
|
|
502
|
-
expect('userId' in result[0]).toBe(true);
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
});
|