auth-verify 1.0.3 → 1.2.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/index.js +3 -0
- package/package.json +3 -3
- package/readme.md +177 -20
- package/src/jwt/cookie/index.js +29 -0
- package/src/jwt/index.js +327 -143
- package/src/oauth/index.js +180 -0
- package/test.js +47 -3
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const JWTManager = require("./src/jwt");
|
|
2
2
|
const OTPManager = require("./src/otp");
|
|
3
3
|
const SessionManager = require("./src/session");
|
|
4
|
+
const OAuthManager = require("./src/oauth")
|
|
4
5
|
|
|
5
6
|
class AuthVerify {
|
|
6
7
|
constructor(options = {}) {
|
|
@@ -39,6 +40,8 @@ class AuthVerify {
|
|
|
39
40
|
// }
|
|
40
41
|
// }
|
|
41
42
|
// ✅ No getters — directly reference otp.dev (it's a plain object)
|
|
43
|
+
|
|
44
|
+
this.oauth = new OAuthManager();
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
// Session helpers
|
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.2.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
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# auth-verify
|
|
2
2
|
|
|
3
3
|
**auth-verify** is a Node.js authentication utility that provides:
|
|
4
|
-
- Secure OTP (one-time password) generation and verification
|
|
5
|
-
- Sending OTPs via Email, SMS (pluggable helpers), and Telegram bot
|
|
6
|
-
- JWT creation, verification and optional token revocation with memory/Redis storage
|
|
7
|
-
- Session management (in-memory or Redis)
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
> This README documents the code structure and APIs found in the library files you provided (OTPManager, JWTManager, SessionManager, AuthVerify).
|
|
11
|
-
|
|
4
|
+
- ✅ Secure OTP (one-time password) generation and verification
|
|
5
|
+
- ✅ Sending OTPs via Email, SMS (pluggable helpers), and Telegram bot
|
|
6
|
+
- ✅ JWT creation, verification and optional token revocation with memory/Redis storage
|
|
7
|
+
- ✅ Session management (in-memory or Redis)
|
|
8
|
+
- ✅ New: OAuth 2.0 integration for Google, Facebook, GitHub, and X (Twitter)
|
|
9
|
+
- ⚙️ Developer extensibility: custom senders and `auth.register.sender()` / `auth.use(name).send(...)`
|
|
12
10
|
---
|
|
13
11
|
|
|
14
|
-
## Installation
|
|
12
|
+
## 🧩 Installation
|
|
15
13
|
|
|
16
14
|
```bash
|
|
17
15
|
# from npm (when published)
|
|
@@ -23,16 +21,16 @@ npm install auth-verify
|
|
|
23
21
|
|
|
24
22
|
---
|
|
25
23
|
|
|
26
|
-
## Quick overview
|
|
24
|
+
## ⚙️ Quick overview
|
|
27
25
|
|
|
28
26
|
- `AuthVerify` (entry): constructs and exposes `.jwt`, `.otp`, and (optionally) `.session` managers.
|
|
29
27
|
- `JWTManager`: sign, verify, decode, revoke tokens. Supports `storeTokens: "memory" | "redis" | "none"`.
|
|
30
28
|
- `OTPManager`: generate, store, send, verify, resend OTPs. Supports `storeTokens: "memory" | "redis" | "none"`. Supports email, SMS helper, Telegram bot, and custom dev senders.
|
|
31
29
|
- `SessionManager`: simple session creation/verification/destroy with memory or Redis backend.
|
|
32
|
-
|
|
30
|
+
- `OAuthManager`: Handle OAuth 2.0 logins for Google, Facebook, GitHub, X
|
|
33
31
|
---
|
|
34
32
|
|
|
35
|
-
## Example: Initialize library (CommonJS)
|
|
33
|
+
## 🚀 Example: Initialize library (CommonJS)
|
|
36
34
|
|
|
37
35
|
```js
|
|
38
36
|
const AuthVerify = require('auth-verify');
|
|
@@ -48,7 +46,7 @@ const auth = new AuthVerify({
|
|
|
48
46
|
|
|
49
47
|
---
|
|
50
48
|
|
|
51
|
-
## JWT
|
|
49
|
+
## 🔐 JWT Usage
|
|
52
50
|
|
|
53
51
|
```js
|
|
54
52
|
// create JWT
|
|
@@ -71,15 +69,49 @@ await auth.jwt.revokeUntil(token, '10m');
|
|
|
71
69
|
// check if token is revoked (returns boolean)
|
|
72
70
|
const isRevoked = await auth.jwt.isRevoked(token);
|
|
73
71
|
```
|
|
72
|
+
### 🍪 Automatic Cookie Handling (v1.1.0+)
|
|
73
|
+
|
|
74
|
+
You can now automatically store and verify JWTs via HTTP cookies — no need to manually send them!
|
|
75
|
+
```js
|
|
76
|
+
const AuthVerify = require("auth-verify");
|
|
77
|
+
const express = require("express");
|
|
78
|
+
const app = express();
|
|
79
|
+
|
|
80
|
+
const auth = new AuthVerify({
|
|
81
|
+
jwtSecret: "supersecret", storeTokens: "memory"
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
app.post("/login", async (req, res) => {
|
|
85
|
+
const token = await auth.jwt.sign({ userId: 1 }, "5s", { res });
|
|
86
|
+
res.json({ token }); // token is also set as cookie automatically
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
app.get("/verify", async (req, res) => {
|
|
90
|
+
try {
|
|
91
|
+
const data = await auth.jwt.verify(req); // auto reads from cookie
|
|
92
|
+
res.json({ valid: true, data });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
res.json({ valid: false, error: err.message });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
app.listen(3000, () => console.log("🚀 Server running at http://localhost:3000"));
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
What it does automatically:
|
|
102
|
+
|
|
103
|
+
- Saves token in a secure HTTP-only cookie
|
|
104
|
+
- Reads and verifies token from cookies
|
|
105
|
+
- Supports both async/await and callback styles
|
|
74
106
|
|
|
75
107
|
Notes:
|
|
76
108
|
- `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).
|
|
77
109
|
|
|
78
110
|
---
|
|
79
111
|
|
|
80
|
-
## OTP (email / sms / telegram / custom sender)
|
|
112
|
+
## 🔢 OTP (email / sms / telegram / custom sender)
|
|
81
113
|
|
|
82
|
-
### Configure sender
|
|
114
|
+
### 🤝 Configure sender
|
|
83
115
|
|
|
84
116
|
You can set the default sender (email/sms/telegram):
|
|
85
117
|
|
|
@@ -111,7 +143,7 @@ auth.otp.setSender({
|
|
|
111
143
|
});
|
|
112
144
|
```
|
|
113
145
|
|
|
114
|
-
### Generate → Save → Send (chainable)
|
|
146
|
+
### ⛓️ Generate → Save → Send (chainable)
|
|
115
147
|
|
|
116
148
|
OTP generation is chainable: `generate()` returns the OTP manager instance.
|
|
117
149
|
|
|
@@ -142,7 +174,7 @@ await auth.otp.message({
|
|
|
142
174
|
});
|
|
143
175
|
```
|
|
144
176
|
|
|
145
|
-
### Verify
|
|
177
|
+
### ✔️ Verify
|
|
146
178
|
|
|
147
179
|
```js
|
|
148
180
|
// Promise style
|
|
@@ -164,6 +196,128 @@ try {
|
|
|
164
196
|
|
|
165
197
|
`resend` returns the new code (promise style) or calls callback.
|
|
166
198
|
|
|
199
|
+
---
|
|
200
|
+
## 🌍 OAuth 2.0 Integration (New in v1.2.0)
|
|
201
|
+
`auth.oauth` supports login via Google, Facebook, GitHub, and X (Twitter).
|
|
202
|
+
### Example (Google Login with Express)
|
|
203
|
+
```js
|
|
204
|
+
const express = require('express');
|
|
205
|
+
const AuthVerify = require("auth-verify");
|
|
206
|
+
const app = express();
|
|
207
|
+
app.use(express.json());
|
|
208
|
+
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
209
|
+
|
|
210
|
+
const google = auth.oauth.google({clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', redirectUri: 'http://localhost:3000/auth/google/callback'});
|
|
211
|
+
app.get('/', async (req, res) => {
|
|
212
|
+
res.send(`
|
|
213
|
+
<h1>Login with Google</h1>
|
|
214
|
+
<a href="/auth/google">Login</a>
|
|
215
|
+
`);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
app.get('/auth/google', (req, res) => google.redirect(res));
|
|
220
|
+
|
|
221
|
+
app.get('/auth/google/callback', async (req, res)=>{
|
|
222
|
+
const code = req.query.code;
|
|
223
|
+
try {
|
|
224
|
+
const user = await google.callback(code);
|
|
225
|
+
res.send(`
|
|
226
|
+
<h2>Welcome, ${user.name}!</h2>
|
|
227
|
+
<img src="${user.picture}" width="100" style="border-radius:50%">
|
|
228
|
+
<p>Email: ${user.email}</p>
|
|
229
|
+
<p>Access Token: ${user.access_token.slice(0, 20)}...</p>
|
|
230
|
+
`);
|
|
231
|
+
} catch(err){
|
|
232
|
+
res.status(500).send("Error: " + err.message);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
app.listen(3000, ()=>{
|
|
237
|
+
console.log('Server is running...');
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
---
|
|
241
|
+
### API documentation for OAuth
|
|
242
|
+
- `auth.oauth.google({...})` for making connection to your Google cloud app.
|
|
243
|
+
- `google.redirect(res)` for sending user/client to the Google OAuth page for verifying and selecting his accaount
|
|
244
|
+
- `google.callback(code)` for exchanging server code to the user/client token.
|
|
245
|
+
|
|
246
|
+
### Other examples with other platforms
|
|
247
|
+
```js
|
|
248
|
+
const express = require('express');
|
|
249
|
+
const AuthVerify = require("auth-verify");
|
|
250
|
+
const app = express();
|
|
251
|
+
app.use(express.json());
|
|
252
|
+
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
253
|
+
|
|
254
|
+
// --- Example: FACEBOOK LOGIN ---
|
|
255
|
+
const facebook = auth.oauth.facebook({
|
|
256
|
+
clientId: "YOUR_FB_APP_ID",
|
|
257
|
+
clientSecret: "YOUR_FB_APP_SECRET",
|
|
258
|
+
redirectUri: "http://localhost:3000/auth/facebook/callback",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// --- Example: GITHUB LOGIN ---
|
|
262
|
+
const github = auth.oauth.github({
|
|
263
|
+
clientId: "YOUR_GITHUB_CLIENT_ID",
|
|
264
|
+
clientSecret: "YOUR_GITHUB_CLIENT_SECRET",
|
|
265
|
+
redirectUri: "http://localhost:3000/auth/github/callback",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// --- Example: X (Twitter) LOGIN ---
|
|
269
|
+
const twitter = auth.oauth.x({
|
|
270
|
+
clientId: "YOUR_TWITTER_CLIENT_ID",
|
|
271
|
+
clientSecret: "YOUR_TWITTER_CLIENT_SECRET",
|
|
272
|
+
redirectUri: "http://localhost:3000/auth/x/callback",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
// ===== FACEBOOK ROUTES =====
|
|
277
|
+
app.get("/auth/facebook", (req, res) => facebook.redirect(res));
|
|
278
|
+
|
|
279
|
+
app.get("/auth/facebook/callback", async (req, res) => {
|
|
280
|
+
try {
|
|
281
|
+
const { code } = req.query;
|
|
282
|
+
const user = await facebook.callback(code);
|
|
283
|
+
res.json({ success: true, provider: "facebook", user });
|
|
284
|
+
} catch (err) {
|
|
285
|
+
res.status(400).json({ error: err.message });
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
// ===== GITHUB ROUTES =====
|
|
291
|
+
app.get("/auth/github", (req, res) => github.redirect(res));
|
|
292
|
+
|
|
293
|
+
app.get("/auth/github/callback", async (req, res) => {
|
|
294
|
+
try {
|
|
295
|
+
const { code } = req.query;
|
|
296
|
+
const user = await github.callback(code);
|
|
297
|
+
res.json({ success: true, provider: "github", user });
|
|
298
|
+
} catch (err) {
|
|
299
|
+
res.status(400).json({ error: err.message });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
// ===== X (TWITTER) ROUTES =====
|
|
305
|
+
app.get("/auth/x", (req, res) => twitter.redirect(res));
|
|
306
|
+
|
|
307
|
+
app.get("/auth/x/callback", async (req, res) => {
|
|
308
|
+
try {
|
|
309
|
+
const { code } = req.query;
|
|
310
|
+
const user = await twitter.callback(code);
|
|
311
|
+
res.json({ success: true, provider: "x", user });
|
|
312
|
+
} catch (err) {
|
|
313
|
+
res.status(400).json({ error: err.message });
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
app.listen(PORT, () => console.log(`🚀 Server running at http://localhost:${PORT}`));
|
|
319
|
+
|
|
320
|
+
```
|
|
167
321
|
---
|
|
168
322
|
|
|
169
323
|
## Telegram integration
|
|
@@ -247,9 +401,12 @@ auth-verify/
|
|
|
247
401
|
├─ package.json
|
|
248
402
|
├─ src/
|
|
249
403
|
│ ├─ index.js // exports AuthVerify
|
|
250
|
-
│ ├─ jwt
|
|
251
|
-
|
|
252
|
-
|
|
404
|
+
│ ├─ jwt/
|
|
405
|
+
| | ├─ index.js
|
|
406
|
+
| | ├─ cookie/index.js
|
|
407
|
+
│ ├─ /otp/index.js
|
|
408
|
+
│ ├─ /session/index.js
|
|
409
|
+
| ├─ /oauth/index.js
|
|
253
410
|
│ └─ helpers/helper.js
|
|
254
411
|
```
|
|
255
412
|
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/managers/oauth.js
|
|
2
|
+
|
|
3
|
+
class OAuthManager {
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.providers = config.providers || {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// --- GOOGLE LOGIN ---
|
|
9
|
+
google({ clientId, clientSecret, redirectUri }) {
|
|
10
|
+
return {
|
|
11
|
+
redirect(res) {
|
|
12
|
+
const googleURL =
|
|
13
|
+
"https://accounts.google.com/o/oauth2/v2/auth?" +
|
|
14
|
+
new URLSearchParams({
|
|
15
|
+
client_id: clientId,
|
|
16
|
+
redirect_uri: redirectUri,
|
|
17
|
+
response_type: "code",
|
|
18
|
+
scope: "openid email profile",
|
|
19
|
+
access_type: "offline",
|
|
20
|
+
prompt: "consent",
|
|
21
|
+
});
|
|
22
|
+
res.redirect(googleURL);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
async callback(code) {
|
|
26
|
+
// Step 1: Exchange code for access token
|
|
27
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
30
|
+
body: new URLSearchParams({
|
|
31
|
+
client_id: clientId,
|
|
32
|
+
client_secret: clientSecret,
|
|
33
|
+
code,
|
|
34
|
+
redirect_uri: redirectUri,
|
|
35
|
+
grant_type: "authorization_code",
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const tokenData = await tokenRes.json();
|
|
40
|
+
if (tokenData.error) throw new Error("OAuth Error: " + tokenData.error);
|
|
41
|
+
|
|
42
|
+
// Step 2: Get user info
|
|
43
|
+
const userRes = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
|
|
44
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
45
|
+
});
|
|
46
|
+
const user = await userRes.json();
|
|
47
|
+
|
|
48
|
+
return { ...user, access_token: tokenData.access_token };
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- FACEBOOK LOGIN ---
|
|
54
|
+
facebook({ clientId, clientSecret, redirectUri }) {
|
|
55
|
+
return {
|
|
56
|
+
redirect(res) {
|
|
57
|
+
const fbURL =
|
|
58
|
+
"https://www.facebook.com/v19.0/dialog/oauth?" +
|
|
59
|
+
new URLSearchParams({
|
|
60
|
+
client_id: clientId,
|
|
61
|
+
redirect_uri: redirectUri,
|
|
62
|
+
scope: "email,public_profile",
|
|
63
|
+
response_type: "code",
|
|
64
|
+
});
|
|
65
|
+
res.redirect(fbURL);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async callback(code) {
|
|
69
|
+
const tokenRes = await fetch(
|
|
70
|
+
`https://graph.facebook.com/v19.0/oauth/access_token?` +
|
|
71
|
+
new URLSearchParams({
|
|
72
|
+
client_id: clientId,
|
|
73
|
+
client_secret: clientSecret,
|
|
74
|
+
redirect_uri: redirectUri,
|
|
75
|
+
code,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const tokenData = await tokenRes.json();
|
|
80
|
+
if (tokenData.error) throw new Error("OAuth Error: " + tokenData.error.message);
|
|
81
|
+
|
|
82
|
+
const userRes = await fetch(
|
|
83
|
+
`https://graph.facebook.com/me?fields=id,name,email,picture&access_token=${tokenData.access_token}`
|
|
84
|
+
);
|
|
85
|
+
const user = await userRes.json();
|
|
86
|
+
|
|
87
|
+
return { ...user, access_token: tokenData.access_token };
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// --- GITHUB LOGIN ---
|
|
93
|
+
github({ clientId, clientSecret, redirectUri }) {
|
|
94
|
+
return {
|
|
95
|
+
redirect(res) {
|
|
96
|
+
const githubURL =
|
|
97
|
+
"https://github.com/login/oauth/authorize?" +
|
|
98
|
+
new URLSearchParams({
|
|
99
|
+
client_id: clientId,
|
|
100
|
+
redirect_uri: redirectUri,
|
|
101
|
+
scope: "user:email",
|
|
102
|
+
allow_signup: "true",
|
|
103
|
+
});
|
|
104
|
+
res.redirect(githubURL);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
async callback(code) {
|
|
108
|
+
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
client_id: clientId,
|
|
113
|
+
client_secret: clientSecret,
|
|
114
|
+
code,
|
|
115
|
+
redirect_uri: redirectUri,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const tokenData = await tokenRes.json();
|
|
120
|
+
if (tokenData.error) throw new Error("OAuth Error: " + tokenData.error);
|
|
121
|
+
|
|
122
|
+
const userRes = await fetch("https://api.github.com/user", {
|
|
123
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
124
|
+
});
|
|
125
|
+
const user = await userRes.json();
|
|
126
|
+
|
|
127
|
+
return { ...user, access_token: tokenData.access_token };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- X (TWITTER) LOGIN ---
|
|
133
|
+
x({ clientId, clientSecret, redirectUri }) {
|
|
134
|
+
return {
|
|
135
|
+
redirect(res) {
|
|
136
|
+
const twitterURL =
|
|
137
|
+
"https://twitter.com/i/oauth2/authorize?" +
|
|
138
|
+
new URLSearchParams({
|
|
139
|
+
response_type: "code",
|
|
140
|
+
client_id: clientId,
|
|
141
|
+
redirect_uri: redirectUri,
|
|
142
|
+
scope: "tweet.read users.read offline.access",
|
|
143
|
+
state: "state123",
|
|
144
|
+
code_challenge: "challenge",
|
|
145
|
+
code_challenge_method: "plain",
|
|
146
|
+
});
|
|
147
|
+
res.redirect(twitterURL);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
async callback(code) {
|
|
151
|
+
const tokenRes = await fetch("https://api.twitter.com/2/oauth2/token", {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
155
|
+
Authorization:
|
|
156
|
+
"Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64"),
|
|
157
|
+
},
|
|
158
|
+
body: new URLSearchParams({
|
|
159
|
+
code,
|
|
160
|
+
grant_type: "authorization_code",
|
|
161
|
+
redirect_uri: redirectUri,
|
|
162
|
+
code_verifier: "challenge",
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const tokenData = await tokenRes.json();
|
|
167
|
+
if (tokenData.error) throw new Error("OAuth Error: " + tokenData.error);
|
|
168
|
+
|
|
169
|
+
const userRes = await fetch("https://api.twitter.com/2/users/me", {
|
|
170
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
171
|
+
});
|
|
172
|
+
const user = await userRes.json();
|
|
173
|
+
|
|
174
|
+
return { ...user, access_token: tokenData.access_token };
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = OAuthManager;
|
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,49 @@ 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
|
+
const google = auth.oauth.google({clientId: '145870939941-qgeskqo8qlaqqm1osed6f5r8bhl866qk.apps.googleusercontent.com', clientSecret: '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', redirectUri: 'http://localhost:3000/auth/google/callback'});
|
|
157
|
+
app.get('/', async (req, res) => {
|
|
158
|
+
res.send(`
|
|
159
|
+
<h1>Login with Google</h1>
|
|
160
|
+
<a href="/auth/google">Login</a>
|
|
161
|
+
`);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
app.get('/auth/google', (req, res) => google.redirect(res));
|
|
166
|
+
|
|
167
|
+
app.get('/auth/google/callback', async (req, res)=>{
|
|
168
|
+
const code = req.query.code;
|
|
169
|
+
try {
|
|
170
|
+
const user = await google.callback(code);
|
|
171
|
+
res.send(`
|
|
172
|
+
<h2>Welcome, ${user.name}!</h2>
|
|
173
|
+
<img src="${user.picture}" width="100" style="border-radius:50%">
|
|
174
|
+
<p>Email: ${user.email}</p>
|
|
175
|
+
<p>Access Token: ${user.access_token.slice(0, 20)}...</p>
|
|
176
|
+
`);
|
|
177
|
+
} catch(err){
|
|
178
|
+
res.status(500).send("Error: " + err.message);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
app.listen(3000, ()=>{
|
|
183
|
+
console.log('Server is running...');
|
|
184
|
+
});
|
|
185
|
+
// app.get('/verify', async (req, res) => {
|
|
186
|
+
// try {
|
|
187
|
+
// const data = await auth.jwt.verify({ headers: req.headers });
|
|
188
|
+
// res.json({ valid: true, data });
|
|
189
|
+
// } catch (err) {
|
|
190
|
+
// res.status(401).json({ valid: false, error: err.message });
|
|
191
|
+
// }
|
|
192
|
+
// });
|
|
193
|
+
|
|
194
|
+
// app.listen(3000, ()=>{
|
|
195
|
+
// console.log('App is running')
|
|
196
|
+
// });
|
|
153
197
|
|
|
154
198
|
// auth.register.sender("consoleOtp", async ({ to, code }) => {
|
|
155
199
|
// console.log(`🔑 Sending OTP ${code} to ${to}`);
|