Skip to content

Commit 21a6aed

Browse files
feat(authorization): support dynamic model schema
1 parent aa17e6a commit 21a6aed

File tree

3 files changed

+153
-8
lines changed

3 files changed

+153
-8
lines changed

microservices/authorization/__tests__/services/fields-filter-test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,121 @@ describe('services/fields-filter', () => {
259259
userId: input.userId,
260260
});
261261
});
262+
263+
it('should correctly filter field: dynamic schema', async () => {
264+
const input1 = {
265+
value: {
266+
test1: {
267+
user: 'name',
268+
email: 'email',
269+
},
270+
},
271+
};
272+
const input2 = {
273+
value: {
274+
test2: {
275+
response: {
276+
test: true,
277+
private: 'private',
278+
},
279+
},
280+
},
281+
};
282+
const input1Model = modelRepository.create({
283+
alias: 'aliasToInput1',
284+
schema: {
285+
test1: {
286+
object: {
287+
user: {
288+
in: { users: FieldPolicy.allow },
289+
out: { users: FieldPolicy.allow },
290+
},
291+
email: {
292+
in: { admins: FieldPolicy.allow },
293+
out: { admins: FieldPolicy.allow },
294+
},
295+
},
296+
},
297+
},
298+
});
299+
const input2Model = modelRepository.create({
300+
alias: 'aliasToInput2',
301+
schema: {
302+
test2: {
303+
object: {
304+
response: {
305+
object: {
306+
test: {
307+
in: { users: FieldPolicy.deny },
308+
out: { users: FieldPolicy.allow },
309+
},
310+
private: {
311+
in: { admins: FieldPolicy.deny },
312+
out: { admins: FieldPolicy.allow },
313+
},
314+
},
315+
},
316+
},
317+
},
318+
},
319+
});
320+
const getModel = (modelAlias = 'input1') =>
321+
modelRepository.create({
322+
alias: 'alias',
323+
schema: {
324+
value: {
325+
case: {
326+
template: `<%= "${modelAlias}" %>`,
327+
},
328+
object: {
329+
input1: 'aliasToInput1',
330+
input2: 'aliasToInput2',
331+
},
332+
},
333+
},
334+
});
335+
336+
// resolve allow schema on first call (on second call return cached schema)
337+
TypeormMock.entityManager.findOne.callsFake((...args) => {
338+
const [, { alias }] = args;
339+
340+
if (alias === 'aliasToInput1') {
341+
return input1Model;
342+
} else if (alias === 'aliasToInput2') {
343+
return input2Model;
344+
}
345+
346+
return null;
347+
});
348+
349+
const res1 = await service.filter(FilterType.OUT, getModel(), input1);
350+
const res2 = await service.filter(FilterType.OUT, getModel('input2'), input2);
351+
const res3 = await service.filter(FilterType.IN, getModel('input2'), input2);
352+
const res4 = await service.filter(FilterType.IN, getModel(), input2); // mismatch input data and caseValue
353+
354+
expect(res1).to.deep.equal({
355+
value: {
356+
test1: {
357+
user: 'name',
358+
},
359+
},
360+
});
361+
expect(res2).to.deep.equal({
362+
value: {
363+
test2: {
364+
response: {
365+
test: true,
366+
},
367+
},
368+
},
369+
});
370+
expect(res3).to.deep.equal({
371+
value: {
372+
test2: {
373+
response: {},
374+
},
375+
},
376+
});
377+
expect(res4).to.deep.equal({});
378+
});
262379
});

microservices/authorization/src/entities/model.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export interface IModelSchema {
2424
[fieldName: string]:
2525
| string // related model alias
2626
| { object: IModelSchema; isCustom?: boolean }
27+
| {
28+
case: IFieldCondition;
29+
object: { [key: string]: string | IModelSchema };
30+
isCustom?: boolean;
31+
}
2732
| {
2833
in?: IRolePermissions;
2934
out?: IRolePermissions;
@@ -58,6 +63,12 @@ class Model {
5863
{ '*': 'allow' },
5964
{ '*': 'deny' },
6065
{ field1: 'aliasAnotherModel', field2: { object: { nestedField: 'aliasModel' } } }, // aliases
66+
{
67+
field1: {
68+
case: 'case2', // dynamic choose scheme
69+
cases: { case1: 'aliasModel', case2: 'modelAlias' },
70+
},
71+
}, // dynamic schema => { field1: 'modelAlias' }
6172
{ simpleField: { in: { guests: 'deny', users: 'allow' }, out: { guest: 'allow' } } }, // standard fields
6273
{
6374
// standard field

microservices/authorization/src/services/fields-filter.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ class FieldsFilter {
129129
const nestedSchema = await this.getSchemaByAlias(policy);
130130

131131
newValue = await this.filterBySchema(nestedSchema, type, value);
132+
} else if ('case' in policy) {
133+
const caseValue = this.templateValue(policy.case.template, value);
134+
const dynamicSchema = { [field]: policy.object[caseValue] } as IModelSchema;
135+
136+
newValue = (await this.filterBySchema(dynamicSchema, type, { [field]: value }))?.[field];
137+
138+
if (_.isEmpty(newValue)) {
139+
newValue = undefined;
140+
}
132141
} else if ('object' in policy) {
133142
// nested schema
134143
newValue = await this.filterBySchema(policy.object, type, value);
@@ -166,14 +175,7 @@ class FieldsFilter {
166175
}
167176

168177
if (permission.template) {
169-
const newValue = _.template(permission.template)({
170-
value,
171-
params: this.templateOptions,
172-
current: {
173-
userId: this.userId,
174-
roles: this.userRoles,
175-
},
176-
});
178+
const newValue = this.templateValue(permission.template, value);
177179

178180
if (newValue === 'undefined') {
179181
return undefined;
@@ -187,6 +189,21 @@ class FieldsFilter {
187189
}
188190
}
189191

192+
/**
193+
* Template value
194+
* @private
195+
*/
196+
private templateValue(template: string, value?: any): string {
197+
return _.template(template)({
198+
value,
199+
params: this.templateOptions,
200+
current: {
201+
userId: this.userId,
202+
roles: this.userRoles,
203+
},
204+
});
205+
}
206+
190207
/**
191208
* Filter fields in array
192209
* @private

0 commit comments

Comments
 (0)