@oxyhq/core 1.3.0 → 1.4.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.
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.OxyServicesUtilityMixin = OxyServicesUtilityMixin;
|
|
4
37
|
/**
|
|
@@ -59,7 +92,7 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
59
92
|
* @returns Express middleware function
|
|
60
93
|
*/
|
|
61
94
|
auth(options = {}) {
|
|
62
|
-
const { debug = false, onError, loadUser = false, optional = false } = options;
|
|
95
|
+
const { debug = false, onError, loadUser = false, optional = false, jwtSecret } = options;
|
|
63
96
|
// Cast to any for cross-mixin method access (Auth mixin methods available at runtime)
|
|
64
97
|
const oxyInstance = this;
|
|
65
98
|
// Return an async middleware function
|
|
@@ -115,8 +148,56 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
115
148
|
return res.status(401).json(error);
|
|
116
149
|
}
|
|
117
150
|
// Handle service tokens (internal service-to-service auth)
|
|
118
|
-
// Service tokens are stateless JWTs with type: 'service' —
|
|
151
|
+
// Service tokens are stateless JWTs with type: 'service' — requires signature verification
|
|
119
152
|
if (decoded.type === 'service') {
|
|
153
|
+
// Service tokens MUST be cryptographically verified — reject if no secret provided
|
|
154
|
+
if (!jwtSecret) {
|
|
155
|
+
if (optional) {
|
|
156
|
+
req.userId = null;
|
|
157
|
+
req.user = null;
|
|
158
|
+
return next();
|
|
159
|
+
}
|
|
160
|
+
const error = {
|
|
161
|
+
message: 'Service token verification not configured',
|
|
162
|
+
code: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
163
|
+
status: 403
|
|
164
|
+
};
|
|
165
|
+
if (onError)
|
|
166
|
+
return onError(error);
|
|
167
|
+
return res.status(403).json(error);
|
|
168
|
+
}
|
|
169
|
+
// Verify JWT signature (not just decode)
|
|
170
|
+
try {
|
|
171
|
+
const { createHmac } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
172
|
+
const [headerB64, payloadB64, signatureB64] = token.split('.');
|
|
173
|
+
if (!headerB64 || !payloadB64 || !signatureB64) {
|
|
174
|
+
throw new Error('Invalid token structure');
|
|
175
|
+
}
|
|
176
|
+
const expectedSig = createHmac('sha256', jwtSecret)
|
|
177
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
178
|
+
.digest('base64')
|
|
179
|
+
.replace(/\+/g, '-')
|
|
180
|
+
.replace(/\//g, '_')
|
|
181
|
+
.replace(/=/g, '');
|
|
182
|
+
// Timing-safe comparison
|
|
183
|
+
const sigBuf = Buffer.from(signatureB64);
|
|
184
|
+
const expectedBuf = Buffer.from(expectedSig);
|
|
185
|
+
const { timingSafeEqual } = await Promise.resolve().then(() => __importStar(require('crypto')));
|
|
186
|
+
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
187
|
+
throw new Error('Invalid signature');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
if (optional) {
|
|
192
|
+
req.userId = null;
|
|
193
|
+
req.user = null;
|
|
194
|
+
return next();
|
|
195
|
+
}
|
|
196
|
+
const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
197
|
+
if (onError)
|
|
198
|
+
return onError(error);
|
|
199
|
+
return res.status(401).json(error);
|
|
200
|
+
}
|
|
120
201
|
// Check expiration
|
|
121
202
|
if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
|
|
122
203
|
if (optional) {
|
|
@@ -129,6 +210,18 @@ function OxyServicesUtilityMixin(Base) {
|
|
|
129
210
|
return onError(error);
|
|
130
211
|
return res.status(401).json(error);
|
|
131
212
|
}
|
|
213
|
+
// Validate required service token fields
|
|
214
|
+
if (!decoded.appId) {
|
|
215
|
+
if (optional) {
|
|
216
|
+
req.userId = null;
|
|
217
|
+
req.user = null;
|
|
218
|
+
return next();
|
|
219
|
+
}
|
|
220
|
+
const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
221
|
+
if (onError)
|
|
222
|
+
return onError(error);
|
|
223
|
+
return res.status(401).json(error);
|
|
224
|
+
}
|
|
132
225
|
// Read delegated user ID from header
|
|
133
226
|
const oxyUserId = req.headers['x-oxy-user-id'];
|
|
134
227
|
req.userId = oxyUserId || null;
|
|
@@ -56,7 +56,7 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
56
56
|
* @returns Express middleware function
|
|
57
57
|
*/
|
|
58
58
|
auth(options = {}) {
|
|
59
|
-
const { debug = false, onError, loadUser = false, optional = false } = options;
|
|
59
|
+
const { debug = false, onError, loadUser = false, optional = false, jwtSecret } = options;
|
|
60
60
|
// Cast to any for cross-mixin method access (Auth mixin methods available at runtime)
|
|
61
61
|
const oxyInstance = this;
|
|
62
62
|
// Return an async middleware function
|
|
@@ -112,8 +112,56 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
112
112
|
return res.status(401).json(error);
|
|
113
113
|
}
|
|
114
114
|
// Handle service tokens (internal service-to-service auth)
|
|
115
|
-
// Service tokens are stateless JWTs with type: 'service' —
|
|
115
|
+
// Service tokens are stateless JWTs with type: 'service' — requires signature verification
|
|
116
116
|
if (decoded.type === 'service') {
|
|
117
|
+
// Service tokens MUST be cryptographically verified — reject if no secret provided
|
|
118
|
+
if (!jwtSecret) {
|
|
119
|
+
if (optional) {
|
|
120
|
+
req.userId = null;
|
|
121
|
+
req.user = null;
|
|
122
|
+
return next();
|
|
123
|
+
}
|
|
124
|
+
const error = {
|
|
125
|
+
message: 'Service token verification not configured',
|
|
126
|
+
code: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
127
|
+
status: 403
|
|
128
|
+
};
|
|
129
|
+
if (onError)
|
|
130
|
+
return onError(error);
|
|
131
|
+
return res.status(403).json(error);
|
|
132
|
+
}
|
|
133
|
+
// Verify JWT signature (not just decode)
|
|
134
|
+
try {
|
|
135
|
+
const { createHmac } = await import('crypto');
|
|
136
|
+
const [headerB64, payloadB64, signatureB64] = token.split('.');
|
|
137
|
+
if (!headerB64 || !payloadB64 || !signatureB64) {
|
|
138
|
+
throw new Error('Invalid token structure');
|
|
139
|
+
}
|
|
140
|
+
const expectedSig = createHmac('sha256', jwtSecret)
|
|
141
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
142
|
+
.digest('base64')
|
|
143
|
+
.replace(/\+/g, '-')
|
|
144
|
+
.replace(/\//g, '_')
|
|
145
|
+
.replace(/=/g, '');
|
|
146
|
+
// Timing-safe comparison
|
|
147
|
+
const sigBuf = Buffer.from(signatureB64);
|
|
148
|
+
const expectedBuf = Buffer.from(expectedSig);
|
|
149
|
+
const { timingSafeEqual } = await import('crypto');
|
|
150
|
+
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
151
|
+
throw new Error('Invalid signature');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
if (optional) {
|
|
156
|
+
req.userId = null;
|
|
157
|
+
req.user = null;
|
|
158
|
+
return next();
|
|
159
|
+
}
|
|
160
|
+
const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
161
|
+
if (onError)
|
|
162
|
+
return onError(error);
|
|
163
|
+
return res.status(401).json(error);
|
|
164
|
+
}
|
|
117
165
|
// Check expiration
|
|
118
166
|
if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
|
|
119
167
|
if (optional) {
|
|
@@ -126,6 +174,18 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
126
174
|
return onError(error);
|
|
127
175
|
return res.status(401).json(error);
|
|
128
176
|
}
|
|
177
|
+
// Validate required service token fields
|
|
178
|
+
if (!decoded.appId) {
|
|
179
|
+
if (optional) {
|
|
180
|
+
req.userId = null;
|
|
181
|
+
req.user = null;
|
|
182
|
+
return next();
|
|
183
|
+
}
|
|
184
|
+
const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
185
|
+
if (onError)
|
|
186
|
+
return onError(error);
|
|
187
|
+
return res.status(401).json(error);
|
|
188
|
+
}
|
|
129
189
|
// Read delegated user ID from header
|
|
130
190
|
const oxyUserId = req.headers['x-oxy-user-id'];
|
|
131
191
|
req.userId = oxyUserId || null;
|
|
@@ -19,6 +19,12 @@ interface AuthMiddlewareOptions {
|
|
|
19
19
|
loadUser?: boolean;
|
|
20
20
|
/** Optional auth - attach user if token present but don't block (default: false) */
|
|
21
21
|
optional?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* JWT secret for verifying service token signatures locally.
|
|
24
|
+
* When provided, service tokens will be cryptographically verified.
|
|
25
|
+
* When omitted, service tokens will be rejected (secure default).
|
|
26
|
+
*/
|
|
27
|
+
jwtSecret?: string;
|
|
22
28
|
}
|
|
23
29
|
export declare function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
24
30
|
new (...args: any[]): {
|
package/package.json
CHANGED
|
@@ -40,6 +40,12 @@ interface AuthMiddlewareOptions {
|
|
|
40
40
|
loadUser?: boolean;
|
|
41
41
|
/** Optional auth - attach user if token present but don't block (default: false) */
|
|
42
42
|
optional?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* JWT secret for verifying service token signatures locally.
|
|
45
|
+
* When provided, service tokens will be cryptographically verified.
|
|
46
|
+
* When omitted, service tokens will be rejected (secure default).
|
|
47
|
+
*/
|
|
48
|
+
jwtSecret?: string;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
@@ -102,7 +108,7 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
102
108
|
* @returns Express middleware function
|
|
103
109
|
*/
|
|
104
110
|
auth(options: AuthMiddlewareOptions = {}) {
|
|
105
|
-
const { debug = false, onError, loadUser = false, optional = false } = options;
|
|
111
|
+
const { debug = false, onError, loadUser = false, optional = false, jwtSecret } = options;
|
|
106
112
|
// Cast to any for cross-mixin method access (Auth mixin methods available at runtime)
|
|
107
113
|
const oxyInstance = this as any;
|
|
108
114
|
|
|
@@ -161,8 +167,56 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
// Handle service tokens (internal service-to-service auth)
|
|
164
|
-
// Service tokens are stateless JWTs with type: 'service' —
|
|
170
|
+
// Service tokens are stateless JWTs with type: 'service' — requires signature verification
|
|
165
171
|
if (decoded.type === 'service') {
|
|
172
|
+
// Service tokens MUST be cryptographically verified — reject if no secret provided
|
|
173
|
+
if (!jwtSecret) {
|
|
174
|
+
if (optional) {
|
|
175
|
+
req.userId = null;
|
|
176
|
+
req.user = null;
|
|
177
|
+
return next();
|
|
178
|
+
}
|
|
179
|
+
const error = {
|
|
180
|
+
message: 'Service token verification not configured',
|
|
181
|
+
code: 'SERVICE_TOKEN_NOT_CONFIGURED',
|
|
182
|
+
status: 403
|
|
183
|
+
};
|
|
184
|
+
if (onError) return onError(error);
|
|
185
|
+
return res.status(403).json(error);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Verify JWT signature (not just decode)
|
|
189
|
+
try {
|
|
190
|
+
const { createHmac } = await import('crypto');
|
|
191
|
+
const [headerB64, payloadB64, signatureB64] = token.split('.');
|
|
192
|
+
if (!headerB64 || !payloadB64 || !signatureB64) {
|
|
193
|
+
throw new Error('Invalid token structure');
|
|
194
|
+
}
|
|
195
|
+
const expectedSig = createHmac('sha256', jwtSecret)
|
|
196
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
197
|
+
.digest('base64')
|
|
198
|
+
.replace(/\+/g, '-')
|
|
199
|
+
.replace(/\//g, '_')
|
|
200
|
+
.replace(/=/g, '');
|
|
201
|
+
|
|
202
|
+
// Timing-safe comparison
|
|
203
|
+
const sigBuf = Buffer.from(signatureB64);
|
|
204
|
+
const expectedBuf = Buffer.from(expectedSig);
|
|
205
|
+
const { timingSafeEqual } = await import('crypto');
|
|
206
|
+
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
207
|
+
throw new Error('Invalid signature');
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
if (optional) {
|
|
211
|
+
req.userId = null;
|
|
212
|
+
req.user = null;
|
|
213
|
+
return next();
|
|
214
|
+
}
|
|
215
|
+
const error = { message: 'Invalid service token signature', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
216
|
+
if (onError) return onError(error);
|
|
217
|
+
return res.status(401).json(error);
|
|
218
|
+
}
|
|
219
|
+
|
|
166
220
|
// Check expiration
|
|
167
221
|
if (decoded.exp && decoded.exp < Math.floor(Date.now() / 1000)) {
|
|
168
222
|
if (optional) {
|
|
@@ -175,6 +229,18 @@ export function OxyServicesUtilityMixin<T extends typeof OxyServicesBase>(Base:
|
|
|
175
229
|
return res.status(401).json(error);
|
|
176
230
|
}
|
|
177
231
|
|
|
232
|
+
// Validate required service token fields
|
|
233
|
+
if (!decoded.appId) {
|
|
234
|
+
if (optional) {
|
|
235
|
+
req.userId = null;
|
|
236
|
+
req.user = null;
|
|
237
|
+
return next();
|
|
238
|
+
}
|
|
239
|
+
const error = { message: 'Invalid service token: missing appId', code: 'INVALID_SERVICE_TOKEN', status: 401 };
|
|
240
|
+
if (onError) return onError(error);
|
|
241
|
+
return res.status(401).json(error);
|
|
242
|
+
}
|
|
243
|
+
|
|
178
244
|
// Read delegated user ID from header
|
|
179
245
|
const oxyUserId = req.headers['x-oxy-user-id'] as string;
|
|
180
246
|
|