-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Validate User model config at app boot #3265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,7 @@ var classify = require('underscore.string/classify'); | |||||||||
| var camelize = require('underscore.string/camelize'); | ||||||||||
| var path = require('path'); | ||||||||||
| var util = require('util'); | ||||||||||
| var Promise = require('bluebird'); | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * The `App` object represents a Loopback application. | ||||||||||
|
|
@@ -398,85 +399,217 @@ app.enableAuth = function(options) { | |||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| this._verifyAuthModelRelations(); | ||||||||||
|
|
||||||||||
| this.isAuthEnabled = true; | ||||||||||
| return this._verifyAuthModelRelations(app._warnOnBadAuthModelRelations) | ||||||||||
| .then(() => { | ||||||||||
| this.isAuthEnabled = true; | ||||||||||
| }); | ||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as This is useful in the tests so we can wait for enableAuth to return before checking the displayed warnings
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am quite concerned about this change. We use loopback/server/middleware/rest.js Lines 51 to 54 in c3ab932
Having said that, I agree that making What is your reasoning for enabling BTW in the current proposal, the app will end up in an inconsistent state when
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, i agree this is the main point to discuss. In loopback i think this is not a problem since the features/modules relying on this flag comes few ticks later, but i agree we should care about 3rd party apps. In fact i noted when implementing the tests that i could not easily test directly the _verifyAuthModelRelations method alone since it require the auth models and relations are all set up. Which is usually a benefit of using The rationale: 👉 For now: Setting app.isAuthEnabling to
So this could be // ...
// still sync up to this point
this.isAuthEnabled = true;
return this._verifyAuthModelRelations(app._warnOnBadAuthModelRelations); // async
}As the enableAuth() method currently returns nothing, there's no risk that it will conflict in existing 3rd party apps. Of course we can additionaly already implement the target implement with a flag and set the flag only latter and decide to halt the app if warnings occur with a feature flag (wrapping your other proposal about an option to return sync or async)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍
I agree that if the backwards compatibility was not an issue (e.g. in v4), then // still sync up to this point
this.isAuthEnabled = true;
return this._verifyAuthModelRelations(app._warnOnBadAuthModelRelations); // async👍 let's take this approach now. |
||||||||||
| }; | ||||||||||
|
|
||||||||||
| app._verifyAuthModelRelations = function() { | ||||||||||
| app._verifyAuthModelRelations = function(warnFn) { | ||||||||||
| let self = this; | ||||||||||
|
|
||||||||||
| // Allow unit-tests (but also LoopBack users) to disable the warnings | ||||||||||
| if (this.get('_verifyAuthModelRelations') === false) return; | ||||||||||
| const warnOnBadSetup = this.get('_warnOnBadAuthModelsSetup') !== false; | ||||||||||
| // Prevent the app from being killed although the configuration is inconsistent | ||||||||||
| const abortOnBadUserSetup = this.get('_abortOnBadUserSetup') !== false; | ||||||||||
|
|
||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reworded the flags to avoid double negations |
||||||||||
| 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); | ||||||||||
| } | ||||||||||
| const models = this.models(); | ||||||||||
|
|
||||||||||
| if (Model === User || Model.prototype instanceof User) { | ||||||||||
| scheduleVerification(Model, verifyUserRelations); | ||||||||||
| } | ||||||||||
| }); | ||||||||||
| return verifyUserModelsSetup().then(verifyRelationsSetup); | ||||||||||
|
|
||||||||||
| function verifyUserModelsSetup() { | ||||||||||
| let userModels = models.filter(model => { | ||||||||||
| return (model === User || model.prototype instanceof User) || | ||||||||||
| (model === AccessToken || model.prototype instanceof AccessToken); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| // we use Promise.reflect() here to let all the tests go without throwing, | ||||||||||
| // this way we can get all the logs for all models we are checking | ||||||||||
| return Promise.map(userModels, | ||||||||||
| model => scheduleVerification(model, hasMultipleUserModelsConfig).reflect() | ||||||||||
| ) | ||||||||||
| .then(inspections => { | ||||||||||
| // proceed to next checkups if no error | ||||||||||
| let hasErrors = inspections.filter(inspection => inspection.isRejected()); | ||||||||||
| if (!hasErrors.length) { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // else log and eventually kill the app | ||||||||||
| warnFn('BAD_USER_MODELS_SETUP'); | ||||||||||
|
|
||||||||||
| // detailed logs | ||||||||||
| inspections.forEach(inspection => { | ||||||||||
| console.log(inspection); | ||||||||||
| let modelSetup = (inspection.value() || inspection.reason()).modelSetup; | ||||||||||
| console.warn( | ||||||||||
| 'Model %j of type %j is set to use the %j user model config', | ||||||||||
| modelSetup.model, | ||||||||||
| modelSetup.instanceOf, | ||||||||||
| modelSetup.hasMultipleUserModelsConfig ? 'Multiple' : 'Single' | ||||||||||
| ); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| delete this.hasMultipleUserModelsConfig; | ||||||||||
|
|
||||||||||
| if (abortOnBadUserSetup) { | ||||||||||
| const msg = 'Application setup is inconsistent, please see the log ' + | ||||||||||
| 'for more information'; | ||||||||||
| const error = new Error(msg); | ||||||||||
| error.code = 'BAD_USER_MODELS_SETUP'; | ||||||||||
| throw error; | ||||||||||
| } | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function verifyRelationsSetup() { | ||||||||||
| models.forEach(Model => { | ||||||||||
| if (Model === AccessToken || Model.prototype instanceof AccessToken) { | ||||||||||
| scheduleVerification(Model, verifyAccessTokenRelations); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (Model === User || Model.prototype instanceof User) { | ||||||||||
| scheduleVerification(Model, verifyUserRelations); | ||||||||||
| } | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // instant or scheduled Model verifications, as a promise | ||||||||||
| function scheduleVerification(Model, verifyFn) { | ||||||||||
| if (Model.dataSource) { | ||||||||||
| verifyFn(Model); | ||||||||||
| } else { | ||||||||||
| Model.on('attached', () => verifyFn(Model)); | ||||||||||
| return new Promise((resolve, reject) => { | ||||||||||
| if (Model.dataSource) { | ||||||||||
| try { | ||||||||||
| resolve(verifyFn(Model)); | ||||||||||
| } catch (e) { | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the Promise constructor is required to catch errors thrown by the factory function, therefore it shouldn't be needed to catch them ourselves.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, i checked. If i just do
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's weird. This is what Bluebird's docs say about the error:
var p = Promise.delay(1000);
p.inspect().value();
This looks like a possible bug in bluebird to me.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nevermind... thanks for digging, the error was on my side... |
||||||||||
| reject(e); | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| Model.on('attached', () => { | ||||||||||
| scheduleVerification(Model, verifyFn); | ||||||||||
| }); | ||||||||||
| } | ||||||||||
| }); | ||||||||||
| } | ||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i needed to change @bajtos first implem to add async support as we now expect on this function to resolve when done, so we can use it to first check user models config and then check how the relations are set up |
||||||||||
|
|
||||||||||
| function hasMultipleUserModelsConfig(model) { | ||||||||||
| let hasMultipleUserModelsConfig, isInstanceOfUser; | ||||||||||
|
|
||||||||||
| // check is the model is set to use mutiple users config (with polymorphic relations) | ||||||||||
| if (model === User || model.prototype instanceof User) { | ||||||||||
| isInstanceOfUser = true; | ||||||||||
| const hasManyTokens = model.relations && model.relations.accessTokens; | ||||||||||
| // TODO: handle when hasManyTokens is undefined? | ||||||||||
| hasMultipleUserModelsConfig = !!(hasManyTokens.polymorphic); | ||||||||||
| } else if (model === AccessToken || model.prototype instanceof AccessToken) { | ||||||||||
| const belongsToUser = model.relations && model.relations.user; | ||||||||||
| // the test on belongsToUser is required as we allow AccessToken model not | ||||||||||
| // to define the relation with custom user model for backward compatibility | ||||||||||
| // see https://github.com/strongloop/loopback/pull/3227 | ||||||||||
| hasMultipleUserModelsConfig = !!(belongsToUser && belongsToUser.polymorphic); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let modelSetup = { | ||||||||||
| model: model.modelName, | ||||||||||
| instanceOf: isInstanceOfUser ? 'User' : 'AccessToken', | ||||||||||
| hasMultipleUserModelsConfig, | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // check if model config is consistent with already parsed models' config | ||||||||||
| if (self.hasMultipleUserModelsConfig === undefined) { | ||||||||||
| self.hasMultipleUserModelsConfig = hasMultipleUserModelsConfig; | ||||||||||
| } else if (self.hasMultipleUserModelsConfig !== hasMultipleUserModelsConfig) { | ||||||||||
| let error = new Error('User models setup is inconsistent'); | ||||||||||
| error.modelSetup = modelSetup; | ||||||||||
| throw error; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // if no error, return the modelSetup for further logging purposes | ||||||||||
| return {modelSetup}; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function verifyAccessTokenRelations(Model) { | ||||||||||
| if (!warnOnBadSetup) return; | ||||||||||
| const belongsToUser = Model.relations && Model.relations.user; | ||||||||||
| if (belongsToUser) return; | ||||||||||
|
|
||||||||||
| const modelFrom = Model.modelName; | ||||||||||
| 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); | ||||||||||
| const modelTo = (relationsConfig.user || {}).model; | ||||||||||
| if (modelTo) { | ||||||||||
| warnFn('CUSTOM_USER_MODEL_NOT_AVAILABLE', {modelFrom, modelTo}); | ||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i suggest changing the var names to modelFrom and modelTo, rather than modelName and userName, as it makes the tests more readable. |
||||||||||
| 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); | ||||||||||
| warnFn('MISSING_RELATION_TO_USER', {modelFrom}); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function verifyUserRelations(Model) { | ||||||||||
| if (!warnOnBadSetup) return; | ||||||||||
|
|
||||||||||
| const hasManyTokens = Model.relations && Model.relations.accessTokens; | ||||||||||
| if (hasManyTokens) return; | ||||||||||
|
|
||||||||||
| const modelFrom = Model.modelName; | ||||||||||
| const relationsConfig = Model.settings.relations || {}; | ||||||||||
| const accessTokenName = (relationsConfig.accessTokens || {}).model; | ||||||||||
| if (accessTokenName) { | ||||||||||
| console.warn( | ||||||||||
| const modelTo = (relationsConfig.accessTokens || {}).model; | ||||||||||
| if (modelTo) { | ||||||||||
| warnFn('CUSTOM_ACCESS_TOKEN_MODEL_NOT_AVAILABLE', {modelFrom, modelTo}); | ||||||||||
| return; | ||||||||||
| } | ||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dito |
||||||||||
|
|
||||||||||
| warnFn('MISSING_RELATION_TO_ACCESS_TOKEN', {modelFrom}); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| // function warnOnBadAuthModelRelations(code, args) { | ||||||||||
| app._warnOnBadAuthModelRelations = function(code, args) { | ||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. built-in warning function ("private")
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered passing the custom implementation via
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big deal though. |
||||||||||
| switch (code) { | ||||||||||
| case 'BAD_USER_MODELS_SETUP': | ||||||||||
| g.warn('Application setup is inconsistent: some models are set to use ' + | ||||||||||
| 'the Multiple user models configuration while some other models are set to ' + | ||||||||||
| 'use the Single user model configuration. The model config is listed below.'); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'CUSTOM_USER_MODEL_NOT_AVAILABLE': | ||||||||||
| g.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', | ||||||||||
| args.modelFrom, args.modelTo, args.modelTo); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'MISSING_RELATION_TO_USER': | ||||||||||
| g.warn( | ||||||||||
| 'The model %j does not have "belongsTo User-like model" relation ' + | ||||||||||
| 'configured.\n' + | ||||||||||
| 'Learn more at http://ibm.biz/setup-loopback-auth', | ||||||||||
| args.modelFrom); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'CUSTOM_ACCESS_TOKEN_MODEL_NOT_AVAILABLE': | ||||||||||
| g.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; | ||||||||||
| } | ||||||||||
| args.modelFrom, args.modelTo, args.modelTo); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| 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); | ||||||||||
| case 'MISSING_RELATION_TO_ACCESS_TOKEN': | ||||||||||
| g.warn( | ||||||||||
| 'The model %j does not have "hasMany AccessToken-like models" relation ' + | ||||||||||
| 'configured.\n' + | ||||||||||
| 'Learn more at http://ibm.biz/setup-loopback-auth', | ||||||||||
| args.modelFrom); | ||||||||||
| } | ||||||||||
| }; | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,8 @@ var expect = require('./helpers/expect'); | |
| var it = require('./util/it'); | ||
| var request = require('supertest'); | ||
|
|
||
| var Promise = require('bluebird'); | ||
|
|
||
| describe('app', function() { | ||
| var app; | ||
| beforeEach(function() { | ||
|
|
@@ -859,10 +861,11 @@ describe('app', function() { | |
| }); | ||
|
|
||
| describe.onServer('enableAuth', function() { | ||
| it('should set app.isAuthEnabled to true', function() { | ||
| it('sets app.isAuthEnabled to true', function() { | ||
| expect(app.isAuthEnabled).to.not.equal(true); | ||
| app.enableAuth(); | ||
| expect(app.isAuthEnabled).to.equal(true); | ||
| return app.enableAuth().then(() => { | ||
| expect(app.isAuthEnabled).to.equal(true); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This goes back to my first comment https://github.com/strongloop/loopback/pull/3265/files#r107652656. When we have to change a test in a significant way, as we are doing here, then I think it's a very strong sign that we are breaking backwards compatibility. |
||
| }); | ||
| }); | ||
|
|
||
| it('auto-configures required models to provided dataSource', function() { | ||
|
|
@@ -871,14 +874,14 @@ describe('app', function() { | |
| require('../lib/builtin-models')(app.registry); | ||
| var db = app.dataSource('db', {connector: 'memory'}); | ||
|
|
||
| app.enableAuth({dataSource: 'db'}); | ||
|
|
||
| expect(Object.keys(app.models)).to.include.members(AUTH_MODELS); | ||
| return app.enableAuth({dataSource: 'db'}).then(() => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change shouldn't be needed, because we are attaching the models synchronously in the current implementation. Unless I am missing something? |
||
| expect(Object.keys(app.models)).to.include.members(AUTH_MODELS); | ||
|
|
||
| AUTH_MODELS.forEach(function(m) { | ||
| var Model = app.models[m]; | ||
| expect(Model.dataSource, m + '.dataSource').to.equal(db); | ||
| expect(Model.shared, m + '.shared').to.equal(m === 'User'); | ||
| AUTH_MODELS.forEach(function(m) { | ||
| var Model = app.models[m]; | ||
| expect(Model.dataSource, m + '.dataSource').to.equal(db); | ||
| expect(Model.shared, m + '.shared').to.equal(m === 'User'); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
|
|
@@ -892,9 +895,71 @@ describe('app', function() { | |
| const AccessToken = app.registry.getModel('AccessToken'); | ||
| AccessToken.settings.relations.user.model = 'Customer'; | ||
|
|
||
| app.enableAuth({dataSource: 'db'}); | ||
| return app.enableAuth({dataSource: 'db'}).then(() => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto, I think this change is not needed. |
||
| expect(Object.keys(app.models)).to.not.include('User'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('auth models config health check', function() { | ||
| var app, warnings; | ||
| beforeEach(function() { | ||
| app = loopback({localRegistry: true, loadBuiltinModels: true}); | ||
| app.dataSource('db', {connector: 'memory'}); | ||
|
|
||
| expect(Object.keys(app.models)).to.not.include('User'); | ||
| warnings = []; | ||
| function warnFn(code, args) { | ||
| warnings.push(Object.assign(args || {}, {code})); | ||
| } | ||
| app._warnOnBadAuthModelRelations = warnFn; | ||
| }); | ||
|
|
||
| it('sets app.hasMultipleUserModelsConfig to false if the app ' + | ||
| 'has a single User model correctly configured', function() { | ||
| return app.enableAuth({dataSource: 'db'}).then(() => { | ||
| expect(app.hasMultipleUserModelsConfig).to.be.false(); | ||
| }); | ||
| }); | ||
|
|
||
| it('sets app.hasMultipleUserModelsConfig to true if the app ' + | ||
| 'has multiple User models correctly configured', function() { | ||
| var Merchant = createModel(app, 'Merchant', {base: 'User'}); | ||
| var Customer = createModel(app, 'Customer', {base: 'User'}); | ||
| var Token = createModel(app, 'Token', {base: 'AccessToken'}); | ||
|
|
||
| // Update AccessToken and Users to bind them through polymorphic relations | ||
| Token.belongsTo('user', {idName: 'id', polymorphic: {idType: 'string', | ||
| foreignKey: 'userId', discriminator: 'principalType'}}); | ||
| Merchant.hasMany('accessTokens', {model: Token, polymorphic: {foreignKey: 'userId', | ||
| discriminator: 'principalType'}}); | ||
| Customer.hasMany('accessTokens', {model: Token, polymorphic: {foreignKey: 'userId', | ||
| discriminator: 'principalType'}}); | ||
| return app.enableAuth({dataSource: 'db'}).then(() => { | ||
| expect(app.hasMultipleUserModelsConfig).to.be.true(); | ||
| }); | ||
| }); | ||
|
|
||
| it('warns if a custom user model is referenced in the ' + | ||
| 'AccessToken "user" relation, but is not available', function() { | ||
| // Set AccessToken's belongsTo relation "user" to use a custom user model | ||
| // This model is deliberately not available | ||
| const AccessToken = app.registry.getModel('AccessToken'); | ||
| AccessToken.settings.relations.user.model = 'Customer'; | ||
|
|
||
| return app.enableAuth({dataSource: 'db'}).then(() => { | ||
| expect(warnings).to.eql([{ | ||
| code: 'CUSTOM_USER_MODEL_NOT_AVAILABLE', | ||
| modelFrom: 'AccessToken', | ||
| modelTo: 'Customer', | ||
| }]); | ||
| }); | ||
| }); | ||
|
|
||
| // helpers | ||
| function createModel(app, name, options) { | ||
| var model = app.registry.createModel(Object.assign({name: name}, options)); | ||
| app.model(model, {dataSource: 'db'}); | ||
| return model; | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2442,7 +2442,7 @@ describe('User', function() { | |
| it('handles subclassed user with no accessToken relation', () => { | ||
| // setup a new LoopBack app, we don't want to use shared models | ||
| app = loopback({localRegistry: true, loadBuiltinModels: true}); | ||
| app.set('_verifyAuthModelRelations', false); | ||
| app.set('_warnOnBadAuthModelsSetup', false); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you mind reverting this rename? I don't really care about the name, it's just to avoid code churn.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it would mean that it's the exact same name as the app method the rationale for this change is: the _verifyAuthModelRelations method, for a same name, has now a larger scope than the equally named flag
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That shouldn't matter, because the flag is stored in a config hash-map.
I see your point, let's make the proposed change then. |
||
| app.set('remoting', {errorHandler: {debug: true, log: false}}); | ||
| app.dataSource('db', {connector: 'memory'}); | ||
| const User = app.registry.createModel({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In existing applications that are not waiting for
enableAuthto resolve, when the verification code throws an error, the rejected promise will end up as unhandled promise rejection. This typically produces only a console warning, it does not crash the application as an uncaught error would.I am proposing to introduce an
optionsflag to control the sync/async behaviour. For example:(The "Async" prefix is already used by Bluebird's promisify API)