Skip to content

Commit

Permalink
support for async firewall functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Bero committed Aug 12, 2024
1 parent ae88d68 commit da1d144
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 124 deletions.
6 changes: 3 additions & 3 deletions @types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ declare module 'meteor/cultofcoders:grapher' {
filters: { [key: string]: unknown },
options: Mongo.Options<T>,
userId?: string,
) => void;
) => void | Promise<void>;

export type Params = {
[key: string]: unknown;
Expand Down Expand Up @@ -210,7 +210,7 @@ declare module 'meteor/cultofcoders:grapher' {

export interface QueryBase {
// client-side
fetch(callback: DataCallback): void;
fetch(callback: DataCallback): unknown[];
fetchAsync(): Promise<unknown>;
// @deprecated
fetchSync(): Promise<unknown>;
Expand Down Expand Up @@ -288,7 +288,7 @@ namespace Mongo {
filters: Grapher.Filters<T>,
options: Grapher.Options<T>,
userId?: string,
) => void;
) => Promise<void>;

addLinks(links: Record<string, Grapher.LinkConfig>): void;
getLinker(name: string): Grapher.LinkerClass | undefined;
Expand Down
56 changes: 41 additions & 15 deletions lib/exposure/exposure.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,17 @@ export default class Exposure {
const collection = this.collection;
const { firewall, maxLimit, restrictedFields } = this.config;
const find = collection.find.bind(collection);
const findOne = collection.findOne.bind(collection);
const findOneAsync = collection.findOneAsync.bind(collection);

collection.firewall = (filters, options, userId) => {
collection.firewall = async (filters, options, userId) => {
if (userId !== undefined) {
this._callFirewall(
await this._callFirewall(
{ collection: collection },
filters,
options,
userId,
);

enforceMaxLimit(options, maxLimit);

if (restrictedFields) {
Exposure.restrictFields(filters, options, restrictedFields);
}
Expand All @@ -296,16 +294,43 @@ export default class Exposure {
return find(undefined, options);
}

collection.firewall(filters, options, userId);
// Before it was in firewall function, but now
// that function is async, it is moved here.
if (userId !== undefined) {
enforceMaxLimit(options, maxLimit);
}

if (!enforceMaxDepth) {
delete options.limit;
}

return find(filters, options);
const cursor = find(filters, options);
[
'fetchAsync',
'countAsync',
'forEachAsync',
'mapAsync',
'observe',
// 'observeAsync',
// 'observeChangesAsync',
].forEach((method) => {
if (cursor[method]) {
const oldCursorMethod = cursor[method];
cursor[method] = async (...args) => {
await collection.firewall(filters, options, userId);
return oldCursorMethod.call(cursor, ...args);
};
}
});

return cursor;
};

collection.findOne = function (filters, options = {}, userId = undefined) {
collection.findOneAsync = async function (
filters,
options = {},
userId = undefined,
) {
// If filters is undefined it should return an empty item
if (arguments.length > 0 && filters === undefined) {
return null;
Expand All @@ -315,27 +340,28 @@ export default class Exposure {
filters = { _id: filters };
}

collection.firewall(filters, options, userId);
await collection.firewall(filters, options, userId);

return findOne(filters, options);
return findOneAsync(filters, options);
};
}

/**
* @param {[unknown, ...Parameters<Grapher.ExposureFirewallFn>]} args
* @private
*/
_callFirewall(...args) {
async _callFirewall(...args) {
const { firewall } = this.config;
if (!firewall) {
return;
}

if (Array.isArray(firewall)) {
firewall.forEach((fire) => {
fire.call(...args);
});
for (const f of firewall) {
await f.call(...args);
}
} else {
firewall.call(...args);
await firewall.call(...args);
}
}
}
50 changes: 28 additions & 22 deletions lib/exposure/testing/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@ import Intersect, {
import { _ } from 'meteor/underscore';

describe('Exposure Tests', function () {
it('Should fetch only allowed data and limitations should be applied', function (done) {
it('Should fetch only allowed data and limitations should be applied', async function () {
const query = Demo.createQuery({
$options: { limit: 3 },
$options: { limit: 4 },
restrictedField: 1,
});

query.fetch((err, res) => {
assert.isUndefined(err);
assert.isDefined(res);
const res = await query.fetchAsync();
assert.isDefined(res);

assert.lengthOf(res, 2);
done();
});
assert.lengthOf(res, 2);

// query.fetch((err, res) => {
// console.log('err/res', err, res);
// assert.isUndefined(err);
// assert.isDefined(res);

// assert.lengthOf(res, 2);
// done();
// });
});

it('Should not allow me to fetch the graph data, because of maxDepth', function (done) {
Expand All @@ -39,13 +45,9 @@ describe('Exposure Tests', function () {
});
});

it('Should return the correct count', function (done) {
Meteor.call('exposure_exposure_test.count', {}, function (err, res) {
assert.isUndefined(err);

assert.equal(3, res);
done();
});
it('Should return the correct count', async function () {
const res = await Meteor.callAsync('exposure_exposure_test.count', {});
assert.equal(3, res);
});

it('Should return the correct count via query', function (done) {
Expand Down Expand Up @@ -115,16 +117,20 @@ describe('Exposure Tests', function () {
});

query.fetch((err, res) => {
assert.isUndefined(err);
try {
assert.isUndefined(err);

_.each(res, (item) => {
assert.isUndefined(item.restrictedLink);
});
_.each(res, (item) => {
assert.isUndefined(item.restrictedLink);
});

assert.isArray(res);
assert.isFalse(res.length === 0);
assert.isArray(res);
assert.isFalse(res.length === 0);

done();
done();
} catch (err) {
done(err);
}
});
});

Expand Down
14 changes: 7 additions & 7 deletions lib/namedQuery/expose/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ _.extend(NamedQuery.prototype, {
return query.getCursorForCounting();
},

getSession(params) {
async getSession(params) {
self.doValidateParams(params);
self._callFirewall(this, this.userId, params);
await self._callFirewall(this, this.userId, params);

return { name: self.name, params };
},
Expand Down Expand Up @@ -189,18 +189,18 @@ _.extend(NamedQuery.prototype, {
* @param params
* @private
*/
_callFirewall(context, userId, params) {
async _callFirewall(context, userId, params) {
const { firewall } = this.exposeConfig;
if (!firewall) {
return;
}

if (Array.isArray(firewall)) {
firewall.forEach((fire) => {
fire.call(context, userId, params);
});
for (const f of firewall) {
await f.call(context, userId, params);
}
} else {
firewall.call(context, userId, params);
await firewall.call(context, userId, params);
}
},

Expand Down
12 changes: 6 additions & 6 deletions lib/namedQuery/namedQuery.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ export default class NamedQueryBase {
* Validates the parameters
* @param {P} params
*
* @returns {Promise<void>}
* @returns {void}
*/
async doValidateParams(params) {
doValidateParams(params) {
params = params || this.params;

const { validateParams } = this.options;
if (!validateParams) return;

try {
await this._validate(validateParams, params);
this._validate(validateParams, params);
} catch (validationError) {
console.error(
`Invalid parameters supplied to the query "${this.queryName}"\n`,
Expand Down Expand Up @@ -142,12 +142,12 @@ export default class NamedQueryBase {
/**
* @param {Grapher.ValidateParamsParam} validator
* @param {P} params
* @returns {Promise<void>}
* @returns {void}
* @private
*/
async _validate(validator, params) {
_validate(validator, params) {
if (typeof validator === 'function') {
await validator.call(null, params);
validator.call(null, params);
} else {
check(params, validator);
}
Expand Down
19 changes: 4 additions & 15 deletions lib/namedQuery/namedQuery.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,7 @@ export default class extends Base {
* @returns {any}
*/
getCount(context) {
this._performSecurityChecks(context, this.params);

const countCursor = this.getCursorForCounting();

if (this.cacher) {
const cacheId =
'count::' + this.cacher.generateQueryId(this.queryName, this.params);

return this.cacher.fetch(cacheId, { countCursor });
}

return countCursor.count();
throw new Error('count is not available on server, please use countAsync');
}

/**
Expand All @@ -90,8 +79,8 @@ export default class extends Base {
* @param {Grapher.QueryFetchContext<T>} [context]
* @returns {Promise<number>}
*/
getCountAsync(context) {
this._performSecurityChecks(context, this.params);
async getCountAsync(context) {
await this._performSecurityChecks(context, this.params);

const countCursor = this.getCursorForCounting();

Expand Down Expand Up @@ -177,6 +166,6 @@ export default class extends Base {
await this._callFirewall(context, context.userId, params);
}

await this.doValidateParams(params);
this.doValidateParams(params);
}
}
60 changes: 31 additions & 29 deletions lib/namedQuery/testing/bootstrap/queries/postListExposure.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import { createQuery } from 'meteor/cultofcoders:grapher';

const postListExposure = createQuery('postListExposure', {
posts: {
title: 1,
author: {
name: 1
},
group: {
name: 1
}
}
posts: {
title: 1,
author: {
name: 1,
},
group: {
name: 1,
},
},
});

export const postListFilteredWithDate = createQuery('postListFilteredWithDate', {
export const postListFilteredWithDate = createQuery(
'postListFilteredWithDate',
{
posts: {
$filter({filters, options, params}) {
if (params.date) {
filters.createdAt = {$lte: params.date}
}
},
createdAt:1,
}
});
$filter({ filters, options, params }) {
if (params.date) {
filters.createdAt = { $lte: params.date };
}
},
createdAt: 1,
},
},
);

if (Meteor.isServer) {
postListExposure.expose({
firewall(userId, params) {
},
embody: {
$filter({filters, params}) {
filters.title = params.title
}
}
});
postListFilteredWithDate.expose({});
postListExposure.expose({
firewall(userId, params) {},
embody: {
$filter({ filters, params }) {
filters.title = params.title;
},
},
});
postListFilteredWithDate.expose({});
}

export default postListExposure;
export default postListExposure;
Loading

0 comments on commit da1d144

Please sign in to comment.