@nauth-toolkit/core 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/bootstrap.d.ts +3 -0
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +63 -0
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/dto/admin-api-key.dto.d.ts +69 -0
  6. package/dist/dto/admin-api-key.dto.d.ts.map +1 -0
  7. package/dist/dto/admin-api-key.dto.js +144 -0
  8. package/dist/dto/admin-api-key.dto.js.map +1 -0
  9. package/dist/dto/admin-signup-social.dto.d.ts +1 -1
  10. package/dist/dto/admin-signup-social.dto.js +1 -1
  11. package/dist/dto/admin-signup.dto.d.ts +1 -1
  12. package/dist/dto/admin-signup.dto.js +1 -1
  13. package/dist/dto/api-key.dto.d.ts +132 -0
  14. package/dist/dto/api-key.dto.d.ts.map +1 -0
  15. package/dist/dto/api-key.dto.js +198 -0
  16. package/dist/dto/api-key.dto.js.map +1 -0
  17. package/dist/dto/change-password.dto.d.ts +2 -2
  18. package/dist/dto/change-password.dto.js +2 -2
  19. package/dist/dto/index.d.ts +2 -0
  20. package/dist/dto/index.d.ts.map +1 -1
  21. package/dist/dto/index.js +3 -0
  22. package/dist/dto/index.js.map +1 -1
  23. package/dist/dto/reset-password.dto.d.ts +1 -1
  24. package/dist/dto/reset-password.dto.js +1 -1
  25. package/dist/dto/respond-challenge.dto.d.ts +1 -1
  26. package/dist/dto/respond-challenge.dto.js +1 -1
  27. package/dist/dto/signup.dto.d.ts +1 -1
  28. package/dist/dto/signup.dto.js +1 -1
  29. package/dist/entities/api-key.entity.d.ts +135 -0
  30. package/dist/entities/api-key.entity.d.ts.map +1 -0
  31. package/dist/entities/api-key.entity.js +149 -0
  32. package/dist/entities/api-key.entity.js.map +1 -0
  33. package/dist/entities/index.d.ts +1 -0
  34. package/dist/entities/index.d.ts.map +1 -1
  35. package/dist/entities/index.js +3 -1
  36. package/dist/entities/index.js.map +1 -1
  37. package/dist/enums/auth-audit-event-type.enum.d.ts +25 -1
  38. package/dist/enums/auth-audit-event-type.enum.d.ts.map +1 -1
  39. package/dist/enums/auth-audit-event-type.enum.js +27 -0
  40. package/dist/enums/auth-audit-event-type.enum.js.map +1 -1
  41. package/dist/enums/error-codes.enum.d.ts +56 -1
  42. package/dist/enums/error-codes.enum.d.ts.map +1 -1
  43. package/dist/enums/error-codes.enum.js +58 -0
  44. package/dist/enums/error-codes.enum.js.map +1 -1
  45. package/dist/exceptions/nauth.exception.d.ts.map +1 -1
  46. package/dist/exceptions/nauth.exception.js +13 -0
  47. package/dist/exceptions/nauth.exception.js.map +1 -1
  48. package/dist/handlers/api-key.handler.d.ts +45 -0
  49. package/dist/handlers/api-key.handler.d.ts.map +1 -0
  50. package/dist/handlers/api-key.handler.js +99 -0
  51. package/dist/handlers/api-key.handler.js.map +1 -0
  52. package/dist/handlers/auth.handler.d.ts.map +1 -1
  53. package/dist/handlers/auth.handler.js +6 -0
  54. package/dist/handlers/auth.handler.js.map +1 -1
  55. package/dist/index.d.ts +7 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +8 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/interfaces/config.interface.d.ts +162 -0
  60. package/dist/interfaces/config.interface.d.ts.map +1 -1
  61. package/dist/internal.d.ts +7 -0
  62. package/dist/internal.d.ts.map +1 -1
  63. package/dist/internal.js +8 -1
  64. package/dist/internal.js.map +1 -1
  65. package/dist/openapi/components.schemas.json +284 -7
  66. package/dist/platform/interfaces.d.ts +8 -0
  67. package/dist/platform/interfaces.d.ts.map +1 -1
  68. package/dist/schemas/auth-config.schema.d.ts +211 -0
  69. package/dist/schemas/auth-config.schema.d.ts.map +1 -1
  70. package/dist/schemas/auth-config.schema.js +33 -1
  71. package/dist/schemas/auth-config.schema.js.map +1 -1
  72. package/dist/services/admin-auth.service.d.ts +59 -1
  73. package/dist/services/admin-auth.service.d.ts.map +1 -1
  74. package/dist/services/admin-auth.service.js +99 -1
  75. package/dist/services/admin-auth.service.js.map +1 -1
  76. package/dist/services/api-key.service.d.ts +152 -0
  77. package/dist/services/api-key.service.d.ts.map +1 -0
  78. package/dist/services/api-key.service.js +378 -0
  79. package/dist/services/api-key.service.js.map +1 -0
  80. package/dist/services/telemetry.service.d.ts +154 -0
  81. package/dist/services/telemetry.service.d.ts.map +1 -0
  82. package/dist/services/telemetry.service.js +345 -0
  83. package/dist/services/telemetry.service.js.map +1 -0
  84. package/dist/utils/get-package-version.d.ts +15 -0
  85. package/dist/utils/get-package-version.d.ts.map +1 -0
  86. package/dist/utils/get-package-version.js +84 -0
  87. package/dist/utils/get-package-version.js.map +1 -0
  88. package/dist/utils/index.d.ts +1 -0
  89. package/dist/utils/index.d.ts.map +1 -1
  90. package/dist/utils/index.js +1 -0
  91. package/dist/utils/index.js.map +1 -1
  92. package/dist/utils/ip-match.d.ts +44 -0
  93. package/dist/utils/ip-match.d.ts.map +1 -0
  94. package/dist/utils/ip-match.js +135 -0
  95. package/dist/utils/ip-match.js.map +1 -0
  96. package/dist/utils/setup/get-repositories.d.ts +2 -1
  97. package/dist/utils/setup/get-repositories.d.ts.map +1 -1
  98. package/dist/utils/setup/get-repositories.js +2 -0
  99. package/dist/utils/setup/get-repositories.js.map +1 -1
  100. package/dist/utils/setup/init-services.d.ts +4 -2
  101. package/dist/utils/setup/init-services.d.ts.map +1 -1
  102. package/dist/utils/setup/init-services.js +8 -1
  103. package/dist/utils/setup/init-services.js.map +1 -1
  104. package/package.json +2 -2
