mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-30 02:32:58 +05:30
fix: correct logic for handling deleted users during oauth (#27)
This commit is contained in:
parent
79ff3b9410
commit
831ab42155
@ -1,3 +1,5 @@
|
|||||||
|
import type { Account } from 'app/components/accounts/reducer';
|
||||||
|
|
||||||
import AbstractState from './AbstractState';
|
import AbstractState from './AbstractState';
|
||||||
import { AuthContext } from './AuthFlow';
|
import { AuthContext } from './AuthFlow';
|
||||||
import LoginState from './LoginState';
|
import LoginState from './LoginState';
|
||||||
@ -14,10 +16,12 @@ export default class ChooseAccountState extends AbstractState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(context: AuthContext, payload: Record<string, any>): Promise<void> | void {
|
resolve(context: AuthContext, payload: Account | Record<string, any>): Promise<void> | void {
|
||||||
if (payload.id) {
|
if (payload.id) {
|
||||||
|
// payload is Account
|
||||||
context.setState(new CompleteState());
|
context.setState(new CompleteState());
|
||||||
} else {
|
} else {
|
||||||
|
// log in to another account
|
||||||
context.navigate('/login');
|
context.navigate('/login');
|
||||||
context.run('setLogin', null);
|
context.run('setLogin', null);
|
||||||
context.setState(new LoginState());
|
context.setState(new LoginState());
|
||||||
|
@ -22,8 +22,7 @@ describe('CompleteState', () => {
|
|||||||
state = new CompleteState();
|
state = new CompleteState();
|
||||||
|
|
||||||
const data = bootstrap();
|
const data = bootstrap();
|
||||||
context = data.context;
|
({ context, mock } = data);
|
||||||
mock = data.mock;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -116,6 +115,7 @@ describe('CompleteState', () => {
|
|||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('oauth', () => {
|
||||||
it('should transition to finish state if code is present', () => {
|
it('should transition to finish state if code is present', () => {
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
user: {
|
user: {
|
||||||
@ -135,6 +135,7 @@ describe('CompleteState', () => {
|
|||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('permissions', () => {
|
||||||
it('should transition to permissions state if acceptRequired', () => {
|
it('should transition to permissions state if acceptRequired', () => {
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
user: {
|
user: {
|
||||||
@ -172,7 +173,9 @@ describe('CompleteState', () => {
|
|||||||
|
|
||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('account switcher', () => {
|
||||||
it('should transition to ChooseAccountState if user has multiple accs and switcher enabled', () => {
|
it('should transition to ChooseAccountState if user has multiple accs and switcher enabled', () => {
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
user: {
|
user: {
|
||||||
@ -197,6 +200,31 @@ describe('CompleteState', () => {
|
|||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should transition to ChooseAccountState if user isDeleted', () => {
|
||||||
|
context.getState.returns({
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
isDeleted: true,
|
||||||
|
isGuest: false,
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
available: [{ id: 1 }],
|
||||||
|
active: 1,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
isSwitcherEnabled: true,
|
||||||
|
oauth: {
|
||||||
|
clientId: 'ely.by',
|
||||||
|
prompt: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expectState(mock, ChooseAccountState);
|
||||||
|
|
||||||
|
state.enter(context);
|
||||||
|
});
|
||||||
|
|
||||||
it('should NOT transition to ChooseAccountState if user has multiple accs and switcher disabled', () => {
|
it('should NOT transition to ChooseAccountState if user has multiple accs and switcher disabled', () => {
|
||||||
context.getState.returns({
|
context.getState.returns({
|
||||||
user: {
|
user: {
|
||||||
@ -269,6 +297,8 @@ describe('CompleteState', () => {
|
|||||||
state.enter(context);
|
state.enter(context);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when user completes oauth', () => {
|
describe('when user completes oauth', () => {
|
||||||
it('should run oAuthComplete', () => {
|
it('should run oAuthComplete', () => {
|
||||||
|
@ -34,9 +34,7 @@ export default class CompleteState extends AbstractState {
|
|||||||
context.setState(new LoginState());
|
context.setState(new LoginState());
|
||||||
} else if (!user.isActive) {
|
} else if (!user.isActive) {
|
||||||
context.setState(new ActivationState());
|
context.setState(new ActivationState());
|
||||||
} else if (user.isDeleted) {
|
} else if (user.shouldAcceptRules && !user.isDeleted) {
|
||||||
context.navigate('/');
|
|
||||||
} else if (user.shouldAcceptRules) {
|
|
||||||
context.setState(new AcceptRulesState());
|
context.setState(new AcceptRulesState());
|
||||||
} else if (oauth && oauth.clientId) {
|
} else if (oauth && oauth.clientId) {
|
||||||
return this.processOAuth(context);
|
return this.processOAuth(context);
|
||||||
@ -46,7 +44,7 @@ export default class CompleteState extends AbstractState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processOAuth(context: AuthContext): Promise<void> | void {
|
processOAuth(context: AuthContext): Promise<void> | void {
|
||||||
const { auth, accounts } = context.getState();
|
const { auth, accounts, user } = context.getState();
|
||||||
|
|
||||||
let { isSwitcherEnabled } = auth;
|
let { isSwitcherEnabled } = auth;
|
||||||
const { oauth } = auth;
|
const { oauth } = auth;
|
||||||
@ -75,8 +73,22 @@ export default class CompleteState extends AbstractState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSwitcherEnabled && (accounts.available.length > 1 || oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE))) {
|
if (
|
||||||
|
isSwitcherEnabled &&
|
||||||
|
(accounts.available.length > 1 ||
|
||||||
|
// we are always showing account switcher for deleted users
|
||||||
|
// so that they can see, that their account was deleted
|
||||||
|
// (this info is displayed on switcher)
|
||||||
|
user.isDeleted ||
|
||||||
|
oauth.prompt.includes(PROMPT_ACCOUNT_CHOOSE))
|
||||||
|
) {
|
||||||
context.setState(new ChooseAccountState());
|
context.setState(new ChooseAccountState());
|
||||||
|
} else if (user.isDeleted) {
|
||||||
|
// you shall not pass
|
||||||
|
// if we are here, this means that user have already seen account
|
||||||
|
// switcher and now we should redirect him to his profile,
|
||||||
|
// because oauth is not available for deleted accounts
|
||||||
|
context.navigate('/');
|
||||||
} else if (oauth.code) {
|
} else if (oauth.code) {
|
||||||
context.setState(new FinishState());
|
context.setState(new FinishState());
|
||||||
} else {
|
} else {
|
||||||
|
@ -17,36 +17,6 @@ describe('OAuth', () => {
|
|||||||
cy.url().should('equal', 'https://dev.ely.by/');
|
cy.url().should('equal', 'https://dev.ely.by/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not complete oauth if account is deleted', () => {
|
|
||||||
cy.login({ accounts: ['default'] });
|
|
||||||
|
|
||||||
cy.server();
|
|
||||||
cy.route({
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/v1/accounts/${account1.id}`,
|
|
||||||
response: {
|
|
||||||
id: 7,
|
|
||||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
|
||||||
username: 'SleepWalker',
|
|
||||||
isOtpEnabled: false,
|
|
||||||
registeredAt: 1475568334,
|
|
||||||
lang: 'en',
|
|
||||||
elyProfileLink: 'http://ely.by/u7',
|
|
||||||
email: 'danilenkos@auroraglobal.com',
|
|
||||||
isActive: true,
|
|
||||||
isDeleted: true, // force user into the deleted state
|
|
||||||
passwordChangedAt: 1476075696,
|
|
||||||
hasMojangUsernameCollision: true,
|
|
||||||
shouldAcceptRules: false,
|
|
||||||
} as UserResponse,
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
|
||||||
|
|
||||||
cy.location('pathname').should('eq', '/');
|
|
||||||
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should restore previous oauthData if any', () => {
|
it('should restore previous oauthData if any', () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'oauthData',
|
'oauthData',
|
||||||
@ -70,6 +40,7 @@ describe('OAuth', () => {
|
|||||||
cy.url().should('equal', 'https://dev.ely.by/');
|
cy.url().should('equal', 'https://dev.ely.by/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('AccountSwitcher', () => {
|
||||||
it('should ask to choose an account if user has multiple', () => {
|
it('should ask to choose an account if user has multiple', () => {
|
||||||
cy.login({ accounts: ['default', 'default2'] }).then(({ accounts: [account] }) => {
|
cy.login({ accounts: ['default', 'default2'] }).then(({ accounts: [account] }) => {
|
||||||
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
||||||
@ -83,7 +54,9 @@ describe('OAuth', () => {
|
|||||||
cy.url().should('equal', 'https://dev.ely.by/');
|
cy.url().should('equal', 'https://dev.ely.by/');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Permissions prompt', () => {
|
||||||
// TODO: remove api mocks, when we will be able to revoke permissions
|
// TODO: remove api mocks, when we will be able to revoke permissions
|
||||||
it('should prompt for permissions', () => {
|
it('should prompt for permissions', () => {
|
||||||
cy.server();
|
cy.server();
|
||||||
@ -122,56 +95,6 @@ describe('OAuth', () => {
|
|||||||
cy.url().should('match', /^http:\/\/localhost:8080\/?\?code=[^&]+&state=$/);
|
cy.url().should('match', /^http:\/\/localhost:8080\/?\?code=[^&]+&state=$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow sign in during oauth (guest oauth)', () => {
|
|
||||||
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
|
||||||
|
|
||||||
cy.location('pathname').should('eq', '/login');
|
|
||||||
|
|
||||||
cy.get('[name=login]').type(`${account1.login}{enter}`);
|
|
||||||
|
|
||||||
cy.url().should('include', '/password');
|
|
||||||
|
|
||||||
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
|
||||||
|
|
||||||
cy.url().should('equal', 'https://dev.ely.by/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow sign in during oauth and not finish process if the account is deleted', () => {
|
|
||||||
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
|
||||||
|
|
||||||
cy.location('pathname').should('eq', '/login');
|
|
||||||
|
|
||||||
cy.get('[name=login]').type(`${account1.login}{enter}`);
|
|
||||||
|
|
||||||
cy.url().should('include', '/password');
|
|
||||||
|
|
||||||
cy.server();
|
|
||||||
cy.route({
|
|
||||||
method: 'GET',
|
|
||||||
url: `/api/v1/accounts/${account1.id}`,
|
|
||||||
response: {
|
|
||||||
id: 7,
|
|
||||||
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
|
||||||
username: 'SleepWalker',
|
|
||||||
isOtpEnabled: false,
|
|
||||||
registeredAt: 1475568334,
|
|
||||||
lang: 'en',
|
|
||||||
elyProfileLink: 'http://ely.by/u7',
|
|
||||||
email: 'danilenkos@auroraglobal.com',
|
|
||||||
isActive: true,
|
|
||||||
isDeleted: true, // force user into the deleted state
|
|
||||||
passwordChangedAt: 1476075696,
|
|
||||||
hasMojangUsernameCollision: true,
|
|
||||||
shouldAcceptRules: false,
|
|
||||||
} as UserResponse,
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
|
||||||
|
|
||||||
cy.location('pathname').should('eq', '/');
|
|
||||||
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: enable, when backend api will return correct response on auth decline
|
// TODO: enable, when backend api will return correct response on auth decline
|
||||||
xit('should redirect to error page, when permission request declined', () => {
|
xit('should redirect to error page, when permission request declined', () => {
|
||||||
cy.server();
|
cy.server();
|
||||||
@ -209,6 +132,95 @@ describe('OAuth', () => {
|
|||||||
|
|
||||||
cy.url().should('include', 'error=access_denied');
|
cy.url().should('include', 'error=access_denied');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Sign-in during oauth', () => {
|
||||||
|
it('should allow sign in during oauth (guest oauth)', () => {
|
||||||
|
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
|
||||||
|
cy.get('[name=login]').type(`${account1.login}{enter}`);
|
||||||
|
|
||||||
|
cy.url().should('include', '/password');
|
||||||
|
|
||||||
|
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
||||||
|
|
||||||
|
cy.url().should('equal', 'https://dev.ely.by/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Deleted account', () => {
|
||||||
|
it('should show account switcher and then abort oauth and redirect to profile', () => {
|
||||||
|
cy.login({ accounts: ['default'] }).then(({ accounts: [account] }) => {
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/v1/accounts/${account1.id}`,
|
||||||
|
response: {
|
||||||
|
id: account.id,
|
||||||
|
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||||
|
username: account.username,
|
||||||
|
isOtpEnabled: false,
|
||||||
|
registeredAt: 1475568334,
|
||||||
|
lang: 'en',
|
||||||
|
elyProfileLink: 'http://ely.by/u7',
|
||||||
|
email: account.email,
|
||||||
|
isActive: true,
|
||||||
|
isDeleted: true, // force user into the deleted state
|
||||||
|
passwordChangedAt: 1476075696,
|
||||||
|
hasMojangUsernameCollision: true,
|
||||||
|
shouldAcceptRules: false,
|
||||||
|
} as UserResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
||||||
|
|
||||||
|
cy.findByTestId('auth-header').should('contain', 'Choose an account');
|
||||||
|
|
||||||
|
cy.findByTestId('auth-body').contains(account.email).click();
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/');
|
||||||
|
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow sign and then abort oauth and redirect to profile', () => {
|
||||||
|
cy.visit(`/oauth2/v1/ely?${new URLSearchParams(defaults)}`);
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/login');
|
||||||
|
|
||||||
|
cy.get('[name=login]').type(`${account1.login}{enter}`);
|
||||||
|
|
||||||
|
cy.url().should('include', '/password');
|
||||||
|
|
||||||
|
cy.server();
|
||||||
|
cy.route({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/v1/accounts/${account1.id}`,
|
||||||
|
response: {
|
||||||
|
id: 7,
|
||||||
|
uuid: '522e8c19-89d8-4a6d-a2ec-72ebb58c2dbe',
|
||||||
|
username: 'SleepWalker',
|
||||||
|
isOtpEnabled: false,
|
||||||
|
registeredAt: 1475568334,
|
||||||
|
lang: 'en',
|
||||||
|
elyProfileLink: 'http://ely.by/u7',
|
||||||
|
email: 'danilenkos@auroraglobal.com',
|
||||||
|
isActive: true,
|
||||||
|
isDeleted: true, // force user into the deleted state
|
||||||
|
passwordChangedAt: 1476075696,
|
||||||
|
hasMojangUsernameCollision: true,
|
||||||
|
shouldAcceptRules: false,
|
||||||
|
} as UserResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[name=password]').type(`${account1.password}{enter}`);
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/');
|
||||||
|
cy.findByTestId('deletedAccount').should('contain', 'Account is deleted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('login_hint', () => {
|
describe('login_hint', () => {
|
||||||
it('should automatically choose account, when id in login_hint is present', () => {
|
it('should automatically choose account, when id in login_hint is present', () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user