Skip to content
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

CVAT authentication #5147

Merged
merged 26 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6d62901
Add social accounts authentication && improve email confirmation
Marishka17 Oct 20, 2022
73258dc
Pass env variables to docker
Marishka17 Oct 20, 2022
f81564b
Update helm chart
Marishka17 Oct 20, 2022
a15725f
Update email verification templates
Marishka17 Oct 20, 2022
60e0535
Small refactoring
Marishka17 Oct 20, 2022
b9808f2
Send email verification && redirect to /auth/email-verification-sent
Marishka17 Oct 21, 2022
fa6d06a
Fix helm chart
Marishka17 Oct 25, 2022
606ee5a
Fix typo
Marishka17 Oct 25, 2022
fa5e5db
Pass enabled advanced auth methods to client
Marishka17 Oct 25, 2022
643dc48
Merge branch 'develop' into mk/authentication
Marishka17 Oct 25, 2022
83ffc62
Rename class
Marishka17 Oct 25, 2022
3f4b7c7
Fix
Marishka17 Oct 26, 2022
6ca9a2e
Fix helm
Marishka17 Oct 26, 2022
b52285f
Fix github scope
Marishka17 Oct 26, 2022
bcd93c5
Some fixes
Marishka17 Oct 26, 2022
36e42bd
Fix schema generation
Marishka17 Oct 26, 2022
e6859e2
Fixes
Marishka17 Oct 26, 2022
25a6ddc
Apply comments
Marishka17 Oct 27, 2022
e8c4dd6
Update changelog
Marishka17 Oct 27, 2022
936a25f
Add more user friendly page when email confirmation url is incorrect …
Marishka17 Oct 28, 2022
d1cdb7e
Fix pylint
Marishka17 Oct 28, 2022
63bfbb1
Redirect to login page after cansel authorization
Marishka17 Oct 28, 2022
a2f010f
Fix bandit
Marishka17 Oct 28, 2022
da92b3f
Merge branch 'develop' into mk/authentication
Marishka17 Nov 3, 2022
c71ea8f
Update enabled advanced authentication methods
Marishka17 Nov 4, 2022
d0f27cc
Update dev callback urls
Marishka17 Nov 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Mask tools are supported now (brush, eraser, polygon-plus, polygon-minus, returning masks
from online detectors & interactors) (<https://github.com/opencv/cvat/pull/4543>)
- Added Webhooks (<https://github.com/opencv/cvat/pull/4863>)
- Authentication with social accounts google & github (<https://github.com/opencv/cvat/pull/5147>)

### Changed
- `api/docs`, `api/swagger`, `api/schema`, `server/about` endpoints now allow unauthorized access (<https://github.com/opencv/cvat/pull/4928>, <https://github.com/opencv/cvat/pull/4935>)
Expand Down
5 changes: 5 additions & 0 deletions cvat-core/src/api-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ const config = require('./config').default;
await serverProxy.server.logout();
};

cvat.server.advancedAuthentication.implementation = async () => {
const result = await serverProxy.server.advancedAuthentication();
return result;
};

cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};
Expand Down
12 changes: 12 additions & 0 deletions cvat-core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ function build() {
const result = await PluginRegistry.apiWrapper(cvat.server.logout);
return result;
},
/**
* Method returns enabled advanced authentication methods
* @method advancedAuthentication
* @async
* @memberof module:API.cvat.server
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async advancedAuthentication() {
const result = await PluginRegistry.apiWrapper(cvat.server.advancedAuthentication);
return result;
},
/**
* Method allows to change user password
* @method changePassword
Expand Down
13 changes: 13 additions & 0 deletions cvat-core/src/server-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,18 @@ class ServerProxy {
}
}

async function advancedAuthentication(): Promise<any> {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/server/advanced-auth`, {
proxy: config.proxy,
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}

Object.defineProperties(
this,
Object.freeze({
Expand All @@ -2207,6 +2219,7 @@ class ServerProxy {
exception,
login,
logout,
advancedAuthentication,
changePassword,
requestPasswordReset,
resetPassword,
Expand Down
28 changes: 26 additions & 2 deletions cvat-ui/src/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import { getCore } from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';
import { AdvancedAuthMethodsList } from '../reducers';

const cvat = getCore();

Expand Down Expand Up @@ -35,14 +36,19 @@ export enum AuthActionTypes {
LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS',
LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS',
LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED',
LOAD_ADVANCED_AUTHENTICATION = 'LOAD_ADVANCED_AUTHENTICATION',
LOAD_ADVANCED_AUTHENTICATION_SUCCESS = 'LOAD_ADVANCED_AUTHENTICATION_SUCCESS',
LOAD_ADVANCED_AUTHENTICATION_FAILED = 'LOAD_ADVANCED_AUTHENTICATION_FAILED',
}

export const authActions = {
authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }),
authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }),
login: () => createAction(AuthActionTypes.LOGIN),
loginSuccess: (user: any) => createAction(AuthActionTypes.LOGIN_SUCCESS, { user }),
loginFailed: (error: any) => createAction(AuthActionTypes.LOGIN_FAILED, { error }),
loginFailed: (error: any, hasEmailVerificationBeenSent = false) => (
createAction(AuthActionTypes.LOGIN_FAILED, { error, hasEmailVerificationBeenSent })
),
register: () => createAction(AuthActionTypes.REGISTER),
registerSuccess: (user: any) => createAction(AuthActionTypes.REGISTER_SUCCESS, { user }),
registerFailed: (error: any) => createAction(AuthActionTypes.REGISTER_FAILED, { error }),
Expand All @@ -69,6 +75,13 @@ export const authActions = {
})
),
loadServerAuthActionsFailed: (error: any) => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error }),
loadAdvancedAuth: () => createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION),
loadAdvancedAuthSuccess: (list: AdvancedAuthMethodsList) => (
createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION_SUCCESS, { list })
),
loadAdvancedAuthFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION_FAILED, { error })
),
};

export type AuthActions = ActionUnion<typeof authActions>;
Expand Down Expand Up @@ -109,7 +122,8 @@ export const loginAsync = (credential: string, password: string): ThunkAction =>
const users = await cvat.users.get({ self: true });
dispatch(authActions.loginSuccess(users[0]));
} catch (error) {
dispatch(authActions.loginFailed(error));
const hasEmailVerificationBeenSent = error.message.includes('Unverified email');
dispatch(authActions.loginFailed(error, hasEmailVerificationBeenSent));
}
};

Expand Down Expand Up @@ -197,3 +211,13 @@ export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.loadServerAuthActionsFailed(error));
}
};

export const loadAdvancedAuthAsync = (): ThunkAction => async (dispatch): Promise<void> => {
dispatch(authActions.loadAdvancedAuth());
try {
const list: AdvancedAuthMethodsList = await cvat.server.advancedAuthentication();
dispatch(authActions.loadAdvancedAuthSuccess(list));
} catch (error) {
dispatch(authActions.loadAdvancedAuthFailed(error));
}
};
6 changes: 5 additions & 1 deletion cvat-ui/src/components/cvat-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ import showPlatformNotification, {
showUnsupportedNotification,
} from 'utils/platform-checker';
import '../styles.scss';
import EmailConfirmationPage from './email-confirmation-page/email-confirmed';
import EmailConfirmationPage from './email-confirmation-pages/email-confirmed';
import EmailVerificationSentPage from './email-confirmation-pages/email-verification-sent';
import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation';

interface CVATAppProps {
loadFormats: () => void;
Expand Down Expand Up @@ -427,6 +429,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<GlobalErrorBoundary>
<Switch>
<Route exact path='/auth/register' component={RegisterPageContainer} />
<Route exact path='/auth/email-verification-sent' component={EmailVerificationSentPage} />
<Route exact path='/auth/incorrect-email-confirmation' component={IncorrectEmailConfirmationPage} />
<Route exact path='/auth/login' component={LoginPageContainer} />
<Route
exact
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import React from 'react';
import { Col, Row } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import Button from 'antd/lib/button';
import './styles.scss';

const { Content } = Layout;

/**
* Component for displaying message that email should be verified
*/

export default function EmailVerificationSentPage(): JSX.Element {
return (
<Layout>
<Content>
<Row justify='center' align='middle' id='email-verification-sent-page-container'>
<Col>
<h1>Please, confirm your email</h1>
<Button type='link' href='/auth/login'>
Go to login page
</Button>
</Col>
</Row>
</Content>
</Layout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

import React from 'react';
import { Col, Row } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import Button from 'antd/lib/button';
import './styles.scss';

const { Content } = Layout;

/**
* Component for displaying message that email confirmation URL is incorrect
*/

export default function IncorrectEmailConfirmationPage(): JSX.Element {
return (
<Layout>
<Content>
<Row justify='center' align='middle' id='incorrect-email-confirmation-page-container'>
<Col>
<h1>
This e-mail confirmation link expired or is invalid.
</h1>
<p>
Please issue a new e-mail confirmation request.
</p>
<Button type='link' href='/auth/login'>
Go to login page
</Button>
</Col>
</Row>
</Content>
</Layout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//
// SPDX-License-Identifier: MIT

#email-confirmation-page-container {
#email-confirmation-page-container,
#email-verification-sent-page-container,
#incorrect-email-confirmation-page-container {
height: 100%;
text-align: center;
}
64 changes: 61 additions & 3 deletions cvat-ui/src/components/login-page/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,35 @@
//
// SPDX-License-Identifier: MIT

import React from 'react';
import { RouteComponentProps } from 'react-router';
import React, { useEffect } from 'react';
import { RouteComponentProps, useHistory } from 'react-router';
import { Link, withRouter } from 'react-router-dom';
import Button from 'antd/lib/button';
import Title from 'antd/lib/typography/Title';
import Text from 'antd/lib/typography/Text';
import { Row, Col } from 'antd/lib/grid';
import Layout from 'antd/lib/layout';
import Space from 'antd/lib/space';
import { GithubOutlined, GooglePlusOutlined } from '@ant-design/icons';

import LoginForm, { LoginData } from './login-form';
import { getCore } from '../../cvat-core-wrapper';

const cvat = getCore();

interface LoginPageComponentProps {
fetching: boolean;
renderResetPassword: boolean;
hasEmailVerificationBeenSent: boolean;
googleAuthentication: boolean;
githubAuthentication: boolean;
onLogin: (credential: string, password: string) => void;
loadAdvancedAuthenticationMethods: () => void;
}

function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps): JSX.Element {
const history = useHistory();
const { backendAPI } = cvat.config;
const sizes = {
style: {
width: 400,
Expand All @@ -28,7 +40,18 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps

const { Content } = Layout;

const { fetching, onLogin, renderResetPassword } = props;
const {
fetching, renderResetPassword, hasEmailVerificationBeenSent,
googleAuthentication, githubAuthentication, onLogin, loadAdvancedAuthenticationMethods,
} = props;

if (hasEmailVerificationBeenSent) {
history.push('/auth/email-verification-sent');
}

useEffect(() => {
loadAdvancedAuthenticationMethods();
}, []);

return (
<Layout>
Expand All @@ -43,6 +66,41 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
onLogin(loginData.credential, loginData.password);
}}
/>
{(googleAuthentication || githubAuthentication) &&
(
<>
<Row justify='center' align='top'>
<Col>
or
</Col>
</Row>
<Row justify='space-between' align='middle'>
{googleAuthentication && (
<Col span={11}>
<Button href={`${backendAPI}/auth/google/login`}>
<Space>
<GooglePlusOutlined />
Continue with Google
</Space>
</Button>
</Col>
)}
{githubAuthentication && (
<Col
span={11}
offset={googleAuthentication ? 1 : 0}
>
<Button href={`${backendAPI}/auth/github/login`}>
<Space>
<GithubOutlined />
Continue with Github
</Space>
</Button>
</Col>
)}
</Row>
</>
)}
<Row justify='start' align='top'>
<Col>
<Text strong>
Expand Down
10 changes: 9 additions & 1 deletion cvat-ui/src/containers/login-page/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,34 @@
import { connect } from 'react-redux';
import LoginPageComponent from 'components/login-page/login-page';
import { CombinedState } from 'reducers';
import { loginAsync } from 'actions/auth-actions';
import { loginAsync, loadAdvancedAuthAsync } from 'actions/auth-actions';

interface StateToProps {
fetching: boolean;
renderResetPassword: boolean;
hasEmailVerificationBeenSent: boolean;
googleAuthentication: boolean;
githubAuthentication: boolean;
}

interface DispatchToProps {
onLogin: typeof loginAsync;
loadAdvancedAuthenticationMethods: typeof loadAdvancedAuthAsync;
}

function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
renderResetPassword: state.auth.allowResetPassword,
hasEmailVerificationBeenSent: state.auth.hasEmailVerificationBeenSent,
googleAuthentication: state.auth.advancedAuthList.GOOGLE_ACCOUNT_AUTHENTICATION,
githubAuthentication: state.auth.advancedAuthList.GITHUB_ACCOUNT_AUTHENTICATION,
};
}

const mapDispatchToProps: DispatchToProps = {
onLogin: loginAsync,
loadAdvancedAuthenticationMethods: loadAdvancedAuthAsync,
};

export default connect(mapStateToProps, mapDispatchToProps)(LoginPageComponent);
Loading