auth-verify 1.0.3 → 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.
- package/package.json +3 -3
- package/readme.md +34 -0
- package/src/jwt/cookie/index.js +29 -0
- package/src/jwt/index.js +327 -143
- package/test.js +23 -3
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"axios": "^1.12.2",
|
|
4
4
|
"crypto": "^1.0.1",
|
|
5
|
+
"express": "^5.1.0",
|
|
5
6
|
"ioredis": "^5.8.1",
|
|
6
7
|
"jsonwebtoken": "^9.0.2",
|
|
7
8
|
"node-telegram-bot-api": "^0.66.0",
|
|
@@ -11,7 +12,7 @@
|
|
|
11
12
|
"uuid": "^13.0.0"
|
|
12
13
|
},
|
|
13
14
|
"name": "auth-verify",
|
|
14
|
-
"version": "1.0
|
|
15
|
+
"version": "1.1.0",
|
|
15
16
|
"description": "A simple Node.js library for sending and verifying OTP via email",
|
|
16
17
|
"main": "index.js",
|
|
17
18
|
"scripts": {
|
|
@@ -43,6 +44,5 @@
|
|
|
43
44
|
"bugs": {
|
|
44
45
|
"url": "https://github.com/jahongir2007/auth-verify/issues"
|
|
45
46
|
},
|
|
46
|
-
"homepage": "https://jahongir2007.github.io/auth-verify/"
|
|
47
|
-
"devDependencies": {}
|
|
47
|
+
"homepage": "https://jahongir2007.github.io/auth-verify/"
|
|
48
48
|
}
|
package/readme.md
CHANGED
|
@@ -71,6 +71,40 @@ await auth.jwt.revokeUntil(token, '10m');
|
|
|
71
71
|
// check if token is revoked (returns boolean)
|
|
72
72
|
const isRevoked = await auth.jwt.isRevoked(token);
|
|
73
73
|
```
|
|
74
|
+
## 🍪 Automatic Cookie Handling (New in v1.1.0)
|
|
75
|
+
|
|
76
|
+
You can now automatically store and verify JWTs via HTTP cookies — no need to manually send them!
|
|
77
|
+
```js
|
|
78
|
+
const AuthVerify = require("auth-verify");
|
|
79
|
+
const express = require("express");
|
|
80
|
+
const app = express();
|
|
81
|
+
|
|
82
|
+
const auth = new AuthVerify({
|
|
83
|
+
jwtSecret: "supersecret", storeTokens: "memory"
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
app.post("/login", async (req, res) => {
|
|
87
|
+
const token = await auth.jwt.sign({ userId: 1 }, "5s", { res });
|
|
88
|
+
res.json({ token }); // token is also set as cookie automatically
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
app.get("/verify", async (req, res) => {
|
|
92
|
+
try {
|
|
93
|
+
const data = await auth.jwt.verify(req); // auto reads from cookie
|
|
94
|
+
res.json({ valid: true, data });
|
|
95
|
+
} catch (err) {
|
|
96
|
+
res.json({ valid: false, error: err.message });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
app.listen(3000, () => console.log("🚀 Server running at http://localhost:3000"));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
What it does automatically:
|
|
104
|
+
|
|
105
|
+
- Saves token in a secure HTTP-only cookie
|
|
106
|
+
- Reads and verifies token from cookies
|
|
107
|
+
- Supports both async/await and callback styles
|
|
74
108
|
|
|
75
109
|
Notes:
|
|
76
110
|
- `sign` and `verify` support callback and promise styles in the implementation. When `storeTokens` is `"redis"` you should use the promise/async style (callback mode returns an error for redis in the current implementation).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class CookieManager {
|
|
2
|
+
static setCookie(res, name, value, options = {}) {
|
|
3
|
+
if (!res || typeof res.setHeader !== 'function') {
|
|
4
|
+
throw new Error("Response object must have setHeader() method");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let cookieStr = `${name}=${encodeURIComponent(value)}; Path=/;`;
|
|
8
|
+
|
|
9
|
+
if (options.httpOnly) cookieStr += " HttpOnly;";
|
|
10
|
+
if (options.secure) cookieStr += " Secure;";
|
|
11
|
+
if (options.maxAge) cookieStr += ` Max-Age=${Math.floor(options.maxAge / 1000)};`;
|
|
12
|
+
if (options.sameSite) cookieStr += ` SameSite=${options.sameSite};`;
|
|
13
|
+
if (options.domain) cookieStr += ` Domain=${options.domain};`;
|
|
14
|
+
|
|
15
|
+
const existing = res.getHeader('Set-Cookie') || [];
|
|
16
|
+
const newCookies = Array.isArray(existing) ? [...existing, cookieStr] : [cookieStr];
|
|
17
|
+
res.setHeader('Set-Cookie', newCookies);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getCookie(req, name) {
|
|
21
|
+
const cookieHeader = req.headers?.cookie;
|
|
22
|
+
if (!cookieHeader) return null;
|
|
23
|
+
const cookies = cookieHeader.split(';').map(c => c.trim());
|
|
24
|
+
const found = cookies.find(c => c.startsWith(name + '='));
|
|
25
|
+
return found ? decodeURIComponent(found.split('=')[1]) : null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = CookieManager;
|
package/src/jwt/index.js
CHANGED
|
@@ -1,6 +1,250 @@
|
|
|
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
|
+
|
|
1
245
|
const jwt = require("jsonwebtoken");
|
|
2
246
|
const Redis = require("ioredis");
|
|
3
|
-
const
|
|
247
|
+
const CookieManager = require("./cookie");
|
|
4
248
|
|
|
5
249
|
class JWTManager {
|
|
6
250
|
constructor(secret, options = {}) {
|
|
@@ -8,199 +252,139 @@ class JWTManager {
|
|
|
8
252
|
this.secret = secret;
|
|
9
253
|
this.storeType = options.storeTokens || "none";
|
|
10
254
|
|
|
11
|
-
if (this.storeType
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
throw new Error("{storeTokens} should be 'none', 'memory', or 'redis'");
|
|
18
|
-
}
|
|
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'");
|
|
19
261
|
}
|
|
20
262
|
}
|
|
21
263
|
|
|
22
|
-
// Helper: convert expiry string like "1h" / "30m" / "10s" to milliseconds
|
|
23
264
|
_parseExpiry(expiry) {
|
|
24
|
-
if (typeof expiry ===
|
|
25
|
-
if (typeof expiry !==
|
|
26
|
-
|
|
265
|
+
if (typeof expiry === "number") return expiry;
|
|
266
|
+
if (typeof expiry !== "string") return 3600000; // default 1h
|
|
27
267
|
const num = parseInt(expiry);
|
|
28
|
-
if (expiry.endsWith(
|
|
29
|
-
if (expiry.endsWith(
|
|
30
|
-
if (expiry.endsWith(
|
|
268
|
+
if (expiry.endsWith("h")) return num * 3600000;
|
|
269
|
+
if (expiry.endsWith("m")) return num * 60000;
|
|
270
|
+
if (expiry.endsWith("s")) return num * 1000;
|
|
31
271
|
return num;
|
|
32
272
|
}
|
|
33
273
|
|
|
34
|
-
async sign(payload, expiry, callback) {
|
|
35
|
-
if (typeof expiry ===
|
|
274
|
+
async sign(payload, expiry = "1h", options = {}, callback) {
|
|
275
|
+
if (typeof expiry === "function") {
|
|
36
276
|
callback = expiry;
|
|
37
|
-
expiry =
|
|
277
|
+
expiry = "1h";
|
|
278
|
+
}
|
|
279
|
+
if (typeof options === "function") {
|
|
280
|
+
callback = options;
|
|
281
|
+
options = {};
|
|
38
282
|
}
|
|
39
283
|
|
|
40
|
-
const expiryMs = this._parseExpiry(expiry
|
|
41
|
-
const expiryJwt = typeof expiry ===
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Callback version
|
|
46
|
-
if(callback && typeof callback === 'function') {
|
|
47
|
-
if(this.storeType === 'redis') return callback(new Error("⚠️ Redis requires async/await use promise style functions"));
|
|
284
|
+
const expiryMs = this._parseExpiry(expiry);
|
|
285
|
+
const expiryJwt = typeof expiry === "number"
|
|
286
|
+
? Math.floor(expiry / 1000)
|
|
287
|
+
: expiry;
|
|
48
288
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
});
|
|
51
296
|
|
|
52
|
-
|
|
53
|
-
this.tokenStore.set(token, {payload, createdAt: Date.now()});
|
|
54
|
-
setTimeout(() => this.tokenStore.delete(token), expiryMs);
|
|
55
|
-
}
|
|
297
|
+
const token = await createToken();
|
|
56
298
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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));
|
|
60
305
|
}
|
|
61
306
|
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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,
|
|
67
314
|
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if(this.storeType === 'memory') {
|
|
71
|
-
this.tokenStore.set(token, {payload, createdAt: Date.now()});
|
|
72
|
-
setTimeout(() => this.tokenStore.delete(token), expiryMs);
|
|
73
|
-
} else if(this.storeType === 'redis') {
|
|
74
|
-
await this.redis.set(token, JSON.stringify({payload, createdAt: Date.now()}), "EX", Math.floor(expiryMs/1000));
|
|
75
315
|
}
|
|
76
316
|
|
|
317
|
+
if (callback) return callback(null, token);
|
|
77
318
|
return token;
|
|
78
319
|
}
|
|
79
320
|
|
|
80
|
-
async verify(
|
|
81
|
-
|
|
82
|
-
if (callback && typeof callback === 'function') {
|
|
83
|
-
if (this.storeType === 'redis') {
|
|
84
|
-
return callback(new Error("⚠️ Redis requires async/await. Use Promise style."));
|
|
85
|
-
}
|
|
321
|
+
async verify(input, callback) {
|
|
322
|
+
let token = input;
|
|
86
323
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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);
|
|
90
331
|
|
|
91
|
-
|
|
92
|
-
if (err) {
|
|
93
|
-
if (err.name === "TokenExpiredError") return callback(new Error("❌ Token expired!"));
|
|
94
|
-
if (err.name === "JsonWebTokenError") return callback(new Error("❌ Invalid token!"));
|
|
95
|
-
return callback(new Error("❌ JWT error: " + err.message));
|
|
96
|
-
}
|
|
97
|
-
callback(null, decoded);
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
332
|
+
if (!token) throw new Error("JWT not found in cookies or headers");
|
|
100
333
|
}
|
|
101
334
|
|
|
102
|
-
// Async / Promise version
|
|
103
335
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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");
|
|
107
340
|
|
|
108
|
-
if (this.storeType ===
|
|
341
|
+
if (this.storeType === "redis") {
|
|
109
342
|
const data = await this.redis.get(token);
|
|
110
|
-
if (!data) throw new Error("
|
|
343
|
+
if (!data) throw new Error("Token not found or revoked");
|
|
111
344
|
}
|
|
112
345
|
|
|
113
|
-
|
|
346
|
+
if (callback) return callback(null, decoded);
|
|
114
347
|
return decoded;
|
|
115
348
|
} catch (err) {
|
|
116
|
-
|
|
349
|
+
if (callback) return callback(err);
|
|
350
|
+
throw new Error("JWT verification failed: " + err.message);
|
|
117
351
|
}
|
|
118
352
|
}
|
|
119
353
|
|
|
120
|
-
async decode(token
|
|
121
|
-
|
|
122
|
-
const decoded = jwt.decode(token);
|
|
123
|
-
// if (callback && typeof callback === 'function') return callback(null, decoded);
|
|
124
|
-
return decoded;
|
|
125
|
-
} catch (err) {
|
|
126
|
-
// if (callback && typeof callback === 'function') return callback(err);
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
354
|
+
async decode(token) {
|
|
355
|
+
return jwt.decode(token);
|
|
129
356
|
}
|
|
130
357
|
|
|
131
|
-
// Optional: manual token revoke for memory/redis
|
|
132
358
|
async revoke(token, revokeTime = 0) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// return num;
|
|
142
|
-
// }
|
|
143
|
-
|
|
144
|
-
const revokeMs = parseTime(revokeTime);
|
|
145
|
-
const revokeAfter = Date.now() + revokeMs;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (this.storeType === 'memory') {
|
|
149
|
-
if(revokeTime == 0){
|
|
150
|
-
this.tokenStore.delete(token);
|
|
151
|
-
}else{
|
|
152
|
-
setTimeout(() => this.tokenStore.delete(token), revokeAfter);
|
|
153
|
-
}
|
|
154
|
-
}else if(this.storeType == 'redis'){
|
|
155
|
-
if(revokeTime == 0){
|
|
156
|
-
await this.redis.del(token);
|
|
157
|
-
}else{
|
|
158
|
-
await this.redis.pexpire(token, revokeAfter);
|
|
159
|
-
}
|
|
160
|
-
}else{
|
|
161
|
-
throw new Error("❌ {storeTokens} should be 'memory' or 'redis' or 'none'");
|
|
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);
|
|
162
367
|
}
|
|
163
368
|
}
|
|
164
369
|
|
|
165
370
|
async isRevoked(token) {
|
|
166
|
-
if(this.storeType ===
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
} else if(this.storeType === 'redis') {
|
|
170
|
-
const dataRaw = await this.redis.get(token);
|
|
171
|
-
if (!dataRaw) return true;
|
|
172
|
-
const data = JSON.parse(dataRaw);
|
|
173
|
-
return data?.revokedUntil && data.revokedUntil > Date.now();
|
|
174
|
-
} else {
|
|
175
|
-
return false; // no store, can't revoke
|
|
176
|
-
}
|
|
371
|
+
if (this.storeType === "memory") return !this.tokenStore.has(token);
|
|
372
|
+
if (this.storeType === "redis") return !(await this.redis.get(token));
|
|
373
|
+
return false;
|
|
177
374
|
}
|
|
178
375
|
|
|
179
|
-
async revokeUntil(token, timestamp){
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// if (typeof str !== 'string') return 0;
|
|
183
|
-
|
|
184
|
-
// const num = parseInt(str);
|
|
185
|
-
// if (str.endsWith('h')) return num * 60 * 60 * 1000;
|
|
186
|
-
// if (str.endsWith('m')) return num * 60 * 1000;
|
|
187
|
-
// if (str.endsWith('s')) return num * 1000;
|
|
188
|
-
// return num;
|
|
189
|
-
// }
|
|
190
|
-
|
|
191
|
-
const revokeMs = parseTime(timestamp);
|
|
192
|
-
const revokedUntil = Date.now() + revokeMs;
|
|
376
|
+
async revokeUntil(token, timestamp) {
|
|
377
|
+
const revokeMs = this._parseExpiry(timestamp);
|
|
378
|
+
const revokedUntil = Date.now() + revokeMs;
|
|
193
379
|
|
|
194
|
-
if(this.storeType
|
|
380
|
+
if (this.storeType === "memory") {
|
|
195
381
|
const data = this.tokenStore.get(token) || {};
|
|
196
|
-
this.tokenStore.set(token, {...data, revokedUntil
|
|
197
|
-
}else if(this.storeType
|
|
382
|
+
this.tokenStore.set(token, { ...data, revokedUntil });
|
|
383
|
+
} else if (this.storeType === "redis") {
|
|
198
384
|
const dataRaw = await this.redis.get(token);
|
|
199
385
|
const data = dataRaw ? JSON.parse(dataRaw) : {};
|
|
200
386
|
data.revokedUntil = revokedUntil;
|
|
201
387
|
await this.redis.set(token, JSON.stringify(data));
|
|
202
|
-
}else{
|
|
203
|
-
throw new Error("{storeTokens} should be 'memory' or 'redis'");
|
|
204
388
|
}
|
|
205
389
|
}
|
|
206
390
|
}
|
package/test.js
CHANGED
|
@@ -101,9 +101,11 @@
|
|
|
101
101
|
// }catch(err){}
|
|
102
102
|
// })();
|
|
103
103
|
|
|
104
|
-
const TelegramBot = require("node-telegram-bot-api");
|
|
104
|
+
// const TelegramBot = require("node-telegram-bot-api");
|
|
105
|
+
const express = require('express');
|
|
105
106
|
const AuthVerify = require("./index"); // your library
|
|
106
|
-
|
|
107
|
+
const app = express();
|
|
108
|
+
app.use(express.json());
|
|
107
109
|
// // Your bot token from BotFather
|
|
108
110
|
// const BOT_TOKEN = "6657700716:AAGW3s5Yxk5SrRH7yRqrgG-kVrG9a6PXBxk";
|
|
109
111
|
|
|
@@ -149,7 +151,25 @@ const AuthVerify = require("./index"); // your library
|
|
|
149
151
|
// }
|
|
150
152
|
// });
|
|
151
153
|
|
|
152
|
-
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'
|
|
154
|
+
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
155
|
+
|
|
156
|
+
app.get('/', async (req, res) => {
|
|
157
|
+
const token = await auth.jwt.sign({ id: 1, role: 'user' }, '1h', { res });
|
|
158
|
+
res.send(`JWT saved in cookie!`);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
app.get('/verify', async (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const data = await auth.jwt.verify({ headers: req.headers });
|
|
164
|
+
res.json({ valid: true, data });
|
|
165
|
+
} catch (err) {
|
|
166
|
+
res.status(401).json({ valid: false, error: err.message });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
app.listen(3000, ()=>{
|
|
171
|
+
console.log('App is running')
|
|
172
|
+
});
|
|
153
173
|
|
|
154
174
|
// auth.register.sender("consoleOtp", async ({ to, code }) => {
|
|
155
175
|
// console.log(`🔑 Sending OTP ${code} to ${to}`);
|