auth-verify 1.1.0 → 1.2.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/index.js +3 -0
- package/package.json +3 -2
- package/readme.md +170 -22
- package/src/jwt/index.js +1 -0
- package/src/oauth/index.js +240 -0
- package/test.js +32 -8
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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"uuid": "^13.0.0"
|
|
13
13
|
},
|
|
14
14
|
"name": "auth-verify",
|
|
15
|
-
"version": "1.1
|
|
15
|
+
"version": "1.2.1",
|
|
16
16
|
"description": "A simple Node.js library for sending and verifying OTP via email",
|
|
17
17
|
"main": "index.js",
|
|
18
18
|
"scripts": {
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"two-factor",
|
|
38
38
|
"2fa",
|
|
39
39
|
"email",
|
|
40
|
-
"jwt"
|
|
40
|
+
"jwt",
|
|
41
|
+
"oauth"
|
|
41
42
|
],
|
|
42
43
|
"author": "Jahongir Sobirov",
|
|
43
44
|
"license": "MIT",
|
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,11 +46,11 @@ const auth = new AuthVerify({
|
|
|
48
46
|
|
|
49
47
|
---
|
|
50
48
|
|
|
51
|
-
## JWT
|
|
49
|
+
## 🔐 JWT Usage
|
|
52
50
|
|
|
53
51
|
```js
|
|
54
52
|
// create JWT
|
|
55
|
-
const token = await auth.jwt.sign({ userId: 123 }, '1h'); // expiry string or number (ms)
|
|
53
|
+
const token = await auth.jwt.sign({ userId: 123 }, '1h'); // expiry string or number (ms) (and also you can add '1m' (minute), '5s' (second) and '7d' (day))
|
|
56
54
|
console.log('token', token);
|
|
57
55
|
|
|
58
56
|
// verify
|
|
@@ -71,7 +69,7 @@ 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
|
```
|
|
74
|
-
|
|
72
|
+
### 🍪 Automatic Cookie Handling (v1.1.0+)
|
|
75
73
|
|
|
76
74
|
You can now automatically store and verify JWTs via HTTP cookies — no need to manually send them!
|
|
77
75
|
```js
|
|
@@ -111,9 +109,9 @@ Notes:
|
|
|
111
109
|
|
|
112
110
|
---
|
|
113
111
|
|
|
114
|
-
## OTP (email / sms / telegram / custom sender)
|
|
112
|
+
## 🔢 OTP (email / sms / telegram / custom sender)
|
|
115
113
|
|
|
116
|
-
### Configure sender
|
|
114
|
+
### 🤝 Configure sender
|
|
117
115
|
|
|
118
116
|
You can set the default sender (email/sms/telegram):
|
|
119
117
|
|
|
@@ -145,7 +143,7 @@ auth.otp.setSender({
|
|
|
145
143
|
});
|
|
146
144
|
```
|
|
147
145
|
|
|
148
|
-
### Generate → Save → Send (chainable)
|
|
146
|
+
### ⛓️ Generate → Save → Send (chainable)
|
|
149
147
|
|
|
150
148
|
OTP generation is chainable: `generate()` returns the OTP manager instance.
|
|
151
149
|
|
|
@@ -176,7 +174,7 @@ await auth.otp.message({
|
|
|
176
174
|
});
|
|
177
175
|
```
|
|
178
176
|
|
|
179
|
-
### Verify
|
|
177
|
+
### ✔️ Verify
|
|
180
178
|
|
|
181
179
|
```js
|
|
182
180
|
// Promise style
|
|
@@ -188,6 +186,11 @@ try {
|
|
|
188
186
|
}
|
|
189
187
|
|
|
190
188
|
// Callback style also supported: auth.otp.verify({check, code}, callback)
|
|
189
|
+
auth.otp.verify({ check: 'user@example.com', code: '123456' }, (err, isValid)=>{
|
|
190
|
+
if(err) console.log(err);
|
|
191
|
+
if(isValid) console.log('Correct code!');
|
|
192
|
+
else console.log('Incorrect code!');
|
|
193
|
+
});
|
|
191
194
|
```
|
|
192
195
|
|
|
193
196
|
### Resend and cooldown / max attempts
|
|
@@ -198,6 +201,148 @@ try {
|
|
|
198
201
|
|
|
199
202
|
`resend` returns the new code (promise style) or calls callback.
|
|
200
203
|
|
|
204
|
+
---
|
|
205
|
+
## 🌍 OAuth 2.0 Integration (New in v1.2.0)
|
|
206
|
+
`auth.oauth` supports login via Google, Facebook, GitHub, X (Twitter) and Linkedin.
|
|
207
|
+
### Example (Google Login with Express)
|
|
208
|
+
```js
|
|
209
|
+
const express = require('express');
|
|
210
|
+
const AuthVerify = require("auth-verify");
|
|
211
|
+
const app = express();
|
|
212
|
+
app.use(express.json());
|
|
213
|
+
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
214
|
+
|
|
215
|
+
const google = auth.oauth.google({clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', redirectUri: 'http://localhost:3000/auth/google/callback'});
|
|
216
|
+
app.get('/', async (req, res) => {
|
|
217
|
+
res.send(`
|
|
218
|
+
<h1>Login with Google</h1>
|
|
219
|
+
<a href="/auth/google">Login</a>
|
|
220
|
+
`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
app.get('/auth/google', (req, res) => google.redirect(res));
|
|
225
|
+
|
|
226
|
+
app.get('/auth/google/callback', async (req, res)=>{
|
|
227
|
+
const code = req.query.code;
|
|
228
|
+
try {
|
|
229
|
+
const user = await google.callback(code);
|
|
230
|
+
res.send(`
|
|
231
|
+
<h2>Welcome, ${user.name}!</h2>
|
|
232
|
+
<img src="${user.picture}" width="100" style="border-radius:50%">
|
|
233
|
+
<p>Email: ${user.email}</p>
|
|
234
|
+
<p>Access Token: ${user.access_token.slice(0, 20)}...</p>
|
|
235
|
+
`);
|
|
236
|
+
} catch(err){
|
|
237
|
+
res.status(500).send("Error: " + err.message);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
app.listen(3000, ()=>{
|
|
242
|
+
console.log('Server is running...');
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
---
|
|
246
|
+
### API documentation for OAuth
|
|
247
|
+
- `auth.oauth.google({...})` for making connection to your Google cloud app.
|
|
248
|
+
- `google.redirect(res)` for sending user/client to the Google OAuth page for verifying and selecting his accaount
|
|
249
|
+
- `google.callback(code)` for exchanging server code to the user/client token.
|
|
250
|
+
|
|
251
|
+
### Other examples with other platforms
|
|
252
|
+
```js
|
|
253
|
+
const express = require('express');
|
|
254
|
+
const AuthVerify = require("auth-verify");
|
|
255
|
+
const app = express();
|
|
256
|
+
app.use(express.json());
|
|
257
|
+
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
258
|
+
|
|
259
|
+
// --- Example: FACEBOOK LOGIN ---
|
|
260
|
+
const facebook = auth.oauth.facebook({
|
|
261
|
+
clientId: "YOUR_FB_APP_ID",
|
|
262
|
+
clientSecret: "YOUR_FB_APP_SECRET",
|
|
263
|
+
redirectUri: "http://localhost:3000/auth/facebook/callback",
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// --- Example: GITHUB LOGIN ---
|
|
267
|
+
const github = auth.oauth.github({
|
|
268
|
+
clientId: "YOUR_GITHUB_CLIENT_ID",
|
|
269
|
+
clientSecret: "YOUR_GITHUB_CLIENT_SECRET",
|
|
270
|
+
redirectUri: "http://localhost:3000/auth/github/callback",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// --- Example: X (Twitter) LOGIN ---
|
|
274
|
+
const twitter = auth.oauth.x({
|
|
275
|
+
clientId: "YOUR_TWITTER_CLIENT_ID",
|
|
276
|
+
clientSecret: "YOUR_TWITTER_CLIENT_SECRET",
|
|
277
|
+
redirectUri: "http://localhost:3000/auth/x/callback",
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// --- Example: Linkedin LOGIN ---
|
|
281
|
+
const linkedin = auth.oauth.linkedin({
|
|
282
|
+
clientId: "YOUR_LINKEDIN_CLIENT_ID",
|
|
283
|
+
clientSecret: "YOUR_LINKEDIN_CLIENT_SECRET",
|
|
284
|
+
redirectUri: "http://localhost:3000/auth/linkedin/callback"
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
// ===== FACEBOOK ROUTES =====
|
|
289
|
+
app.get("/auth/facebook", (req, res) => facebook.redirect(res));
|
|
290
|
+
|
|
291
|
+
app.get("/auth/facebook/callback", async (req, res) => {
|
|
292
|
+
try {
|
|
293
|
+
const { code } = req.query;
|
|
294
|
+
const user = await facebook.callback(code);
|
|
295
|
+
res.json({ success: true, provider: "facebook", user });
|
|
296
|
+
} catch (err) {
|
|
297
|
+
res.status(400).json({ error: err.message });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
// ===== GITHUB ROUTES =====
|
|
303
|
+
app.get("/auth/github", (req, res) => github.redirect(res));
|
|
304
|
+
|
|
305
|
+
app.get("/auth/github/callback", async (req, res) => {
|
|
306
|
+
try {
|
|
307
|
+
const { code } = req.query;
|
|
308
|
+
const user = await github.callback(code);
|
|
309
|
+
res.json({ success: true, provider: "github", user });
|
|
310
|
+
} catch (err) {
|
|
311
|
+
res.status(400).json({ error: err.message });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
// ===== X (TWITTER) ROUTES =====
|
|
317
|
+
app.get("/auth/x", (req, res) => twitter.redirect(res));
|
|
318
|
+
|
|
319
|
+
app.get("/auth/x/callback", async (req, res) => {
|
|
320
|
+
try {
|
|
321
|
+
const { code } = req.query;
|
|
322
|
+
const user = await twitter.callback(code);
|
|
323
|
+
res.json({ success: true, provider: "x", user });
|
|
324
|
+
} catch (err) {
|
|
325
|
+
res.status(400).json({ error: err.message });
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ==== LINKEDIN ROUTES ====
|
|
330
|
+
app.get("/auth/linkedin", (req, res) => linkedin.redirect(res));
|
|
331
|
+
|
|
332
|
+
app.get("/auth/linkedin/callback", async (req, res)=>{
|
|
333
|
+
try{
|
|
334
|
+
const { code } = req.query;
|
|
335
|
+
const user = await linkedin.callback(code);
|
|
336
|
+
res.json({ success: true, provider: "x", user });
|
|
337
|
+
}catch(err){
|
|
338
|
+
res.status(400).json({ error: err.message });
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
app.listen(PORT, () => console.log(`🚀 Server running at http://localhost:${PORT}`));
|
|
344
|
+
|
|
345
|
+
```
|
|
201
346
|
---
|
|
202
347
|
|
|
203
348
|
## Telegram integration
|
|
@@ -281,9 +426,12 @@ auth-verify/
|
|
|
281
426
|
├─ package.json
|
|
282
427
|
├─ src/
|
|
283
428
|
│ ├─ index.js // exports AuthVerify
|
|
284
|
-
│ ├─ jwt
|
|
285
|
-
|
|
286
|
-
|
|
429
|
+
│ ├─ jwt/
|
|
430
|
+
| | ├─ index.js
|
|
431
|
+
| | ├─ cookie/index.js
|
|
432
|
+
│ ├─ /otp/index.js
|
|
433
|
+
│ ├─ /session/index.js
|
|
434
|
+
| ├─ /oauth/index.js
|
|
287
435
|
│ └─ helpers/helper.js
|
|
288
436
|
```
|
|
289
437
|
|
package/src/jwt/index.js
CHANGED
|
@@ -265,6 +265,7 @@ class JWTManager {
|
|
|
265
265
|
if (typeof expiry === "number") return expiry;
|
|
266
266
|
if (typeof expiry !== "string") return 3600000; // default 1h
|
|
267
267
|
const num = parseInt(expiry);
|
|
268
|
+
if (expiry.endsWith("d")) return num * 3600000 * 24;
|
|
268
269
|
if (expiry.endsWith("h")) return num * 3600000;
|
|
269
270
|
if (expiry.endsWith("m")) return num * 60000;
|
|
270
271
|
if (expiry.endsWith("s")) return num * 1000;
|
|
@@ -0,0 +1,240 @@
|
|
|
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
|
+
// --- LINKEDIN LOGIN ---
|
|
180
|
+
linkedin({ clientId, clientSecret, redirectUri }) {
|
|
181
|
+
return {
|
|
182
|
+
// Step 1: Redirect user to LinkedIn's authorization page
|
|
183
|
+
redirect(res) {
|
|
184
|
+
const linkedinURL =
|
|
185
|
+
"https://www.linkedin.com/oauth/v2/authorization?" +
|
|
186
|
+
new URLSearchParams({
|
|
187
|
+
response_type: "code",
|
|
188
|
+
client_id: clientId,
|
|
189
|
+
redirect_uri: redirectUri,
|
|
190
|
+
scope: "r_liteprofile r_emailaddress",
|
|
191
|
+
state: "secure123", // optional: you can randomize this
|
|
192
|
+
});
|
|
193
|
+
res.redirect(linkedinURL);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Step 2: Handle callback, exchange code for token, then get user data
|
|
197
|
+
async callback(code) {
|
|
198
|
+
// Exchange authorization code for access token
|
|
199
|
+
const tokenRes = await fetch("https://www.linkedin.com/oauth/v2/accessToken", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
202
|
+
body: new URLSearchParams({
|
|
203
|
+
grant_type: "authorization_code",
|
|
204
|
+
code,
|
|
205
|
+
redirect_uri: redirectUri,
|
|
206
|
+
client_id: clientId,
|
|
207
|
+
client_secret: clientSecret,
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const tokenData = await tokenRes.json();
|
|
212
|
+
if (tokenData.error)
|
|
213
|
+
throw new Error("OAuth Error: " + tokenData.error_description);
|
|
214
|
+
|
|
215
|
+
// Fetch basic profile info
|
|
216
|
+
const profileRes = await fetch("https://api.linkedin.com/v2/me", {
|
|
217
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
|
218
|
+
});
|
|
219
|
+
const profile = await profileRes.json();
|
|
220
|
+
|
|
221
|
+
// Fetch user's primary email
|
|
222
|
+
const emailRes = await fetch(
|
|
223
|
+
"https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))",
|
|
224
|
+
{ headers: { Authorization: `Bearer ${tokenData.access_token}` } }
|
|
225
|
+
);
|
|
226
|
+
const emailData = await emailRes.json();
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
id: profile.id,
|
|
230
|
+
name: profile.localizedFirstName + " " + profile.localizedLastName,
|
|
231
|
+
email: emailData.elements?.[0]?.["handle~"]?.emailAddress || null,
|
|
232
|
+
access_token: tokenData.access_token,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = OAuthManager;
|
package/test.js
CHANGED
|
@@ -153,23 +153,47 @@ app.use(express.json());
|
|
|
153
153
|
|
|
154
154
|
const auth = new AuthVerify({ jwtSecret: 's', storeTokens: 'memory'});
|
|
155
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'});
|
|
156
157
|
app.get('/', async (req, res) => {
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
res.send(`
|
|
159
|
+
<h1>Login with Google</h1>
|
|
160
|
+
<a href="/auth/google">Login</a>
|
|
161
|
+
`);
|
|
159
162
|
});
|
|
160
163
|
|
|
161
|
-
|
|
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;
|
|
162
169
|
try {
|
|
163
|
-
const
|
|
164
|
-
res.
|
|
165
|
-
|
|
166
|
-
|
|
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);
|
|
167
179
|
}
|
|
168
180
|
});
|
|
169
181
|
|
|
170
182
|
app.listen(3000, ()=>{
|
|
171
|
-
console.log('
|
|
183
|
+
console.log('Server is running...');
|
|
172
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
|
+
// });
|
|
173
197
|
|
|
174
198
|
// auth.register.sender("consoleOtp", async ({ to, code }) => {
|
|
175
199
|
// console.log(`🔑 Sending OTP ${code} to ${to}`);
|