@ministerjs/auth 2.0.1 → 3.0.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/README.md +53 -8
- package/dist/nestjs.d.mts +163 -8
- package/dist/nestjs.d.ts +163 -8
- package/dist/nestjs.js +429 -35
- package/dist/nestjs.mjs +410 -16
- package/dist/vue.d.mts +2 -0
- package/dist/vue.d.ts +2 -0
- package/dist/vue.js +20 -0
- package/dist/vue.mjs +20 -0
- package/package.json +12 -7
- package/types/nestjs/AuthController.d.ts +1 -0
- package/types/nestjs/AuthModule.d.ts +2 -0
- package/types/nestjs/JwtPrismaCookieAuthDriver.d.ts +64 -6
- package/types/nestjs/guards/JwtAuthGuard.d.ts +17 -0
- package/types/nestjs/guards/RolesGuard.d.ts +8 -0
- package/types/nestjs/guards/index.d.ts +2 -0
- package/types/nestjs/index.d.ts +3 -0
- package/types/nestjs/rbac.d.ts +3 -0
- package/types/nestjs/stores.d.ts +36 -0
- package/types/nestjs/types.d.ts +37 -2
- package/types/vue/VueAuth.d.ts +2 -0
package/dist/nestjs.js
CHANGED
|
@@ -32,15 +32,174 @@ __export(nestjs_exports, {
|
|
|
32
32
|
AUTH_DRIVER: () => AUTH_DRIVER,
|
|
33
33
|
AuthController: () => AuthController,
|
|
34
34
|
AuthModule: () => AuthModule,
|
|
35
|
-
|
|
35
|
+
InMemoryLockoutStore: () => InMemoryLockoutStore,
|
|
36
|
+
InMemoryRefreshTokenStore: () => InMemoryRefreshTokenStore,
|
|
37
|
+
JwtAuthGuard: () => JwtAuthGuard,
|
|
38
|
+
JwtPrismaCookieAuthDriver: () => JwtPrismaCookieAuthDriver,
|
|
39
|
+
ROLES_KEY: () => ROLES_KEY,
|
|
40
|
+
Roles: () => Roles,
|
|
41
|
+
RolesGuard: () => RolesGuard
|
|
36
42
|
});
|
|
37
43
|
module.exports = __toCommonJS(nestjs_exports);
|
|
38
44
|
|
|
39
45
|
// src/nestjs/token.ts
|
|
40
46
|
var AUTH_DRIVER = Symbol("AUTH_DRIVER");
|
|
41
47
|
|
|
42
|
-
// src/nestjs/
|
|
48
|
+
// src/nestjs/stores.ts
|
|
49
|
+
var InMemoryLockoutStore = class {
|
|
50
|
+
constructor(windowMs) {
|
|
51
|
+
this.windowMs = windowMs;
|
|
52
|
+
}
|
|
53
|
+
entries = /* @__PURE__ */ new Map();
|
|
54
|
+
async recordFailure(key) {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const entry = this.entries.get(key);
|
|
57
|
+
if (!entry || now - entry.firstFailure > this.windowMs) {
|
|
58
|
+
this.entries.set(key, { count: 1, firstFailure: now });
|
|
59
|
+
} else {
|
|
60
|
+
entry.count++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getFailures(key) {
|
|
64
|
+
const entry = this.entries.get(key);
|
|
65
|
+
if (!entry) return 0;
|
|
66
|
+
if (Date.now() - entry.firstFailure > this.windowMs) {
|
|
67
|
+
this.entries.delete(key);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
return entry.count;
|
|
71
|
+
}
|
|
72
|
+
async reset(key) {
|
|
73
|
+
this.entries.delete(key);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var InMemoryRefreshTokenStore = class {
|
|
77
|
+
tokens = /* @__PURE__ */ new Map();
|
|
78
|
+
userJtis = /* @__PURE__ */ new Map();
|
|
79
|
+
async save(jti, userId, expiresAt) {
|
|
80
|
+
this.tokens.set(jti, { userId, expiresAt });
|
|
81
|
+
let jtis = this.userJtis.get(userId);
|
|
82
|
+
if (!jtis) {
|
|
83
|
+
jtis = /* @__PURE__ */ new Set();
|
|
84
|
+
this.userJtis.set(userId, jtis);
|
|
85
|
+
}
|
|
86
|
+
jtis.add(jti);
|
|
87
|
+
}
|
|
88
|
+
async find(jti) {
|
|
89
|
+
const entry = this.tokens.get(jti);
|
|
90
|
+
if (!entry) return null;
|
|
91
|
+
if (entry.expiresAt.getTime() < Date.now()) {
|
|
92
|
+
this.tokens.delete(jti);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
async revoke(jti) {
|
|
98
|
+
const entry = this.tokens.get(jti);
|
|
99
|
+
if (entry) {
|
|
100
|
+
this.userJtis.get(entry.userId)?.delete(jti);
|
|
101
|
+
}
|
|
102
|
+
this.tokens.delete(jti);
|
|
103
|
+
}
|
|
104
|
+
async revokeAll(userId) {
|
|
105
|
+
const jtis = this.userJtis.get(userId);
|
|
106
|
+
if (jtis) {
|
|
107
|
+
for (const jti of jtis) {
|
|
108
|
+
this.tokens.delete(jti);
|
|
109
|
+
}
|
|
110
|
+
this.userJtis.delete(userId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/nestjs/rbac.ts
|
|
43
116
|
var import_common = require("@nestjs/common");
|
|
117
|
+
var ROLES_KEY = "ministerjs:roles";
|
|
118
|
+
var Roles = (...roles) => (0, import_common.SetMetadata)(ROLES_KEY, roles);
|
|
119
|
+
|
|
120
|
+
// src/nestjs/guards/JwtAuthGuard.ts
|
|
121
|
+
var import_common2 = require("@nestjs/common");
|
|
122
|
+
var import_jsonwebtoken = require("jsonwebtoken");
|
|
123
|
+
var JwtAuthGuard = class {
|
|
124
|
+
jwtSecret;
|
|
125
|
+
cookieName;
|
|
126
|
+
csrfEnabled;
|
|
127
|
+
constructor(options) {
|
|
128
|
+
this.jwtSecret = options.jwtSecret;
|
|
129
|
+
this.cookieName = options.cookieName ?? "auth_token";
|
|
130
|
+
this.csrfEnabled = options.csrfEnabled ?? false;
|
|
131
|
+
}
|
|
132
|
+
canActivate(context) {
|
|
133
|
+
const request = context.switchToHttp().getRequest();
|
|
134
|
+
const token = this.extractToken(request);
|
|
135
|
+
if (!token) {
|
|
136
|
+
throw new import_common2.UnauthorizedException("No token provided");
|
|
137
|
+
}
|
|
138
|
+
let payload;
|
|
139
|
+
try {
|
|
140
|
+
payload = (0, import_jsonwebtoken.verify)(token, this.jwtSecret, {
|
|
141
|
+
algorithms: ["HS256"]
|
|
142
|
+
});
|
|
143
|
+
} catch {
|
|
144
|
+
throw new import_common2.UnauthorizedException("Invalid token");
|
|
145
|
+
}
|
|
146
|
+
if (this.csrfEnabled) {
|
|
147
|
+
this.validateCsrf(request);
|
|
148
|
+
}
|
|
149
|
+
request.user = {
|
|
150
|
+
id: payload.sub,
|
|
151
|
+
roles: payload.roles ?? []
|
|
152
|
+
};
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
extractToken(request) {
|
|
156
|
+
const fromCookie = request.cookies?.[this.cookieName];
|
|
157
|
+
if (fromCookie) return fromCookie;
|
|
158
|
+
const authHeader = request.headers.authorization;
|
|
159
|
+
if (authHeader?.startsWith("Bearer ")) return authHeader.slice(7);
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
validateCsrf(request) {
|
|
163
|
+
const headerToken = request.headers["x-csrf-token"];
|
|
164
|
+
const cookieToken = request.cookies?.["csrf_token"];
|
|
165
|
+
if (!headerToken || headerToken !== cookieToken) {
|
|
166
|
+
throw new import_common2.UnauthorizedException("CSRF token mismatch");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
JwtAuthGuard = __decorateClass([
|
|
171
|
+
(0, import_common2.Injectable)()
|
|
172
|
+
], JwtAuthGuard);
|
|
173
|
+
|
|
174
|
+
// src/nestjs/guards/RolesGuard.ts
|
|
175
|
+
var import_common3 = require("@nestjs/common");
|
|
176
|
+
var RolesGuard = class {
|
|
177
|
+
constructor(reflector) {
|
|
178
|
+
this.reflector = reflector;
|
|
179
|
+
}
|
|
180
|
+
canActivate(context) {
|
|
181
|
+
const required = this.reflector.getAllAndOverride(ROLES_KEY, [
|
|
182
|
+
context.getHandler(),
|
|
183
|
+
context.getClass()
|
|
184
|
+
]);
|
|
185
|
+
if (!required?.length) return true;
|
|
186
|
+
const request = context.switchToHttp().getRequest();
|
|
187
|
+
if (!request.user) {
|
|
188
|
+
throw new import_common3.ForbiddenException("Not authenticated");
|
|
189
|
+
}
|
|
190
|
+
const hasRole = required.some((r) => request.user.roles.includes(r));
|
|
191
|
+
if (!hasRole) {
|
|
192
|
+
throw new import_common3.ForbiddenException("Insufficient permissions");
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
RolesGuard = __decorateClass([
|
|
198
|
+
(0, import_common3.Injectable)()
|
|
199
|
+
], RolesGuard);
|
|
200
|
+
|
|
201
|
+
// src/nestjs/AuthController.ts
|
|
202
|
+
var import_common4 = require("@nestjs/common");
|
|
44
203
|
var AuthController = class {
|
|
45
204
|
constructor(driver) {
|
|
46
205
|
this.driver = driver;
|
|
@@ -52,7 +211,17 @@ var AuthController = class {
|
|
|
52
211
|
async checkIn(request, response) {
|
|
53
212
|
const user = await this.driver.checkIn(request, response);
|
|
54
213
|
if (!user) {
|
|
55
|
-
throw new
|
|
214
|
+
throw new import_common4.UnauthorizedException("Not authenticated");
|
|
215
|
+
}
|
|
216
|
+
return { message: "ok", data: user };
|
|
217
|
+
}
|
|
218
|
+
async refresh(request, response) {
|
|
219
|
+
if (!this.driver.refresh) {
|
|
220
|
+
throw new import_common4.UnauthorizedException("Refresh not supported");
|
|
221
|
+
}
|
|
222
|
+
const user = await this.driver.refresh(request, response);
|
|
223
|
+
if (!user) {
|
|
224
|
+
throw new import_common4.UnauthorizedException("Invalid refresh token");
|
|
56
225
|
}
|
|
57
226
|
return { message: "ok", data: user };
|
|
58
227
|
}
|
|
@@ -62,31 +231,37 @@ var AuthController = class {
|
|
|
62
231
|
}
|
|
63
232
|
};
|
|
64
233
|
__decorateClass([
|
|
65
|
-
(0,
|
|
66
|
-
(0,
|
|
67
|
-
__decorateParam(0, (0,
|
|
68
|
-
__decorateParam(1, (0,
|
|
69
|
-
__decorateParam(2, (0,
|
|
234
|
+
(0, import_common4.Post)("/login"),
|
|
235
|
+
(0, import_common4.HttpCode)(200),
|
|
236
|
+
__decorateParam(0, (0, import_common4.Body)()),
|
|
237
|
+
__decorateParam(1, (0, import_common4.Req)()),
|
|
238
|
+
__decorateParam(2, (0, import_common4.Res)({ passthrough: true }))
|
|
70
239
|
], AuthController.prototype, "login", 1);
|
|
71
240
|
__decorateClass([
|
|
72
|
-
(0,
|
|
73
|
-
(0,
|
|
74
|
-
__decorateParam(0, (0,
|
|
75
|
-
__decorateParam(1, (0,
|
|
241
|
+
(0, import_common4.Post)("/checkin"),
|
|
242
|
+
(0, import_common4.HttpCode)(200),
|
|
243
|
+
__decorateParam(0, (0, import_common4.Req)()),
|
|
244
|
+
__decorateParam(1, (0, import_common4.Res)({ passthrough: true }))
|
|
76
245
|
], AuthController.prototype, "checkIn", 1);
|
|
77
246
|
__decorateClass([
|
|
78
|
-
(0,
|
|
79
|
-
(0,
|
|
80
|
-
__decorateParam(0, (0,
|
|
81
|
-
__decorateParam(1, (0,
|
|
247
|
+
(0, import_common4.Post)("/refresh"),
|
|
248
|
+
(0, import_common4.HttpCode)(200),
|
|
249
|
+
__decorateParam(0, (0, import_common4.Req)()),
|
|
250
|
+
__decorateParam(1, (0, import_common4.Res)({ passthrough: true }))
|
|
251
|
+
], AuthController.prototype, "refresh", 1);
|
|
252
|
+
__decorateClass([
|
|
253
|
+
(0, import_common4.Post)("/logout"),
|
|
254
|
+
(0, import_common4.HttpCode)(200),
|
|
255
|
+
__decorateParam(0, (0, import_common4.Req)()),
|
|
256
|
+
__decorateParam(1, (0, import_common4.Res)({ passthrough: true }))
|
|
82
257
|
], AuthController.prototype, "logout", 1);
|
|
83
258
|
AuthController = __decorateClass([
|
|
84
|
-
(0,
|
|
85
|
-
__decorateParam(0, (0,
|
|
259
|
+
(0, import_common4.Controller)(),
|
|
260
|
+
__decorateParam(0, (0, import_common4.Inject)(AUTH_DRIVER))
|
|
86
261
|
], AuthController);
|
|
87
262
|
|
|
88
263
|
// src/nestjs/AuthModule.ts
|
|
89
|
-
var
|
|
264
|
+
var import_common5 = require("@nestjs/common");
|
|
90
265
|
var AuthModule = class {
|
|
91
266
|
static register(options) {
|
|
92
267
|
const driverProvider = this.normalizeDriverProvider(options.driver);
|
|
@@ -111,6 +286,9 @@ var AuthModule = class {
|
|
|
111
286
|
exports: [driverProvider]
|
|
112
287
|
};
|
|
113
288
|
}
|
|
289
|
+
static createGuard(options) {
|
|
290
|
+
return new JwtAuthGuard(options);
|
|
291
|
+
}
|
|
114
292
|
static normalizeDriverProvider(driver) {
|
|
115
293
|
if (typeof driver === "function") {
|
|
116
294
|
return { provide: AUTH_DRIVER, useClass: driver };
|
|
@@ -132,16 +310,30 @@ var AuthModule = class {
|
|
|
132
310
|
}
|
|
133
311
|
};
|
|
134
312
|
AuthModule = __decorateClass([
|
|
135
|
-
(0,
|
|
313
|
+
(0, import_common5.Module)({})
|
|
136
314
|
], AuthModule);
|
|
137
315
|
|
|
138
316
|
// src/nestjs/JwtPrismaCookieAuthDriver.ts
|
|
139
|
-
var
|
|
140
|
-
var
|
|
317
|
+
var import_node_crypto = require("crypto");
|
|
318
|
+
var import_common6 = require("@nestjs/common");
|
|
319
|
+
var import_jsonwebtoken2 = require("jsonwebtoken");
|
|
320
|
+
var DURATION_UNITS = {
|
|
321
|
+
s: 1e3,
|
|
322
|
+
m: 6e4,
|
|
323
|
+
h: 36e5,
|
|
324
|
+
d: 864e5
|
|
325
|
+
};
|
|
326
|
+
function parseDuration(value) {
|
|
327
|
+
const match = value.match(/^(\d+)\s*([smhd])$/);
|
|
328
|
+
if (!match) return 7 * 864e5;
|
|
329
|
+
return parseInt(match[1], 10) * DURATION_UNITS[match[2]];
|
|
330
|
+
}
|
|
331
|
+
var isProduction = process.env.NODE_ENV === "production";
|
|
141
332
|
var defaultCookieOptions = {
|
|
142
333
|
httpOnly: true,
|
|
143
334
|
sameSite: "lax",
|
|
144
|
-
path: "/"
|
|
335
|
+
path: "/",
|
|
336
|
+
secure: isProduction
|
|
145
337
|
};
|
|
146
338
|
var JwtPrismaCookieAuthDriver = class {
|
|
147
339
|
prismaModel;
|
|
@@ -154,13 +346,23 @@ var JwtPrismaCookieAuthDriver = class {
|
|
|
154
346
|
cookieOptions;
|
|
155
347
|
comparePassword;
|
|
156
348
|
transformUser;
|
|
349
|
+
csrf;
|
|
350
|
+
onAudit;
|
|
351
|
+
onRateLimitCheck;
|
|
352
|
+
lockoutStore;
|
|
353
|
+
lockoutMaxAttempts;
|
|
354
|
+
refreshTokenEnabled;
|
|
355
|
+
refreshTokenCookieName;
|
|
356
|
+
refreshTokenExpiresIn;
|
|
357
|
+
refreshTokenStore;
|
|
157
358
|
constructor(options) {
|
|
158
|
-
|
|
159
|
-
if (!
|
|
359
|
+
const model = options.prisma[options.modelName];
|
|
360
|
+
if (!model) {
|
|
160
361
|
throw new Error(
|
|
161
362
|
`Model "${options.modelName}" n\xE3o encontrado no PrismaClient.`
|
|
162
363
|
);
|
|
163
364
|
}
|
|
365
|
+
this.prismaModel = model;
|
|
164
366
|
this.identifierField = options.identifierField ?? "email";
|
|
165
367
|
this.passwordField = options.passwordField ?? "password";
|
|
166
368
|
this.idField = options.idField ?? "id";
|
|
@@ -171,34 +373,107 @@ var JwtPrismaCookieAuthDriver = class {
|
|
|
171
373
|
...defaultCookieOptions,
|
|
172
374
|
...options.cookieOptions ?? {}
|
|
173
375
|
};
|
|
174
|
-
this.comparePassword = options.comparePassword
|
|
376
|
+
this.comparePassword = options.comparePassword;
|
|
175
377
|
this.transformUser = options.transformUser ?? ((user) => {
|
|
176
378
|
const clone = { ...user };
|
|
177
379
|
delete clone[this.passwordField];
|
|
178
380
|
return clone;
|
|
179
381
|
});
|
|
382
|
+
this.csrf = options.csrf ?? false;
|
|
383
|
+
this.onAudit = options.onAudit;
|
|
384
|
+
this.onRateLimitCheck = options.onRateLimitCheck;
|
|
385
|
+
if (options.lockout) {
|
|
386
|
+
const windowMs = options.lockout.windowMs ?? 15 * 60 * 1e3;
|
|
387
|
+
this.lockoutMaxAttempts = options.lockout.maxAttempts ?? 5;
|
|
388
|
+
this.lockoutStore = options.lockout.store ?? new InMemoryLockoutStore(windowMs);
|
|
389
|
+
} else {
|
|
390
|
+
this.lockoutMaxAttempts = 0;
|
|
391
|
+
}
|
|
392
|
+
if (options.refreshToken?.enabled) {
|
|
393
|
+
this.refreshTokenEnabled = true;
|
|
394
|
+
this.refreshTokenCookieName = options.refreshToken.cookieName ?? "refresh_token";
|
|
395
|
+
this.refreshTokenExpiresIn = options.refreshToken.expiresIn ?? "30d";
|
|
396
|
+
this.refreshTokenStore = options.refreshToken.store ?? new InMemoryRefreshTokenStore();
|
|
397
|
+
} else {
|
|
398
|
+
this.refreshTokenEnabled = false;
|
|
399
|
+
this.refreshTokenCookieName = "refresh_token";
|
|
400
|
+
this.refreshTokenExpiresIn = "30d";
|
|
401
|
+
}
|
|
180
402
|
}
|
|
181
|
-
async login(payload,
|
|
403
|
+
async login(payload, req, res) {
|
|
182
404
|
const identifier = payload[this.identifierField];
|
|
405
|
+
const ip = req.ip ?? req.socket?.remoteAddress ?? "unknown";
|
|
406
|
+
if (this.onRateLimitCheck) {
|
|
407
|
+
const allowed = await this.onRateLimitCheck(ip, identifier);
|
|
408
|
+
if (!allowed) {
|
|
409
|
+
await this.emitAudit({
|
|
410
|
+
action: "login",
|
|
411
|
+
ip,
|
|
412
|
+
success: false,
|
|
413
|
+
metadata: { reason: "rate_limited" }
|
|
414
|
+
});
|
|
415
|
+
throw new import_common6.UnauthorizedException("Too many requests");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (this.lockoutStore) {
|
|
419
|
+
const failures = await this.lockoutStore.getFailures(identifier);
|
|
420
|
+
if (failures >= this.lockoutMaxAttempts) {
|
|
421
|
+
await this.emitAudit({
|
|
422
|
+
action: "lockout",
|
|
423
|
+
ip,
|
|
424
|
+
success: false,
|
|
425
|
+
metadata: { identifier }
|
|
426
|
+
});
|
|
427
|
+
throw new import_common6.UnauthorizedException("Invalid credentials");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
183
430
|
const user = await this.prismaModel.findUnique({
|
|
184
431
|
where: { [this.identifierField]: identifier }
|
|
185
432
|
});
|
|
186
433
|
if (!user) {
|
|
187
|
-
|
|
434
|
+
await this.lockoutStore?.recordFailure(identifier);
|
|
435
|
+
await this.emitAudit({ action: "login", ip, success: false });
|
|
436
|
+
throw new import_common6.UnauthorizedException("Invalid credentials");
|
|
188
437
|
}
|
|
189
438
|
const ok = await this.comparePassword(
|
|
190
439
|
payload[this.passwordField],
|
|
191
440
|
user[this.passwordField]
|
|
192
441
|
);
|
|
193
442
|
if (!ok) {
|
|
194
|
-
|
|
443
|
+
await this.lockoutStore?.recordFailure(identifier);
|
|
444
|
+
await this.emitAudit({
|
|
445
|
+
action: "login",
|
|
446
|
+
userId: String(user[this.idField]),
|
|
447
|
+
ip,
|
|
448
|
+
success: false
|
|
449
|
+
});
|
|
450
|
+
throw new import_common6.UnauthorizedException("Invalid credentials");
|
|
195
451
|
}
|
|
196
|
-
|
|
452
|
+
await this.lockoutStore?.reset(identifier);
|
|
453
|
+
const token = (0, import_jsonwebtoken2.sign)(
|
|
197
454
|
{ sub: user[this.idField] },
|
|
198
455
|
this.jwtSecret,
|
|
199
|
-
{ expiresIn: this.jwtExpiresIn }
|
|
456
|
+
{ expiresIn: this.jwtExpiresIn, algorithm: "HS256" }
|
|
200
457
|
);
|
|
201
458
|
res.cookie(this.cookieName, token, this.cookieOptions);
|
|
459
|
+
if (this.refreshTokenEnabled && this.refreshTokenStore) {
|
|
460
|
+
await this.issueRefreshToken(String(user[this.idField]), res);
|
|
461
|
+
}
|
|
462
|
+
if (this.csrf) {
|
|
463
|
+
const csrfToken = (0, import_node_crypto.randomBytes)(32).toString("hex");
|
|
464
|
+
res.cookie("csrf_token", csrfToken, {
|
|
465
|
+
httpOnly: false,
|
|
466
|
+
sameSite: this.cookieOptions.sameSite,
|
|
467
|
+
path: "/",
|
|
468
|
+
secure: this.cookieOptions.secure
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
await this.emitAudit({
|
|
472
|
+
action: "login",
|
|
473
|
+
userId: String(user[this.idField]),
|
|
474
|
+
ip,
|
|
475
|
+
success: true
|
|
476
|
+
});
|
|
202
477
|
return this.transformUser(user);
|
|
203
478
|
}
|
|
204
479
|
async checkIn(req, res) {
|
|
@@ -208,7 +483,9 @@ var JwtPrismaCookieAuthDriver = class {
|
|
|
208
483
|
}
|
|
209
484
|
let payload;
|
|
210
485
|
try {
|
|
211
|
-
payload = (0,
|
|
486
|
+
payload = (0, import_jsonwebtoken2.verify)(token, this.jwtSecret, {
|
|
487
|
+
algorithms: ["HS256"]
|
|
488
|
+
});
|
|
212
489
|
} catch {
|
|
213
490
|
return null;
|
|
214
491
|
}
|
|
@@ -218,14 +495,125 @@ var JwtPrismaCookieAuthDriver = class {
|
|
|
218
495
|
if (!user) {
|
|
219
496
|
return null;
|
|
220
497
|
}
|
|
221
|
-
|
|
498
|
+
const newToken = (0, import_jsonwebtoken2.sign)(
|
|
499
|
+
{ sub: payload.sub },
|
|
500
|
+
this.jwtSecret,
|
|
501
|
+
{ expiresIn: this.jwtExpiresIn, algorithm: "HS256" }
|
|
502
|
+
);
|
|
503
|
+
res.cookie(this.cookieName, newToken, this.cookieOptions);
|
|
504
|
+
await this.emitAudit({
|
|
505
|
+
action: "checkin",
|
|
506
|
+
userId: String(payload.sub),
|
|
507
|
+
ip: req.ip ?? req.socket?.remoteAddress,
|
|
508
|
+
success: true
|
|
509
|
+
});
|
|
222
510
|
return this.transformUser(user);
|
|
223
511
|
}
|
|
224
|
-
async logout(
|
|
512
|
+
async logout(req, res) {
|
|
225
513
|
res.clearCookie(this.cookieName, {
|
|
226
514
|
...this.cookieOptions,
|
|
227
515
|
maxAge: 0
|
|
228
516
|
});
|
|
517
|
+
if (this.refreshTokenEnabled) {
|
|
518
|
+
const refreshCookie = req.cookies?.[this.refreshTokenCookieName];
|
|
519
|
+
if (refreshCookie && this.refreshTokenStore) {
|
|
520
|
+
try {
|
|
521
|
+
const payload = (0, import_jsonwebtoken2.verify)(refreshCookie, this.jwtSecret, {
|
|
522
|
+
algorithms: ["HS256"]
|
|
523
|
+
});
|
|
524
|
+
if (payload.jti) {
|
|
525
|
+
await this.refreshTokenStore.revokeAll(String(payload.sub));
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
res.clearCookie(this.refreshTokenCookieName, {
|
|
531
|
+
...this.cookieOptions,
|
|
532
|
+
maxAge: 0
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (this.csrf) {
|
|
536
|
+
res.clearCookie("csrf_token", { path: "/", maxAge: 0 });
|
|
537
|
+
}
|
|
538
|
+
await this.emitAudit({
|
|
539
|
+
action: "logout",
|
|
540
|
+
ip: req.ip ?? req.socket?.remoteAddress,
|
|
541
|
+
success: true
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
async refresh(req, res) {
|
|
545
|
+
if (!this.refreshTokenEnabled || !this.refreshTokenStore) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
const refreshCookie = req.cookies?.[this.refreshTokenCookieName];
|
|
549
|
+
if (!refreshCookie) {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
let payload;
|
|
553
|
+
try {
|
|
554
|
+
payload = (0, import_jsonwebtoken2.verify)(refreshCookie, this.jwtSecret, {
|
|
555
|
+
algorithms: ["HS256"]
|
|
556
|
+
});
|
|
557
|
+
} catch {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
if (!payload.jti) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
const stored = await this.refreshTokenStore.find(payload.jti);
|
|
564
|
+
if (!stored) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
await this.refreshTokenStore.revoke(payload.jti);
|
|
568
|
+
const userId = String(payload.sub);
|
|
569
|
+
const user = await this.prismaModel.findUnique({
|
|
570
|
+
where: { [this.idField]: userId }
|
|
571
|
+
});
|
|
572
|
+
if (!user) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
const accessToken = (0, import_jsonwebtoken2.sign)(
|
|
576
|
+
{ sub: userId },
|
|
577
|
+
this.jwtSecret,
|
|
578
|
+
{ expiresIn: this.jwtExpiresIn, algorithm: "HS256" }
|
|
579
|
+
);
|
|
580
|
+
res.cookie(this.cookieName, accessToken, this.cookieOptions);
|
|
581
|
+
await this.issueRefreshToken(userId, res);
|
|
582
|
+
if (this.csrf) {
|
|
583
|
+
const csrfToken = (0, import_node_crypto.randomBytes)(32).toString("hex");
|
|
584
|
+
res.cookie("csrf_token", csrfToken, {
|
|
585
|
+
httpOnly: false,
|
|
586
|
+
sameSite: this.cookieOptions.sameSite,
|
|
587
|
+
path: "/",
|
|
588
|
+
secure: this.cookieOptions.secure
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
await this.emitAudit({
|
|
592
|
+
action: "refresh",
|
|
593
|
+
userId,
|
|
594
|
+
ip: req.ip ?? req.socket?.remoteAddress,
|
|
595
|
+
success: true
|
|
596
|
+
});
|
|
597
|
+
return this.transformUser(user);
|
|
598
|
+
}
|
|
599
|
+
async issueRefreshToken(userId, res) {
|
|
600
|
+
const jti = (0, import_node_crypto.randomUUID)();
|
|
601
|
+
const expiresInMs = typeof this.refreshTokenExpiresIn === "number" ? this.refreshTokenExpiresIn * 1e3 : parseDuration(this.refreshTokenExpiresIn);
|
|
602
|
+
const expiresAt = new Date(Date.now() + expiresInMs);
|
|
603
|
+
const refreshToken = (0, import_jsonwebtoken2.sign)(
|
|
604
|
+
{ sub: userId, jti },
|
|
605
|
+
this.jwtSecret,
|
|
606
|
+
{ expiresIn: this.refreshTokenExpiresIn, algorithm: "HS256" }
|
|
607
|
+
);
|
|
608
|
+
await this.refreshTokenStore.save(jti, userId, expiresAt);
|
|
609
|
+
res.cookie(this.refreshTokenCookieName, refreshToken, {
|
|
610
|
+
...this.cookieOptions,
|
|
611
|
+
path: "/"
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
async emitAudit(partial) {
|
|
615
|
+
if (!this.onAudit) return;
|
|
616
|
+
await this.onAudit({ ...partial, timestamp: /* @__PURE__ */ new Date() });
|
|
229
617
|
}
|
|
230
618
|
};
|
|
231
619
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -233,5 +621,11 @@ var JwtPrismaCookieAuthDriver = class {
|
|
|
233
621
|
AUTH_DRIVER,
|
|
234
622
|
AuthController,
|
|
235
623
|
AuthModule,
|
|
236
|
-
|
|
624
|
+
InMemoryLockoutStore,
|
|
625
|
+
InMemoryRefreshTokenStore,
|
|
626
|
+
JwtAuthGuard,
|
|
627
|
+
JwtPrismaCookieAuthDriver,
|
|
628
|
+
ROLES_KEY,
|
|
629
|
+
Roles,
|
|
630
|
+
RolesGuard
|
|
237
631
|
});
|