@@ -0,0 +1,378 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyService = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const nauth_exception_1 = require("../exceptions/nauth.exception");
6
+ const error_codes_enum_1 = require("../enums/error-codes.enum");
7
+ const auth_audit_event_type_enum_1 = require("../enums/auth-audit-event-type.enum");
8
+ const ip_match_1 = require("../utils/ip-match");
9
+ /**
10
+ * API Key Service
11
+ *
12
+ * Manages the lifecycle of API keys (create/list/update/revoke/delete) and validates
13
+ * keys presented on requests. Keys authenticate as their owning user.
14
+ *
15
+ * Security:
16
+ * - Only a SHA-256 hash of the full key is stored; the plaintext is returned once at creation.
17
+ * - Lookup uses a non-secret, indexed `lookupId`; the secret is compared in constant time.
18
+ * - Per-key IP allowlists (optional) restrict which source IPs may use a key.
19
+ *
20
+ * @remarks
21
+ * This service does NOT enforce endpoint authorization. Route protection (which endpoints
22
+ * accept API keys) is the responsibility of the framework adapter (guard/middleware).
23
+ */
24
+ class ApiKeyService {
25
+ apiKeyRepository;
26
+ userRepository;
27
+ config;
28
+ logger;
29
+ auditService;
30
+ constructor(apiKeyRepository, userRepository, config, logger, auditService) {
31
+ this.apiKeyRepository = apiKeyRepository;
32
+ this.userRepository = userRepository;
33
+ this.config = config;
34
+ this.logger = logger;
35
+ this.auditService = auditService;
36
+ this.logger?.log?.('ApiKeyService initialized');
37
+ }
38
+ /**
39
+ * Create a new API key.
40
+ *
41
+ * @param params - Creation parameters (owning userId is required)
42
+ * @returns The plaintext key (shown once) plus sanitized metadata
43
+ * @throws {NAuthException} API_KEY_CREATION_DISABLED, API_KEY_LIMIT_REACHED,
44
+ * API_KEY_EXPIRY_REQUIRED, API_KEY_INDEFINITE_NOT_ALLOWED, API_KEY_EXPIRY_TOO_LONG,
45
+ * VALIDATION_FAILED (invalid IP allowlist), USER_NOT_FOUND
46
+ */
47
+ async createKey(params) {
48
+ const cfg = this.config.apiKeys ?? {};
49
+ // Creation-rights gate: only admins may create keys unless user creation is enabled.
50
+ if (!params.createdByAdmin && cfg.allowUserCreation !== true) {
51
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_CREATION_DISABLED, 'API key creation is disabled for users. Contact an administrator.');
52
+ }
53
+ const user = await this.userRepository.findOne({ where: { id: params.userId } });
54
+ if (!user) {
55
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.USER_NOT_FOUND, 'User not found');
56
+ }
57
+ // Enforce per-user active key limit.
58
+ const maxKeys = cfg.maxKeysPerUser ?? 10;
59
+ const activeCount = await this.apiKeyRepository.count({ where: { userId: params.userId, isActive: true } });
60
+ if (activeCount >= maxKeys) {
61
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_LIMIT_REACHED, `Maximum of ${maxKeys} active API keys reached.`, {
62
+ maxKeysPerUser: maxKeys,
63
+ });
64
+ }
65
+ const expiresAt = this.resolveExpiry(params.expiresInDays);
66
+ const allowedIps = this.normalizeAllowedIps(params.allowedIps);
67
+ // Generate the key material.
68
+ const keyId = (0, crypto_1.randomUUID)();
69
+ const lookupId = (0, crypto_1.randomBytes)(8).toString('hex'); // 16 non-secret hex chars
70
+ const secret = (0, crypto_1.randomBytes)(32).toString('base64url');
71
+ const prefix = cfg.keyPrefix ?? 'nauth';
72
+ const fullKey = `${prefix}_${lookupId}.${secret}`;
73
+ const keyHash = this.hashKey(fullKey);
74
+ const entity = this.apiKeyRepository.create({
75
+ keyId,
76
+ userId: params.userId,
77
+ lookupId,
78
+ keyHash,
79
+ name: params.name ?? null,
80
+ lastFour: secret.slice(-4),
81
+ allowedIps: allowedIps.length > 0 ? allowedIps : null,
82
+ expiresAt,
83
+ isActive: true,
84
+ createdByAdmin: params.createdByAdmin === true,
85
+ usageCount: 0,
86
+ });
87
+ const saved = await this.apiKeyRepository.save(entity);
88
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_CREATED, params.userId, 'SUCCESS', {
89
+ keyId,
90
+ createdByAdmin: entity.createdByAdmin,
91
+ hasExpiry: expiresAt !== null,
92
+ ipRestricted: allowedIps.length > 0,
93
+ });
94
+ return { key: fullKey, apiKey: this.toResponse(saved) };
95
+ }
96
+ /**
97
+ * Update a key's mutable fields (name and IP allowlist).
98
+ * The secret and expiry are immutable — rotate/extend by deleting and recreating.
99
+ *
100
+ * @throws {NAuthException} API_KEY_NOT_FOUND, VALIDATION_FAILED
101
+ */
102
+ async updateKey(params) {
103
+ const key = await this.apiKeyRepository.findOne({ where: { keyId: params.keyId, userId: params.userId } });
104
+ if (!key) {
105
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_NOT_FOUND, 'API key not found');
106
+ }
107
+ if (params.name !== undefined) {
108
+ key.name = params.name ?? null;
109
+ }
110
+ if (params.allowedIps !== undefined) {
111
+ const allowedIps = this.normalizeAllowedIps(params.allowedIps);
112
+ key.allowedIps = allowedIps.length > 0 ? allowedIps : null;
113
+ }
114
+ const saved = await this.apiKeyRepository.save(key);
115
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_UPDATED, params.userId, 'INFO', { keyId: params.keyId });
116
+ return this.toResponse(saved);
117
+ }
118
+ /**
119
+ * List all keys owned by a user (sanitized; never includes secrets).
120
+ */
121
+ async listKeys(userId) {
122
+ const keys = await this.apiKeyRepository.find({
123
+ where: { userId },
124
+ order: { createdAt: 'DESC' },
125
+ });
126
+ return keys.map((k) => this.toResponse(k));
127
+ }
128
+ /**
129
+ * Revoke (soft-delete) a key. The record is retained for audit history.
130
+ *
131
+ * @throws {NAuthException} API_KEY_NOT_FOUND
132
+ */
133
+ async revokeKey(params) {
134
+ const key = await this.apiKeyRepository.findOne({ where: { keyId: params.keyId, userId: params.userId } });
135
+ if (!key) {
136
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_NOT_FOUND, 'API key not found');
137
+ }
138
+ if (key.isActive) {
139
+ key.isActive = false;
140
+ key.revokedAt = new Date();
141
+ key.revokeReason = params.reason ?? 'user_revoked';
142
+ await this.apiKeyRepository.save(key);
143
+ }
144
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_REVOKED, params.userId, 'INFO', { keyId: params.keyId });
145
+ return { success: true };
146
+ }
147
+ /**
148
+ * Permanently delete a key.
149
+ *
150
+ * @throws {NAuthException} API_KEY_NOT_FOUND
151
+ */
152
+ async deleteKey(params) {
153
+ const key = await this.apiKeyRepository.findOne({ where: { keyId: params.keyId, userId: params.userId } });
154
+ if (!key) {
155
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_NOT_FOUND, 'API key not found');
156
+ }
157
+ await this.apiKeyRepository.remove(key);
158
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_DELETED, params.userId, 'INFO', { keyId: params.keyId });
159
+ return { success: true };
160
+ }
161
+ /**
162
+ * Validate a presented API key and resolve its owner.
163
+ *
164
+ * On any failure this throws a precise {@link NAuthException} (access denied) — callers
165
+ * MUST NOT fall back to other credentials.
166
+ *
167
+ * @param rawKey - The full plaintext key from the request header
168
+ * @param callerIp - Source IP of the request (for IP-allowlist enforcement + usage tracking)
169
+ * @returns The owning user's identifiers on success
170
+ * @throws {NAuthException} API_KEY_INVALID, API_KEY_EXPIRED, API_KEY_IP_NOT_ALLOWED
171
+ */
172
+ async validateKey(rawKey, callerIp) {
173
+ const lookupId = this.parseLookupId(rawKey);
174
+ if (!lookupId) {
175
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_AUTH_FAILED, null, 'FAILURE', { reason: 'malformed' });
176
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_INVALID, 'Invalid API key');
177
+ }
178
+ const key = await this.apiKeyRepository.findOne({ where: { lookupId } });
179
+ if (!key || !key.isActive || !this.hashesEqual(key.keyHash, this.hashKey(rawKey))) {
180
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_AUTH_FAILED, key?.userId ?? null, 'FAILURE', { reason: 'invalid' });
181
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_INVALID, 'Invalid API key');
182
+ }
183
+ if (key.isExpired()) {
184
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_AUTH_FAILED, key.userId, 'FAILURE', {
185
+ keyId: key.keyId,
186
+ reason: 'expired',
187
+ });
188
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_EXPIRED, 'API key has expired');
189
+ }
190
+ if (!key.isIpAllowed(callerIp, ip_match_1.ipMatchesEntry)) {
191
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_AUTH_FAILED, key.userId, 'SUSPICIOUS', {
192
+ keyId: key.keyId,
193
+ reason: 'ip_not_allowed',
194
+ });
195
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_IP_NOT_ALLOWED, 'API key not permitted from this IP address');
196
+ }
197
+ // Resolve the owner's external id (sub) so callers can load the shared auth context.
198
+ const owner = await this.userRepository.findOne({
199
+ where: { id: key.userId },
200
+ select: { id: true, sub: true, isActive: true },
201
+ });
202
+ if (!owner || !owner.isActive) {
203
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_AUTH_FAILED, key.userId, 'FAILURE', {
204
+ keyId: key.keyId,
205
+ reason: 'owner_inactive',
206
+ });
207
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_INVALID, 'Invalid API key');
208
+ }
209
+ await this.recordUsage(key, callerIp ?? null);
210
+ return { keyId: key.keyId, userId: key.userId, sub: owner.sub };
211
+ }
212
+ // ==========================================================================
213
+ // Internal helpers
214
+ // ==========================================================================
215
+ /**
216
+ * Resolve the mandatory, config-bounded expiry.
217
+ */
218
+ resolveExpiry(expiresInDays) {
219
+ const cfg = this.config.apiKeys ?? {};
220
+ if (expiresInDays === undefined) {
221
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_EXPIRY_REQUIRED, 'An expiry must be specified when creating an API key (a number of days, or null for never).');
222
+ }
223
+ if (expiresInDays === null) {
224
+ if (cfg.allowIndefinite === false) {
225
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_INDEFINITE_NOT_ALLOWED, 'Non-expiring API keys are not allowed. Specify an expiry in days.');
226
+ }
227
+ return null;
228
+ }
229
+ if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
230
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'expiresInDays must be a positive integer or null.');
231
+ }
232
+ if (typeof cfg.maxExpiryDays === 'number' && expiresInDays > cfg.maxExpiryDays) {
233
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.API_KEY_EXPIRY_TOO_LONG, `Expiry exceeds the maximum of ${cfg.maxExpiryDays} days.`, { maxExpiryDays: cfg.maxExpiryDays });
234
+ }
235
+ const expiresAt = new Date();
236
+ expiresAt.setDate(expiresAt.getDate() + expiresInDays);
237
+ return expiresAt;
238
+ }
239
+ /**
240
+ * Validate and normalize an IP allowlist per configuration.
241
+ */
242
+ normalizeAllowedIps(allowedIps) {
243
+ const cfg = this.config.apiKeys ?? {};
244
+ const ipCfg = cfg.ipRestrictions ?? {};
245
+ // When IP restrictions are disabled, allowlists are ignored entirely.
246
+ if (ipCfg.enabled === false) {
247
+ if (allowedIps && allowedIps.length > 0) {
248
+ this.logger?.debug?.('[ApiKey] IP restrictions disabled - ignoring provided allowedIps');
249
+ }
250
+ return [];
251
+ }
252
+ const normalized = (allowedIps ?? []).map((e) => e.trim()).filter((e) => e.length > 0);
253
+ if (ipCfg.requireForNewKeys === true && normalized.length === 0) {
254
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'An IP allowlist is required for new API keys (apiKeys.ipRestrictions.requireForNewKeys).');
255
+ }
256
+ const maxIps = ipCfg.maxIpsPerKey ?? 20;
257
+ if (normalized.length > maxIps) {
258
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `An API key may have at most ${maxIps} IP entries.`, {
259
+ maxIpsPerKey: maxIps,
260
+ });
261
+ }
262
+ const invalid = normalized.filter((e) => !(0, ip_match_1.isValidIpOrCidr)(e));
263
+ if (invalid.length > 0) {
264
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'allowedIps contains invalid IP or CIDR entries.', {
265
+ invalid,
266
+ });
267
+ }
268
+ return normalized;
269
+ }
270
+ /**
271
+ * Compute the SHA-256 hex hash of a full key string.
272
+ */
273
+ hashKey(rawKey) {
274
+ return (0, crypto_1.createHash)('sha256').update(rawKey).digest('hex');
275
+ }
276
+ /**
277
+ * Constant-time comparison of two equal-length hex hashes.
278
+ */
279
+ hashesEqual(a, b) {
280
+ if (a.length !== b.length) {
281
+ return false;
282
+ }
283
+ try {
284
+ return (0, crypto_1.timingSafeEqual)(Buffer.from(a), Buffer.from(b));
285
+ }
286
+ catch {
287
+ return false;
288
+ }
289
+ }
290
+ /**
291
+ * Extract the non-secret lookup id from a raw key of the form `prefix_lookupId.secret`.
292
+ */
293
+ parseLookupId(rawKey) {
294
+ if (typeof rawKey !== 'string') {
295
+ return null;
296
+ }
297
+ const dotIndex = rawKey.indexOf('.');
298
+ if (dotIndex <= 0) {
299
+ return null;
300
+ }
301
+ const head = rawKey.slice(0, dotIndex); // prefix_lookupId
302
+ const underscoreIndex = head.lastIndexOf('_');
303
+ if (underscoreIndex < 0 || underscoreIndex === head.length - 1) {
304
+ return null;
305
+ }
306
+ return head.slice(underscoreIndex + 1);
307
+ }
308
+ /**
309
+ * Update last-used metadata, throttled to avoid a write on every request.
310
+ * Never throws — usage tracking must not block authentication.
311
+ */
312
+ async recordUsage(key, callerIp) {
313
+ const cfg = this.config.apiKeys ?? {};
314
+ const throttleMs = (cfg.lastUsedThrottleSeconds ?? 60) * 1000;
315
+ const now = Date.now();
316
+ const last = key.lastUsedAt ? key.lastUsedAt.getTime() : 0;
317
+ if (last !== 0 && now - last < throttleMs) {
318
+ return; // Within throttle window - skip write and audit noise.
319
+ }
320
+ try {
321
+ const update = {
322
+ lastUsedAt: new Date(now),
323
+ };
324
+ if (cfg.trackUsageIp !== false) {
325
+ update.lastUsedIp = callerIp;
326
+ }
327
+ await this.apiKeyRepository.update({ id: key.id }, update);
328
+ // Atomic increment avoids lost updates under concurrent use (read-modify-write race).
329
+ await this.apiKeyRepository.increment({ id: key.id }, 'usageCount', 1);
330
+ await this.audit(auth_audit_event_type_enum_1.AuthAuditEventType.API_KEY_USED, key.userId, 'SUCCESS', { keyId: key.keyId });
331
+ }
332
+ catch (error) {
333
+ const message = error instanceof Error ? error.message : 'Unknown error';
334
+ this.logger?.debug?.(`[ApiKey] Failed to record usage for key ${key.keyId}: ${message}`);
335
+ }
336
+ }
337
+ /**
338
+ * Build a sanitized response DTO from an entity.
339
+ */
340
+ toResponse(key) {
341
+ return {
342
+ keyId: key.keyId,
343
+ name: key.name ?? null,
344
+ lastFour: key.lastFour ?? null,
345
+ allowedIps: key.allowedIps ?? null,
346
+ expiresAt: key.expiresAt ?? null,
347
+ isActive: key.isActive,
348
+ createdByAdmin: key.createdByAdmin,
349
+ lastUsedAt: key.lastUsedAt ?? null,
350
+ lastUsedIp: key.lastUsedIp ?? null,
351
+ usageCount: key.usageCount ?? 0,
352
+ createdAt: key.createdAt,
353
+ };
354
+ }
355
+ /**
356
+ * Record an audit event (no-op when audit logging is disabled). Never throws.
357
+ */
358
+ async audit(eventType, userId, eventStatus, metadata) {
359
+ if (!this.auditService) {
360
+ return;
361
+ }
362
+ try {
363
+ await this.auditService.recordEvent({
364
+ userId: userId ?? undefined,
365
+ eventType,
366
+ eventStatus,
367
+ authMethod: 'api-key',
368
+ metadata,
369
+ });
370
+ }
371
+ catch (error) {
372
+ const message = error instanceof Error ? error.message : 'Unknown error';
373
+ this.logger?.debug?.(`[ApiKey] Failed to record audit event ${eventType}: ${message}`);
374
+ }
375
+ }
376
+ }
377
+ exports.ApiKeyService = ApiKeyService;
378
+ //# sourceMappingURL=api-key.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.service.js","sourceRoot":"","sources":["../../src/services/api-key.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ import { NAuthConfig } from '../interfaces/config.interface';
2
+ import { StorageAdapter } from '../interfaces/storage-adapter.interface';
3
+ import { NAuthLogger } from '../utils/nauth-logger';
4
+ import { MFAService } from './mfa.service';
5
+ import { SocialProviderRegistry } from './social-provider-registry.service';
6
+ /**
7
+ * Anonymous telemetry payload — configuration shape only.
8
+ *
9
+ * Contains no personal data: no IP addresses, secrets, domains, emails,
10
+ * table names, or free-text configuration values. Documented publicly at
11
+ * https://nauth.dev/docs/concepts/telemetry
12
+ */
13
+ export interface TelemetryPayload {
14
+ schemaVersion: 1;
15
+ instanceId: string;
16
+ event: 'boot' | 'heartbeat';
17
+ coreVersion: string;
18
+ nodeMajor: number;
19
+ platform: string;
20
+ arch: string;
21
+ nodeEnv: 'production' | 'development' | 'other';
22
+ framework: string;
23
+ config: {
24
+ tokenDeliveryMethod: 'json' | 'cookies' | 'hybrid';
25
+ mfa: {
26
+ enabled: boolean;
27
+ enforcement: 'OPTIONAL' | 'REQUIRED' | 'ADAPTIVE' | null;
28
+ gracePeriodSet: boolean;
29
+ allowedMethods: string[];
30
+ };
31
+ mfaProviders: string[];
32
+ socialProviders: string[];
33
+ storageAdapter: string;
34
+ signupVerificationMethod: string | null;
35
+ auditLogsEnabled: boolean;
36
+ recaptchaEnabled: boolean;
37
+ geoLocationConfigured: boolean;
38
+ };
39
+ }
40
+ /**
41
+ * Anonymous usage telemetry (opt-out)
42
+ *
43
+ * Sends a small, anonymous payload describing the *shape* of the nauth
44
+ * configuration (enums, booleans, and registered provider names — never
45
+ * values) once at boot and once per day thereafter. The data guides
46
+ * development priorities; see https://nauth.dev/docs/concepts/telemetry
47
+ * for the exact payload and rationale.
48
+ *
49
+ * **Performance guarantees:**
50
+ * - Never runs inside a request path — no middleware or handler involvement
51
+ * - The boot ping is deferred and fire-and-forget; `NAuth.create()` gains no awaits
52
+ * - The heartbeat timer is unref'd and never keeps the process alive
53
+ * - Network failures are swallowed at debug level; this service never throws
54
+ *
55
+ * **Disabled automatically** when any of the following holds:
56
+ * - `config.telemetry.enabled === false`
57
+ * - `NAUTH_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1`
58
+ * - `CI=true` or `NODE_ENV=test`
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const telemetry = new TelemetryService(config, storage, logger, 'express', mfaService, socialRegistry);
63
+ * telemetry.sendBootPing();
64
+ * telemetry.startHeartbeat();
65
+ * // ...on shutdown:
66
+ * telemetry.shutdown();
67
+ * ```
68
+ */
69
+ export declare class TelemetryService {
70
+ private readonly config;
71
+ private readonly storageAdapter;
72
+ private readonly logger?;
73
+ private readonly framework;
74
+ private readonly mfaService?;
75
+ private readonly socialProviderRegistry?;
76
+ private heartbeatTimer?;
77
+ private cachedInstanceId?;
78
+ private disclosureShown;
79
+ constructor(config: NAuthConfig, storageAdapter: StorageAdapter, logger?: NAuthLogger | undefined, framework?: string, mfaService?: MFAService | undefined, socialProviderRegistry?: SocialProviderRegistry | undefined);
80
+ /**
81
+ * Whether telemetry is active for this process.
82
+ *
83
+ * Evaluates the config flag and all environment opt-outs
84
+ * (NAUTH_TELEMETRY_DISABLED, DO_NOT_TRACK, CI, NODE_ENV=test).
85
+ *
86
+ * @returns true when telemetry may be sent
87
+ */
88
+ isEnabled(): boolean;
89
+ /**
90
+ * Send the boot ping (fire-and-forget).
91
+ *
92
+ * On the first boot of an install (when the anonymous instance ID is
93
+ * created), a one-time disclosure notice is logged. Subsequent boots of
94
+ * the same install are silent. This method returns immediately and never
95
+ * throws; all work happens off the startup path.
96
+ */
97
+ sendBootPing(): void;
98
+ /**
99
+ * Start the daily heartbeat timer.
100
+ *
101
+ * The timer is unref'd so it never prevents process exit. A random jitter
102
+ * of up to one hour avoids synchronized pings from fleets that restart
103
+ * together. No-op when telemetry is disabled.
104
+ */
105
+ startHeartbeat(): void;
106
+ /**
107
+ * Stop the heartbeat timer. Safe to call multiple times.
108
+ */
109
+ shutdown(): void;
110
+ /**
111
+ * Resolve the anonymous instance ID, with layered persistence:
112
+ *
113
+ * 1. **Storage adapter** (Redis/database) — deployment-scoped: all processes
114
+ * sharing the deployment converge on one ID via an NX (set-if-absent) write.
115
+ * 2. **Home-directory file** (`~/.nauth-toolkit/telemetry-instance-id`) — used
116
+ * when the storage adapter is the non-persistent in-memory adapter, so
117
+ * restarts on the same machine keep one ID instead of minting a new
118
+ * "install" per boot.
119
+ * 3. **Per-process UUID** — last resort when both stores are unavailable
120
+ * (e.g. read-only filesystem); never throws.
121
+ *
122
+ * `isNew` is true only when this process created the ID — used to show the
123
+ * disclosure notice exactly once per install.
124
+ */
125
+ private resolveInstanceId;
126
+ /**
127
+ * File-backed instance ID under the user's home directory. Returns a
128
+ * per-process UUID when the filesystem is unavailable or read-only.
129
+ */
130
+ private resolveInstanceIdFromFile;
131
+ /**
132
+ * Build the telemetry payload from the resolved configuration and the
133
+ * registered provider lists. Pure shape extraction — no values are read
134
+ * beyond enums, booleans, and provider identifiers.
135
+ */
136
+ private buildPayload;
137
+ /**
138
+ * Resolve the storage adapter class name for the payload.
139
+ *
140
+ * Lazy wrappers (used by the NestJS module and the core storage factory)
141
+ * would otherwise report 'LazyStorageAdapter' for every install; when the
142
+ * wrapper has an initialized inner adapter, its class name is reported
143
+ * instead. By the time the payload is built the instance-ID lookup has
144
+ * already gone through the adapter, so the inner adapter exists.
145
+ */
146
+ private storageAdapterName;
147
+ /**
148
+ * POST the payload to the telemetry endpoint with a hard timeout.
149
+ * All failures are swallowed (debug log only) — telemetry must be
150
+ * invisible when it fails.
151
+ */
152
+ private send;
153
+ }
154
+ //# sourceMappingURL=telemetry.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.service.d.ts","sourceRoot":"","sources":["../../src/services/telemetry.service.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAkB5E;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QACN,mBAAmB,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;QACnD,GAAG,EAAE;YACH,OAAO,EAAE,OAAO,CAAC;YACjB,WAAW,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC;YACzD,cAAc,EAAE,OAAO,CAAC;YACxB,cAAc,EAAE,MAAM,EAAE,CAAC;SAC1B,CAAC;QACF,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,cAAc,EAAE,MAAM,CAAC;QACvB,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;QACxC,gBAAgB,EAAE,OAAO,CAAC;QAC1B,gBAAgB,EAAE,OAAO,CAAC;QAC1B,qBAAqB,EAAE,OAAO,CAAC;KAChC,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,gBAAgB;IAMzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IAV1C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAClC,OAAO,CAAC,eAAe,CAAS;gBAGb,MAAM,EAAE,WAAW,EACnB,cAAc,EAAE,cAAc,EAC9B,MAAM,CAAC,EAAE,WAAW,YAAA,EACpB,SAAS,GAAE,MAAkB,EAC7B,UAAU,CAAC,EAAE,UAAU,YAAA,EACvB,sBAAsB,CAAC,EAAE,sBAAsB,YAAA;IAGlE;;;;;;;OAOG;IACH,SAAS,IAAI,OAAO;IAkBpB;;;;;;;OAOG;IACH,YAAY,IAAI,IAAI;IAqBpB;;;;;;OAMG;IACH,cAAc,IAAI,IAAI;IAgBtB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAOhB;;;;;;;;;;;;;;OAcG;YACW,iBAAiB;IA2B/B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAqBjC;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAmDpB;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAK1B;;;;OAIG;YACW,IAAI;CAmBnB"}