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 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.3",
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
- - Developer extensibility: custom senders and `auth.register.sender()` / `auth.use(name).send(...)`
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 usage
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.js
251
- ├─ otp.js
252
- ├─ session.js
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 { parseTime } = require('../helpers/helper');
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 !== 'none') {
12
- if (this.storeType === 'memory') {
13
- this.tokenStore = new Map();
14
- } else if (this.storeType === 'redis') {
15
- this.redis = new Redis(options.redisUrl || "redis://localhost:6379");
16
- } else {
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 === 'number') return expiry;
25
- if (typeof expiry !== 'string') return 3600000; // default 1h
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('h')) return num * 60 * 60 * 1000;
29
- if (expiry.endsWith('m')) return num * 60 * 1000;
30
- if (expiry.endsWith('s')) return num * 1000;
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 === 'function') {
274
+ async sign(payload, expiry = "1h", options = {}, callback) {
275
+ if (typeof expiry === "function") {
36
276
  callback = expiry;
37
- expiry = '1h'; // default
277
+ expiry = "1h";
278
+ }
279
+ if (typeof options === "function") {
280
+ callback = options;
281
+ options = {};
38
282
  }
39
283
 
40
- const expiryMs = this._parseExpiry(expiry || '1h'); // fallback to '1h'
41
- const expiryJwt = typeof expiry === 'number'
42
- ? Math.floor(expiry / 1000)
43
- : (typeof expiry === 'string' ? expiry : '1h');
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
- jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, token) => {
50
- if(err) return callback(err);
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
- if(this.storeType === 'memory') {
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
- callback(null, token);
58
- });
59
- return;
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
- // Async version
63
- const token = await new Promise((res, rej) => {
64
- jwt.sign(payload, this.secret, { expiresIn: expiryJwt }, (err, t) => {
65
- if(err) rej(err);
66
- else res(t);
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(token, callback) {
81
- // Callback version
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
- if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
88
- return callback(new Error("❌ Token not found or revoked"));
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
- jwt.verify(token, this.secret, (err, decoded) => {
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
- if (this.storeType === 'memory' && !this.tokenStore.has(token)) {
105
- throw new Error("❌ Token not found or revoked");
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 === 'redis') {
341
+ if (this.storeType === "redis") {
109
342
  const data = await this.redis.get(token);
110
- if (!data) throw new Error("Token not found or revoked");
343
+ if (!data) throw new Error("Token not found or revoked");
111
344
  }
112
345
 
113
- const decoded = jwt.verify(token, this.secret);
346
+ if (callback) return callback(null, decoded);
114
347
  return decoded;
115
348
  } catch (err) {
116
- throw new Error("❌ JWT verification failed: " + err.message);
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, callback) {
121
- try {
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
- // function parseTime(str) {
134
- // if (typeof str === 'number') return str; // already in ms
135
- // if (typeof str !== 'string') return 0;
136
-
137
- // const num = parseInt(str);
138
- // if (str.endsWith('h')) return num * 60 * 60 * 1000;
139
- // if (str.endsWith('m')) return num * 60 * 1000;
140
- // if (str.endsWith('s')) return num * 1000;
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 === 'memory') {
167
- const data = this.tokenStore.get(token);
168
- return data?.revokedUntil && data.revokedUntil > Date.now();
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
- // function parseTime(str) {
181
- // if (typeof str === 'number') return str; // already in ms
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 == 'memory'){
380
+ if (this.storeType === "memory") {
195
381
  const data = this.tokenStore.get(token) || {};
196
- this.tokenStore.set(token, {...data, revokedUntil: revokedUntil});
197
- }else if(this.storeType == 'redis'){
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', otpExpiry: '5m' });
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}`);