Verify User and AccessToken relations at startup

Modify `app.enableAuth()` to verify that (custom) User and AccessToken
models have correctly configured their hasMany/belongsTo relations
and print a warning otherwise.
This commit is contained in:
Miroslav Bajtoš 2017-02-24 14:07:41 +01:00
parent f0c9700e1d
commit 79f441b9c4
No known key found for this signature in database
GPG Key ID: 797723F23CE0A94A
5 changed files with 97 additions and 2 deletions

View File

@ -398,9 +398,88 @@ app.enableAuth = function(options) {
} }
}; };
this._verifyAuthModelRelations();
this.isAuthEnabled = true; this.isAuthEnabled = true;
}; };
app._verifyAuthModelRelations = function() {
// Allow unit-tests (but also LoopBack users) to disable the warnings
if (this.get('_verifyAuthModelRelations') === false) return;
const AccessToken = this.registry.findModel('AccessToken');
const User = this.registry.findModel('User');
this.models().forEach(Model => {
if (Model === AccessToken || Model.prototype instanceof AccessToken) {
scheduleVerification(Model, verifyAccessTokenRelations);
}
if (Model === User || Model.prototype instanceof User) {
scheduleVerification(Model, verifyUserRelations);
}
});
function scheduleVerification(Model, verifyFn) {
if (Model.dataSource) {
verifyFn(Model);
} else {
Model.on('attached', () => verifyFn(Model));
}
}
function verifyAccessTokenRelations(Model) {
const belongsToUser = Model.relations && Model.relations.user;
if (belongsToUser) return;
const relationsConfig = Model.settings.relations || {};
const userName = (relationsConfig.user || {}).model;
if (userName) {
console.warn(
'The model %j configures "belongsTo User-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'custom User subclass, but does not fix AccessToken relations ' +
'to use this new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, userName, userName);
return;
}
console.warn(
'The model %j does not have "belongsTo User-like model" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName);
}
function verifyUserRelations(Model) {
const hasManyTokens = Model.relations && Model.relations.accessTokens;
if (hasManyTokens) return;
const relationsConfig = Model.settings.relations || {};
const accessTokenName = (relationsConfig.accessTokens || {}).model;
if (accessTokenName) {
console.warn(
'The model %j configures "hasMany AccessToken-like models" relation ' +
'with target model %j. However, the model %j is not attached to ' +
'the application and therefore cannot be used by this relation. ' +
'This typically happens when the application has a custom ' +
'AccessToken subclass, but does not fix User relations to use this ' +
'new model.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName, accessTokenName, accessTokenName);
return;
}
console.warn(
'The model %j does not have "hasMany AccessToken-like models" relation ' +
'configured.\n' +
'Learn more at http://ibm.biz/setup-loopback-auth',
Model.modelName);
}
};
app.boot = function(options) { app.boot = function(options) {
throw new Error( throw new Error(
g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead')); g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'));

View File

@ -454,6 +454,10 @@ describe('app.enableAuth()', function() {
ACL = app.registry.getModel('ACL'); ACL = app.registry.getModel('ACL');
// Fix User's "hasMany accessTokens" relation to use our new MyToken model
const User = app.registry.getModel('User');
User.settings.relations.accessTokens.model = 'MyToken';
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
}); });
beforeEach(createTestingToken); beforeEach(createTestingToken);

View File

@ -888,6 +888,10 @@ describe('app', function() {
var Customer = app.registry.createModel('Customer', {}, {base: 'User'}); var Customer = app.registry.createModel('Customer', {}, {base: 'User'});
app.model(Customer, {dataSource: 'db'}); app.model(Customer, {dataSource: 'db'});
// Fix AccessToken's "belongsTo user" relation to use our new Customer model
const AccessToken = app.registry.getModel('AccessToken');
AccessToken.settings.relations.user.model = 'Customer';
app.enableAuth({dataSource: 'db'}); app.enableAuth({dataSource: 'db'});
expect(Object.keys(app.models)).to.not.include('User'); expect(Object.keys(app.models)).to.not.include('User');

View File

@ -15,5 +15,12 @@
"principalId": "$everyone", "principalId": "$everyone",
"property": "create" "property": "create"
} }
] ],
} "relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
}
}

View File

@ -2374,6 +2374,7 @@ describe('User', function() {
it('handles subclassed user with no accessToken relation', () => { it('handles subclassed user with no accessToken relation', () => {
// setup a new LoopBack app, we don't want to use shared models // setup a new LoopBack app, we don't want to use shared models
app = loopback({localRegistry: true, loadBuiltinModels: true}); app = loopback({localRegistry: true, loadBuiltinModels: true});
app.set('_verifyAuthModelRelations', false);
app.set('remoting', {errorHandler: {debug: true, log: false}}); app.set('remoting', {errorHandler: {debug: true, log: false}});
app.dataSource('db', {connector: 'memory'}); app.dataSource('db', {connector: 'memory'});
const User = app.registry.createModel({ const User = app.registry.createModel({