@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,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Authorization Mocks Tests
|
|
3
|
+
* @description Validates that all mock data generators produce schema-compliant data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
validateAuthorizationRequirements,
|
|
8
|
+
validateAuthorizationResponse,
|
|
9
|
+
validateAuthorizationSession,
|
|
10
|
+
} = require('../../index');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
createOAuth2Requirements,
|
|
14
|
+
createFormRequirements,
|
|
15
|
+
createOTPMultiStepFlow,
|
|
16
|
+
createAuthorizationSuccess,
|
|
17
|
+
createAuthorizationNextStep,
|
|
18
|
+
createAuthorizationSession,
|
|
19
|
+
createNagarisOTPFlowMock,
|
|
20
|
+
createOAuth2FlowMock,
|
|
21
|
+
} = require('../authorization-mocks');
|
|
22
|
+
|
|
23
|
+
describe('Authorization Mocks Schema Validation', () => {
|
|
24
|
+
describe('createOAuth2Requirements', () => {
|
|
25
|
+
it('should create valid OAuth2 requirements', () => {
|
|
26
|
+
const requirements = createOAuth2Requirements('hubspot');
|
|
27
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
28
|
+
|
|
29
|
+
expect(result.valid).toBe(true);
|
|
30
|
+
expect(result.errors).toBeNull();
|
|
31
|
+
expect(requirements.type).toBe('oauth2');
|
|
32
|
+
expect(requirements.data.url).toContain('hubspot');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should support custom scopes', () => {
|
|
36
|
+
const requirements = createOAuth2Requirements('salesforce', {
|
|
37
|
+
scopes: ['full', 'refresh_token'],
|
|
38
|
+
});
|
|
39
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
40
|
+
|
|
41
|
+
expect(result.valid).toBe(true);
|
|
42
|
+
expect(requirements.data.scopes).toEqual(['full', 'refresh_token']);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should support multi-step OAuth', () => {
|
|
46
|
+
const sessionId = 'session-123';
|
|
47
|
+
const requirements = createOAuth2Requirements('hubspot', {
|
|
48
|
+
isMultiStep: true,
|
|
49
|
+
step: 2,
|
|
50
|
+
totalSteps: 3,
|
|
51
|
+
sessionId,
|
|
52
|
+
});
|
|
53
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
54
|
+
|
|
55
|
+
expect(result.valid).toBe(true);
|
|
56
|
+
expect(requirements.isMultiStep).toBe(true);
|
|
57
|
+
expect(requirements.step).toBe(2);
|
|
58
|
+
expect(requirements.sessionId).toBe(sessionId);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('createFormRequirements', () => {
|
|
63
|
+
it('should create valid form requirements with email/password', () => {
|
|
64
|
+
const requirements = createFormRequirements('custom-api', {
|
|
65
|
+
fields: ['email', 'password'],
|
|
66
|
+
});
|
|
67
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
68
|
+
|
|
69
|
+
expect(result.valid).toBe(true);
|
|
70
|
+
expect(requirements.type).toBe('form');
|
|
71
|
+
expect(requirements.data.jsonSchema.properties).toHaveProperty('email');
|
|
72
|
+
expect(requirements.data.jsonSchema.properties).toHaveProperty('password');
|
|
73
|
+
expect(requirements.data.jsonSchema.required).toContain('email');
|
|
74
|
+
expect(requirements.data.jsonSchema.required).toContain('password');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should create valid OTP field', () => {
|
|
78
|
+
const requirements = createFormRequirements('nagaris', {
|
|
79
|
+
fields: ['otp'],
|
|
80
|
+
});
|
|
81
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
82
|
+
|
|
83
|
+
expect(result.valid).toBe(true);
|
|
84
|
+
expect(requirements.data.jsonSchema.properties.otp).toBeDefined();
|
|
85
|
+
expect(requirements.data.jsonSchema.properties.otp.pattern).toBe('^[0-9]{6}$');
|
|
86
|
+
expect(requirements.data.uiSchema.otp['ui:help']).toContain('6-digit');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should support API key fields', () => {
|
|
90
|
+
const requirements = createFormRequirements('api-service', {
|
|
91
|
+
fields: ['api_key'],
|
|
92
|
+
});
|
|
93
|
+
const result = validateAuthorizationRequirements(requirements);
|
|
94
|
+
|
|
95
|
+
expect(result.valid).toBe(true);
|
|
96
|
+
expect(requirements.data.jsonSchema.properties).toHaveProperty('api_key');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('createOTPMultiStepFlow', () => {
|
|
101
|
+
it('should create valid multi-step flow', () => {
|
|
102
|
+
const flow = createOTPMultiStepFlow('nagaris');
|
|
103
|
+
|
|
104
|
+
// Validate step 1
|
|
105
|
+
const step1Result = validateAuthorizationRequirements(flow.step1);
|
|
106
|
+
expect(step1Result.valid).toBe(true);
|
|
107
|
+
expect(flow.step1.step).toBe(1);
|
|
108
|
+
expect(flow.step1.totalSteps).toBe(2);
|
|
109
|
+
expect(flow.step1.isMultiStep).toBe(true);
|
|
110
|
+
|
|
111
|
+
// Validate step 2
|
|
112
|
+
const step2 = flow.step2('session-abc');
|
|
113
|
+
const step2Result = validateAuthorizationRequirements(step2);
|
|
114
|
+
expect(step2Result.valid).toBe(true);
|
|
115
|
+
expect(step2.step).toBe(2);
|
|
116
|
+
expect(step2.sessionId).toBe('session-abc');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('createAuthorizationSuccess', () => {
|
|
121
|
+
it('should create valid success response', () => {
|
|
122
|
+
const success = createAuthorizationSuccess('hubspot');
|
|
123
|
+
const result = validateAuthorizationResponse(success);
|
|
124
|
+
|
|
125
|
+
expect(result.valid).toBe(true);
|
|
126
|
+
expect(success.entity_id).toBeDefined();
|
|
127
|
+
expect(success.credential_id).toBeDefined();
|
|
128
|
+
expect(success.type).toBe('hubspot');
|
|
129
|
+
expect(success.display).toContain('hubspot');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should support custom IDs', () => {
|
|
133
|
+
const success = createAuthorizationSuccess('salesforce', {
|
|
134
|
+
entityId: 'entity-123',
|
|
135
|
+
credentialId: 'cred-456',
|
|
136
|
+
display: 'My Salesforce Org',
|
|
137
|
+
});
|
|
138
|
+
const result = validateAuthorizationResponse(success);
|
|
139
|
+
|
|
140
|
+
expect(result.valid).toBe(true);
|
|
141
|
+
expect(success.entity_id).toBe('entity-123');
|
|
142
|
+
expect(success.credential_id).toBe('cred-456');
|
|
143
|
+
expect(success.display).toBe('My Salesforce Org');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('createAuthorizationNextStep', () => {
|
|
148
|
+
it('should create valid next step response', () => {
|
|
149
|
+
const step2Reqs = createFormRequirements('nagaris', {
|
|
150
|
+
fields: ['otp'],
|
|
151
|
+
step: 2,
|
|
152
|
+
totalSteps: 2,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const nextStep = createAuthorizationNextStep(2, step2Reqs);
|
|
156
|
+
const result = validateAuthorizationResponse(nextStep);
|
|
157
|
+
|
|
158
|
+
expect(result.valid).toBe(true);
|
|
159
|
+
expect(nextStep.nextStep).toBe(2);
|
|
160
|
+
expect(nextStep.sessionId).toBeDefined();
|
|
161
|
+
expect(nextStep.requirements).toEqual(step2Reqs);
|
|
162
|
+
expect(nextStep.message).toContain('step');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should support custom session ID and message', () => {
|
|
166
|
+
const step2Reqs = createFormRequirements('nagaris', { fields: ['otp'] });
|
|
167
|
+
const nextStep = createAuthorizationNextStep(2, step2Reqs, {
|
|
168
|
+
sessionId: 'custom-session',
|
|
169
|
+
message: 'OTP sent to your email',
|
|
170
|
+
});
|
|
171
|
+
const result = validateAuthorizationResponse(nextStep);
|
|
172
|
+
|
|
173
|
+
expect(result.valid).toBe(true);
|
|
174
|
+
expect(nextStep.sessionId).toBe('custom-session');
|
|
175
|
+
expect(nextStep.message).toBe('OTP sent to your email');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('createAuthorizationSession', () => {
|
|
180
|
+
it('should create valid authorization session', () => {
|
|
181
|
+
const session = createAuthorizationSession('user-123', 'nagaris');
|
|
182
|
+
const result = validateAuthorizationSession(session);
|
|
183
|
+
|
|
184
|
+
expect(result.valid).toBe(true);
|
|
185
|
+
expect(session.userId).toBe('user-123');
|
|
186
|
+
expect(session.entityType).toBe('nagaris');
|
|
187
|
+
expect(session.sessionId).toBeDefined();
|
|
188
|
+
expect(session.currentStep).toBe(1);
|
|
189
|
+
expect(session.maxSteps).toBe(2);
|
|
190
|
+
expect(session.completed).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should support custom step data and completion', () => {
|
|
194
|
+
const session = createAuthorizationSession('user-456', 'hubspot', {
|
|
195
|
+
currentStep: 2,
|
|
196
|
+
maxSteps: 3,
|
|
197
|
+
stepData: { email: 'test@example.com', domain: 'mycompany' },
|
|
198
|
+
completed: true,
|
|
199
|
+
});
|
|
200
|
+
const result = validateAuthorizationSession(session);
|
|
201
|
+
|
|
202
|
+
expect(result.valid).toBe(true);
|
|
203
|
+
expect(session.currentStep).toBe(2);
|
|
204
|
+
expect(session.maxSteps).toBe(3);
|
|
205
|
+
expect(session.stepData.email).toBe('test@example.com');
|
|
206
|
+
expect(session.completed).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should have valid expiration timestamp', () => {
|
|
210
|
+
const session = createAuthorizationSession('user-789', 'salesforce', {
|
|
211
|
+
expiresInMinutes: 30,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const now = new Date();
|
|
215
|
+
const expiresAt = new Date(session.expiresAt);
|
|
216
|
+
const diffMinutes = (expiresAt - now) / (60 * 1000);
|
|
217
|
+
|
|
218
|
+
expect(diffMinutes).toBeGreaterThanOrEqual(29);
|
|
219
|
+
expect(diffMinutes).toBeLessThanOrEqual(31);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('createNagarisOTPFlowMock', () => {
|
|
224
|
+
it('should create complete valid Nagaris OTP flow', () => {
|
|
225
|
+
const flow = createNagarisOTPFlowMock('user-123');
|
|
226
|
+
|
|
227
|
+
// Step 1: Get requirements
|
|
228
|
+
const step1Reqs = flow.getStep1Requirements();
|
|
229
|
+
const step1ReqsResult = validateAuthorizationRequirements(step1Reqs);
|
|
230
|
+
expect(step1ReqsResult.valid).toBe(true);
|
|
231
|
+
expect(step1Reqs.step).toBe(1);
|
|
232
|
+
|
|
233
|
+
// Step 1: Submit
|
|
234
|
+
const step1Response = flow.submitStep1({ email: flow.email });
|
|
235
|
+
const step1ResponseResult = validateAuthorizationResponse(step1Response);
|
|
236
|
+
expect(step1ResponseResult.valid).toBe(true);
|
|
237
|
+
expect(step1Response.nextStep).toBe(2);
|
|
238
|
+
expect(step1Response.sessionId).toBe(flow.sessionId);
|
|
239
|
+
|
|
240
|
+
// Step 2: Submit
|
|
241
|
+
const step2Response = flow.submitStep2({ otp: '123456' });
|
|
242
|
+
const step2ResponseResult = validateAuthorizationResponse(step2Response);
|
|
243
|
+
expect(step2ResponseResult.valid).toBe(true);
|
|
244
|
+
expect(step2Response.entity_id).toBeDefined();
|
|
245
|
+
expect(step2Response.type).toBe('nagaris');
|
|
246
|
+
|
|
247
|
+
// Session
|
|
248
|
+
const sessionResult = validateAuthorizationSession(flow.session);
|
|
249
|
+
expect(sessionResult.valid).toBe(true);
|
|
250
|
+
expect(flow.session.userId).toBe('user-123');
|
|
251
|
+
expect(flow.session.entityType).toBe('nagaris');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('createOAuth2FlowMock', () => {
|
|
256
|
+
it('should create complete valid OAuth2 flow', () => {
|
|
257
|
+
const flow = createOAuth2FlowMock('hubspot', 'user-456');
|
|
258
|
+
|
|
259
|
+
// Get requirements
|
|
260
|
+
const requirements = flow.getRequirements();
|
|
261
|
+
const reqsResult = validateAuthorizationRequirements(requirements);
|
|
262
|
+
expect(reqsResult.valid).toBe(true);
|
|
263
|
+
expect(requirements.type).toBe('oauth2');
|
|
264
|
+
expect(requirements.isMultiStep).toBe(false);
|
|
265
|
+
|
|
266
|
+
// Handle callback
|
|
267
|
+
const callbackResponse = flow.handleCallback('auth-code-123', 'state-xyz');
|
|
268
|
+
const callbackResult = validateAuthorizationResponse(callbackResponse);
|
|
269
|
+
expect(callbackResult.valid).toBe(true);
|
|
270
|
+
expect(callbackResponse.entity_id).toBeDefined();
|
|
271
|
+
expect(callbackResponse.type).toBe('hubspot');
|
|
272
|
+
|
|
273
|
+
// No session for single-step OAuth
|
|
274
|
+
expect(flow.session).toBeNull();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('Cross-Package Compatibility', () => {
|
|
280
|
+
it('should work with @friggframework/core integration tests', () => {
|
|
281
|
+
// This mock can be used in core integration tests
|
|
282
|
+
const flow = createNagarisOTPFlowMock('test-user');
|
|
283
|
+
const session = flow.session;
|
|
284
|
+
|
|
285
|
+
// Core would validate session before storing
|
|
286
|
+
const result = validateAuthorizationSession(session);
|
|
287
|
+
expect(result.valid).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should work with @friggframework/ui component tests', () => {
|
|
291
|
+
// This mock can be used in UI component tests
|
|
292
|
+
const requirements = createFormRequirements('nagaris', {
|
|
293
|
+
fields: ['email'],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// UI would render form based on jsonSchema
|
|
297
|
+
expect(requirements.data.jsonSchema.properties.email).toBeDefined();
|
|
298
|
+
expect(requirements.data.uiSchema.email).toBeDefined();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should work with management-ui tests', () => {
|
|
302
|
+
// Management UI would display auth flows
|
|
303
|
+
const oauthReqs = createOAuth2Requirements('hubspot');
|
|
304
|
+
const formReqs = createFormRequirements('api-service', {
|
|
305
|
+
fields: ['api_key'],
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect(oauthReqs.type).toBe('oauth2');
|
|
309
|
+
expect(formReqs.type).toBe('form');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Authorization Mock Data Generators
|
|
3
|
+
* @description Canonical mock data for authorization flows
|
|
4
|
+
*
|
|
5
|
+
* These mocks are schema-compliant and can be used across:
|
|
6
|
+
* - @friggframework/core tests
|
|
7
|
+
* - @friggframework/ui tests
|
|
8
|
+
* - @friggframework/devtools/management-ui tests
|
|
9
|
+
* - Integration tests
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```js
|
|
13
|
+
* const { createOAuth2Requirements, createFormRequirements } = require('@friggframework/schemas/mocks/authorization-mocks');
|
|
14
|
+
*
|
|
15
|
+
* const mockData = createOAuth2Requirements('hubspot');
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a unique session ID
|
|
23
|
+
*/
|
|
24
|
+
function generateSessionId() {
|
|
25
|
+
return crypto.randomUUID();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create OAuth2 authorization requirements
|
|
30
|
+
* @param {string} moduleType - Module type (e.g., 'hubspot', 'salesforce')
|
|
31
|
+
* @param {object} options - Additional options
|
|
32
|
+
* @returns {object} OAuth2 requirements object
|
|
33
|
+
*/
|
|
34
|
+
function createOAuth2Requirements(moduleType, options = {}) {
|
|
35
|
+
const {
|
|
36
|
+
scopes = ['read', 'write'],
|
|
37
|
+
isMultiStep = false,
|
|
38
|
+
step = 1,
|
|
39
|
+
totalSteps = 1,
|
|
40
|
+
sessionId = null
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
const requirements = {
|
|
44
|
+
type: 'oauth2',
|
|
45
|
+
step,
|
|
46
|
+
totalSteps,
|
|
47
|
+
isMultiStep,
|
|
48
|
+
data: {
|
|
49
|
+
url: `https://auth.${moduleType}.com/oauth/authorize?client_id=abc123&redirect_uri=http://localhost:3000/callback&state=xyz`,
|
|
50
|
+
scopes
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (sessionId) {
|
|
55
|
+
requirements.sessionId = sessionId;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return requirements;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create form-based authorization requirements
|
|
63
|
+
* @param {string} moduleType - Module type
|
|
64
|
+
* @param {object} options - Additional options
|
|
65
|
+
* @returns {object} Form requirements object
|
|
66
|
+
*/
|
|
67
|
+
function createFormRequirements(moduleType, options = {}) {
|
|
68
|
+
const {
|
|
69
|
+
fields = ['email', 'password'],
|
|
70
|
+
isMultiStep = false,
|
|
71
|
+
step = 1,
|
|
72
|
+
totalSteps = 1,
|
|
73
|
+
sessionId = null
|
|
74
|
+
} = options;
|
|
75
|
+
|
|
76
|
+
const properties = {};
|
|
77
|
+
const required = [];
|
|
78
|
+
|
|
79
|
+
// Build JSON schema properties
|
|
80
|
+
fields.forEach(field => {
|
|
81
|
+
if (field === 'email') {
|
|
82
|
+
properties.email = {
|
|
83
|
+
type: 'string',
|
|
84
|
+
format: 'email',
|
|
85
|
+
title: 'Email Address'
|
|
86
|
+
};
|
|
87
|
+
required.push('email');
|
|
88
|
+
} else if (field === 'password') {
|
|
89
|
+
properties.password = {
|
|
90
|
+
type: 'string',
|
|
91
|
+
title: 'Password',
|
|
92
|
+
minLength: 6
|
|
93
|
+
};
|
|
94
|
+
required.push('password');
|
|
95
|
+
} else if (field === 'api_key') {
|
|
96
|
+
properties.api_key = {
|
|
97
|
+
type: 'string',
|
|
98
|
+
title: 'API Key'
|
|
99
|
+
};
|
|
100
|
+
required.push('api_key');
|
|
101
|
+
} else if (field === 'otp') {
|
|
102
|
+
properties.otp = {
|
|
103
|
+
type: 'string',
|
|
104
|
+
title: 'One-Time Password',
|
|
105
|
+
pattern: '^[0-9]{6}$'
|
|
106
|
+
};
|
|
107
|
+
required.push('otp');
|
|
108
|
+
} else {
|
|
109
|
+
properties[field] = {
|
|
110
|
+
type: 'string',
|
|
111
|
+
title: field.charAt(0).toUpperCase() + field.slice(1)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const requirements = {
|
|
117
|
+
type: 'form',
|
|
118
|
+
step,
|
|
119
|
+
totalSteps,
|
|
120
|
+
isMultiStep,
|
|
121
|
+
data: {
|
|
122
|
+
jsonSchema: {
|
|
123
|
+
title: `Connect ${moduleType}`,
|
|
124
|
+
description: `Enter your ${moduleType} credentials`,
|
|
125
|
+
type: 'object',
|
|
126
|
+
required,
|
|
127
|
+
properties
|
|
128
|
+
},
|
|
129
|
+
uiSchema: {
|
|
130
|
+
email: {
|
|
131
|
+
'ui:placeholder': 'your.email@company.com'
|
|
132
|
+
},
|
|
133
|
+
password: {
|
|
134
|
+
'ui:widget': 'password'
|
|
135
|
+
},
|
|
136
|
+
otp: {
|
|
137
|
+
'ui:placeholder': '123456',
|
|
138
|
+
'ui:help': 'Enter the 6-digit code sent to your email'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (sessionId) {
|
|
145
|
+
requirements.sessionId = sessionId;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return requirements;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create multi-step OTP authorization flow (like Nagaris)
|
|
153
|
+
* @param {string} moduleType - Module type
|
|
154
|
+
* @returns {object} Multi-step requirements
|
|
155
|
+
*/
|
|
156
|
+
function createOTPMultiStepFlow(moduleType = 'nagaris') {
|
|
157
|
+
return {
|
|
158
|
+
step1: createFormRequirements(moduleType, {
|
|
159
|
+
fields: ['email'],
|
|
160
|
+
isMultiStep: true,
|
|
161
|
+
step: 1,
|
|
162
|
+
totalSteps: 2
|
|
163
|
+
}),
|
|
164
|
+
step2: (sessionId) => createFormRequirements(moduleType, {
|
|
165
|
+
fields: ['otp'],
|
|
166
|
+
isMultiStep: true,
|
|
167
|
+
step: 2,
|
|
168
|
+
totalSteps: 2,
|
|
169
|
+
sessionId
|
|
170
|
+
})
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Create authorization success response
|
|
176
|
+
* @param {string} moduleType - Module type
|
|
177
|
+
* @param {object} options - Additional options
|
|
178
|
+
* @returns {object} Authorization success response
|
|
179
|
+
*/
|
|
180
|
+
function createAuthorizationSuccess(moduleType, options = {}) {
|
|
181
|
+
const {
|
|
182
|
+
entityId = crypto.randomUUID(),
|
|
183
|
+
credentialId = crypto.randomUUID(),
|
|
184
|
+
display = `My ${moduleType} Account`
|
|
185
|
+
} = options;
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
entity_id: entityId,
|
|
189
|
+
credential_id: credentialId,
|
|
190
|
+
type: moduleType,
|
|
191
|
+
display
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create authorization next step response
|
|
197
|
+
* @param {number} nextStep - Next step number
|
|
198
|
+
* @param {object} requirements - Requirements for next step
|
|
199
|
+
* @param {object} options - Additional options
|
|
200
|
+
* @returns {object} Next step response
|
|
201
|
+
*/
|
|
202
|
+
function createAuthorizationNextStep(nextStep, requirements, options = {}) {
|
|
203
|
+
const {
|
|
204
|
+
sessionId = generateSessionId(),
|
|
205
|
+
message = `Step ${nextStep - 1} completed. Proceed to step ${nextStep}.`
|
|
206
|
+
} = options;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
nextStep,
|
|
210
|
+
sessionId,
|
|
211
|
+
requirements,
|
|
212
|
+
message
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create authorization session object
|
|
218
|
+
* @param {string} userId - User ID
|
|
219
|
+
* @param {string} entityType - Entity/module type
|
|
220
|
+
* @param {object} options - Additional options
|
|
221
|
+
* @returns {object} Authorization session
|
|
222
|
+
*/
|
|
223
|
+
function createAuthorizationSession(userId, entityType, options = {}) {
|
|
224
|
+
const {
|
|
225
|
+
currentStep = 1,
|
|
226
|
+
maxSteps = 2,
|
|
227
|
+
stepData = {},
|
|
228
|
+
expiresInMinutes = 15,
|
|
229
|
+
completed = false
|
|
230
|
+
} = options;
|
|
231
|
+
|
|
232
|
+
const now = new Date();
|
|
233
|
+
const expiresAt = new Date(now.getTime() + expiresInMinutes * 60000);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
sessionId: generateSessionId(),
|
|
237
|
+
userId,
|
|
238
|
+
entityType,
|
|
239
|
+
currentStep,
|
|
240
|
+
maxSteps,
|
|
241
|
+
stepData,
|
|
242
|
+
expiresAt: expiresAt.toISOString(),
|
|
243
|
+
completed,
|
|
244
|
+
createdAt: now.toISOString(),
|
|
245
|
+
updatedAt: now.toISOString()
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create a complete Nagaris OTP flow mock
|
|
251
|
+
* @param {string} userId - User ID
|
|
252
|
+
* @returns {object} Complete flow mock with step functions
|
|
253
|
+
*/
|
|
254
|
+
function createNagarisOTPFlowMock(userId = 'user123') {
|
|
255
|
+
const sessionId = generateSessionId();
|
|
256
|
+
const email = 'test@example.com';
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
// Step 1: Get requirements (email)
|
|
260
|
+
getStep1Requirements: () => createFormRequirements('nagaris', {
|
|
261
|
+
fields: ['email'],
|
|
262
|
+
isMultiStep: true,
|
|
263
|
+
step: 1,
|
|
264
|
+
totalSteps: 2
|
|
265
|
+
}),
|
|
266
|
+
|
|
267
|
+
// Step 1: Submit email, get next step
|
|
268
|
+
submitStep1: (emailData) => {
|
|
269
|
+
const step2Reqs = createFormRequirements('nagaris', {
|
|
270
|
+
fields: ['otp'],
|
|
271
|
+
isMultiStep: true,
|
|
272
|
+
step: 2,
|
|
273
|
+
totalSteps: 2,
|
|
274
|
+
sessionId
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return createAuthorizationNextStep(2, step2Reqs, {
|
|
278
|
+
sessionId,
|
|
279
|
+
message: 'OTP sent to your email. Please check your inbox.'
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// Step 2: Submit OTP, get success
|
|
284
|
+
submitStep2: (otpData) => {
|
|
285
|
+
return createAuthorizationSuccess('nagaris', {
|
|
286
|
+
display: `Nagaris Account (${email})`
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// Session object
|
|
291
|
+
session: createAuthorizationSession(userId, 'nagaris', {
|
|
292
|
+
currentStep: 1,
|
|
293
|
+
maxSteps: 2,
|
|
294
|
+
stepData: { email }
|
|
295
|
+
}),
|
|
296
|
+
|
|
297
|
+
// Utility
|
|
298
|
+
sessionId,
|
|
299
|
+
email
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create a complete OAuth2 flow mock
|
|
305
|
+
* @param {string} moduleType - Module type (e.g., 'hubspot')
|
|
306
|
+
* @param {string} userId - User ID
|
|
307
|
+
* @returns {object} Complete OAuth flow mock
|
|
308
|
+
*/
|
|
309
|
+
function createOAuth2FlowMock(moduleType, userId = 'user123') {
|
|
310
|
+
return {
|
|
311
|
+
// Get initial requirements
|
|
312
|
+
getRequirements: () => createOAuth2Requirements(moduleType),
|
|
313
|
+
|
|
314
|
+
// OAuth callback with code
|
|
315
|
+
handleCallback: (code, state) => {
|
|
316
|
+
return createAuthorizationSuccess(moduleType, {
|
|
317
|
+
display: `My ${moduleType} Account`
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// Session (single-step OAuth doesn't need session)
|
|
322
|
+
session: null
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = {
|
|
327
|
+
// Generators
|
|
328
|
+
generateSessionId,
|
|
329
|
+
createOAuth2Requirements,
|
|
330
|
+
createFormRequirements,
|
|
331
|
+
createOTPMultiStepFlow,
|
|
332
|
+
createAuthorizationSuccess,
|
|
333
|
+
createAuthorizationNextStep,
|
|
334
|
+
createAuthorizationSession,
|
|
335
|
+
|
|
336
|
+
// Complete flow mocks
|
|
337
|
+
createNagarisOTPFlowMock,
|
|
338
|
+
createOAuth2FlowMock,
|
|
339
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/schemas",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.80",
|
|
4
4
|
"description": "Canonical JSON Schema definitions for Frigg Framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"files": [
|
|
38
38
|
"schemas/",
|
|
39
39
|
"validators/",
|
|
40
|
+
"mocks/",
|
|
41
|
+
"middleware/",
|
|
40
42
|
"index.js"
|
|
41
43
|
],
|
|
42
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "7a66dfcb02143941411ff26aaee866afc1473df8"
|
|
43
45
|
}
|