mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-02-06 00:50:29 +05:30
#48: add support for prompt and login_hint oauth params
This commit is contained in:
parent
2beab5b6bc
commit
6498858d33
@ -450,17 +450,23 @@ class PanelTransition extends Component {
|
||||
|
||||
export default connect((state) => {
|
||||
const {login} = state.auth;
|
||||
const user = {
|
||||
...state.user,
|
||||
isGuest: true,
|
||||
email: '',
|
||||
username: ''
|
||||
let user = {
|
||||
...state.user
|
||||
};
|
||||
|
||||
if (/[@.]/.test(login)) {
|
||||
user.email = login;
|
||||
} else {
|
||||
user.username = login;
|
||||
if (login) {
|
||||
user = {
|
||||
...user,
|
||||
isGuest: true,
|
||||
email: '',
|
||||
username: ''
|
||||
};
|
||||
|
||||
if (/[@.]/.test(login)) {
|
||||
user.email = login;
|
||||
} else {
|
||||
user.username = login;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -145,6 +145,7 @@ export function clearErrors() {
|
||||
}
|
||||
|
||||
export { logout, updateUser } from 'components/user/actions';
|
||||
export { authenticate } from 'components/accounts/actions';
|
||||
|
||||
/**
|
||||
* @param {object} oauthData
|
||||
@ -153,6 +154,13 @@ export { logout, updateUser } from 'components/user/actions';
|
||||
* @param {string} oauthData.responseType
|
||||
* @param {string} oauthData.description
|
||||
* @param {string} oauthData.scope
|
||||
* @param {string} [oauthData.prompt='none'] - comma-separated list of values to adjust auth flow
|
||||
* Posible values:
|
||||
* * none - default behaviour
|
||||
* * consent - forcibly prompt user for rules acceptance
|
||||
* * select_account - force account choosage, even if user has only one
|
||||
* @param {string} oauthData.loginHint - allows to choose the account, which will be used for auth
|
||||
* The possible values: account id, email, username
|
||||
* @param {string} oauthData.state
|
||||
*
|
||||
* @return {Promise}
|
||||
@ -163,8 +171,17 @@ export function oAuthValidate(oauthData) {
|
||||
return wrapInLoader((dispatch) =>
|
||||
oauth.validate(oauthData)
|
||||
.then((resp) => {
|
||||
let prompt = (oauthData.prompt || 'none').split(',').map((item) => item.trim);
|
||||
if (prompt.includes('none')) {
|
||||
prompt = ['none'];
|
||||
}
|
||||
|
||||
dispatch(setClient(resp.client));
|
||||
dispatch(setOAuthRequest(resp.oAuth));
|
||||
dispatch(setOAuthRequest({
|
||||
...resp.oAuth,
|
||||
prompt: oauthData.prompt || 'none',
|
||||
loginHint: oauthData.loginHint
|
||||
}));
|
||||
dispatch(setScopes(resp.session.scopes));
|
||||
localStorage.setItem('oauthData', JSON.stringify({ // @see services/authFlow/AuthFlow
|
||||
timestamp: Date.now(),
|
||||
@ -246,6 +263,8 @@ export function setOAuthRequest(oauth) {
|
||||
redirectUrl: oauth.redirect_uri,
|
||||
responseType: oauth.response_type,
|
||||
scope: oauth.scope,
|
||||
prompt: oauth.prompt,
|
||||
loginHint: oauth.loginHint,
|
||||
state: oauth.state
|
||||
}
|
||||
};
|
||||
|
@ -114,6 +114,8 @@ function oauth(
|
||||
redirectUrl: payload.redirectUrl,
|
||||
responseType: payload.responseType,
|
||||
scope: payload.scope,
|
||||
prompt: payload.prompt,
|
||||
loginHint: payload.loginHint,
|
||||
state: payload.state
|
||||
};
|
||||
|
||||
|
@ -90,7 +90,10 @@ function restoreScroll() {
|
||||
/* global process: false */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// some shortcuts for testing on localhost
|
||||
window.testOAuth = () => location.href = '/oauth2/v1/ely?client_id=ely&redirect_uri=http%3A%2F%2Fely.by%2Fauthorization%2Foauth&response_type=code&scope=account_info%2Caccount_email';
|
||||
window.testOAuth = (loginHint = '') => location.href = `/oauth2/v1/ely?client_id=ely&redirect_uri=http%3A%2F%2Fely.by%2Fauthorization%2Foauth&response_type=code&scope=account_info%2Caccount_email&login_hint=${loginHint}`;
|
||||
window.testOAuthPromptAccount = () => location.href = '/oauth2/v1/ely?client_id=ely&redirect_uri=http%3A%2F%2Fely.by%2Fauthorization%2Foauth&response_type=code&scope=account_info%2Caccount_email&prompt=select_account';
|
||||
window.testOAuthPromptPermissions = (loginHint = '') => location.href = `/oauth2/v1/ely?client_id=ely&redirect_uri=http%3A%2F%2Fely.by%2Fauthorization%2Foauth&response_type=code&scope=account_info%2Caccount_email&prompt=consent&login_hint=${loginHint}`;
|
||||
window.testOAuthPromptAll = () => location.href = '/oauth2/v1/ely?client_id=ely&redirect_uri=http%3A%2F%2Fely.by%2Fauthorization%2Foauth&response_type=code&scope=account_info%2Caccount_email&prompt=select_account,consent';
|
||||
window.testOAuthStatic = () => location.href = '/oauth2/v1/ely?client_id=ely&redirect_uri=static_page_with_code&response_type=code&scope=account_info%2Caccount_email';
|
||||
window.testOAuthStaticCode = () => location.href = '/oauth2/v1/ely?client_id=ely&redirect_uri=static_page&response_type=code&scope=account_info%2Caccount_email';
|
||||
|
||||
|
@ -57,6 +57,8 @@ function getOAuthRequest(oauthData) {
|
||||
response_type: oauthData.responseType,
|
||||
description: oauthData.description,
|
||||
scope: oauthData.scope,
|
||||
prompt: oauthData.prompt,
|
||||
login_hint: oauthData.loginHint,
|
||||
state: oauthData.state
|
||||
};
|
||||
}
|
||||
|
@ -192,8 +192,8 @@ export default class AuthFlow {
|
||||
* @return {bool} - whether oauth state is being restored
|
||||
*/
|
||||
restoreOAuthState() {
|
||||
if (this.getRequest().path.indexOf('/register') === 0) {
|
||||
// allow register
|
||||
if (/^\/(register|oauth2)/.test(this.getRequest().path)) {
|
||||
// allow register or the new oauth requests
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ export default class ChooseAccountState extends AbstractState {
|
||||
}
|
||||
|
||||
resolve(context, payload) {
|
||||
// do not ask again after user adds account, or chooses an existed one
|
||||
context.run('setAccountSwitcher', false);
|
||||
|
||||
if (payload.id) {
|
||||
|
@ -6,6 +6,9 @@ import ActivationState from './ActivationState';
|
||||
import AcceptRulesState from './AcceptRulesState';
|
||||
import FinishState from './FinishState';
|
||||
|
||||
const PROMPT_ACCOUNT_CHOOSE = 'select_account';
|
||||
const PROMPT_PERMISSIONS = 'consent';
|
||||
|
||||
export default class CompleteState extends AbstractState {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
@ -23,7 +26,33 @@ export default class CompleteState extends AbstractState {
|
||||
} else if (user.shouldAcceptRules) {
|
||||
context.setState(new AcceptRulesState());
|
||||
} else if (auth.oauth && auth.oauth.clientId) {
|
||||
if (auth.isSwitcherEnabled && accounts.available.length > 1) {
|
||||
let isSwitcherEnabled = auth.isSwitcherEnabled;
|
||||
|
||||
if (auth.oauth.loginHint) {
|
||||
const account = accounts.available.filter((account) =>
|
||||
account.id === auth.oauth.loginHint * 1
|
||||
|| account.email === auth.oauth.loginHint
|
||||
|| account.username === auth.oauth.loginHint
|
||||
)[0];
|
||||
|
||||
if (account) {
|
||||
// disable switching, because we are know the account, user must be authorized with
|
||||
context.run('setAccountSwitcher', false);
|
||||
isSwitcherEnabled = false;
|
||||
|
||||
if (account.id !== accounts.active.id) {
|
||||
// lets switch user to an account, that is needed for auth
|
||||
return context.run('authenticate', account)
|
||||
.then(() => context.setState(new CompleteState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSwitcherEnabled
|
||||
&& (accounts.available.length > 1
|
||||
|| auth.oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE)
|
||||
)
|
||||
) {
|
||||
context.setState(new ChooseAccountState());
|
||||
} else if (auth.oauth.code) {
|
||||
context.setState(new FinishState());
|
||||
@ -31,7 +60,7 @@ export default class CompleteState extends AbstractState {
|
||||
const data = {};
|
||||
if (typeof this.isPermissionsAccepted !== 'undefined') {
|
||||
data.accept = this.isPermissionsAccepted;
|
||||
} else if (auth.oauth.acceptRequired) {
|
||||
} else if (auth.oauth.acceptRequired || auth.oauth.prompt.includes(PROMPT_PERMISSIONS)) {
|
||||
context.setState(new PermissionsState());
|
||||
return;
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ export default class OAuthState extends AbstractState {
|
||||
responseType: query.response_type,
|
||||
description: query.description,
|
||||
scope: query.scope,
|
||||
prompt: query.prompt,
|
||||
loginHint: query.login_hint,
|
||||
state: query.state
|
||||
}).then(() => context.setState(new CompleteState()));
|
||||
}
|
||||
|
@ -79,7 +79,11 @@ describe('components/auth/actions', () => {
|
||||
callThunk(oAuthValidate, oauthData).then(() => {
|
||||
expectDispatchCalls([
|
||||
[setClient(resp.client)],
|
||||
[setOAuthRequest(resp.oAuth)],
|
||||
[setOAuthRequest({
|
||||
...resp.oAuth,
|
||||
prompt: 'none',
|
||||
loginHint: undefined
|
||||
})],
|
||||
[setScopes(resp.session.scopes)]
|
||||
]);
|
||||
})
|
||||
@ -102,7 +106,7 @@ describe('components/auth/actions', () => {
|
||||
|
||||
return callThunk(oAuthComplete).then(() => {
|
||||
expect(request.post, 'to have a call satisfying', [
|
||||
'/api/oauth2/v1/complete?client_id=&redirect_uri=&response_type=&description=&scope=&state=',
|
||||
'/api/oauth2/v1/complete?client_id=&redirect_uri=&response_type=&description=&scope=&prompt=&login_hint=&state=',
|
||||
{}
|
||||
]);
|
||||
});
|
||||
|
@ -84,7 +84,8 @@ describe('AuthFlow.functional', () => {
|
||||
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 123
|
||||
clientId: 123,
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
56
tests/services/authFlow/ChooseAccountState.test.js
Normal file
56
tests/services/authFlow/ChooseAccountState.test.js
Normal file
@ -0,0 +1,56 @@
|
||||
import ChooseAccountState from 'services/authFlow/ChooseAccountState';
|
||||
import CompleteState from 'services/authFlow/CompleteState';
|
||||
import LoginState from 'services/authFlow/LoginState';
|
||||
|
||||
import { bootstrap, expectState, expectNavigate, expectRun } from './helpers';
|
||||
|
||||
describe('ChooseAccountState', () => {
|
||||
let state;
|
||||
let context;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
state = new ChooseAccountState();
|
||||
|
||||
const data = bootstrap();
|
||||
context = data.context;
|
||||
mock = data.mock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.verify();
|
||||
});
|
||||
|
||||
describe('#enter', () => {
|
||||
it('should navigate to /oauth/choose-account', () => {
|
||||
expectNavigate(mock, '/oauth/choose-account');
|
||||
|
||||
state.enter(context);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#resolve', () => {
|
||||
it('should transition to complete if existed account was choosen', () => {
|
||||
expectRun(mock, 'setAccountSwitcher', false);
|
||||
expectState(mock, CompleteState);
|
||||
|
||||
state.resolve(context, {id: 123});
|
||||
});
|
||||
|
||||
it('should transition to login if user wants to add new account', () => {
|
||||
expectRun(mock, 'setAccountSwitcher', false);
|
||||
expectNavigate(mock, '/login');
|
||||
expectState(mock, LoginState);
|
||||
|
||||
state.resolve(context, {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reject', () => {
|
||||
it('should logout', () => {
|
||||
expectRun(mock, 'logout');
|
||||
|
||||
state.reject(context);
|
||||
});
|
||||
});
|
||||
});
|
@ -144,7 +144,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -166,7 +167,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -194,7 +196,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -225,7 +228,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -242,21 +246,21 @@ describe('CompleteState', () => {
|
||||
return promise.catch(mock.verify.bind(mock));
|
||||
};
|
||||
|
||||
it('should transition to finish state if rejected with static_page', () => {
|
||||
return testOAuth('resolve', {redirectUri: 'static_page'}, FinishState);
|
||||
});
|
||||
it('should transition to finish state if rejected with static_page', () =>
|
||||
testOAuth('resolve', {redirectUri: 'static_page'}, FinishState)
|
||||
);
|
||||
|
||||
it('should transition to finish state if rejected with static_page_with_code', () => {
|
||||
return testOAuth('resolve', {redirectUri: 'static_page_with_code'}, FinishState);
|
||||
});
|
||||
it('should transition to finish state if rejected with static_page_with_code', () =>
|
||||
testOAuth('resolve', {redirectUri: 'static_page_with_code'}, FinishState)
|
||||
);
|
||||
|
||||
it('should transition to login state if rejected with unauthorized', () => {
|
||||
return testOAuth('reject', {unauthorized: true}, LoginState);
|
||||
});
|
||||
it('should transition to login state if rejected with unauthorized', () =>
|
||||
testOAuth('reject', {unauthorized: true}, LoginState)
|
||||
);
|
||||
|
||||
it('should transition to permissions state if rejected with acceptRequired', () => {
|
||||
return testOAuth('reject', {acceptRequired: true}, PermissionsState);
|
||||
});
|
||||
it('should transition to permissions state if rejected with acceptRequired', () =>
|
||||
testOAuth('reject', {acceptRequired: true}, PermissionsState)
|
||||
);
|
||||
});
|
||||
|
||||
describe('permissions accept', () => {
|
||||
@ -285,7 +289,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -309,7 +314,8 @@ describe('CompleteState', () => {
|
||||
},
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by'
|
||||
clientId: 'ely.by',
|
||||
prompt: []
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -337,6 +343,7 @@ describe('CompleteState', () => {
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by',
|
||||
prompt: [],
|
||||
acceptRequired: true
|
||||
}
|
||||
}
|
||||
@ -365,6 +372,7 @@ describe('CompleteState', () => {
|
||||
auth: {
|
||||
oauth: {
|
||||
clientId: 'ely.by',
|
||||
prompt: [],
|
||||
acceptRequired: true
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ describe('OAuthState', () => {
|
||||
response_type: 'response_type',
|
||||
description: 'description',
|
||||
scope: 'scope',
|
||||
prompt: 'none',
|
||||
login_hint: 1,
|
||||
state: 'state'
|
||||
};
|
||||
|
||||
@ -42,6 +44,8 @@ describe('OAuthState', () => {
|
||||
responseType: query.response_type,
|
||||
description: query.description,
|
||||
scope: query.scope,
|
||||
prompt: query.prompt,
|
||||
loginHint: query.login_hint,
|
||||
state: query.state
|
||||
})
|
||||
).returns({then() {}});
|
||||
|
Loading…
x
Reference in New Issue
Block a user