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:
parent
f0c9700e1d
commit
79f441b9c4
|
@ -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'));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -15,5 +15,12 @@
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"property": "create"
|
"property": "create"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
}
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "user",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
Loading…
Reference in New Issue