@friggframework/schemas 2.0.0-next.78 → 2.0.0-next.80
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 +232 -5
- package/middleware/__tests__/schema-validation.test.js +508 -0
- package/middleware/schema-validation.js +388 -0
- package/mocks/README.md +280 -0
- package/mocks/__tests__/authorization-mocks.test.js +311 -0
- package/mocks/authorization-mocks.js +339 -0
- package/package.json +4 -2
- package/schemas/api-authorization.schema.json +302 -0
- package/schemas/api-credentials.schema.json +176 -0
- package/schemas/api-entities.schema.json +292 -0
- package/schemas/api-proxy.schema.json +251 -0
- package/schemas/app-definition.schema.json +19 -0
- package/schemas/core-models.schema.json +16 -16
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validation Middleware for Express
|
|
3
|
+
*
|
|
4
|
+
* Provides request and response validation against JSON schemas using AJV.
|
|
5
|
+
* Like TypeScript for APIs - enforces contracts at runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Ajv = require('ajv');
|
|
9
|
+
const addFormats = require('ajv-formats');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
// Initialize AJV with formats
|
|
14
|
+
const ajv = new Ajv({
|
|
15
|
+
allErrors: true,
|
|
16
|
+
verbose: true,
|
|
17
|
+
strict: false
|
|
18
|
+
// Note: coerceTypes disabled to avoid conflicts with oneOf schemas
|
|
19
|
+
});
|
|
20
|
+
addFormats(ajv);
|
|
21
|
+
|
|
22
|
+
// Load all schemas
|
|
23
|
+
const schemaDir = path.join(__dirname, '..', 'schemas');
|
|
24
|
+
const schemaFiles = [
|
|
25
|
+
'api-entities.schema.json',
|
|
26
|
+
'api-credentials.schema.json',
|
|
27
|
+
'api-proxy.schema.json',
|
|
28
|
+
'api-authorization.schema.json'
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
schemaFiles.forEach(file => {
|
|
32
|
+
const schemaPath = path.join(schemaDir, file);
|
|
33
|
+
if (fs.existsSync(schemaPath)) {
|
|
34
|
+
const schemaContent = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
35
|
+
const schemaName = file.replace('.schema.json', '');
|
|
36
|
+
ajv.addSchema(schemaContent, schemaName);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validation error class for schema validation failures
|
|
42
|
+
*/
|
|
43
|
+
class SchemaValidationError extends Error {
|
|
44
|
+
constructor(message, errors, location) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = 'SchemaValidationError';
|
|
47
|
+
this.errors = errors;
|
|
48
|
+
this.location = location; // 'body', 'query', 'params', 'response'
|
|
49
|
+
this.statusCode = location === 'response' ? 500 : 400;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
toJSON() {
|
|
53
|
+
return {
|
|
54
|
+
error: 'ValidationError',
|
|
55
|
+
message: this.message,
|
|
56
|
+
location: this.location,
|
|
57
|
+
details: this.errors.map(err => ({
|
|
58
|
+
path: err.instancePath || 'root',
|
|
59
|
+
message: err.message,
|
|
60
|
+
params: err.params
|
|
61
|
+
}))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format AJV errors into human-readable messages
|
|
68
|
+
* @param {Array} errors - AJV validation errors
|
|
69
|
+
* @returns {string} - Formatted error message
|
|
70
|
+
*/
|
|
71
|
+
function formatValidationErrors(errors) {
|
|
72
|
+
if (!errors || errors.length === 0) {
|
|
73
|
+
return 'Unknown validation error';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return errors.map(error => {
|
|
77
|
+
const path = error.instancePath || 'root';
|
|
78
|
+
const message = error.message;
|
|
79
|
+
const allowedValues = error.params?.allowedValues
|
|
80
|
+
? ` (allowed: ${error.params.allowedValues.join(', ')})`
|
|
81
|
+
: '';
|
|
82
|
+
return `${path}: ${message}${allowedValues}`;
|
|
83
|
+
}).join('; ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get a compiled validator for a schema reference
|
|
88
|
+
* @param {string} schemaRef - Schema reference (e.g., 'api-entities#/definitions/entity')
|
|
89
|
+
* @returns {Function} - Compiled AJV validator
|
|
90
|
+
*/
|
|
91
|
+
function getValidator(schemaRef) {
|
|
92
|
+
const validator = ajv.getSchema(schemaRef);
|
|
93
|
+
if (!validator) {
|
|
94
|
+
throw new Error(`Schema not found: ${schemaRef}`);
|
|
95
|
+
}
|
|
96
|
+
return validator;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate request body against a schema
|
|
101
|
+
* @param {string} schemaRef - Schema reference
|
|
102
|
+
* @param {Object} options - Validation options
|
|
103
|
+
* @param {boolean} options.strict - If true, throws on validation failure (default: true)
|
|
104
|
+
* @param {boolean} options.coerceTypes - If true, coerces types (default: false for body)
|
|
105
|
+
* @returns {Function} - Express middleware
|
|
106
|
+
*/
|
|
107
|
+
function validateBody(schemaRef, options = {}) {
|
|
108
|
+
const { strict = true } = options;
|
|
109
|
+
|
|
110
|
+
return (req, res, next) => {
|
|
111
|
+
try {
|
|
112
|
+
const validator = getValidator(schemaRef);
|
|
113
|
+
const valid = validator(req.body);
|
|
114
|
+
|
|
115
|
+
if (!valid) {
|
|
116
|
+
const error = new SchemaValidationError(
|
|
117
|
+
`Request body validation failed: ${formatValidationErrors(validator.errors)}`,
|
|
118
|
+
validator.errors,
|
|
119
|
+
'body'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (strict) {
|
|
123
|
+
return res.status(400).json(error.toJSON());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Non-strict: attach errors but continue
|
|
127
|
+
req.validationErrors = req.validationErrors || {};
|
|
128
|
+
req.validationErrors.body = validator.errors;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
next();
|
|
132
|
+
} catch (err) {
|
|
133
|
+
next(err);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Validate query parameters against a schema
|
|
140
|
+
* @param {string} schemaRef - Schema reference
|
|
141
|
+
* @param {Object} options - Validation options
|
|
142
|
+
* @returns {Function} - Express middleware
|
|
143
|
+
*/
|
|
144
|
+
function validateQuery(schemaRef, options = {}) {
|
|
145
|
+
const { strict = true } = options;
|
|
146
|
+
|
|
147
|
+
return (req, res, next) => {
|
|
148
|
+
try {
|
|
149
|
+
const validator = getValidator(schemaRef);
|
|
150
|
+
const valid = validator(req.query);
|
|
151
|
+
|
|
152
|
+
if (!valid) {
|
|
153
|
+
const error = new SchemaValidationError(
|
|
154
|
+
`Query parameter validation failed: ${formatValidationErrors(validator.errors)}`,
|
|
155
|
+
validator.errors,
|
|
156
|
+
'query'
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (strict) {
|
|
160
|
+
return res.status(400).json(error.toJSON());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
req.validationErrors = req.validationErrors || {};
|
|
164
|
+
req.validationErrors.query = validator.errors;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
next();
|
|
168
|
+
} catch (err) {
|
|
169
|
+
next(err);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validate route parameters against a schema
|
|
176
|
+
* @param {string} schemaRef - Schema reference
|
|
177
|
+
* @param {Object} options - Validation options
|
|
178
|
+
* @returns {Function} - Express middleware
|
|
179
|
+
*/
|
|
180
|
+
function validateParams(schemaRef, options = {}) {
|
|
181
|
+
const { strict = true } = options;
|
|
182
|
+
|
|
183
|
+
return (req, res, next) => {
|
|
184
|
+
try {
|
|
185
|
+
const validator = getValidator(schemaRef);
|
|
186
|
+
const valid = validator(req.params);
|
|
187
|
+
|
|
188
|
+
if (!valid) {
|
|
189
|
+
const error = new SchemaValidationError(
|
|
190
|
+
`Route parameter validation failed: ${formatValidationErrors(validator.errors)}`,
|
|
191
|
+
validator.errors,
|
|
192
|
+
'params'
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (strict) {
|
|
196
|
+
return res.status(400).json(error.toJSON());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
req.validationErrors = req.validationErrors || {};
|
|
200
|
+
req.validationErrors.params = validator.errors;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
next();
|
|
204
|
+
} catch (err) {
|
|
205
|
+
next(err);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Validate response body against a schema (for development/testing)
|
|
212
|
+
* Wraps res.json() to validate response data
|
|
213
|
+
* @param {string} schemaRef - Schema reference
|
|
214
|
+
* @param {Object} options - Validation options
|
|
215
|
+
* @returns {Function} - Express middleware
|
|
216
|
+
*/
|
|
217
|
+
function validateResponse(schemaRef, options = {}) {
|
|
218
|
+
const { strict = false, logErrors = true } = options;
|
|
219
|
+
|
|
220
|
+
return (req, res, next) => {
|
|
221
|
+
const originalJson = res.json.bind(res);
|
|
222
|
+
|
|
223
|
+
res.json = function(data) {
|
|
224
|
+
try {
|
|
225
|
+
const validator = getValidator(schemaRef);
|
|
226
|
+
const valid = validator(data);
|
|
227
|
+
|
|
228
|
+
if (!valid) {
|
|
229
|
+
const errorMessage = formatValidationErrors(validator.errors);
|
|
230
|
+
|
|
231
|
+
if (logErrors) {
|
|
232
|
+
console.error(`Response validation failed for ${req.method} ${req.path}:`, errorMessage);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (strict) {
|
|
236
|
+
const error = new SchemaValidationError(
|
|
237
|
+
`Response validation failed: ${errorMessage}`,
|
|
238
|
+
validator.errors,
|
|
239
|
+
'response'
|
|
240
|
+
);
|
|
241
|
+
return originalJson.call(res.status(500), error.toJSON());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Non-strict: log but continue
|
|
245
|
+
// Optionally add validation metadata to response
|
|
246
|
+
if (options.includeMetadata) {
|
|
247
|
+
data._validation = {
|
|
248
|
+
valid: false,
|
|
249
|
+
errors: validator.errors
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (logErrors) {
|
|
255
|
+
console.error('Response validation error:', err.message);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return originalJson.call(res, data);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
next();
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Combined validation middleware - validates request and response
|
|
268
|
+
* @param {Object} config - Validation configuration
|
|
269
|
+
* @param {string} config.body - Schema ref for body validation
|
|
270
|
+
* @param {string} config.query - Schema ref for query validation
|
|
271
|
+
* @param {string} config.params - Schema ref for params validation
|
|
272
|
+
* @param {string} config.response - Schema ref for response validation
|
|
273
|
+
* @param {Object} config.options - Validation options
|
|
274
|
+
* @returns {Array<Function>} - Array of Express middleware
|
|
275
|
+
*/
|
|
276
|
+
function validate(config = {}) {
|
|
277
|
+
const middlewares = [];
|
|
278
|
+
const { options = {} } = config;
|
|
279
|
+
|
|
280
|
+
if (config.body) {
|
|
281
|
+
middlewares.push(validateBody(config.body, options));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (config.query) {
|
|
285
|
+
middlewares.push(validateQuery(config.query, options));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (config.params) {
|
|
289
|
+
middlewares.push(validateParams(config.params, options));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (config.response) {
|
|
293
|
+
middlewares.push(validateResponse(config.response, options));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return middlewares;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Schema reference helpers for common API schemas
|
|
301
|
+
*/
|
|
302
|
+
const SchemaRefs = {
|
|
303
|
+
// Entities
|
|
304
|
+
entity: 'api-entities#/definitions/entity',
|
|
305
|
+
listEntitiesResponse: 'api-entities#/definitions/listEntitiesResponse',
|
|
306
|
+
createEntityRequest: 'api-entities#/definitions/createEntityRequest',
|
|
307
|
+
createEntityResponse: 'api-entities#/definitions/createEntityResponse',
|
|
308
|
+
entityType: 'api-entities#/definitions/entityType',
|
|
309
|
+
listEntityTypesResponse: 'api-entities#/definitions/listEntityTypesResponse',
|
|
310
|
+
getEntityTypeResponse: 'api-entities#/definitions/getEntityTypeResponse',
|
|
311
|
+
reauthorizeEntityRequest: 'api-entities#/definitions/reauthorizeEntityRequest',
|
|
312
|
+
reauthorizeEntityResponse: 'api-entities#/definitions/reauthorizeEntityResponse',
|
|
313
|
+
|
|
314
|
+
// Credentials
|
|
315
|
+
credential: 'api-credentials#/definitions/credential',
|
|
316
|
+
listCredentialsResponse: 'api-credentials#/definitions/listCredentialsResponse',
|
|
317
|
+
getCredentialResponse: 'api-credentials#/definitions/getCredentialResponse',
|
|
318
|
+
deleteCredentialResponse: 'api-credentials#/definitions/deleteCredentialResponse',
|
|
319
|
+
reauthorizeCredentialRequest: 'api-credentials#/definitions/reauthorizeCredentialRequest',
|
|
320
|
+
reauthorizeCredentialResponse: 'api-credentials#/definitions/reauthorizeCredentialResponse',
|
|
321
|
+
|
|
322
|
+
// Proxy
|
|
323
|
+
proxyRequest: 'api-proxy#/definitions/proxyRequest',
|
|
324
|
+
proxyResponse: 'api-proxy#/definitions/proxyResponse',
|
|
325
|
+
proxyErrorResponse: 'api-proxy#/definitions/proxyErrorResponse',
|
|
326
|
+
proxyResponseUnion: 'api-proxy#/definitions/proxyResponseUnion',
|
|
327
|
+
|
|
328
|
+
// Authorization
|
|
329
|
+
authorizationRequirements: 'api-authorization#/definitions/authorizationRequirements',
|
|
330
|
+
authorizationRequest: 'api-authorization#/definitions/authorizationRequest',
|
|
331
|
+
authorizationResponse: 'api-authorization#/definitions/authorizationResponse',
|
|
332
|
+
authorizationSession: 'api-authorization#/definitions/authorizationSession',
|
|
333
|
+
getEntityTypeRequirementsResponse: 'api-authorization#/definitions/getEntityTypeRequirementsResponse'
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Precompiled validators for performance
|
|
338
|
+
*/
|
|
339
|
+
const Validators = {};
|
|
340
|
+
|
|
341
|
+
// Lazy-load validators on first use
|
|
342
|
+
function getCompiledValidator(name) {
|
|
343
|
+
if (!Validators[name]) {
|
|
344
|
+
const ref = SchemaRefs[name];
|
|
345
|
+
if (!ref) {
|
|
346
|
+
throw new Error(`Unknown schema name: ${name}`);
|
|
347
|
+
}
|
|
348
|
+
Validators[name] = getValidator(ref);
|
|
349
|
+
}
|
|
350
|
+
return Validators[name];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Direct validation functions (not middleware)
|
|
355
|
+
* Useful for testing and manual validation
|
|
356
|
+
*/
|
|
357
|
+
function validateData(schemaName, data) {
|
|
358
|
+
const validator = getCompiledValidator(schemaName);
|
|
359
|
+
const valid = validator(data);
|
|
360
|
+
return {
|
|
361
|
+
valid,
|
|
362
|
+
errors: valid ? null : validator.errors,
|
|
363
|
+
formatted: valid ? null : formatValidationErrors(validator.errors)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
module.exports = {
|
|
368
|
+
// Middleware
|
|
369
|
+
validateBody,
|
|
370
|
+
validateQuery,
|
|
371
|
+
validateParams,
|
|
372
|
+
validateResponse,
|
|
373
|
+
validate,
|
|
374
|
+
|
|
375
|
+
// Direct validation
|
|
376
|
+
validateData,
|
|
377
|
+
getValidator,
|
|
378
|
+
formatValidationErrors,
|
|
379
|
+
|
|
380
|
+
// Schema references
|
|
381
|
+
SchemaRefs,
|
|
382
|
+
|
|
383
|
+
// Error class
|
|
384
|
+
SchemaValidationError,
|
|
385
|
+
|
|
386
|
+
// AJV instance (for advanced use)
|
|
387
|
+
ajv
|
|
388
|
+
};
|
package/mocks/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Authorization Mocks
|
|
2
|
+
|
|
3
|
+
Schema-compliant mock data generators for testing authentication and authorization flows across the Frigg Framework.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
These mocks ensure consistency across all Frigg packages:
|
|
8
|
+
- `@friggframework/core` - Backend authorization logic
|
|
9
|
+
- `@friggframework/ui` - Frontend integration components
|
|
10
|
+
- `@friggframework/devtools/management-ui` - Developer tooling
|
|
11
|
+
|
|
12
|
+
All mock data is **validated against canonical JSON schemas** to guarantee accuracy.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @friggframework/schemas
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Examples
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const {
|
|
26
|
+
createOAuth2Requirements,
|
|
27
|
+
createFormRequirements,
|
|
28
|
+
createNagarisOTPFlowMock,
|
|
29
|
+
createAuthorizationSuccess,
|
|
30
|
+
} = require('@friggframework/schemas/mocks/authorization-mocks');
|
|
31
|
+
|
|
32
|
+
// OAuth2 flow
|
|
33
|
+
const hubspotAuth = createOAuth2Requirements('hubspot');
|
|
34
|
+
// {
|
|
35
|
+
// type: 'oauth2',
|
|
36
|
+
// step: 1,
|
|
37
|
+
// totalSteps: 1,
|
|
38
|
+
// isMultiStep: false,
|
|
39
|
+
// data: {
|
|
40
|
+
// url: 'https://auth.hubspot.com/oauth/authorize?...',
|
|
41
|
+
// scopes: ['read', 'write']
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// Form-based auth
|
|
46
|
+
const apiKeyAuth = createFormRequirements('custom-api', {
|
|
47
|
+
fields: ['api_key']
|
|
48
|
+
});
|
|
49
|
+
// {
|
|
50
|
+
// type: 'form',
|
|
51
|
+
// data: {
|
|
52
|
+
// jsonSchema: { ... },
|
|
53
|
+
// uiSchema: { ... }
|
|
54
|
+
// }
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
// Multi-step OTP flow (like Nagaris)
|
|
58
|
+
const nagarisFlow = createNagarisOTPFlowMock('user-123');
|
|
59
|
+
const step1Reqs = nagarisFlow.getStep1Requirements(); // Email form
|
|
60
|
+
const step1Response = nagarisFlow.submitStep1({ email: 'test@example.com' }); // OTP sent
|
|
61
|
+
const step2Response = nagarisFlow.submitStep2({ otp: '123456' }); // Success
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### In Tests
|
|
65
|
+
|
|
66
|
+
#### Core Package Tests
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// packages/core/__tests__/authorization-flow.test.js
|
|
70
|
+
const { createNagarisOTPFlowMock } = require('@friggframework/schemas/mocks/authorization-mocks');
|
|
71
|
+
const { validateAuthorizationSession } = require('@friggframework/schemas');
|
|
72
|
+
|
|
73
|
+
test('processes multi-step auth', async () => {
|
|
74
|
+
const mockFlow = createNagarisOTPFlowMock('user-123');
|
|
75
|
+
const session = mockFlow.session;
|
|
76
|
+
|
|
77
|
+
// Validate before storing
|
|
78
|
+
const validation = validateAuthorizationSession(session);
|
|
79
|
+
expect(validation.valid).toBe(true);
|
|
80
|
+
|
|
81
|
+
// Use in repository test
|
|
82
|
+
await authSessionRepository.create(session);
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### UI Package Tests
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// packages/ui/__tests__/AuthorizationWizard.test.jsx
|
|
90
|
+
const { createFormRequirements } = require('@friggframework/schemas/mocks/authorization-mocks');
|
|
91
|
+
|
|
92
|
+
test('renders multi-step OTP form', async () => {
|
|
93
|
+
const mockApi = {
|
|
94
|
+
getAuthorizationRequirements: jest.fn().mockResolvedValue(
|
|
95
|
+
createFormRequirements('nagaris', {
|
|
96
|
+
fields: ['email'],
|
|
97
|
+
isMultiStep: true,
|
|
98
|
+
step: 1,
|
|
99
|
+
totalSteps: 2
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
render(<AuthorizationWizard api={mockApi} moduleType="nagaris" />);
|
|
105
|
+
// Test form rendering...
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Management UI Tests
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
// packages/devtools/management-ui/__tests__/TestingZone.test.jsx
|
|
113
|
+
const { createOAuth2Requirements } = require('@friggframework/schemas/mocks/authorization-mocks');
|
|
114
|
+
|
|
115
|
+
test('displays OAuth authorization URL', () => {
|
|
116
|
+
const mockData = createOAuth2Requirements('hubspot');
|
|
117
|
+
|
|
118
|
+
render(<AuthFlowDisplay requirements={mockData} />);
|
|
119
|
+
expect(screen.getByText(/hubspot.com\/oauth/)).toBeInTheDocument();
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### OAuth2 Flows
|
|
126
|
+
|
|
127
|
+
#### `createOAuth2Requirements(moduleType, options)`
|
|
128
|
+
|
|
129
|
+
Create OAuth2 authorization requirements.
|
|
130
|
+
|
|
131
|
+
**Parameters:**
|
|
132
|
+
- `moduleType` (string): Module name (e.g., 'hubspot', 'salesforce')
|
|
133
|
+
- `options.scopes` (array): OAuth scopes (default: ['read', 'write'])
|
|
134
|
+
- `options.isMultiStep` (boolean): Multi-step flow (default: false)
|
|
135
|
+
- `options.step` (number): Current step (default: 1)
|
|
136
|
+
- `options.totalSteps` (number): Total steps (default: 1)
|
|
137
|
+
- `options.sessionId` (string): Session ID for multi-step
|
|
138
|
+
|
|
139
|
+
**Returns:** Authorization requirements object (validated against schema)
|
|
140
|
+
|
|
141
|
+
#### `createOAuth2FlowMock(moduleType, userId)`
|
|
142
|
+
|
|
143
|
+
Create complete OAuth2 flow with methods for each step.
|
|
144
|
+
|
|
145
|
+
**Returns:** Object with `getRequirements()` and `handleCallback()` methods
|
|
146
|
+
|
|
147
|
+
### Form-Based Flows
|
|
148
|
+
|
|
149
|
+
#### `createFormRequirements(moduleType, options)`
|
|
150
|
+
|
|
151
|
+
Create form-based authorization requirements with JSON Schema.
|
|
152
|
+
|
|
153
|
+
**Parameters:**
|
|
154
|
+
- `moduleType` (string): Module name
|
|
155
|
+
- `options.fields` (array): Field names (email, password, api_key, otp, etc.)
|
|
156
|
+
- `options.isMultiStep` (boolean): Multi-step flow
|
|
157
|
+
- `options.step` (number): Current step
|
|
158
|
+
- `options.totalSteps` (number): Total steps
|
|
159
|
+
- `options.sessionId` (string): Session ID
|
|
160
|
+
|
|
161
|
+
**Returns:** Form requirements with jsonSchema and uiSchema
|
|
162
|
+
|
|
163
|
+
**Supported Fields:**
|
|
164
|
+
- `email` - Email input with validation
|
|
165
|
+
- `password` - Password input (min 6 chars)
|
|
166
|
+
- `api_key` - API key text input
|
|
167
|
+
- `otp` - 6-digit OTP input with pattern validation
|
|
168
|
+
- Custom fields - Generic text inputs
|
|
169
|
+
|
|
170
|
+
### Multi-Step OTP Flows
|
|
171
|
+
|
|
172
|
+
#### `createOTPMultiStepFlow(moduleType)`
|
|
173
|
+
|
|
174
|
+
Create multi-step flow structure (email → OTP).
|
|
175
|
+
|
|
176
|
+
**Returns:** Object with `step1` and `step2(sessionId)` properties
|
|
177
|
+
|
|
178
|
+
#### `createNagarisOTPFlowMock(userId)`
|
|
179
|
+
|
|
180
|
+
Create complete Nagaris-style OTP flow with all steps.
|
|
181
|
+
|
|
182
|
+
**Returns:** Object with methods:
|
|
183
|
+
- `getStep1Requirements()` - Get email form
|
|
184
|
+
- `submitStep1(emailData)` - Submit email, get OTP prompt
|
|
185
|
+
- `submitStep2(otpData)` - Submit OTP, get success
|
|
186
|
+
- `session` - Authorization session object
|
|
187
|
+
- `sessionId` - Session identifier
|
|
188
|
+
- `email` - Test email address
|
|
189
|
+
|
|
190
|
+
### Response Builders
|
|
191
|
+
|
|
192
|
+
#### `createAuthorizationSuccess(moduleType, options)`
|
|
193
|
+
|
|
194
|
+
Create successful authorization response.
|
|
195
|
+
|
|
196
|
+
**Parameters:**
|
|
197
|
+
- `moduleType` (string): Module name
|
|
198
|
+
- `options.entityId` (string): Entity ID (auto-generated if not provided)
|
|
199
|
+
- `options.credentialId` (string): Credential ID (auto-generated)
|
|
200
|
+
- `options.display` (string): Display name
|
|
201
|
+
|
|
202
|
+
**Returns:** Success response object
|
|
203
|
+
|
|
204
|
+
#### `createAuthorizationNextStep(nextStep, requirements, options)`
|
|
205
|
+
|
|
206
|
+
Create next step response for multi-step flows.
|
|
207
|
+
|
|
208
|
+
**Parameters:**
|
|
209
|
+
- `nextStep` (number): Next step number
|
|
210
|
+
- `requirements` (object): Requirements for next step
|
|
211
|
+
- `options.sessionId` (string): Session ID (auto-generated)
|
|
212
|
+
- `options.message` (string): User message
|
|
213
|
+
|
|
214
|
+
**Returns:** Next step response object
|
|
215
|
+
|
|
216
|
+
### Session Management
|
|
217
|
+
|
|
218
|
+
#### `createAuthorizationSession(userId, entityType, options)`
|
|
219
|
+
|
|
220
|
+
Create authorization session database object.
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
- `userId` (string): User ID
|
|
224
|
+
- `entityType` (string): Module type
|
|
225
|
+
- `options.currentStep` (number): Current step (default: 1)
|
|
226
|
+
- `options.maxSteps` (number): Total steps (default: 2)
|
|
227
|
+
- `options.stepData` (object): Data from previous steps
|
|
228
|
+
- `options.expiresInMinutes` (number): Expiration time (default: 15)
|
|
229
|
+
- `options.completed` (boolean): Completion status
|
|
230
|
+
|
|
231
|
+
**Returns:** Session object ready for database storage
|
|
232
|
+
|
|
233
|
+
#### `generateSessionId()`
|
|
234
|
+
|
|
235
|
+
Generate a UUID v4 session ID.
|
|
236
|
+
|
|
237
|
+
**Returns:** UUID string
|
|
238
|
+
|
|
239
|
+
## Validation
|
|
240
|
+
|
|
241
|
+
All mock data is validated against schemas in `packages/schemas/schemas/api-authorization.schema.json`.
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const { validateAuthorizationRequirements } = require('@friggframework/schemas');
|
|
245
|
+
|
|
246
|
+
const mockData = createFormRequirements('nagaris', { fields: ['email'] });
|
|
247
|
+
const result = validateAuthorizationRequirements(mockData);
|
|
248
|
+
|
|
249
|
+
if (result.valid) {
|
|
250
|
+
console.log('✅ Mock data is schema-compliant');
|
|
251
|
+
} else {
|
|
252
|
+
console.error('❌ Validation errors:', result.errors);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Testing
|
|
257
|
+
|
|
258
|
+
Run the mock validation tests:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
cd packages/schemas
|
|
262
|
+
npm test mocks/__tests__/authorization-mocks.test.js
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
All tests validate that mocks are schema-compliant and work across packages.
|
|
266
|
+
|
|
267
|
+
## Contributing
|
|
268
|
+
|
|
269
|
+
When adding new authorization types:
|
|
270
|
+
|
|
271
|
+
1. Add schema definition to `api-authorization.schema.json`
|
|
272
|
+
2. Add mock generator to `authorization-mocks.js`
|
|
273
|
+
3. Add validation tests to `__tests__/authorization-mocks.test.js`
|
|
274
|
+
4. Update this README with usage examples
|
|
275
|
+
|
|
276
|
+
## Related
|
|
277
|
+
|
|
278
|
+
- [API Authorization Schema](../schemas/api-authorization.schema.json)
|
|
279
|
+
- [Core Authorization Use Cases](../../core/modules/use-cases/)
|
|
280
|
+
- [UI Authorization Components](../../ui/lib/integration/presentation/components/)
|