@warlock.js/auth 4.0.42 → 4.0.47
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/cjs/index.js +46 -715
- package/cjs/index.js.map +1 -1
- package/esm/index.js +7 -703
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
package/cjs/index.js
CHANGED
|
@@ -1,725 +1,56 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var fastJwt = require('fast-jwt');
|
|
11
|
-
var fs = require('@mongez/fs');
|
|
12
|
-
var logger = require('@warlock.js/logger');
|
|
3
|
+
var authCleanupCommand = require('./commands/auth-cleanup-command');
|
|
4
|
+
var jwtSecretGeneratorCommand = require('./commands/jwt-secret-generator-command');
|
|
5
|
+
var contracts = require('./contracts');
|
|
6
|
+
var middleware = require('./middleware');
|
|
7
|
+
var models = require('./models');
|
|
8
|
+
var services = require('./services');
|
|
9
|
+
var utils = require('./utils');
|
|
13
10
|
|
|
14
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
11
|
|
|
16
|
-
var events__default = /*#__PURE__*/_interopDefault(events);
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
id: seal.v.number().required(),
|
|
24
|
-
userType: seal.v.string()
|
|
25
|
-
}).allowUnknown().required()
|
|
13
|
+
Object.keys(authCleanupCommand).forEach(function (k) {
|
|
14
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return authCleanupCommand[k]; }
|
|
17
|
+
});
|
|
26
18
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
static schema = accessTokenSchema;
|
|
33
|
-
};
|
|
34
|
-
cascade.migrate(AccessToken, {
|
|
35
|
-
name: "accessToken",
|
|
36
|
-
up() {
|
|
37
|
-
this.string("accessToken").index();
|
|
38
|
-
this.date("lastAccess");
|
|
39
|
-
},
|
|
40
|
-
down() {
|
|
41
|
-
this.dropIndex("token");
|
|
42
|
-
}
|
|
19
|
+
Object.keys(jwtSecretGeneratorCommand).forEach(function (k) {
|
|
20
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () { return jwtSecretGeneratorCommand[k]; }
|
|
23
|
+
});
|
|
43
24
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
25
|
+
Object.keys(contracts).forEach(function (k) {
|
|
26
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function () { return contracts[k]; }
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
Object.keys(middleware).forEach(function (k) {
|
|
32
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function () { return middleware[k]; }
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
Object.keys(models).forEach(function (k) {
|
|
38
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function () { return models[k]; }
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
Object.keys(services).forEach(function (k) {
|
|
44
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
get: function () { return services[k]; }
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
Object.keys(utils).forEach(function (k) {
|
|
50
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function () { return utils[k]; }
|
|
53
|
+
});
|
|
53
54
|
});
|
|
54
|
-
var RefreshToken = class extends cascade.Model {
|
|
55
|
-
/**
|
|
56
|
-
* {@inheritDoc}
|
|
57
|
-
*/
|
|
58
|
-
static table = "refreshTokens";
|
|
59
|
-
/**
|
|
60
|
-
* {@inheritDoc}
|
|
61
|
-
*/
|
|
62
|
-
static schema = refreshTokenSchema;
|
|
63
|
-
/**
|
|
64
|
-
* Check if token is expired
|
|
65
|
-
*/
|
|
66
|
-
get isExpired() {
|
|
67
|
-
const expiresAt = this.get("expiresAt");
|
|
68
|
-
if (!expiresAt) return false;
|
|
69
|
-
return /* @__PURE__ */ new Date() > new Date(expiresAt);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Check if token is revoked
|
|
73
|
-
*/
|
|
74
|
-
get isRevoked() {
|
|
75
|
-
return !!this.get("revokedAt");
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Check if token is valid (not expired and not revoked)
|
|
79
|
-
*/
|
|
80
|
-
get isValid() {
|
|
81
|
-
return !this.isExpired && !this.isRevoked;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Revoke this token
|
|
85
|
-
*/
|
|
86
|
-
async revoke() {
|
|
87
|
-
return this.merge({ revokedAt: /* @__PURE__ */ new Date() }).save();
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Mark token as used (update lastUsedAt)
|
|
91
|
-
*/
|
|
92
|
-
async markAsUsed() {
|
|
93
|
-
await this.merge({ lastUsedAt: /* @__PURE__ */ new Date() }).save();
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// ../../warlock.js/auth/src/contracts/types.ts
|
|
98
|
-
var NO_EXPIRATION = /* @__PURE__ */ Symbol("NO_EXPIRATION");
|
|
99
|
-
|
|
100
|
-
// ../../warlock.js/auth/src/utils/duration.ts
|
|
101
|
-
function parseExpirationToMs(expiration, defaultMs = 36e5) {
|
|
102
|
-
if (expiration === void 0) {
|
|
103
|
-
return defaultMs;
|
|
104
|
-
}
|
|
105
|
-
if (expiration === NO_EXPIRATION) {
|
|
106
|
-
return void 0;
|
|
107
|
-
}
|
|
108
|
-
if (typeof expiration === "number") {
|
|
109
|
-
return expiration;
|
|
110
|
-
}
|
|
111
|
-
if (typeof expiration === "string") {
|
|
112
|
-
return parseStringDuration(expiration);
|
|
113
|
-
}
|
|
114
|
-
return parseDurationObject(expiration);
|
|
115
|
-
}
|
|
116
|
-
function parseDurationObject(duration) {
|
|
117
|
-
let ms = 0;
|
|
118
|
-
if (duration.milliseconds) ms += duration.milliseconds;
|
|
119
|
-
if (duration.seconds) ms += duration.seconds * 1e3;
|
|
120
|
-
if (duration.minutes) ms += duration.minutes * 60 * 1e3;
|
|
121
|
-
if (duration.hours) ms += duration.hours * 60 * 60 * 1e3;
|
|
122
|
-
if (duration.days) ms += duration.days * 24 * 60 * 60 * 1e3;
|
|
123
|
-
if (duration.weeks) ms += duration.weeks * 7 * 24 * 60 * 60 * 1e3;
|
|
124
|
-
return ms;
|
|
125
|
-
}
|
|
126
|
-
function parseStringDuration(str) {
|
|
127
|
-
let totalMs = 0;
|
|
128
|
-
const parts = str.trim().split(/\s+/);
|
|
129
|
-
for (const part of parts) {
|
|
130
|
-
const match = part.match(/^(\d+(?:\.\d+)?)([smhdw])$/i);
|
|
131
|
-
if (!match) continue;
|
|
132
|
-
const value = parseFloat(match[1]);
|
|
133
|
-
const unit = match[2].toLowerCase();
|
|
134
|
-
switch (unit) {
|
|
135
|
-
case "s":
|
|
136
|
-
totalMs += value * 1e3;
|
|
137
|
-
break;
|
|
138
|
-
case "m":
|
|
139
|
-
totalMs += value * 60 * 1e3;
|
|
140
|
-
break;
|
|
141
|
-
case "h":
|
|
142
|
-
totalMs += value * 60 * 60 * 1e3;
|
|
143
|
-
break;
|
|
144
|
-
case "d":
|
|
145
|
-
totalMs += value * 24 * 60 * 60 * 1e3;
|
|
146
|
-
break;
|
|
147
|
-
case "w":
|
|
148
|
-
totalMs += value * 7 * 24 * 60 * 60 * 1e3;
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return totalMs || 36e5;
|
|
153
|
-
}
|
|
154
|
-
function toJwtExpiresIn(expiration, defaultMs = 36e5) {
|
|
155
|
-
const ms = parseExpirationToMs(expiration, defaultMs);
|
|
156
|
-
if (ms === void 0) return void 0;
|
|
157
|
-
return Math.floor(ms / 1e3) + "s";
|
|
158
|
-
}
|
|
159
|
-
var AUTH_EVENT_PREFIX = "auth.";
|
|
160
|
-
var authEvents = {
|
|
161
|
-
/**
|
|
162
|
-
* Subscribe to an auth event
|
|
163
|
-
*/
|
|
164
|
-
on(event, callback) {
|
|
165
|
-
return events__default.default.subscribe(AUTH_EVENT_PREFIX + event, callback);
|
|
166
|
-
},
|
|
167
|
-
/**
|
|
168
|
-
* Subscribe to an auth event (alias for `on`)
|
|
169
|
-
*/
|
|
170
|
-
subscribe(event, callback) {
|
|
171
|
-
return this.on(event, callback);
|
|
172
|
-
},
|
|
173
|
-
/**
|
|
174
|
-
* Emit an auth event
|
|
175
|
-
*/
|
|
176
|
-
emit(event, ...args) {
|
|
177
|
-
events__default.default.trigger(AUTH_EVENT_PREFIX + event, ...args);
|
|
178
|
-
},
|
|
179
|
-
/**
|
|
180
|
-
* Emit an auth event (alias for `emit`)
|
|
181
|
-
*/
|
|
182
|
-
trigger(event, ...args) {
|
|
183
|
-
this.emit(event, ...args);
|
|
184
|
-
},
|
|
185
|
-
/**
|
|
186
|
-
* Unsubscribe from all auth events
|
|
187
|
-
*/
|
|
188
|
-
unsubscribeAll() {
|
|
189
|
-
events__default.default.unsubscribeNamespace(AUTH_EVENT_PREFIX.slice(0, -1));
|
|
190
|
-
},
|
|
191
|
-
/**
|
|
192
|
-
* Unsubscribe from a specific auth event
|
|
193
|
-
*/
|
|
194
|
-
off(event) {
|
|
195
|
-
if (event) {
|
|
196
|
-
events__default.default.unsubscribe(AUTH_EVENT_PREFIX + event);
|
|
197
|
-
} else {
|
|
198
|
-
this.unsubscribeAll();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
var getSecretKey = () => core.config.key("auth.jwt.secret");
|
|
203
|
-
var getAlgorithm = () => core.config.key("auth.jwt.algorithm");
|
|
204
|
-
var getRefreshSecretKey = () => core.config.key("auth.jwt.refresh.secret");
|
|
205
|
-
var getRefreshTokenValidity = () => core.config.key("auth.jwt.refresh.expiresIn");
|
|
206
|
-
var jwt = {
|
|
207
|
-
/**
|
|
208
|
-
* Generate a new JWT token for the user.
|
|
209
|
-
* @param payload The payload to encode in the JWT token.
|
|
210
|
-
*/
|
|
211
|
-
async generate(payload, {
|
|
212
|
-
key = getSecretKey(),
|
|
213
|
-
algorithm = getAlgorithm(),
|
|
214
|
-
...options
|
|
215
|
-
} = {}) {
|
|
216
|
-
const sign = fastJwt.createSigner({ key, ...options, algorithm });
|
|
217
|
-
return sign({ ...payload });
|
|
218
|
-
},
|
|
219
|
-
/**
|
|
220
|
-
* Verify the given token.
|
|
221
|
-
* @param token The JWT token to verify.
|
|
222
|
-
* @returns The decoded token payload if verification is successful.
|
|
223
|
-
*/
|
|
224
|
-
async verify(token, {
|
|
225
|
-
key = getSecretKey(),
|
|
226
|
-
algorithms = getAlgorithm() ? [getAlgorithm()] : void 0,
|
|
227
|
-
...options
|
|
228
|
-
} = {}) {
|
|
229
|
-
const verify2 = fastJwt.createVerifier({ key, ...options, algorithms });
|
|
230
|
-
return await verify2(token);
|
|
231
|
-
},
|
|
232
|
-
/**
|
|
233
|
-
* Generate a new refresh token for the user.
|
|
234
|
-
*/
|
|
235
|
-
async generateRefreshToken(payload, {
|
|
236
|
-
key = getRefreshSecretKey(),
|
|
237
|
-
expiresIn = getRefreshTokenValidity(),
|
|
238
|
-
algorithm = getAlgorithm(),
|
|
239
|
-
...options
|
|
240
|
-
} = {}) {
|
|
241
|
-
const sign = fastJwt.createSigner({ key, expiresIn, algorithm, ...options });
|
|
242
|
-
return sign({ ...payload });
|
|
243
|
-
},
|
|
244
|
-
/**
|
|
245
|
-
* Verify the given refresh token.
|
|
246
|
-
*/
|
|
247
|
-
async verifyRefreshToken(token, {
|
|
248
|
-
key = getRefreshSecretKey(),
|
|
249
|
-
algorithms = [getAlgorithm()],
|
|
250
|
-
...options
|
|
251
|
-
} = {}) {
|
|
252
|
-
const verify2 = fastJwt.createVerifier({ key, algorithms, ...options });
|
|
253
|
-
return await verify2(token);
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
// ../../warlock.js/auth/src/services/auth.service.ts
|
|
258
|
-
var AuthService = class {
|
|
259
|
-
/**
|
|
260
|
-
* Build access token payload from user
|
|
261
|
-
*/
|
|
262
|
-
buildAccessTokenPayload(user) {
|
|
263
|
-
return {
|
|
264
|
-
id: user.id,
|
|
265
|
-
_id: user.get("_id"),
|
|
266
|
-
userType: user.userType,
|
|
267
|
-
createdAt: Date.now()
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Generate access token for user
|
|
272
|
-
*/
|
|
273
|
-
async generateAccessToken(user, payload) {
|
|
274
|
-
const data = payload || this.buildAccessTokenPayload(user);
|
|
275
|
-
const expiresInConfig = core.config.key("auth.jwt.expiresIn");
|
|
276
|
-
const expiresIn = toJwtExpiresIn(expiresInConfig, 36e5);
|
|
277
|
-
const token = expiresIn ? await jwt.generate(data, { expiresIn }) : await jwt.generate(data);
|
|
278
|
-
await AccessToken.create({
|
|
279
|
-
token,
|
|
280
|
-
user: data
|
|
281
|
-
});
|
|
282
|
-
return token;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Create refresh token for user
|
|
286
|
-
*/
|
|
287
|
-
async createRefreshToken(user, deviceInfo) {
|
|
288
|
-
const familyId = deviceInfo?.familyId || reinforcements.Random.string(32);
|
|
289
|
-
const expiresInConfig = core.config.key("auth.jwt.refresh.expiresIn");
|
|
290
|
-
const expiresInMs = parseExpirationToMs(expiresInConfig, 7 * 24 * 60 * 60 * 1e3);
|
|
291
|
-
const payload = {
|
|
292
|
-
userId: user.id,
|
|
293
|
-
userType: user.userType,
|
|
294
|
-
familyId
|
|
295
|
-
};
|
|
296
|
-
const token = await jwt.generateRefreshToken(payload);
|
|
297
|
-
await this.enforceMaxRefreshTokens(user);
|
|
298
|
-
const expiresAt = expiresInMs ? new Date(Date.now() + expiresInMs) : new Date(Date.now() + 100 * 365 * 24 * 60 * 60 * 1e3);
|
|
299
|
-
return RefreshToken.create({
|
|
300
|
-
token,
|
|
301
|
-
userId: user.id,
|
|
302
|
-
userType: user.userType,
|
|
303
|
-
familyId,
|
|
304
|
-
expiresAt,
|
|
305
|
-
deviceInfo: deviceInfo ? {
|
|
306
|
-
userAgent: deviceInfo.userAgent,
|
|
307
|
-
ip: deviceInfo.ip,
|
|
308
|
-
deviceId: deviceInfo.deviceId
|
|
309
|
-
} : void 0
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Create both access and refresh tokens
|
|
314
|
-
*/
|
|
315
|
-
async createTokenPair(user, deviceInfo) {
|
|
316
|
-
const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);
|
|
317
|
-
const refreshToken = await this.createRefreshToken(user, deviceInfo);
|
|
318
|
-
const tokenPair = {
|
|
319
|
-
accessToken,
|
|
320
|
-
refreshToken: refreshToken.get("token"),
|
|
321
|
-
expiresIn: core.config.key("auth.jwt.expiresIn", "1h")
|
|
322
|
-
};
|
|
323
|
-
authEvents.emit("token.created", user, tokenPair);
|
|
324
|
-
authEvents.emit("session.created", user, refreshToken, deviceInfo);
|
|
325
|
-
return tokenPair;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Refresh tokens using a refresh token
|
|
329
|
-
*/
|
|
330
|
-
async refreshTokens(refreshTokenString, deviceInfo) {
|
|
331
|
-
try {
|
|
332
|
-
const decoded = await jwt.verifyRefreshToken(refreshTokenString);
|
|
333
|
-
if (!decoded) return null;
|
|
334
|
-
const refreshToken = await RefreshToken.first({ token: refreshTokenString });
|
|
335
|
-
if (!refreshToken?.isValid) {
|
|
336
|
-
if (refreshToken) {
|
|
337
|
-
await this.revokeTokenFamily(refreshToken.get("familyId"));
|
|
338
|
-
}
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
const UserModel = core.config.key(`auth.userType.${decoded.userType}`);
|
|
342
|
-
if (!UserModel) return null;
|
|
343
|
-
const user = await UserModel.find(decoded.userId);
|
|
344
|
-
if (!user) return null;
|
|
345
|
-
const rotationEnabled = core.config.key("auth.jwt.refresh.rotation", true);
|
|
346
|
-
if (rotationEnabled) {
|
|
347
|
-
await refreshToken.revoke();
|
|
348
|
-
} else {
|
|
349
|
-
await refreshToken.markAsUsed();
|
|
350
|
-
}
|
|
351
|
-
const newTokenPair = await this.createTokenPair(user, {
|
|
352
|
-
...deviceInfo,
|
|
353
|
-
familyId: refreshToken.get("familyId")
|
|
354
|
-
});
|
|
355
|
-
authEvents.emit("token.refreshed", user, newTokenPair, refreshToken);
|
|
356
|
-
return newTokenPair;
|
|
357
|
-
} catch {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Verify password
|
|
363
|
-
*/
|
|
364
|
-
verifyPassword(hashedPassword, plainPassword) {
|
|
365
|
-
return password.verify(String(hashedPassword), String(plainPassword));
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Hash password
|
|
369
|
-
*/
|
|
370
|
-
hashPassword(password$1) {
|
|
371
|
-
return password.hash(String(password$1), core.config.key("auth.password.salt", 12));
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Attempt to login user with given credentials
|
|
375
|
-
*/
|
|
376
|
-
async attemptLogin(Model4, data) {
|
|
377
|
-
const { password, ...otherData } = data;
|
|
378
|
-
authEvents.emit("login.attempt", otherData);
|
|
379
|
-
const user = await Model4.first(otherData);
|
|
380
|
-
if (!user) {
|
|
381
|
-
authEvents.emit("login.failed", otherData, "User not found");
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
if (!this.verifyPassword(user.string("password"), password)) {
|
|
385
|
-
authEvents.emit("login.failed", otherData, "Invalid password");
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
return user;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Full login flow: validate credentials, create tokens, emit events
|
|
392
|
-
* Returns token pair on success, null on failure
|
|
393
|
-
*/
|
|
394
|
-
async login(Model4, credentials, deviceInfo) {
|
|
395
|
-
const user = await this.attemptLogin(Model4, credentials);
|
|
396
|
-
if (!user) {
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
if (!core.config.key("auth.jwt.refresh.enabled", true)) {
|
|
400
|
-
const accessToken = await this.generateAccessToken(user, deviceInfo?.payload);
|
|
401
|
-
return { user, accessToken };
|
|
402
|
-
}
|
|
403
|
-
const tokens = await this.createTokenPair(user, deviceInfo);
|
|
404
|
-
authEvents.emit("login.success", user, tokens, deviceInfo);
|
|
405
|
-
return { user, tokens };
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Logout user
|
|
409
|
-
* @param user - The authenticated user
|
|
410
|
-
* @param accessToken - Optional access token string to revoke
|
|
411
|
-
* @param refreshToken - Optional refresh token string to revoke
|
|
412
|
-
* If refresh token is not provided, behavior is determined by config:
|
|
413
|
-
* - "revoke-all" (default): Revoke ALL refresh tokens for security
|
|
414
|
-
* - "error": Throw error requiring refresh token
|
|
415
|
-
*/
|
|
416
|
-
async logout(user, accessToken, refreshToken) {
|
|
417
|
-
if (accessToken) {
|
|
418
|
-
await this.removeAccessToken(user, accessToken);
|
|
419
|
-
}
|
|
420
|
-
if (refreshToken) {
|
|
421
|
-
const token = await RefreshToken.first({
|
|
422
|
-
token: refreshToken,
|
|
423
|
-
userId: user.id
|
|
424
|
-
// Security: ensure token belongs to this user
|
|
425
|
-
});
|
|
426
|
-
if (token) {
|
|
427
|
-
await token.revoke();
|
|
428
|
-
authEvents.emit("session.destroyed", user, token);
|
|
429
|
-
}
|
|
430
|
-
} else {
|
|
431
|
-
const behavior = core.config.key("auth.jwt.refresh.logoutWithoutToken", "revoke-all");
|
|
432
|
-
if (behavior === "error") {
|
|
433
|
-
throw new Error("Refresh token required for logout");
|
|
434
|
-
}
|
|
435
|
-
await this.revokeAllTokens(user);
|
|
436
|
-
authEvents.emit("logout.failsafe", user);
|
|
437
|
-
}
|
|
438
|
-
authEvents.emit("logout", user);
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Remove specific access token
|
|
442
|
-
*/
|
|
443
|
-
async removeAccessToken(user, token) {
|
|
444
|
-
AccessToken.delete({
|
|
445
|
-
token,
|
|
446
|
-
"user.id": user.id
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Revoke all tokens for a user
|
|
451
|
-
*/
|
|
452
|
-
async revokeAllTokens(user) {
|
|
453
|
-
const refreshTokens = await RefreshToken.query().where("userId", user.id).where("userType", user.userType).where("revokedAt", null).get();
|
|
454
|
-
for (const token of refreshTokens) {
|
|
455
|
-
await token.revoke();
|
|
456
|
-
authEvents.emit("token.revoked", user, token);
|
|
457
|
-
}
|
|
458
|
-
await AccessToken.delete({
|
|
459
|
-
"user.id": user.id,
|
|
460
|
-
"user.userType": user.userType
|
|
461
|
-
});
|
|
462
|
-
authEvents.emit("logout.all", user);
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Revoke entire token family (for rotation breach detection)
|
|
466
|
-
*/
|
|
467
|
-
async revokeTokenFamily(familyId) {
|
|
468
|
-
const tokens = await RefreshToken.query().where("familyId", familyId).where("revokedAt", null).get();
|
|
469
|
-
for (const token of tokens) {
|
|
470
|
-
await token.revoke();
|
|
471
|
-
}
|
|
472
|
-
authEvents.emit("token.familyRevoked", familyId, tokens);
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Cleanup expired tokens
|
|
476
|
-
*/
|
|
477
|
-
async cleanupExpiredTokens() {
|
|
478
|
-
const expiredTokens = await RefreshToken.query().where("expiresAt", "<", /* @__PURE__ */ new Date()).get();
|
|
479
|
-
for (const token of expiredTokens) {
|
|
480
|
-
authEvents.emit("token.expired", token);
|
|
481
|
-
await token.destroy();
|
|
482
|
-
}
|
|
483
|
-
authEvents.emit("cleanup.completed", expiredTokens.length);
|
|
484
|
-
return expiredTokens.length;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Enforce max refresh tokens per user
|
|
488
|
-
*/
|
|
489
|
-
async enforceMaxRefreshTokens(user) {
|
|
490
|
-
const maxPerUser = core.config.key("auth.jwt.refresh.maxPerUser", 5);
|
|
491
|
-
const activeTokens = await RefreshToken.query().where("userId", user.id).where("userType", user.userType).where("revokedAt", null).orderBy("createdAt", "asc").get();
|
|
492
|
-
if (activeTokens.length >= maxPerUser) {
|
|
493
|
-
const tokensToRevoke = activeTokens.slice(0, activeTokens.length - maxPerUser + 1);
|
|
494
|
-
for (const token of tokensToRevoke) {
|
|
495
|
-
await token.revoke();
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Get active sessions for user
|
|
501
|
-
*/
|
|
502
|
-
async getActiveSessions(user) {
|
|
503
|
-
return RefreshToken.query().where("userId", user.id).where("userType", user.userType).where("revokedAt", null).where("expiresAt", ">", /* @__PURE__ */ new Date()).orderBy("createdAt", "desc").get();
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
var authService = new AuthService();
|
|
507
|
-
|
|
508
|
-
// ../../warlock.js/auth/src/commands/auth-cleanup-command.ts
|
|
509
|
-
function registerAuthCleanupCommand() {
|
|
510
|
-
return core.command({
|
|
511
|
-
name: "auth.cleanup",
|
|
512
|
-
description: "Remove expired refresh tokens from the database",
|
|
513
|
-
preload: {
|
|
514
|
-
env: true,
|
|
515
|
-
config: ["auth", "database"],
|
|
516
|
-
connectors: ["database"]
|
|
517
|
-
},
|
|
518
|
-
action: async () => {
|
|
519
|
-
console.log(copper.colors.cyan("\u{1F9F9} Cleaning up expired tokens..."));
|
|
520
|
-
const count = await authService.cleanupExpiredTokens();
|
|
521
|
-
if (count === 0) {
|
|
522
|
-
console.log(copper.colors.green("\u2705 No expired tokens found."));
|
|
523
|
-
} else {
|
|
524
|
-
console.log(copper.colors.green(`\u2705 Removed ${count} expired token(s).`));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
async function generateJWTSecret() {
|
|
530
|
-
let envFile = core.rootPath(".env");
|
|
531
|
-
logger.log.info("jwt", "generating", "Generating JWT secrets");
|
|
532
|
-
const environmentMode = core.environment();
|
|
533
|
-
if (!await fs.fileExistsAsync(envFile)) {
|
|
534
|
-
const envFileType = environmentMode === "production" ? ".env.production" : ".env.development";
|
|
535
|
-
envFile = core.rootPath(envFileType);
|
|
536
|
-
}
|
|
537
|
-
if (!await fs.fileExistsAsync(envFile)) {
|
|
538
|
-
logger.log.error("jwt", "error", ".env file not found");
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
let contents = await fs.getFileAsync(envFile);
|
|
542
|
-
const hasJwtSecret = contents.includes("JWT_SECRET");
|
|
543
|
-
const hasJwtRefreshSecret = contents.includes("JWT_REFRESH_SECRET");
|
|
544
|
-
if (hasJwtSecret && hasJwtRefreshSecret) {
|
|
545
|
-
logger.log.warn("jwt", "exists", "JWT secrets already exist in the .env file.");
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
let secretsToAdd = "";
|
|
549
|
-
if (!hasJwtSecret) {
|
|
550
|
-
const jwtSecret = reinforcements.Random.string(32);
|
|
551
|
-
secretsToAdd += `
|
|
552
|
-
# JWT Secret
|
|
553
|
-
JWT_SECRET=${jwtSecret}
|
|
554
|
-
`;
|
|
555
|
-
logger.log.success("jwt", "generated", "JWT_SECRET generated and added to the .env file.");
|
|
556
|
-
} else {
|
|
557
|
-
logger.log.info("jwt", "exists", "JWT_SECRET already exists in the .env file.");
|
|
558
|
-
}
|
|
559
|
-
if (!hasJwtRefreshSecret) {
|
|
560
|
-
const jwtRefreshSecret = reinforcements.Random.string(32);
|
|
561
|
-
secretsToAdd += `
|
|
562
|
-
# JWT Refresh Secret
|
|
563
|
-
JWT_REFRESH_SECRET=${jwtRefreshSecret}
|
|
564
|
-
`;
|
|
565
|
-
logger.log.success("jwt", "generated", "JWT_REFRESH_SECRET generated and added to the .env file.");
|
|
566
|
-
} else {
|
|
567
|
-
logger.log.info("jwt", "exists", "JWT_REFRESH_SECRET already exists in the .env file.");
|
|
568
|
-
}
|
|
569
|
-
if (secretsToAdd) {
|
|
570
|
-
contents += secretsToAdd;
|
|
571
|
-
await fs.putFileAsync(envFile, contents);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// ../../warlock.js/auth/src/commands/jwt-secret-generator-command.ts
|
|
576
|
-
function registerJWTSecretGeneratorCommand() {
|
|
577
|
-
return core.command({
|
|
578
|
-
name: "jwt.generate",
|
|
579
|
-
description: "Generate JWT Secret key in .env file",
|
|
580
|
-
action: generateJWTSecret
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// ../../warlock.js/auth/src/utils/auth-error-codes.ts
|
|
585
|
-
var AuthErrorCodes = /* @__PURE__ */ ((AuthErrorCodes2) => {
|
|
586
|
-
AuthErrorCodes2["MissingAccessToken"] = "EC001";
|
|
587
|
-
AuthErrorCodes2["InvalidAccessToken"] = "EC002";
|
|
588
|
-
AuthErrorCodes2["Unauthorized"] = "EC003";
|
|
589
|
-
return AuthErrorCodes2;
|
|
590
|
-
})(AuthErrorCodes || {});
|
|
591
|
-
|
|
592
|
-
// ../../warlock.js/auth/src/middleware/auth.middleware.ts
|
|
593
|
-
function authMiddleware(allowedUserType) {
|
|
594
|
-
const allowedTypes = !allowedUserType ? [] : Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];
|
|
595
|
-
const auth = async (request, response) => {
|
|
596
|
-
try {
|
|
597
|
-
const authorizationValue = request.authorizationValue;
|
|
598
|
-
if (!allowedTypes.length && !authorizationValue) return;
|
|
599
|
-
if (!authorizationValue) {
|
|
600
|
-
return response.unauthorized({
|
|
601
|
-
error: core.t("auth.errors.missingAccessToken"),
|
|
602
|
-
errorCode: "EC001" /* MissingAccessToken */
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
const user = await jwt.verify(authorizationValue);
|
|
606
|
-
request.decodedAccessToken = user;
|
|
607
|
-
const accessToken = await AccessToken.first({
|
|
608
|
-
token: authorizationValue
|
|
609
|
-
});
|
|
610
|
-
if (!accessToken) {
|
|
611
|
-
return response.unauthorized({
|
|
612
|
-
error: core.t("auth.errors.invalidAccessToken"),
|
|
613
|
-
errorCode: "EC002" /* InvalidAccessToken */
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
const userType = user.userType || accessToken.get("userType");
|
|
617
|
-
if (allowedTypes.length && !allowedTypes.includes(userType)) {
|
|
618
|
-
return response.unauthorized({
|
|
619
|
-
error: core.t("auth.errors.unauthorized"),
|
|
620
|
-
errorCode: "EC003" /* Unauthorized */
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
const UserModel = core.config.key(`auth.userType.${userType}`);
|
|
624
|
-
if (!UserModel) {
|
|
625
|
-
throw new Error(`User type ${userType} is unknown type.`);
|
|
626
|
-
}
|
|
627
|
-
const currentUser = await UserModel.find(user.id);
|
|
628
|
-
if (!currentUser) {
|
|
629
|
-
accessToken.destroy();
|
|
630
|
-
return response.unauthorized({
|
|
631
|
-
error: core.t("auth.errors.invalidAccessToken"),
|
|
632
|
-
errorCode: "EC002" /* InvalidAccessToken */
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
accessToken.set("lastAccess", /* @__PURE__ */ new Date());
|
|
636
|
-
await accessToken.save({ skipEvents: true });
|
|
637
|
-
request.user = currentUser;
|
|
638
|
-
} catch (err) {
|
|
639
|
-
logger.log.error("http", "auth", err);
|
|
640
|
-
request.clearCurrentUser();
|
|
641
|
-
return response.unauthorized({
|
|
642
|
-
error: core.t("auth.errors.invalidAccessToken"),
|
|
643
|
-
errorCode: "EC002" /* InvalidAccessToken */
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
return auth;
|
|
648
|
-
}
|
|
649
|
-
var Auth = class extends cascade.Model {
|
|
650
|
-
/**
|
|
651
|
-
* Get access token payload
|
|
652
|
-
*/
|
|
653
|
-
accessTokenPayload() {
|
|
654
|
-
return authService.buildAccessTokenPayload(this);
|
|
655
|
-
}
|
|
656
|
-
/**
|
|
657
|
-
* Create both access and refresh tokens
|
|
658
|
-
*/
|
|
659
|
-
async createTokenPair(deviceInfo) {
|
|
660
|
-
return authService.createTokenPair(this, deviceInfo);
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Generate access token
|
|
664
|
-
*/
|
|
665
|
-
async generateAccessToken(data) {
|
|
666
|
-
return authService.generateAccessToken(this, data);
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* Generate refresh token
|
|
670
|
-
*/
|
|
671
|
-
async generateRefreshToken(deviceInfo) {
|
|
672
|
-
return authService.createRefreshToken(this, deviceInfo);
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Remove current access token
|
|
676
|
-
*/
|
|
677
|
-
async removeAccessToken(token) {
|
|
678
|
-
return authService.removeAccessToken(this, token);
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Revoke all tokens (logout from all devices)
|
|
682
|
-
*/
|
|
683
|
-
async revokeAllTokens() {
|
|
684
|
-
return authService.revokeAllTokens(this);
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Get active sessions
|
|
688
|
-
*/
|
|
689
|
-
async activeSessions() {
|
|
690
|
-
return authService.getActiveSessions(this);
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Attempt to login the user
|
|
694
|
-
*/
|
|
695
|
-
static async attempt(data) {
|
|
696
|
-
return authService.attemptLogin(this, data);
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Confirm password
|
|
700
|
-
*/
|
|
701
|
-
confirmPassword(password) {
|
|
702
|
-
return authService.verifyPassword(this.string("password"), password);
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
function castPassword(value, column, model) {
|
|
706
|
-
return value ? password.hash(String(value), core.config.key("auth.password.salt", 12)) : model.getInitial(column);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
exports.AccessToken = AccessToken;
|
|
710
|
-
exports.Auth = Auth;
|
|
711
|
-
exports.AuthErrorCodes = AuthErrorCodes;
|
|
712
|
-
exports.NO_EXPIRATION = NO_EXPIRATION;
|
|
713
|
-
exports.RefreshToken = RefreshToken;
|
|
714
|
-
exports.authEvents = authEvents;
|
|
715
|
-
exports.authMiddleware = authMiddleware;
|
|
716
|
-
exports.authService = authService;
|
|
717
|
-
exports.castPassword = castPassword;
|
|
718
|
-
exports.generateJWTSecret = generateJWTSecret;
|
|
719
|
-
exports.jwt = jwt;
|
|
720
|
-
exports.parseExpirationToMs = parseExpirationToMs;
|
|
721
|
-
exports.registerAuthCleanupCommand = registerAuthCleanupCommand;
|
|
722
|
-
exports.registerJWTSecretGeneratorCommand = registerJWTSecretGeneratorCommand;
|
|
723
|
-
exports.toJwtExpiresIn = toJwtExpiresIn;
|
|
724
55
|
//# sourceMappingURL=index.js.map
|
|
725
56
|
//# sourceMappingURL=index.js.map
|