feat(oauth): show permission screen for untrusted reliers #2377
Conversation
| @@ -0,0 +1,30 @@ | |||
| <div id="main-content" class="card"> | |||
| <header> | |||
| <h1 id="fxa-confirm-header">{{#t}}%serviceName)s would like to know...{{/t}}</h1> | |||
shane-tomlinson
May 5, 2015
Member
%(serviceName)s
%(serviceName)s
| ], | ||
| function (Cocktail, _, FormView, BaseView, Template, p, AuthErrors, Constants, | ||
| URL, ResendMixin, BackMixin, ServiceMixin) { | ||
| //var t = BaseView.t; |
vladikoff
May 5, 2015
Contributor
extra comment
extra comment
| email: account.get('email'), | ||
| serviceName: this.relier.get('serviceName'), | ||
| termsUri: this.relier.get('termsUri'), | ||
| privacyUri: this.relier.get('termsUri'), |
pdehaan
May 5, 2015
Contributor
this.relier.get('privacyUri')?
this.relier.get('privacyUri')?
| return this.get('trusted'); | ||
| }, | ||
|
|
||
| needsPermissions: function (account) { |
shane-tomlinson
May 6, 2015
Member
I'm waffling whether this should be on the relier on on the account. I can see it both ways.
Ask the account if the relier needs permission to access its information.
vs
Ask the relier if it needs permission to access the account.
The first makes more sense to me.
I'm waffling whether this should be on the relier on on the account. I can see it both ways.
Ask the account if the relier needs permission to access its information.
vs
Ask the relier if it needs permission to access the account.
The first makes more sense to me.
zaach
May 6, 2015
Author
Contributor
I was also considering the broker? Dunno.
I was also considering the broker? Dunno.
| @@ -174,6 +175,54 @@ define([ | |||
| .then(function () { | |||
| return profileImage; | |||
| }); | |||
| }, | |||
|
|
|||
| signIn: function (relier) { | |||
shane-tomlinson
May 6, 2015
Member
I totally dig this.
I totally dig this.
| @@ -222,6 +222,22 @@ define([ | |||
| return (Session.email && Session.sessionToken && | |||
| (! Session.cachedCredentials || | |||
| Session.cachedCredentials.email !== Session.email)); | |||
| }, | |||
|
|
|||
| signInAccount: function (account, relier) { | |||
shane-tomlinson
May 6, 2015
Member
YES! I'll be happy to update the PR I showed you to continue with these changes and rip a lot of this account management logic out of the views.
YES! I'll be happy to update the PR I showed you to continue with these changes and rip a lot of this account management logic out of the views.
| @@ -0,0 +1,30 @@ | |||
| <div id="main-content" class="card"> | |||
| <header> | |||
| <h1 id="fxa-confirm-header">{{#t}}%(serviceName)s would like to know...{{/t}}</h1> | |||
shane-tomlinson
May 6, 2015
Member
Do you want the id to be fxa-permissions-header or something different than confirm?
Do you want the id to be fxa-permissions-header or something different than confirm?
| </div> | ||
|
|
||
| <div class="button-row"> | ||
| <a id="proceed" class="button" href="#">{{#t}}Proceed{{/t}}</a> |
|
|
||
| context: function () { | ||
| var account = this.getAccount(); | ||
| var serviceUri = URL.getOrigin(this.relier.get('redirectUri')) |
shane-tomlinson
May 6, 2015
Member
Something we may want to put on the relier when the redirectUri is set?
Something we may want to put on the relier when the redirectUri is set?
|
|
||
| onSignUpSuccess: function (account) { | ||
| var self = this; | ||
| if (account.get('verified')) { |
shane-tomlinson
May 6, 2015
Member
We so need an external state machine and to rip this stuff out of the views.
We so need an external state machine and to rip this stuff out of the views.
| }); | ||
| }, | ||
|
|
||
| saveGrantedPermissions: function (clientId, scope) { |
shane-tomlinson
May 6, 2015
Member
I do not see this called anywhere.
I do not see this called anywhere.
zaach
May 6, 2015
Author
Contributor
Yeah, still WIP
Yeah, still WIP
| return self.user.setSignedInAccount(account) | ||
| .then(function () { | ||
| return account; | ||
| console.log('account???', account.get('sessionToken'), account); |
shane-tomlinson
May 6, 2015
Member
console!
console!
| return p.reject(AuthErrors.toError('UNEXPECTED_ERROR')); | ||
| } | ||
| return self.broker.beforeSignIn(account.get('email')) | ||
| .then(function () { |
shane-tomlinson
May 6, 2015
Member
Looks like an opportune time to re-indent this.
Looks like an opportune time to re-indent this.
|
Overall, I dig this approach. I would really like to see us move towards ripping the implicit state machine out of the views and into something self contained and duplicate logic can be reduced. |
| }, | ||
|
|
||
| needsPermissions: function (account) { | ||
| return ! this.isTrusted() && ! account.hasGrantedPermissions(this.get('clientId'), this.get('scope')); |
shane-tomlinson
May 6, 2015
Member
Minor nit, but the intention could probably be easier to understand if it were re-written a bit like:
if (this.isTrusted()) {
return false;
}
return ! account.hasGrantedPermissions(...
My guess is uglify will combine the two branches into something similar to what you have here.
Minor nit, but the intention could probably be easier to understand if it were re-written a bit like:
if (this.isTrusted()) {
return false;
}
return ! account.hasGrantedPermissions(...My guess is uglify will combine the two branches into something similar to what you have here.
| return account; | ||
| console.log('account???', account.get('sessionToken'), account); | ||
| if (self.relier.needsPermissions(account)) { | ||
| self.navigate('signin_permissions', { |
| var preVerifyToken = self.relier.get('preVerifyToken'); | ||
| var account = self.user.initAccount({ | ||
| email: self.$('.email').val(), |
shane-tomlinson
May 6, 2015
Member
At least the email should be updated to use getElementValue so that leading/trailing whitespace is trimmed.
At least the email should be updated to use getElementValue so that leading/trailing whitespace is trimmed.
shane-tomlinson
May 6, 2015
Member
Side note - how have we gotten away with using .val() for so long?
Side note - how have we gotten away with using .val() for so long?
| } | ||
| }, | ||
|
|
||
| onSignUpSuccess: function (account) { |
shane-tomlinson
May 6, 2015
Member
Perhaps to avoid duplication with the signin/signup modules, these three functions could be extracted into one ore two mixins (one for sign in, one for sign up? overkill?) that are mixed in where needed?
Perhaps to avoid duplication with the signin/signup modules, these three functions could be extracted into one ore two mixins (one for sign in, one for sign up? overkill?) that are mixed in where needed?
| 'views/form', | ||
| 'views/base', | ||
| 'stache!templates/permissions', | ||
| 'lib/promise', |
shane-tomlinson
May 6, 2015
Member
I don't think p is used anywhere in this module.
I don't think p is used anywhere in this module.
pdehaan
May 8, 2015
Contributor
Ooohhh!!! I think i saw a grunt task for this the other day: grunt-amdcheck.
I did a quick test against master with this, grunttasks/amdcheck.js:
module.exports = function (grunt) {
grunt.config('amdcheck', {
app: {
files: [{
expand: true,
cwd: 'app/',
src: [
'**/*.js',
'!bower_components/**'
],
dest: 'build/'
}]
}
});
}
And got a lot of weird output, which may point to some unused and unloved requires:
Total unused dependencies: 89 in 64 files.
Total processed files: 251
The output is actually pretty long, so I'll file a separate bug which we can z-never it.
UPDATE: Filed as #2392
Ooohhh!!! I think i saw a grunt task for this the other day: grunt-amdcheck.
I did a quick test against master with this, grunttasks/amdcheck.js:
module.exports = function (grunt) {
grunt.config('amdcheck', {
app: {
files: [{
expand: true,
cwd: 'app/',
src: [
'**/*.js',
'!bower_components/**'
],
dest: 'build/'
}]
}
});
}And got a lot of weird output, which may point to some unused and unloved requires:
Total unused dependencies: 89 in 64 files.
Total processed files: 251
The output is actually pretty long, so I'll file a separate bug which we can z-never it.
UPDATE: Filed as #2392
zaach
May 8, 2015
Author
Contributor
FTR p is now used.
FTR p is now used.
| 'stache!templates/permissions', | ||
| 'lib/promise', | ||
| 'lib/auth-errors', | ||
| 'lib/constants', |
shane-tomlinson
May 6, 2015
Member
I don't see Constants used anywhere either.
I don't see Constants used anywhere either.
| var self = this; | ||
| var account = self.getAccount(); | ||
|
|
||
| self.logScreenEvent('proceed'); |
|
@zaach - can you rebase this? |
|
@zaach - this PR is epic! It's looking pretty good, mostly small nits at this point. I have to test it pretty thoroughly, I see you have already added functional tests. Have you tested the screen in mobile to ensure it looks good? |
| @@ -27,7 +27,8 @@ define([ | |||
| verified: undefined, | |||
| profileImageUrl: undefined, | |||
| profileImageId: undefined, | |||
| lastLogin: undefined | |||
| lastLogin: undefined, | |||
| grantedPermissions: Object.create(null) | |||
shane-tomlinson
May 10, 2015
Member
A silly question, why Object.create(null) instead of a {}?
A silly question, why Object.create(null) instead of a {}?
| self.logScreenEvent('proceed'); | ||
|
|
||
| return p().then(function () { | ||
| account.saveGrantedPermissions(self.relier.get('clientId'), self.relier.get('scope')); |
shane-tomlinson
May 10, 2015
Member
@zaach - I see the granted permissions are saved, yet when I go back through 321done's signin flow, I am re-asked for the permissions. Expected?
@zaach - I see the granted permissions are saved, yet when I go back through 321done's signin flow, I am re-asked for the permissions. Expected?
zaach
May 12, 2015
Author
Contributor
Bah– so currently when a user signs in we create a new account object and store it in localStorage, overwriting the existing data (including saved permissions). We'll have to merge in the old account info before storing it.
Bah– so currently when a user signs in we create a new account object and store it in localStorage, overwriting the existing data (including saved permissions). We'll have to merge in the old account info before storing it.
| assert.isTrue(relier.isTrusted.called); | ||
| }); | ||
|
|
||
| it('should prompt when relier is untrusted and needs scopes', function () { |
shane-tomlinson
May 10, 2015
Member
What if the scopes match the already granted permissions?
What if the scopes match the already granted permissions?
| return false; | ||
| }); | ||
| sinon.stub(account, 'hasGrantedPermissions', function () { | ||
| return false; |
shane-tomlinson
May 10, 2015
Member
Can you add a test case for true? I am seeing odd behavior in that I am being re-asked for permissions on 321done after I have already granted permissions. Is that the desired behavior? It feels a bit odd.
Can you add a test case for true? I am seeing odd behavior in that I am being re-asked for permissions on 321done after I have already granted permissions. Is that the desired behavior? It feels a bit odd.
zaach
May 12, 2015
Author
Contributor
Fixed.
Fixed.
| }); | ||
| sinon.stub(view.fxaClient, 'recoveryEmailStatus', function () { | ||
| return p.reject(assert.fail); | ||
| sinon.stub(user, 'signInAccount', function (account) { |
shane-tomlinson
May 10, 2015
Member
Less code is a beautiful thing.
Less code is a beautiful thing.
|
@zaach - when local testing, I am re-asked for permissions for 321done even though I have granted them. If that is expected, no problem, but it feels odd. Here is what the screen looks like in both desktop and mobile: DesktopMobileMaybe tighten up the space between the "Proceed" and "Cancel" buttons? @johngruen and @ryanfeeley? |
4acb60a
to
58a59bf
| // returns true if all attributes within ALLOWED_KEYS are undefined | ||
| isEmpty: function () { | ||
| // returns true if all attributes within ALLOWED_KEYS are defaults | ||
| isDefault: function () { |
shane-tomlinson
May 12, 2015
Member
👍
| var oldAccount = self.getAccountByUid(account.get('uid')); | ||
| if (! oldAccount.isDefault()) { | ||
| // allow new account attributes to override old ones | ||
| oldAccount.set(_.omit(account.attributes, function (val) { |
shane-tomlinson
May 12, 2015
Member
Can you add a note here that this is what allows permissions to remain persisted per relier across sign ins? I know my own memory and am afraid I'll forget in 6 months.
Can you add a note here that this is what allows permissions to remain persisted per relier across sign ins? I know my own memory and am afraid I'll forget in 6 months.
|
@zaach - I've been reviewing & testing this PR all morning, it looks really good! Well done! I have a couple of small changes that I'd like to see that I will probably make myself and then merge:
For the longer term, I think we should reconsider how we deal with account management when using multiple accounts. For example, if I sign in as user X, then sign in as user Y, then re-sign in as user X, I have to re-grant permissions. |
* Move brokers->shouldPromptForPermissions to reliers->accountNeedsPermissions * Ensure long email addresses wrap in the permissions page. * Add the screen name to the event names in the permissions screen. * Add functional tests to ensure re-signin after both signup and signin work as expected.
|
For follow on - make the permissions screen generic. The permissions screen is currently hard coded to display the email permission. |
feat(oauth): show permission screen for untrusted reliers @zaach - this PR is awesome. Thank you for working through it with me, and for the cleanup you did along the way. r=@shane-tomlinson
|
Man, the more I look at this, the more I think we should have gone with the original design of a read-only email field. Thoughts @shane-tomlinson @zaach? Should I just file a bug? |
File it @ryanfeeley! |
Do you mean when the user clicks "Use different account"? In that case, yes, all localStorage is wiped out and you have to re-grant permissions. Our long term solution for this is the full-fledged account chooser screen #1844. |
Yeah, the same also happens if an OAuth relier sends a user to signin with an email query parameter other than the currently signed in user. |
|
Hmm, in that case we show the email form field but the cached credentials should still be intact. The only place I see where we clear cached credentials is here: https://github.com/mozilla/fxa-content-server/blob/master/app/scripts/views/sign_in.js#L208 |
|
Latest @zaach @vladikoff @johngruen @pdehaan @jrgm |
|
@ryanfeeley new bug #2423 |





I refactored sign up/in to use the
accountandusermodels, but still need to get the permission screen working.cc @shane-tomlinson