@triophore/falconjs 1.0.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/FalconAuthPlugin.js +473 -0
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/core/auth.js +200 -0
- package/core/cache/redis_cacher.js +7 -0
- package/core/check_collection.js +9 -0
- package/core/crypto/encrypt_decrypt.js +19 -0
- package/core/errors.js +48 -0
- package/core/logger/log4js.js +89 -0
- package/core/logo.js +3 -0
- package/core/mongo/generateModelfromJsonFile.js +128 -0
- package/core/mongo/mongoSchmeFromJson.js +90 -0
- package/core/parse_num.js +8 -0
- package/core/rannum.js +33 -0
- package/core/ranstring.js +33 -0
- package/core/recursive-require-call.js +121 -0
- package/core/uitls/mongoose_to_joi.js +72 -0
- package/core/uitls/return.js +7 -0
- package/falcon.js +1644 -0
- package/falconAuthPlugin.js +17 -0
- package/falconBaseService.js +532 -0
- package/falconBaseWorker.js +540 -0
- package/index.js +4 -0
- package/out/Falcon.html +777 -0
- package/out/falcon.js.html +525 -0
- package/out/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/out/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/out/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/out/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Light-webfont.eot +0 -0
- package/out/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Light-webfont.woff +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/out/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/out/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/out/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/out/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/out/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/out/index.html +65 -0
- package/out/scripts/linenumber.js +25 -0
- package/out/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/out/scripts/prettify/lang-css.js +2 -0
- package/out/scripts/prettify/prettify.js +28 -0
- package/out/styles/jsdoc-default.css +358 -0
- package/out/styles/prettify-jsdoc.css +111 -0
- package/out/styles/prettify-tomorrow.css +132 -0
- package/package.json +106 -0
- package/settings.js +1 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Joi = require('joi');
|
|
4
|
+
const Boom = require('@hapi/boom');
|
|
5
|
+
const { jwtVerify, createRemoteJWKSet, SignJWT } = require('jose');
|
|
6
|
+
const Cookie = require('@hapi/cookie');
|
|
7
|
+
const Basic = require('@hapi/basic');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generalized Authentication Plugin for Falcon.js
|
|
11
|
+
* Supports custom auth, JWT, Cookie, JWKS with caching and logging
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class FalconAuth {
|
|
15
|
+
constructor(options = {}, context = {}) {
|
|
16
|
+
this.options = this.validateOptions(options);
|
|
17
|
+
this.context = context;
|
|
18
|
+
this.logger = context.logger || console;
|
|
19
|
+
this.redis = context.redis;
|
|
20
|
+
this.jwksCache = new Map();
|
|
21
|
+
this.remoteJWKSets = new Map();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validateOptions(options) {
|
|
25
|
+
const schema = Joi.object({
|
|
26
|
+
custom: Joi.object({
|
|
27
|
+
validate: Joi.func().required(),
|
|
28
|
+
extractToken: Joi.func().optional(),
|
|
29
|
+
name: Joi.string().default('custom')
|
|
30
|
+
}).optional(),
|
|
31
|
+
|
|
32
|
+
jwt: Joi.object({
|
|
33
|
+
secret: Joi.string().required(),
|
|
34
|
+
algorithms: Joi.array().items(Joi.string()).default(['HS256']),
|
|
35
|
+
validate: Joi.func().required(),
|
|
36
|
+
cache: Joi.object({
|
|
37
|
+
enabled: Joi.boolean().default(true),
|
|
38
|
+
ttl: Joi.number().default(300) // 5 minutes
|
|
39
|
+
}).default({ enabled: true, ttl: 300 })
|
|
40
|
+
}).optional(),
|
|
41
|
+
|
|
42
|
+
cookie: Joi.object({
|
|
43
|
+
password: Joi.string().min(32).required(),
|
|
44
|
+
name: Joi.string().default('falcon-session'),
|
|
45
|
+
ttl: Joi.number().default(24 * 60 * 60 * 1000),
|
|
46
|
+
isSecure: Joi.boolean().default(true),
|
|
47
|
+
isHttpOnly: Joi.boolean().default(true),
|
|
48
|
+
validate: Joi.func().required()
|
|
49
|
+
}).optional(),
|
|
50
|
+
|
|
51
|
+
jwks: Joi.object({
|
|
52
|
+
jwksUri: Joi.string().uri().required(),
|
|
53
|
+
issuer: Joi.string().optional(),
|
|
54
|
+
audience: Joi.string().optional(),
|
|
55
|
+
algorithms: Joi.array().items(Joi.string()).default(['RS256']),
|
|
56
|
+
validate: Joi.func().required(),
|
|
57
|
+
cache: Joi.object({
|
|
58
|
+
enabled: Joi.boolean().default(true),
|
|
59
|
+
ttl: Joi.number().default(3600) // 1 hour
|
|
60
|
+
}).default({ enabled: true, ttl: 3600 })
|
|
61
|
+
}).optional(),
|
|
62
|
+
|
|
63
|
+
socketio: Joi.object({
|
|
64
|
+
enabled: Joi.boolean().default(true),
|
|
65
|
+
timeout: Joi.number().default(5000)
|
|
66
|
+
}).default({ enabled: true, timeout: 5000 })
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const { error, value } = schema.validate(options);
|
|
70
|
+
if (error) throw error;
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Hapi.js Plugin Registration
|
|
75
|
+
async registerHapi(server) {
|
|
76
|
+
await server.register([Cookie, Basic]);
|
|
77
|
+
|
|
78
|
+
// Custom Authentication
|
|
79
|
+
if (this.options.custom) {
|
|
80
|
+
await this.registerCustom(server);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// JWT Authentication
|
|
84
|
+
if (this.options.jwt) {
|
|
85
|
+
await this.registerJWT(server);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cookie Authentication
|
|
89
|
+
if (this.options.cookie) {
|
|
90
|
+
await this.registerCookie(server);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// JWKS Authentication
|
|
94
|
+
if (this.options.jwks) {
|
|
95
|
+
await this.registerJWKS(server);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set default strategy if only one is configured
|
|
99
|
+
const strategies = Object.keys(this.options).filter(k => k !== 'socketio');
|
|
100
|
+
if (strategies.length === 1) {
|
|
101
|
+
server.auth.default(strategies[0]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async registerCustom(server) {
|
|
106
|
+
const customAuth = {
|
|
107
|
+
authenticate: async (request, h) => {
|
|
108
|
+
try {
|
|
109
|
+
const token = this.extractToken(request, this.options.custom.extractToken);
|
|
110
|
+
if (!token) {
|
|
111
|
+
throw Boom.unauthorized('Authentication required');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check cache first
|
|
115
|
+
const cacheKey = `auth:custom:${this.hashToken(token)}`;
|
|
116
|
+
let user = await this.getFromCache(cacheKey);
|
|
117
|
+
|
|
118
|
+
if (!user) {
|
|
119
|
+
user = await this.options.custom.validate(token, request, this.context);
|
|
120
|
+
if (user) {
|
|
121
|
+
await this.setCache(cacheKey, user, 300); // 5 minutes
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!user) {
|
|
126
|
+
throw Boom.unauthorized('Invalid authentication');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return h.authenticated({ credentials: user });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.logger.warn('Custom auth failed:', error.message);
|
|
132
|
+
throw Boom.unauthorized('Authentication failed');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
server.auth.scheme('falcon-custom', () => customAuth);
|
|
138
|
+
server.auth.strategy(this.options.custom.name, 'falcon-custom');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async registerJWT(server) {
|
|
142
|
+
const jwtAuth = require('hapi-auth-jwt2');
|
|
143
|
+
await server.register(jwtAuth);
|
|
144
|
+
|
|
145
|
+
server.auth.strategy('jwt', 'jwt', {
|
|
146
|
+
key: this.options.jwt.secret,
|
|
147
|
+
validate: async (decoded, request) => {
|
|
148
|
+
try {
|
|
149
|
+
// Check cache first
|
|
150
|
+
const cacheKey = `auth:jwt:${decoded.sub || decoded.id}`;
|
|
151
|
+
let user = null;
|
|
152
|
+
|
|
153
|
+
if (this.options.jwt.cache.enabled) {
|
|
154
|
+
user = await this.getFromCache(cacheKey);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!user) {
|
|
158
|
+
user = await this.options.jwt.validate(decoded, request, this.context);
|
|
159
|
+
if (user && this.options.jwt.cache.enabled) {
|
|
160
|
+
await this.setCache(cacheKey, user, this.options.jwt.cache.ttl);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { isValid: !!user, credentials: user };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.logger.warn('JWT validation failed:', error.message);
|
|
167
|
+
return { isValid: false };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
verifyOptions: {
|
|
171
|
+
algorithms: this.options.jwt.algorithms
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async registerCookie(server) {
|
|
177
|
+
server.auth.strategy('cookie', 'cookie', {
|
|
178
|
+
cookie: {
|
|
179
|
+
name: this.options.cookie.name,
|
|
180
|
+
password: this.options.cookie.password,
|
|
181
|
+
ttl: this.options.cookie.ttl,
|
|
182
|
+
isSecure: this.options.cookie.isSecure,
|
|
183
|
+
isHttpOnly: this.options.cookie.isHttpOnly,
|
|
184
|
+
clearInvalid: true,
|
|
185
|
+
strictHeader: true
|
|
186
|
+
},
|
|
187
|
+
redirectTo: false,
|
|
188
|
+
validate: async (request, session) => {
|
|
189
|
+
try {
|
|
190
|
+
const result = await this.options.cookie.validate(request, session, this.context);
|
|
191
|
+
return { isValid: !!result, credentials: result };
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger.warn('Cookie validation failed:', error.message);
|
|
194
|
+
return { isValid: false };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async registerJWKS(server) {
|
|
201
|
+
const jwtAuth = require('hapi-auth-jwt2');
|
|
202
|
+
await server.register(jwtAuth);
|
|
203
|
+
|
|
204
|
+
// Create remote JWKS using JOSE
|
|
205
|
+
const JWKS = createRemoteJWKSet(new URL(this.options.jwks.jwksUri));
|
|
206
|
+
this.remoteJWKSets.set(this.options.jwks.jwksUri, JWKS);
|
|
207
|
+
|
|
208
|
+
server.auth.strategy('jwks', 'jwt', {
|
|
209
|
+
key: async (decoded) => {
|
|
210
|
+
try {
|
|
211
|
+
// Use JOSE for JWKS verification
|
|
212
|
+
const { payload } = await jwtVerify(decoded, JWKS, {
|
|
213
|
+
issuer: this.options.jwks.issuer,
|
|
214
|
+
audience: this.options.jwks.audience
|
|
215
|
+
});
|
|
216
|
+
return payload;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.logger.warn('JWKS verification failed:', error.message);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
validate: async (decoded, request) => {
|
|
223
|
+
try {
|
|
224
|
+
// Check cache first
|
|
225
|
+
const cacheKey = `auth:jwks:${decoded.sub}`;
|
|
226
|
+
let user = null;
|
|
227
|
+
|
|
228
|
+
if (this.options.jwks.cache.enabled) {
|
|
229
|
+
user = await this.getFromCache(cacheKey);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!user) {
|
|
233
|
+
user = await this.options.jwks.validate(decoded, request, this.context);
|
|
234
|
+
if (user && this.options.jwks.cache.enabled) {
|
|
235
|
+
await this.setCache(cacheKey, user, this.options.jwks.cache.ttl);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { isValid: !!user, credentials: user };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
this.logger.warn('JWKS validation failed:', error.message);
|
|
242
|
+
return { isValid: false };
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
verifyOptions: {
|
|
246
|
+
algorithms: this.options.jwks.algorithms
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Socket.IO Authentication Middleware
|
|
252
|
+
createSocketIOMiddleware() {
|
|
253
|
+
if (!this.options.socketio.enabled) {
|
|
254
|
+
return (socket, next) => next();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return async (socket, next) => {
|
|
258
|
+
try {
|
|
259
|
+
const token = this.extractSocketToken(socket);
|
|
260
|
+
if (!token) {
|
|
261
|
+
return next(new Error('Authentication required'));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const user = await this.validateSocketToken(token);
|
|
265
|
+
if (!user) {
|
|
266
|
+
return next(new Error('Invalid authentication'));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
socket.user = user;
|
|
270
|
+
socket.authenticated = true;
|
|
271
|
+
this.logger.info(`Socket.IO user authenticated: ${user.id || 'unknown'}`);
|
|
272
|
+
next();
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.logger.warn('Socket.IO auth failed:', error.message);
|
|
275
|
+
next(new Error('Authentication failed: ' + error.message));
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
extractSocketToken(socket) {
|
|
281
|
+
const { token, auth, authorization } = socket.handshake.auth || {};
|
|
282
|
+
|
|
283
|
+
if (token) return token;
|
|
284
|
+
if (auth) return auth;
|
|
285
|
+
|
|
286
|
+
const authHeader = socket.handshake.headers.authorization;
|
|
287
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
288
|
+
return authHeader.substring(7);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return socket.handshake.query.token;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async validateSocketToken(token) {
|
|
295
|
+
// Try custom auth first
|
|
296
|
+
if (this.options.custom) {
|
|
297
|
+
try {
|
|
298
|
+
const cacheKey = `auth:custom:${this.hashToken(token)}`;
|
|
299
|
+
let user = await this.getFromCache(cacheKey);
|
|
300
|
+
|
|
301
|
+
if (!user) {
|
|
302
|
+
user = await this.options.custom.validate(token, null, this.context);
|
|
303
|
+
if (user) {
|
|
304
|
+
await this.setCache(cacheKey, user, 300);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (user) return user;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
this.logger.debug('Custom socket auth failed:', error.message);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Try JWT
|
|
315
|
+
if (this.options.jwt) {
|
|
316
|
+
try {
|
|
317
|
+
const jwt = require('jsonwebtoken');
|
|
318
|
+
const decoded = jwt.verify(token, this.options.jwt.secret, {
|
|
319
|
+
algorithms: this.options.jwt.algorithms
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const cacheKey = `auth:jwt:${decoded.sub || decoded.id}`;
|
|
323
|
+
let user = null;
|
|
324
|
+
|
|
325
|
+
if (this.options.jwt.cache.enabled) {
|
|
326
|
+
user = await this.getFromCache(cacheKey);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (!user) {
|
|
330
|
+
user = await this.options.jwt.validate(decoded, null, this.context);
|
|
331
|
+
if (user && this.options.jwt.cache.enabled) {
|
|
332
|
+
await this.setCache(cacheKey, user, this.options.jwt.cache.ttl);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (user) return user;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
this.logger.debug('JWT socket auth failed:', error.message);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Try JWKS
|
|
343
|
+
if (this.options.jwks) {
|
|
344
|
+
try {
|
|
345
|
+
const JWKS = this.remoteJWKSets.get(this.options.jwks.jwksUri);
|
|
346
|
+
if (JWKS) {
|
|
347
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
348
|
+
issuer: this.options.jwks.issuer,
|
|
349
|
+
audience: this.options.jwks.audience
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const cacheKey = `auth:jwks:${payload.sub}`;
|
|
353
|
+
let user = null;
|
|
354
|
+
|
|
355
|
+
if (this.options.jwks.cache.enabled) {
|
|
356
|
+
user = await this.getFromCache(cacheKey);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!user) {
|
|
360
|
+
user = await this.options.jwks.validate(payload, null, this.context);
|
|
361
|
+
if (user && this.options.jwks.cache.enabled) {
|
|
362
|
+
await this.setCache(cacheKey, user, this.options.jwks.cache.ttl);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (user) return user;
|
|
367
|
+
}
|
|
368
|
+
} catch (error) {
|
|
369
|
+
this.logger.debug('JWKS socket auth failed:', error.message);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Utility methods
|
|
377
|
+
extractToken(request, customExtractor) {
|
|
378
|
+
if (customExtractor) {
|
|
379
|
+
return customExtractor(request);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const authHeader = request.headers.authorization;
|
|
383
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
384
|
+
return authHeader.substring(7);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return request.query.token || request.payload?.token;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
hashToken(token) {
|
|
391
|
+
const crypto = require('crypto');
|
|
392
|
+
return crypto.createHash('sha256').update(token).digest('hex').substring(0, 16);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async getFromCache(key) {
|
|
396
|
+
if (!this.redis) return null;
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const cached = await this.redis.get(key);
|
|
400
|
+
return cached ? JSON.parse(cached) : null;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
this.logger.warn('Cache get failed:', error.message);
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async setCache(key, value, ttl) {
|
|
408
|
+
if (!this.redis) return;
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
await this.redis.setEx(key, ttl, JSON.stringify(value));
|
|
412
|
+
} catch (error) {
|
|
413
|
+
this.logger.warn('Cache set failed:', error.message);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Manual token validation for custom middleware
|
|
418
|
+
async validateToken(token, type = 'auto') {
|
|
419
|
+
if (type === 'auto' || type === 'custom') {
|
|
420
|
+
if (this.options.custom) {
|
|
421
|
+
try {
|
|
422
|
+
return await this.options.custom.validate(token, null, this.context);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (type === 'custom') throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (type === 'auto' || type === 'jwt') {
|
|
430
|
+
if (this.options.jwt) {
|
|
431
|
+
try {
|
|
432
|
+
const jwt = require('jsonwebtoken');
|
|
433
|
+
const decoded = jwt.verify(token, this.options.jwt.secret);
|
|
434
|
+
return await this.options.jwt.validate(decoded, null, this.context);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (type === 'jwt') throw error;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (type === 'auto' || type === 'jwks') {
|
|
442
|
+
if (this.options.jwks) {
|
|
443
|
+
try {
|
|
444
|
+
const JWKS = this.remoteJWKSets.get(this.options.jwks.jwksUri);
|
|
445
|
+
const { payload } = await jwtVerify(token, JWKS);
|
|
446
|
+
return await this.options.jwks.validate(payload, null, this.context);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (type === 'jwks') throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
throw new Error('Invalid token or unsupported type');
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Hapi.js Plugin Export
|
|
458
|
+
const plugin = {
|
|
459
|
+
name: 'falcon-auth',
|
|
460
|
+
version: '2.0.0',
|
|
461
|
+
register: async function (server, options) {
|
|
462
|
+
// Get context from server app (passed from Falcon.js)
|
|
463
|
+
const context = server.app.falconContext || {};
|
|
464
|
+
|
|
465
|
+
const falconAuth = new FalconAuth(options, context);
|
|
466
|
+
await falconAuth.registerHapi(server);
|
|
467
|
+
|
|
468
|
+
// Expose auth instance for Socket.IO use
|
|
469
|
+
server.app.falconAuth = falconAuth;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
module.exports = { plugin, FalconAuth };
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Triophore Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/core/auth.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const FormData = require('form-data');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AuthBuilder {
|
|
6
|
+
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this._state_got_json = false;
|
|
10
|
+
this.log = console.log
|
|
11
|
+
this.intervalID = null;
|
|
12
|
+
this.tick = 0
|
|
13
|
+
this.expires_seconds = 0
|
|
14
|
+
this.access_token = null;
|
|
15
|
+
this.scope = null;
|
|
16
|
+
this.api_domain = null;
|
|
17
|
+
this.token_type = null;
|
|
18
|
+
// this.grant_token = null;
|
|
19
|
+
// this.refresh_token = null;
|
|
20
|
+
this.run_timer_grant = false
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async refreshGrantToken() {
|
|
25
|
+
try {
|
|
26
|
+
const _temp_access = await this.getAccessToken();
|
|
27
|
+
if (_temp_access.status == 200) {
|
|
28
|
+
if (_temp_access.data) {
|
|
29
|
+
this.log("network is connected")
|
|
30
|
+
this.log("waiting for token")
|
|
31
|
+
// this.log(_temp_access.data)
|
|
32
|
+
if (_temp_access.data.error) {
|
|
33
|
+
this.log("error in request")
|
|
34
|
+
this.log(_temp_access.data.error)
|
|
35
|
+
this.log("error in request")
|
|
36
|
+
} else {
|
|
37
|
+
if (this.is_valid_token(_temp_access.data)) {
|
|
38
|
+
this.log("valid token")
|
|
39
|
+
this.log(_temp_access.data)
|
|
40
|
+
this.log("valid token")
|
|
41
|
+
|
|
42
|
+
if (this.run_timer_grant) {
|
|
43
|
+
this.log("starting token watcher..")
|
|
44
|
+
const self = this;
|
|
45
|
+
if (this.intervalID) {
|
|
46
|
+
clearInterval(this.intervalID);
|
|
47
|
+
}
|
|
48
|
+
this.intervalID = setInterval(async function () {
|
|
49
|
+
await self.timer_tick();
|
|
50
|
+
}, 1000);
|
|
51
|
+
this.log("starting token watcher..")
|
|
52
|
+
}
|
|
53
|
+
this.expires_seconds = _temp_access.data.expires_in - 200;
|
|
54
|
+
this.access_token = _temp_access.data.access_token;
|
|
55
|
+
this.scope = _temp_access.data.scope;
|
|
56
|
+
this.api_domain = _temp_access.data.api_domain;
|
|
57
|
+
this.token_type = _temp_access.data.token_type;
|
|
58
|
+
// await this.fileStore("./code.txt",JSON.stringify(_temp_access.data,null,2) + "\n")
|
|
59
|
+
// await this.GetRefreshToken()
|
|
60
|
+
} else {
|
|
61
|
+
this.log("invalid token")
|
|
62
|
+
this.log(_temp_access.data)
|
|
63
|
+
this.log("invalid token")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
this.log("network is connection failed")
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.log("network is connection failed")
|
|
72
|
+
this.log(error)
|
|
73
|
+
this.log("network is connection failed")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async fileStore(fpath,fdata){
|
|
78
|
+
await require("fs/promises").appendFile(fpath,fdata)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async timer_tick() {
|
|
82
|
+
this.log("Timer tick.. " + this.tick + " -- Exp -- " + this.expires_seconds)
|
|
83
|
+
this.tick = this.tick + 1;
|
|
84
|
+
|
|
85
|
+
if (this.tick === this.expires_seconds) {
|
|
86
|
+
this.log("Getting new refresh token..")
|
|
87
|
+
await this.refreshGrantToken();
|
|
88
|
+
this.tick = 0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
is_valid_token(token_schema) {
|
|
93
|
+
/*
|
|
94
|
+
{
|
|
95
|
+
access_token: '1000.7b8a17c59a012b163f9f2372059ef69d.ea44578a4cb4c0bb24808dea929917f3',
|
|
96
|
+
scope: 'ZohoCRM.modules.leads.ALL',
|
|
97
|
+
api_domain: 'https://www.zohoapis.com',
|
|
98
|
+
token_type: 'Bearer',
|
|
99
|
+
expires_in: 3600
|
|
100
|
+
}
|
|
101
|
+
*/
|
|
102
|
+
if (
|
|
103
|
+
token_schema.hasOwnProperty('access_token') &&
|
|
104
|
+
token_schema.hasOwnProperty('scope') &&
|
|
105
|
+
token_schema.hasOwnProperty('api_domain') &&
|
|
106
|
+
token_schema.hasOwnProperty('token_type') &&
|
|
107
|
+
token_schema.hasOwnProperty('expires_in')
|
|
108
|
+
) {
|
|
109
|
+
return true;
|
|
110
|
+
} else {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// is_valid_token_access_refresh(token_schema) {
|
|
116
|
+
// /*
|
|
117
|
+
// {
|
|
118
|
+
// "access_token": "{access_token}",
|
|
119
|
+
// "refresh_token": "{refresh_token}",
|
|
120
|
+
// "api_domain": "https://www.zohoapis.com",
|
|
121
|
+
// "token_type": "Bearer",
|
|
122
|
+
// "expires_in": 3600
|
|
123
|
+
// }
|
|
124
|
+
// */
|
|
125
|
+
// if (
|
|
126
|
+
// token_schema.hasOwnProperty('access_token') &&
|
|
127
|
+
// token_schema.hasOwnProperty('refresh_token') &&
|
|
128
|
+
// token_schema.hasOwnProperty('api_domain') &&
|
|
129
|
+
// token_schema.hasOwnProperty('token_type') &&
|
|
130
|
+
// token_schema.hasOwnProperty('expires_in')
|
|
131
|
+
// ) {
|
|
132
|
+
// return true;
|
|
133
|
+
// } else {
|
|
134
|
+
// return false;
|
|
135
|
+
// }
|
|
136
|
+
// }
|
|
137
|
+
|
|
138
|
+
// async GetAccessToken() { }
|
|
139
|
+
async GetRefreshToken() {
|
|
140
|
+
try {
|
|
141
|
+
var c = await this.getToken()
|
|
142
|
+
this.log("...TOKEN FROM SERVER...")
|
|
143
|
+
this.log(c.data)
|
|
144
|
+
this.log("...TOKEN FROM SERVER...")
|
|
145
|
+
} catch (error) {
|
|
146
|
+
this.log(error)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async init() {
|
|
151
|
+
this.log("init starting token ")
|
|
152
|
+
await this.refreshGrantToken()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// async reset() {
|
|
156
|
+
|
|
157
|
+
// }
|
|
158
|
+
|
|
159
|
+
// async getToken() {
|
|
160
|
+
// const login_auth_url = `${this.config.regions.US}/oauth/v2/token`;
|
|
161
|
+
// const form_data = new FormData();
|
|
162
|
+
// form_data.append('client_id', this.config.zoho_client_id);
|
|
163
|
+
// form_data.append('client_secret', this.config.zoho_client_secret);
|
|
164
|
+
// form_data.append('grant_type', 'authorization_code');
|
|
165
|
+
// form_data.append('redirect_uri', this.config.zoho_redirect_uri);
|
|
166
|
+
// form_data.append('code', this.grant_token);
|
|
167
|
+
// this.log(form_data)
|
|
168
|
+
// return await axios.post(login_auth_url, form_data,{
|
|
169
|
+
// headers: {
|
|
170
|
+
// 'Content-Type': 'multipart/form-data' // Important for file uploads
|
|
171
|
+
// }
|
|
172
|
+
// });
|
|
173
|
+
|
|
174
|
+
// }
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
// login() {
|
|
178
|
+
// const login_url = this.getLoginUrl();
|
|
179
|
+
// const form = new FormData();
|
|
180
|
+
// form.append('client_id', this.config.zoho_client_id);
|
|
181
|
+
// form.append('client_secret', this.config.zoho_client_secret);
|
|
182
|
+
// form.append('code', this.config.zoho_client_code);
|
|
183
|
+
// form.append('redirect_uri', this.config.zoho_redirect_uri);
|
|
184
|
+
// form.append('grant_type', 'authorization_code');
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
// }
|
|
188
|
+
|
|
189
|
+
async getAccessToken() {
|
|
190
|
+
const login_auth_url = `${this.config.regions.US}/oauth/v2/token?client_id=${this.config.zoho_client_id}&client_secret=${this.config.zoho_client_secret}&grant_type=client_credentials&scope=${this.config.zoho_scope}&soid=${this.config.zoho_org_id}`;
|
|
191
|
+
this.log(login_auth_url)
|
|
192
|
+
return await axios.post(login_auth_url);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getLoginUrl() {
|
|
196
|
+
return `${this.config.regions.US}/oauth/v2/token`
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports.AuthBuilder = AuthBuilder;
|