auth-verify 0.0.2 → 1.1.0

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.
@@ -0,0 +1,392 @@
1
+ // const jwt = require("jsonwebtoken");
2
+ // const Redis = require("ioredis");
3
+ // const { parseTime } = require('../helpers/helper');
4
+ // const CookieManager = require('./cookie');
5
+
6
+ // class JWTManager {
7
+ // constructor(secret, options = {}) {
8
+ // if (!secret) throw new Error("JWT secret is required");
9
+ // this.secret = secret;
10
+ // this.storeType = options.storeTokens || "none";
11
+
12
+ // if (this.storeType !== 'none') {
13
+ // if (this.storeType === 'memory') {
14
+ // this.tokenStore = new Map();
15
+ // } else if (this.storeType === 'redis') {
16
+ // this.redis = new Redis(options.redisUrl || "redis://localhost:6379");
17
+ // } else {
18
+ // throw new Error("{storeTokens} should be 'none', 'memory', or 'redis'");
19
+ // }
20
+ // }
21
+ // }
22
+
23
+ // // Helper: convert expiry string like "1h" / "30m" / "10s" to milliseconds
24
+ // _parseExpiry(expiry) {
25
+ // if (typeof expiry === 'number') return expiry;
26
+ // if (typeof expiry !== 'string') return 3600000; // default 1h
27
+
28
+ // const num = parseInt(expiry);
29
+ // if (expiry.endsWith('h')) return num * 60 * 60 * 1000;
30
+ // if (expiry.endsWith('m')) return num * 60 * 1000;
31
+ // if (expiry.endsWith('s')) return num * 1000;
32
+ // return num;
33
+ // }
34
+
35
+ // async sign(payload, expiry, options = {}, callback) {
36
+ // if (typeof expiry === 'function') {
37
+ // callback = expiry;
38
+ // expiry = '1h'; // default
39
+ // }
40
+
41
+ // const expiryMs = this._parseExpiry(expiry || '1h'); // fallback to '1h'
42
+ // const expiryJwt = typeof expiry === 'number'
43
+ // ? Math.floor(expiry / 1000)
44
+ // : (typeof expiry === 'string' ? expiry : '1h');
45
+
46
+ // // Auto-set cookie if res provided
47
+ // if(typeof options == 'function'){
48
+ // callback = options;
49
+ // }
50
+
51
+
52
+ // // Callback version
53
+ // if(callback && typeof callback === 'function') {
54
+ // if(this.storeType === 'redis') return callback(new Error("⚠️ Redis requires async/await use promise style functions"));
55
+
56
+ // jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, token) => {
57
+ // if(err) return callback(err);
58
+
59
+ // if (options.res) {
60
+ // CookieManager.setCookie(options.res, 'jwt_token', token, {
61
+ // httpOnly: true,
62
+ // secure: options.secure ?? true,
63
+ // sameSite: 'Strict',
64
+ // maxAge: this._parseTime(expiresIn),
65
+ // });
66
+ // }else if(this.storeType === 'memory') {
67
+ // this.tokenStore.set(token, {payload, createdAt: Date.now()});
68
+ // setTimeout(() => this.tokenStore.delete(token), expiryMs);
69
+ // }
70
+
71
+ // callback(null, token);
72
+ // });
73
+ // return;
74
+ // }
75
+
76
+ // // Async version
77
+ // const token = await new Promise((res, rej) => {
78
+ // jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, t) => {
79
+ // if(err) rej(err);
80
+ // else res(t);
81
+ // });
82
+ // });
83
+
84
+ // if (options.res) {
85
+ // CookieManager.setCookie(options.res, 'jwt_token', token, {
86
+ // httpOnly: true,
87
+ // secure: options.secure ?? true,
88
+ // sameSite: 'Strict',
89
+ // maxAge: this._parseExpiry(expiresIn),
90
+ // });
91
+ // }else if(this.storeType === 'memory') {
92
+ // this.tokenStore.set(token, {payload, createdAt: Date.now()});
93
+ // setTimeout(() => this.tokenStore.delete(token), expiryMs);
94
+ // } else if(this.storeType === 'redis') {
95
+ // await this.redis.set(token, JSON.stringify({payload, createdAt: Date.now()}), "EX", Math.floor(expiryMs/1000));
96
+ // }
97
+
98
+ // return token;
99
+ // }
100
+
101
+ // async verify(token, callback) {
102
+
103
+ // let cookieToken;
104
+ // if(typeof token == 'object' && token.headers){
105
+ // cookieToken = CookieManager.getCookie(token, 'jwt_token');
106
+ // if (!token) throw new Error('JWT not found in cookies');
107
+
108
+ // try{
109
+ // const decoded = jwt.verify(cookieToken, this.secret);
110
+ // return decoded;
111
+ // }catch(err){
112
+ // throw new Error('Invalid or expired token');
113
+ // }
114
+ // }
115
+
116
+ // // Callback version
117
+ // if (callback && typeof callback === 'function') {
118
+ // if (this.storeType === 'redis') {
119
+ // return callback(new Error("⚠️ Redis requires async/await. Use Promise style."));
120
+ // }
121
+
122
+ // if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
123
+ // return callback(new Error("❌ Token not found or revoked"));
124
+ // }
125
+
126
+ // jwt.verify(token, this.secret, (err, decoded) => {
127
+ // if (err) {
128
+ // if (err.name === "TokenExpiredError") return callback(new Error("❌ Token expired!"));
129
+ // if (err.name === "JsonWebTokenError") return callback(new Error("❌ Invalid token!"));
130
+ // return callback(new Error("❌ JWT error: " + err.message));
131
+ // }
132
+ // callback(null, decoded);
133
+ // });
134
+ // return;
135
+ // }
136
+
137
+ // // Async / Promise version
138
+ // try {
139
+ // if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
140
+ // throw new Error("❌ Token not found or revoked");
141
+ // }
142
+
143
+ // if (this.storeType === 'redis') {
144
+ // const data = await this.redis.get(token);
145
+ // if (!data) throw new Error("❌ Token not found or revoked");
146
+ // }
147
+
148
+ // const decoded = jwt.verify(token, this.secret);
149
+ // return decoded;
150
+ // } catch (err) {
151
+ // throw new Error("❌ JWT verification failed: " + err.message);
152
+ // }
153
+ // }
154
+
155
+ // async decode(token, callback) {
156
+ // try {
157
+ // const decoded = jwt.decode(token);
158
+ // // if (callback && typeof callback === 'function') return callback(null, decoded);
159
+ // return decoded;
160
+ // } catch (err) {
161
+ // // if (callback && typeof callback === 'function') return callback(err);
162
+ // throw err;
163
+ // }
164
+ // }
165
+
166
+ // // Optional: manual token revoke for memory/redis
167
+ // async revoke(token, revokeTime = 0) {
168
+ // // function parseTime(str) {
169
+ // // if (typeof str === 'number') return str; // already in ms
170
+ // // if (typeof str !== 'string') return 0;
171
+
172
+ // // const num = parseInt(str);
173
+ // // if (str.endsWith('h')) return num * 60 * 60 * 1000;
174
+ // // if (str.endsWith('m')) return num * 60 * 1000;
175
+ // // if (str.endsWith('s')) return num * 1000;
176
+ // // return num;
177
+ // // }
178
+
179
+ // const revokeMs = parseTime(revokeTime);
180
+ // const revokeAfter = Date.now() + revokeMs;
181
+
182
+
183
+ // if (this.storeType === 'memory') {
184
+ // if(revokeTime == 0){
185
+ // this.tokenStore.delete(token);
186
+ // }else{
187
+ // setTimeout(() => this.tokenStore.delete(token), revokeAfter);
188
+ // }
189
+ // }else if(this.storeType == 'redis'){
190
+ // if(revokeTime == 0){
191
+ // await this.redis.del(token);
192
+ // }else{
193
+ // await this.redis.pexpire(token, revokeAfter);
194
+ // }
195
+ // }else{
196
+ // throw new Error("❌ {storeTokens} should be 'memory' or 'redis' or 'none'");
197
+ // }
198
+ // }
199
+
200
+ // async isRevoked(token) {
201
+ // if(this.storeType === 'memory') {
202
+ // const data = this.tokenStore.get(token);
203
+ // return data?.revokedUntil && data.revokedUntil > Date.now();
204
+ // } else if(this.storeType === 'redis') {
205
+ // const dataRaw = await this.redis.get(token);
206
+ // if (!dataRaw) return true;
207
+ // const data = JSON.parse(dataRaw);
208
+ // return data?.revokedUntil && data.revokedUntil > Date.now();
209
+ // } else {
210
+ // return false; // no store, can't revoke
211
+ // }
212
+ // }
213
+
214
+ // async revokeUntil(token, timestamp){
215
+ // // function parseTime(str) {
216
+ // // if (typeof str === 'number') return str; // already in ms
217
+ // // if (typeof str !== 'string') return 0;
218
+
219
+ // // const num = parseInt(str);
220
+ // // if (str.endsWith('h')) return num * 60 * 60 * 1000;
221
+ // // if (str.endsWith('m')) return num * 60 * 1000;
222
+ // // if (str.endsWith('s')) return num * 1000;
223
+ // // return num;
224
+ // // }
225
+
226
+ // const revokeMs = parseTime(timestamp);
227
+ // const revokedUntil = Date.now() + revokeMs;
228
+
229
+ // if(this.storeType == 'memory'){
230
+ // const data = this.tokenStore.get(token) || {};
231
+ // this.tokenStore.set(token, {...data, revokedUntil: revokedUntil});
232
+ // }else if(this.storeType == 'redis'){
233
+ // const dataRaw = await this.redis.get(token);
234
+ // const data = dataRaw ? JSON.parse(dataRaw) : {};
235
+ // data.revokedUntil = revokedUntil;
236
+ // await this.redis.set(token, JSON.stringify(data));
237
+ // }else{
238
+ // throw new Error("{storeTokens} should be 'memory' or 'redis'");
239
+ // }
240
+ // }
241
+ // }
242
+
243
+ // module.exports = JWTManager;
244
+
245
+ const jwt = require("jsonwebtoken");
246
+ const Redis = require("ioredis");
247
+ const CookieManager = require("./cookie");
248
+
249
+ class JWTManager {
250
+ constructor(secret, options = {}) {
251
+ if (!secret) throw new Error("JWT secret is required");
252
+ this.secret = secret;
253
+ this.storeType = options.storeTokens || "none";
254
+
255
+ if (this.storeType === "memory") {
256
+ this.tokenStore = new Map();
257
+ } else if (this.storeType === "redis") {
258
+ this.redis = new Redis(options.redisUrl || "redis://localhost:6379");
259
+ } else if (this.storeType !== "none") {
260
+ throw new Error("{storeTokens} must be 'none', 'memory', or 'redis'");
261
+ }
262
+ }
263
+
264
+ _parseExpiry(expiry) {
265
+ if (typeof expiry === "number") return expiry;
266
+ if (typeof expiry !== "string") return 3600000; // default 1h
267
+ const num = parseInt(expiry);
268
+ if (expiry.endsWith("h")) return num * 3600000;
269
+ if (expiry.endsWith("m")) return num * 60000;
270
+ if (expiry.endsWith("s")) return num * 1000;
271
+ return num;
272
+ }
273
+
274
+ async sign(payload, expiry = "1h", options = {}, callback) {
275
+ if (typeof expiry === "function") {
276
+ callback = expiry;
277
+ expiry = "1h";
278
+ }
279
+ if (typeof options === "function") {
280
+ callback = options;
281
+ options = {};
282
+ }
283
+
284
+ const expiryMs = this._parseExpiry(expiry);
285
+ const expiryJwt = typeof expiry === "number"
286
+ ? Math.floor(expiry / 1000)
287
+ : expiry;
288
+
289
+ const createToken = () =>
290
+ new Promise((resolve, reject) => {
291
+ jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, token) => {
292
+ if (err) return reject(err);
293
+ resolve(token);
294
+ });
295
+ });
296
+
297
+ const token = await createToken();
298
+
299
+ // Save token if needed
300
+ if (this.storeType === "memory") {
301
+ this.tokenStore.set(token, { payload, createdAt: Date.now() });
302
+ setTimeout(() => this.tokenStore.delete(token), expiryMs);
303
+ } else if (this.storeType === "redis") {
304
+ await this.redis.set(token, JSON.stringify({ payload, createdAt: Date.now() }), "EX", Math.floor(expiryMs / 1000));
305
+ }
306
+
307
+ // Auto cookie support
308
+ if (options.res) {
309
+ CookieManager.setCookie(options.res, "jwt_token", token, {
310
+ httpOnly: true,
311
+ secure: options.secure ?? true,
312
+ sameSite: "Strict",
313
+ maxAge: expiryMs,
314
+ });
315
+ }
316
+
317
+ if (callback) return callback(null, token);
318
+ return token;
319
+ }
320
+
321
+ async verify(input, callback) {
322
+ let token = input;
323
+
324
+ // If request object provided
325
+ if (typeof input === "object" && input.headers) {
326
+ token =
327
+ CookieManager.getCookie(input, "jwt_token") ||
328
+ (input.headers.authorization
329
+ ? input.headers.authorization.replace("Bearer ", "")
330
+ : null);
331
+
332
+ if (!token) throw new Error("JWT not found in cookies or headers");
333
+ }
334
+
335
+ try {
336
+ const decoded = jwt.verify(token, this.secret);
337
+
338
+ if (this.storeType === "memory" && !this.tokenStore.has(token))
339
+ throw new Error("Token not found or revoked");
340
+
341
+ if (this.storeType === "redis") {
342
+ const data = await this.redis.get(token);
343
+ if (!data) throw new Error("Token not found or revoked");
344
+ }
345
+
346
+ if (callback) return callback(null, decoded);
347
+ return decoded;
348
+ } catch (err) {
349
+ if (callback) return callback(err);
350
+ throw new Error("JWT verification failed: " + err.message);
351
+ }
352
+ }
353
+
354
+ async decode(token) {
355
+ return jwt.decode(token);
356
+ }
357
+
358
+ async revoke(token, revokeTime = 0) {
359
+ const revokeMs = this._parseExpiry(revokeTime);
360
+
361
+ if (this.storeType === "memory") {
362
+ if (revokeTime === 0) this.tokenStore.delete(token);
363
+ else setTimeout(() => this.tokenStore.delete(token), revokeMs);
364
+ } else if (this.storeType === "redis") {
365
+ if (revokeTime === 0) await this.redis.del(token);
366
+ else await this.redis.pexpire(token, revokeMs);
367
+ }
368
+ }
369
+
370
+ async isRevoked(token) {
371
+ if (this.storeType === "memory") return !this.tokenStore.has(token);
372
+ if (this.storeType === "redis") return !(await this.redis.get(token));
373
+ return false;
374
+ }
375
+
376
+ async revokeUntil(token, timestamp) {
377
+ const revokeMs = this._parseExpiry(timestamp);
378
+ const revokedUntil = Date.now() + revokeMs;
379
+
380
+ if (this.storeType === "memory") {
381
+ const data = this.tokenStore.get(token) || {};
382
+ this.tokenStore.set(token, { ...data, revokedUntil });
383
+ } else if (this.storeType === "redis") {
384
+ const dataRaw = await this.redis.get(token);
385
+ const data = dataRaw ? JSON.parse(dataRaw) : {};
386
+ data.revokedUntil = revokedUntil;
387
+ await this.redis.set(token, JSON.stringify(data));
388
+ }
389
+ }
390
+ }
391
+
392
+ module.exports = JWTManager;