cipher-shield 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/LICENSE +21 -0
- package/README.md +666 -0
- package/index.js +75 -0
- package/package.json +57 -0
- package/src/core/aesEngine.js +369 -0
- package/src/core/blacklistMem.js +510 -0
- package/src/core/defconSystem.js +301 -0
- package/src/magicAuth.js +272 -0
- package/src/modules/aiScanner.js +482 -0
- package/src/modules/ghostHandler.js +279 -0
- package/src/modules/shadowHandler.js +266 -0
- package/src/shield.js +694 -0
- package/src/smartLogger.js +268 -0
- package/src/sslManager.js +345 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive DEFCON Threat Escalation System v2.1
|
|
3
|
+
*
|
|
4
|
+
* Intelligent threat level management system that automatically escalates security
|
|
5
|
+
* measures based on detected malicious activity. Implements a cooldown-based
|
|
6
|
+
* system to prevent persistent high-security states while maintaining protection.
|
|
7
|
+
*
|
|
8
|
+
* DEFCON States:
|
|
9
|
+
* - GREEN: Normal operation, all security features active but not aggressive
|
|
10
|
+
* - RED: High alert, conservative security measures (may skip resource-intensive checks)
|
|
11
|
+
*
|
|
12
|
+
* Features:
|
|
13
|
+
* - Automatic escalation based on threat patterns
|
|
14
|
+
* - Configurable thresholds and cooldown periods
|
|
15
|
+
* - Thread-safe operations for concurrent requests
|
|
16
|
+
* - Performance monitoring and logging
|
|
17
|
+
*
|
|
18
|
+
* @module defconSystem
|
|
19
|
+
* @version 1.0.0
|
|
20
|
+
* @author Omindu Dissanayaka
|
|
21
|
+
* @license MIT
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* DEFCON security states enumeration
|
|
26
|
+
* @readonly
|
|
27
|
+
* @enum {string}
|
|
28
|
+
*/
|
|
29
|
+
const DEFCON_STATES = Object.freeze({
|
|
30
|
+
GREEN: 'GREEN',
|
|
31
|
+
RED: 'RED'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Default configuration constants
|
|
36
|
+
* These can be overridden for custom deployments
|
|
37
|
+
*/
|
|
38
|
+
const DEFAULT_CONFIG = Object.freeze({
|
|
39
|
+
ESCALATION_TIMEOUT: 300000,
|
|
40
|
+
ESCALATION_THRESHOLD: 3,
|
|
41
|
+
MAX_ESCALATION_COUNT: 100
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Internal state management
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
let currentState = DEFCON_STATES.GREEN;
|
|
49
|
+
let escalationCount = 0;
|
|
50
|
+
let cooldownTimer = null;
|
|
51
|
+
let lastEscalationTime = null;
|
|
52
|
+
let threatHistory = [];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Initializes the DEFCON system to default state
|
|
56
|
+
*
|
|
57
|
+
* Resets all internal counters, clears any active timers, and prepares
|
|
58
|
+
* the system for fresh threat monitoring. Should be called during
|
|
59
|
+
* application startup or when manually resetting security state.
|
|
60
|
+
*
|
|
61
|
+
* @function initialize
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```javascript
|
|
66
|
+
* const defconSystem = require('./defconSystem');
|
|
67
|
+
* defconSystem.initialize(); // Reset to GREEN state
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function initialize() {
|
|
71
|
+
currentState = DEFCON_STATES.GREEN;
|
|
72
|
+
escalationCount = 0;
|
|
73
|
+
lastEscalationTime = null;
|
|
74
|
+
threatHistory = [];
|
|
75
|
+
|
|
76
|
+
if (cooldownTimer) {
|
|
77
|
+
clearTimeout(cooldownTimer);
|
|
78
|
+
cooldownTimer = null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (process.env.NODE_ENV === 'development') {
|
|
82
|
+
console.log('[DEFCON] System initialized to GREEN state');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Retrieves the current DEFCON security state
|
|
88
|
+
*
|
|
89
|
+
* @function getState
|
|
90
|
+
* @returns {string} Current DEFCON state ('GREEN' or 'RED')
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```javascript
|
|
94
|
+
* const state = defconSystem.getState();
|
|
95
|
+
* if (state === 'RED') {
|
|
96
|
+
* // Apply conservative security measures
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
function getState() {
|
|
101
|
+
return currentState;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Escalates the threat level based on detected malicious activity
|
|
106
|
+
*
|
|
107
|
+
* Increments the escalation counter and automatically transitions to RED state
|
|
108
|
+
* when the threshold is reached. Implements cooldown-based auto-reset to
|
|
109
|
+
* prevent indefinite high-security states.
|
|
110
|
+
*
|
|
111
|
+
* @function escalate
|
|
112
|
+
* @param {string} reason - Description of the threat that triggered escalation
|
|
113
|
+
* @returns {boolean} True if escalation occurred, false if threshold not met
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```javascript
|
|
117
|
+
* defconSystem.escalate('Multiple failed login attempts');
|
|
118
|
+
* defconSystem.escalate('Suspicious payload detected');
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
function escalate(reason) {
|
|
122
|
+
if (!reason || typeof reason !== 'string') {
|
|
123
|
+
reason = 'Unknown threat';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (escalationCount >= DEFAULT_CONFIG.MAX_ESCALATION_COUNT) {
|
|
127
|
+
if (process.env.NODE_ENV === 'development') {
|
|
128
|
+
console.warn('[DEFCON] Escalation count at maximum, maintaining current state');
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
escalationCount++;
|
|
134
|
+
lastEscalationTime = Date.now();
|
|
135
|
+
|
|
136
|
+
threatHistory.push({
|
|
137
|
+
timestamp: lastEscalationTime,
|
|
138
|
+
reason: reason,
|
|
139
|
+
count: escalationCount
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (threatHistory.length > 100) {
|
|
143
|
+
threatHistory = threatHistory.slice(-100);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (escalationCount >= DEFAULT_CONFIG.ESCALATION_THRESHOLD && currentState === DEFCON_STATES.GREEN) {
|
|
147
|
+
currentState = DEFCON_STATES.RED;
|
|
148
|
+
|
|
149
|
+
if (cooldownTimer) {
|
|
150
|
+
clearTimeout(cooldownTimer);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cooldownTimer = setTimeout(() => {
|
|
154
|
+
currentState = DEFCON_STATES.GREEN;
|
|
155
|
+
escalationCount = Math.floor(escalationCount / 2);
|
|
156
|
+
cooldownTimer = null;
|
|
157
|
+
|
|
158
|
+
if (process.env.NODE_ENV === 'development') {
|
|
159
|
+
console.log(`[DEFCON] Auto-reset to GREEN after ${DEFAULT_CONFIG.ESCALATION_TIMEOUT / 1000}s cooldown`);
|
|
160
|
+
}
|
|
161
|
+
}, DEFAULT_CONFIG.ESCALATION_TIMEOUT);
|
|
162
|
+
|
|
163
|
+
if (process.env.NODE_ENV === 'development') {
|
|
164
|
+
console.warn(`[DEFCON] 🚨 ESCALATED to RED state: ${reason} (${escalationCount}/${DEFAULT_CONFIG.ESCALATION_THRESHOLD})`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (process.env.NODE_ENV === 'development') {
|
|
171
|
+
console.log(`[DEFCON] Threat detected: ${reason} (${escalationCount}/${DEFAULT_CONFIG.ESCALATION_THRESHOLD})`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Manually sets the DEFCON state (for testing or administrative override)
|
|
179
|
+
*
|
|
180
|
+
* Allows direct state manipulation, useful for testing, maintenance, or
|
|
181
|
+
* administrative security controls. Automatically handles timer cleanup
|
|
182
|
+
* and counter resets.
|
|
183
|
+
*
|
|
184
|
+
* @function setState
|
|
185
|
+
* @param {string} state - Target state ('GREEN' or 'RED')
|
|
186
|
+
* @returns {boolean} True if state was changed, false if invalid state provided
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```javascript
|
|
190
|
+
* defconSystem.setState('RED'); // Force high alert
|
|
191
|
+
* defconSystem.setState('GREEN'); // Reset to normal
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
function setState(state) {
|
|
195
|
+
if (state !== DEFCON_STATES.GREEN && state !== DEFCON_STATES.RED) {
|
|
196
|
+
if (process.env.NODE_ENV === 'development') {
|
|
197
|
+
console.error(`[DEFCON] Invalid state: ${state}. Must be 'GREEN' or 'RED'`);
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const previousState = currentState;
|
|
203
|
+
currentState = state;
|
|
204
|
+
|
|
205
|
+
if (state === DEFCON_STATES.GREEN) {
|
|
206
|
+
escalationCount = 0;
|
|
207
|
+
lastEscalationTime = null;
|
|
208
|
+
|
|
209
|
+
if (cooldownTimer) {
|
|
210
|
+
clearTimeout(cooldownTimer);
|
|
211
|
+
cooldownTimer = null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (process.env.NODE_ENV === 'development') {
|
|
216
|
+
console.log(`[DEFCON] Manual state change: ${previousState} → ${state}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Retrieves current system status and statistics
|
|
224
|
+
*
|
|
225
|
+
* Provides comprehensive information about the current DEFCON state,
|
|
226
|
+
* escalation history, and system health for monitoring and debugging.
|
|
227
|
+
*
|
|
228
|
+
* @function getStatus
|
|
229
|
+
* @returns {Object} System status information
|
|
230
|
+
* @property {string} state - Current DEFCON state
|
|
231
|
+
* @property {number} escalationCount - Current escalation counter
|
|
232
|
+
* @property {number} threshold - Escalation threshold
|
|
233
|
+
* @property {number|null} lastEscalationTime - Timestamp of last escalation
|
|
234
|
+
* @property {number} cooldownRemaining - Milliseconds until auto-reset (0 if not in RED)
|
|
235
|
+
* @property {Array} recentThreats - Last 10 threats detected
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```javascript
|
|
239
|
+
* const status = defconSystem.getStatus();
|
|
240
|
+
* console.log(`State: ${status.state}, Threats: ${status.escalationCount}`);
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
function getStatus() {
|
|
244
|
+
const now = Date.now();
|
|
245
|
+
let cooldownRemaining = 0;
|
|
246
|
+
|
|
247
|
+
if (currentState === DEFCON_STATES.RED && cooldownTimer) {
|
|
248
|
+
const elapsed = now - (lastEscalationTime || now);
|
|
249
|
+
cooldownRemaining = Math.max(0, DEFAULT_CONFIG.ESCALATION_TIMEOUT - elapsed);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
state: currentState,
|
|
254
|
+
escalationCount,
|
|
255
|
+
threshold: DEFAULT_CONFIG.ESCALATION_THRESHOLD,
|
|
256
|
+
lastEscalationTime,
|
|
257
|
+
cooldownRemaining,
|
|
258
|
+
recentThreats: threatHistory.slice(-10),
|
|
259
|
+
config: {
|
|
260
|
+
escalationTimeout: DEFAULT_CONFIG.ESCALATION_TIMEOUT,
|
|
261
|
+
maxEscalationCount: DEFAULT_CONFIG.MAX_ESCALATION_COUNT
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Resets escalation counter without changing state
|
|
268
|
+
*
|
|
269
|
+
* Useful for gradual cooldown or manual intervention while maintaining
|
|
270
|
+
* current security posture.
|
|
271
|
+
*
|
|
272
|
+
* @function resetCounter
|
|
273
|
+
* @returns {number} Previous escalation count
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```javascript
|
|
277
|
+
* const previousCount = defconSystem.resetCounter();
|
|
278
|
+
* console.log(`Reset counter from ${previousCount} to 0`);
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
function resetCounter() {
|
|
282
|
+
const previousCount = escalationCount;
|
|
283
|
+
escalationCount = 0;
|
|
284
|
+
|
|
285
|
+
if (process.env.NODE_ENV === 'development') {
|
|
286
|
+
console.log(`[DEFCON] Escalation counter reset from ${previousCount} to 0`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return previousCount;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
initialize,
|
|
294
|
+
getState,
|
|
295
|
+
escalate,
|
|
296
|
+
setState,
|
|
297
|
+
getStatus,
|
|
298
|
+
resetCounter,
|
|
299
|
+
DEFCON_STATES,
|
|
300
|
+
DEFAULT_CONFIG
|
|
301
|
+
};
|
package/src/magicAuth.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MagicAuth - Secure JWT Authentication Wrapper
|
|
3
|
+
*
|
|
4
|
+
* A simplified and secure JWT authentication utility with automatic secret management,
|
|
5
|
+
* secure defaults, and Express middleware integration.
|
|
6
|
+
*
|
|
7
|
+
* @module MagicAuth
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
* @author Omindu Dissanayaka
|
|
10
|
+
* @license MIT
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const jwt = require('jsonwebtoken');
|
|
14
|
+
|
|
15
|
+
class MagicAuth {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a MagicAuth instance
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} [options] - Configuration options
|
|
20
|
+
* @param {string} [options.secret] - JWT secret (defaults to process.env.JWT_SECRET)
|
|
21
|
+
* @param {string} [options.algorithm='HS256'] - JWT algorithm
|
|
22
|
+
* @param {string|number} [options.expiresIn='1h'] - Default token expiration
|
|
23
|
+
* @param {string} [options.issuer] - JWT issuer
|
|
24
|
+
* @param {string} [options.audience] - JWT audience
|
|
25
|
+
* @param {boolean} [options.requireSecret=true] - Require secret to be set
|
|
26
|
+
*/
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.options = {
|
|
29
|
+
secret: options.secret || process.env.JWT_SECRET,
|
|
30
|
+
algorithm: options.algorithm || 'HS256',
|
|
31
|
+
expiresIn: options.expiresIn || '1h',
|
|
32
|
+
issuer: options.issuer,
|
|
33
|
+
audience: options.audience,
|
|
34
|
+
requireSecret: options.requireSecret !== false,
|
|
35
|
+
...options
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this._validateSecret();
|
|
39
|
+
|
|
40
|
+
this.defaultSignOptions = {
|
|
41
|
+
algorithm: this.options.algorithm,
|
|
42
|
+
expiresIn: this.options.expiresIn,
|
|
43
|
+
...(this.options.issuer && { issuer: this.options.issuer }),
|
|
44
|
+
...(this.options.audience && { audience: this.options.audience })
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.defaultVerifyOptions = {
|
|
48
|
+
algorithms: [this.options.algorithm],
|
|
49
|
+
...(this.options.issuer && { issuer: this.options.issuer }),
|
|
50
|
+
...(this.options.audience && { audience: this.options.audience })
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate JWT secret configuration
|
|
56
|
+
*
|
|
57
|
+
* @private
|
|
58
|
+
* @throws {Error} If secret is required but not provided
|
|
59
|
+
*/
|
|
60
|
+
_validateSecret() {
|
|
61
|
+
if (this.options.requireSecret && !this.options.secret) {
|
|
62
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
63
|
+
const message = 'JWT_SECRET environment variable is required for MagicAuth. ' +
|
|
64
|
+
'Set process.env.JWT_SECRET or pass secret in options.';
|
|
65
|
+
|
|
66
|
+
if (isProduction) {
|
|
67
|
+
throw new Error(message);
|
|
68
|
+
} else {
|
|
69
|
+
console.warn(`[MagicAuth] ⚠️ WARNING: ${message}`);
|
|
70
|
+
this.options.secret = this._generateDevSecret();
|
|
71
|
+
console.warn(`[MagicAuth] 🔧 Using temporary development secret. DO NOT USE IN PRODUCTION!`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Generate a temporary secret for development
|
|
78
|
+
*
|
|
79
|
+
* @private
|
|
80
|
+
* @returns {string} Temporary secret
|
|
81
|
+
*/
|
|
82
|
+
_generateDevSecret() {
|
|
83
|
+
return require('crypto').randomBytes(32).toString('hex');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Sign a JWT token
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} payload - Payload to encode in the token
|
|
90
|
+
* @param {Object} [options] - Additional sign options
|
|
91
|
+
* @returns {string} JWT token
|
|
92
|
+
* @throws {Error} If signing fails
|
|
93
|
+
*/
|
|
94
|
+
sign(payload, options = {}) {
|
|
95
|
+
try {
|
|
96
|
+
if (!payload || typeof payload !== 'object') {
|
|
97
|
+
throw new Error('Payload must be a non-null object');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const signOptions = {
|
|
101
|
+
...this.defaultSignOptions,
|
|
102
|
+
...options
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (options.expiresIn === null) {
|
|
106
|
+
delete signOptions.expiresIn;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return jwt.sign(payload, this.options.secret, signOptions);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(`JWT signing failed: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Verify and decode a JWT token
|
|
117
|
+
*
|
|
118
|
+
* @param {string} token - JWT token to verify
|
|
119
|
+
* @param {Object} [options] - Additional verify options
|
|
120
|
+
* @returns {Object} Decoded payload
|
|
121
|
+
* @throws {Error} If verification fails
|
|
122
|
+
*/
|
|
123
|
+
verify(token, options = {}) {
|
|
124
|
+
try {
|
|
125
|
+
if (!token || typeof token !== 'string') {
|
|
126
|
+
throw new Error('Token must be a non-empty string');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const verifyOptions = {
|
|
130
|
+
...this.defaultVerifyOptions,
|
|
131
|
+
...options
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return jwt.verify(token, this.options.secret, verifyOptions);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error.name === 'TokenExpiredError') {
|
|
137
|
+
throw new Error('Token has expired');
|
|
138
|
+
} else if (error.name === 'JsonWebTokenError') {
|
|
139
|
+
throw new Error('Invalid token');
|
|
140
|
+
} else if (error.name === 'NotBeforeError') {
|
|
141
|
+
throw new Error('Token not active');
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error(`Token verification failed: ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Express middleware for automatic JWT authentication
|
|
150
|
+
*
|
|
151
|
+
* Checks Authorization header for Bearer token, verifies it,
|
|
152
|
+
* and attaches payload to req.user
|
|
153
|
+
*
|
|
154
|
+
* @returns {Function} Express middleware function
|
|
155
|
+
*/
|
|
156
|
+
middleware() {
|
|
157
|
+
return (req, res, next) => {
|
|
158
|
+
try {
|
|
159
|
+
const authHeader = req.headers.authorization;
|
|
160
|
+
|
|
161
|
+
if (!authHeader) {
|
|
162
|
+
return res.status(401).json({
|
|
163
|
+
error: 'Authentication required',
|
|
164
|
+
message: 'Authorization header is missing',
|
|
165
|
+
code: 'AUTH_MISSING'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!authHeader.startsWith('Bearer ')) {
|
|
170
|
+
return res.status(401).json({
|
|
171
|
+
error: 'Invalid authentication format',
|
|
172
|
+
message: 'Authorization header must start with "Bearer "',
|
|
173
|
+
code: 'AUTH_INVALID_FORMAT'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const token = authHeader.substring(7);
|
|
178
|
+
|
|
179
|
+
if (!token) {
|
|
180
|
+
return res.status(401).json({
|
|
181
|
+
error: 'Authentication required',
|
|
182
|
+
message: 'Bearer token is empty',
|
|
183
|
+
code: 'AUTH_EMPTY_TOKEN'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const payload = this.verify(token);
|
|
188
|
+
|
|
189
|
+
req.user = payload;
|
|
190
|
+
|
|
191
|
+
next();
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const statusCode = error.message.includes('expired') ? 401 : 401;
|
|
194
|
+
const errorCode = error.message.includes('expired') ? 'AUTH_TOKEN_EXPIRED' : 'AUTH_TOKEN_INVALID';
|
|
195
|
+
|
|
196
|
+
return res.status(statusCode).json({
|
|
197
|
+
error: 'Authentication failed',
|
|
198
|
+
message: error.message,
|
|
199
|
+
code: errorCode
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Refresh a token (create new token with same payload)
|
|
207
|
+
*
|
|
208
|
+
* @param {string} token - Existing token to refresh
|
|
209
|
+
* @param {Object} [options] - New sign options
|
|
210
|
+
* @returns {string} New JWT token
|
|
211
|
+
*/
|
|
212
|
+
refresh(token, options = {}) {
|
|
213
|
+
const payload = this.verify(token);
|
|
214
|
+
|
|
215
|
+
const { iat, exp, ...cleanPayload } = payload;
|
|
216
|
+
|
|
217
|
+
return this.sign(cleanPayload, options);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Decode a token without verification (for debugging)
|
|
222
|
+
*
|
|
223
|
+
* @param {string} token - JWT token to decode
|
|
224
|
+
* @param {boolean} [complete=false] - Return complete decoded token
|
|
225
|
+
* @returns {Object|null} Decoded payload or null if invalid
|
|
226
|
+
*/
|
|
227
|
+
decode(token, complete = false) {
|
|
228
|
+
try {
|
|
229
|
+
return jwt.decode(token, { complete });
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get token expiration time
|
|
237
|
+
*
|
|
238
|
+
* @param {string} token - JWT token
|
|
239
|
+
* @returns {Date|null} Expiration date or null if invalid
|
|
240
|
+
*/
|
|
241
|
+
getExpiration(token) {
|
|
242
|
+
try {
|
|
243
|
+
const decoded = jwt.decode(token);
|
|
244
|
+
if (decoded && decoded.exp) {
|
|
245
|
+
return new Date(decoded.exp * 1000);
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check if token is expired
|
|
255
|
+
*
|
|
256
|
+
* @param {string} token - JWT token
|
|
257
|
+
* @returns {boolean} True if expired or invalid
|
|
258
|
+
*/
|
|
259
|
+
isExpired(token) {
|
|
260
|
+
try {
|
|
261
|
+
const decoded = jwt.decode(token);
|
|
262
|
+
if (!decoded || !decoded.exp) {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return Date.now() >= decoded.exp * 1000;
|
|
266
|
+
} catch {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = MagicAuth;
|