@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.
- package/dist/bootstrap.d.ts +3 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +63 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/dto/admin-api-key.dto.d.ts +69 -0
- package/dist/dto/admin-api-key.dto.d.ts.map +1 -0
- package/dist/dto/admin-api-key.dto.js +144 -0
- package/dist/dto/admin-api-key.dto.js.map +1 -0
- package/dist/dto/admin-signup-social.dto.d.ts +1 -1
- package/dist/dto/admin-signup-social.dto.js +1 -1
- package/dist/dto/admin-signup.dto.d.ts +1 -1
- package/dist/dto/admin-signup.dto.js +1 -1
- package/dist/dto/api-key.dto.d.ts +132 -0
- package/dist/dto/api-key.dto.d.ts.map +1 -0
- package/dist/dto/api-key.dto.js +198 -0
- package/dist/dto/api-key.dto.js.map +1 -0
- package/dist/dto/change-password.dto.d.ts +2 -2
- package/dist/dto/change-password.dto.js +2 -2
- package/dist/dto/index.d.ts +2 -0
- package/dist/dto/index.d.ts.map +1 -1
- package/dist/dto/index.js +3 -0
- package/dist/dto/index.js.map +1 -1
- package/dist/dto/reset-password.dto.d.ts +1 -1
- package/dist/dto/reset-password.dto.js +1 -1
- package/dist/dto/respond-challenge.dto.d.ts +1 -1
- package/dist/dto/respond-challenge.dto.js +1 -1
- package/dist/dto/signup.dto.d.ts +1 -1
- package/dist/dto/signup.dto.js +1 -1
- package/dist/entities/api-key.entity.d.ts +135 -0
- package/dist/entities/api-key.entity.d.ts.map +1 -0
- package/dist/entities/api-key.entity.js +149 -0
- package/dist/entities/api-key.entity.js.map +1 -0
- package/dist/entities/index.d.ts +1 -0
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +3 -1
- package/dist/entities/index.js.map +1 -1
- package/dist/enums/auth-audit-event-type.enum.d.ts +25 -1
- package/dist/enums/auth-audit-event-type.enum.d.ts.map +1 -1
- package/dist/enums/auth-audit-event-type.enum.js +27 -0
- package/dist/enums/auth-audit-event-type.enum.js.map +1 -1
- package/dist/enums/error-codes.enum.d.ts +56 -1
- package/dist/enums/error-codes.enum.d.ts.map +1 -1
- package/dist/enums/error-codes.enum.js +58 -0
- package/dist/enums/error-codes.enum.js.map +1 -1
- package/dist/exceptions/nauth.exception.d.ts.map +1 -1
- package/dist/exceptions/nauth.exception.js +13 -0
- package/dist/exceptions/nauth.exception.js.map +1 -1
- package/dist/handlers/api-key.handler.d.ts +45 -0
- package/dist/handlers/api-key.handler.d.ts.map +1 -0
- package/dist/handlers/api-key.handler.js +99 -0
- package/dist/handlers/api-key.handler.js.map +1 -0
- package/dist/handlers/auth.handler.d.ts.map +1 -1
- package/dist/handlers/auth.handler.js +6 -0
- package/dist/handlers/auth.handler.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/config.interface.d.ts +162 -0
- package/dist/interfaces/config.interface.d.ts.map +1 -1
- package/dist/internal.d.ts +7 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +8 -1
- package/dist/internal.js.map +1 -1
- package/dist/openapi/components.schemas.json +284 -7
- package/dist/platform/interfaces.d.ts +8 -0
- package/dist/platform/interfaces.d.ts.map +1 -1
- package/dist/schemas/auth-config.schema.d.ts +211 -0
- package/dist/schemas/auth-config.schema.d.ts.map +1 -1
- package/dist/schemas/auth-config.schema.js +33 -1
- package/dist/schemas/auth-config.schema.js.map +1 -1
- package/dist/services/admin-auth.service.d.ts +59 -1
- package/dist/services/admin-auth.service.d.ts.map +1 -1
- package/dist/services/admin-auth.service.js +99 -1
- package/dist/services/admin-auth.service.js.map +1 -1
- package/dist/services/api-key.service.d.ts +152 -0
- package/dist/services/api-key.service.d.ts.map +1 -0
- package/dist/services/api-key.service.js +378 -0
- package/dist/services/api-key.service.js.map +1 -0
- package/dist/services/telemetry.service.d.ts +154 -0
- package/dist/services/telemetry.service.d.ts.map +1 -0
- package/dist/services/telemetry.service.js +345 -0
- package/dist/services/telemetry.service.js.map +1 -0
- package/dist/utils/get-package-version.d.ts +15 -0
- package/dist/utils/get-package-version.d.ts.map +1 -0
- package/dist/utils/get-package-version.js +84 -0
- package/dist/utils/get-package-version.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/ip-match.d.ts +44 -0
- package/dist/utils/ip-match.d.ts.map +1 -0
- package/dist/utils/ip-match.js +135 -0
- package/dist/utils/ip-match.js.map +1 -0
- package/dist/utils/setup/get-repositories.d.ts +2 -1
- package/dist/utils/setup/get-repositories.d.ts.map +1 -1
- package/dist/utils/setup/get-repositories.js +2 -0
- package/dist/utils/setup/get-repositories.js.map +1 -1
- package/dist/utils/setup/init-services.d.ts +4 -2
- package/dist/utils/setup/init-services.d.ts.map +1 -1
- package/dist/utils/setup/init-services.js +8 -1
- package/dist/utils/setup/init-services.js.map +1 -1
- 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"}
|