Skip to content

Commit

Permalink
Social account authentication tests (cvat-ai#5444)
Browse files Browse the repository at this point in the history
Depends on cvat-ai#5349
Related cvat-ai#5432
Added tests for social account authentication functionality: cypress
test with dummy auth server
  • Loading branch information
Marishka17 authored Jan 12, 2023
1 parent 71a0aaf commit b00bc65
Show file tree
Hide file tree
Showing 35 changed files with 592 additions and 140 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,13 @@ jobs:
- name: Run CVAT instance
run: |
docker compose \
--env-file="tests/python/social_auth/.env" \
-f docker-compose.yml \
-f docker-compose.dev.yml \
-f components/serverless/docker-compose.serverless.yml \
-f tests/docker-compose.minio.yml \
-f tests/docker-compose.file_share.yml up -d
-f tests/docker-compose.file_share.yml \
-f tests/python/social_auth/docker-compose.yml up -d
- name: Waiting for server
env:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,13 @@ jobs:
- name: Run CVAT instance
run: |
docker compose \
--env-file="tests/python/social_auth/.env" \
-f docker-compose.yml \
-f docker-compose.dev.yml \
-f components/serverless/docker-compose.serverless.yml \
-f tests/docker-compose.minio.yml \
-f tests/docker-compose.file_share.yml up -d
-f tests/docker-compose.file_share.yml \
-f tests/python/social_auth/docker-compose.yml up -d
- name: Waiting for server
env:
API_ABOUT_PAGE: "localhost:8080/api/server/about"
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/schedule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,11 +319,13 @@ jobs:
- name: Run CVAT instance
run: |
docker compose \
--env-file="tests/python/social_auth/.env" \
-f docker-compose.yml \
-f docker-compose.dev.yml \
-f tests/docker-compose.file_share.yml \
-f tests/docker-compose.minio.yml \
-f components/serverless/docker-compose.serverless.yml up -d
-f components/serverless/docker-compose.serverless.yml \
-f tests/python/social_auth/docker-compose.yml up -d
- name: Waiting for server
id: wait-server
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- \[SDK\] A PyTorch adapter setting to disable cache updates
(<https://github.com/opencv/cvat/pull/5549>)
- YOLO v7 serverless feature added using ONNX backend (<https://github.com/opencv/cvat/pull/5552>)
- Cypress test for social account authentication (<https://github.com/opencv/cvat/pull/5444>)
- Dummy github and google authentication servers (<https://github.com/opencv/cvat/pull/5444>)

### Changed
- The Docker Compose files now use the Compose Specification version
Expand Down
2 changes: 1 addition & 1 deletion cvat-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "7.4.1",
"version": "7.5.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "src/api.ts",
"scripts": {
Expand Down
7 changes: 4 additions & 3 deletions cvat-core/src/api-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: MIT

import { SocialAuthMethod, SocialAuthMethodsRawType } from './auth-methods';
import config from './config';

import PluginRegistry from './plugins';
Expand Down Expand Up @@ -85,9 +86,9 @@ export default function implementAPI(cvat) {
await serverProxy.server.logout();
};

cvat.server.advancedAuthentication.implementation = async () => {
const result = await serverProxy.server.advancedAuthentication();
return result;
cvat.server.socialAuthentication.implementation = async () => {
const result: SocialAuthMethodsRawType = await serverProxy.server.socialAuthentication();
return Object.entries(result).map(([provider, value]) => new SocialAuthMethod({ ...value, provider }));
};

cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
Expand Down
4 changes: 2 additions & 2 deletions cvat-core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ function build() {
const result = await PluginRegistry.apiWrapper(cvat.server.logout);
return result;
},
async advancedAuthentication() {
const result = await PluginRegistry.apiWrapper(cvat.server.advancedAuthentication);
async socialAuthentication() {
const result = await PluginRegistry.apiWrapper(cvat.server.socialAuthentication);
return result;
},
async changePassword(oldPassword, newPassword1, newPassword2) {
Expand Down
57 changes: 57 additions & 0 deletions cvat-core/src/auth-methods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT

interface SocialAuthMethodCamelCase {
provider: string;
publicName: string;
isEnabled: boolean;
icon: string;
}

interface SocialAuthMethodSnakeCase {
public_name: string;
is_enabled: boolean;
icon: string;
provider?: string;
}

export class SocialAuthMethod {
public provider: string;
public publicName: string;
public isEnabled: boolean;
public icon: string;

constructor(initialData: SocialAuthMethodSnakeCase) {
const data: SocialAuthMethodCamelCase = {
provider: initialData.provider,
publicName: initialData.public_name,
isEnabled: initialData.is_enabled,
icon: initialData.icon,
};

Object.defineProperties(
this,
Object.freeze({
provider: {
get: () => data.provider,
},
publicName: {
get: () => data.publicName,
},
isEnabled: {
get: () => data.isEnabled,
},
icon: {
get: () => data.icon,
},
}),
);
}
}

export type SocialAuthMethodsRawType = {
[index: string]: SocialAuthMethodSnakeCase;
};

export type SocialAuthMethods = SocialAuthMethod[];
6 changes: 3 additions & 3 deletions cvat-core/src/server-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2250,10 +2250,10 @@ async function receiveWebhookEvents(type: WebhookSourceType): Promise<string[]>
}
}

async function advancedAuthentication(): Promise<any> {
async function socialAuthentication(): Promise<any> {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/server/advanced-auth`, {
const response = await Axios.get(`${backendAPI}/auth/social/methods`, {
proxy: config.proxy,
});
return response.data;
Expand All @@ -2270,7 +2270,7 @@ export default Object.freeze({
exception,
login,
logout,
advancedAuthentication,
socialAuthentication,
changePassword,
requestPasswordReset,
resetPassword,
Expand Down
28 changes: 14 additions & 14 deletions cvat-ui/src/actions/auth-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +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';
import { SocialAuthMethods } from '../cvat-core-wrapper';

const cvat = getCore();

Expand Down Expand Up @@ -36,9 +36,9 @@ 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',
LOAD_SOCIAL_AUTHENTICATION = 'LOAD_SOCIAL_AUTHENTICATION',
LOAD_SOCIAL_AUTHENTICATION_SUCCESS = 'LOAD_SOCIAL_AUTHENTICATION_SUCCESS',
LOAD_SOCIAL_AUTHENTICATION_FAILED = 'LOAD_SOCIAL_AUTHENTICATION_FAILED',
}

export const authActions = {
Expand Down Expand Up @@ -75,12 +75,12 @@ 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 })
loadSocialAuth: () => createAction(AuthActionTypes.LOAD_SOCIAL_AUTHENTICATION),
loadSocialAuthSuccess: (methods: SocialAuthMethods) => (
createAction(AuthActionTypes.LOAD_SOCIAL_AUTHENTICATION_SUCCESS, { methods })
),
loadAdvancedAuthFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_ADVANCED_AUTHENTICATION_FAILED, { error })
loadSocialAuthFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_SOCIAL_AUTHENTICATION_FAILED, { error })
),
};

Expand Down Expand Up @@ -210,12 +210,12 @@ export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
}
};

export const loadAdvancedAuthAsync = (): ThunkAction => async (dispatch): Promise<void> => {
dispatch(authActions.loadAdvancedAuth());
export const loadSocialAuthAsync = (): ThunkAction => async (dispatch): Promise<void> => {
dispatch(authActions.loadSocialAuth());
try {
const list: AdvancedAuthMethodsList = await cvat.server.advancedAuthentication();
dispatch(authActions.loadAdvancedAuthSuccess(list));
const methods: SocialAuthMethods = await cvat.server.socialAuthentication();
dispatch(authActions.loadSocialAuthSuccess(methods));
} catch (error) {
dispatch(authActions.loadAdvancedAuthFailed(error));
dispatch(authActions.loadSocialAuthFailed(error));
}
};
49 changes: 22 additions & 27 deletions cvat-ui/src/components/login-page/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,48 @@ import { Row, Col } from 'antd/lib/grid';

import SigningLayout, { formSizes } from 'components/signing-common/signing-layout';
import SocialAccountLink from 'components/signing-common/social-account-link';
import { SocialGithubLogo, SocialGoogleLogo } from 'icons';
import LoginForm, { LoginData } from './login-form';
import { getCore } from '../../cvat-core-wrapper';
import { getCore, SocialAuthMethods, SocialAuthMethod } from '../../cvat-core-wrapper';

const cvat = getCore();

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

const renderSocialAuthMethods = (methods: SocialAuthMethods): JSX.Element[] => {
const { backendAPI } = cvat.config;

return methods.map((item: SocialAuthMethod) => ((item.isEnabled) ? (
<SocialAccountLink
key={item.provider}
icon={item.icon}
href={`${backendAPI}/auth/${item.provider}/login`}
className={`cvat-social-authentication-${item.provider}`}
>
{`Continue with ${item.publicName}`}
</SocialAccountLink>
) : <></>));
};

function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps): JSX.Element {
const history = useHistory();
const { backendAPI } = cvat.config;
const {
fetching, renderResetPassword, hasEmailVerificationBeenSent,
googleAuthentication, githubAuthentication, onLogin, loadAdvancedAuthenticationMethods,
socialAuthMethods, onLogin, loadSocialAuthenticationMethods,
} = props;

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

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

return (
Expand All @@ -50,26 +62,9 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
<LoginForm
fetching={fetching}
renderResetPassword={renderResetPassword}
socialAuthentication={(googleAuthentication || githubAuthentication) ? (
socialAuthentication={(socialAuthMethods) ? (
<div className='cvat-social-authentication'>
{githubAuthentication && (
<SocialAccountLink
icon={SocialGithubLogo}
href={`${backendAPI}/auth/github/login`}
className='cvat-social-authentication-github'
>
Continue with GitHub
</SocialAccountLink>
)}
{googleAuthentication && (
<SocialAccountLink
icon={SocialGoogleLogo}
href={`${backendAPI}/auth/google/login`}
className='cvat-social-authentication-google'
>
Continue with Google
</SocialAccountLink>
)}
{renderSocialAuthMethods(socialAuthMethods)}
</div>
) : null}
onSubmit={(loginData: LoginData): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT

import React, { useEffect } from 'react';
import { useLocation, useHistory } from 'react-router';
import { Redirect, useLocation, useHistory } from 'react-router';
import notification from 'antd/lib/notification';
import Spin from 'antd/lib/spin';

Expand All @@ -29,6 +29,7 @@ export default function LoginWithSocialAppComponent(): JSX.Element {
.catch((exception: Error) => {
if (exception.message.includes('Unverified email')) {
history.push('/auth/email-verification-sent');
return Promise.resolve();
}
history.push('/auth/login');
notification.error({
Expand All @@ -40,6 +41,10 @@ export default function LoginWithSocialAppComponent(): JSX.Element {
}
}, []);

if (localStorage.getItem('token')) {
return <Redirect to={search.get('next') || '/tasks'} />;
}

return (
<div className='cvat-login-page cvat-spinner-container'>
<Spin size='large' className='cvat-spinner' />
Expand Down
18 changes: 14 additions & 4 deletions cvat-ui/src/components/signing-common/social-account-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@ import './styles.scss';
import React from 'react';
import { Col, Row } from 'antd/lib/grid';
import Button from 'antd/lib/button/button';
import Icon from '@ant-design/icons';
import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';

interface SocialAccountLinkProps {
children: string;
className?: string;
href: string;
icon: React.ForwardRefExoticComponent<CustomIconComponentProps>;
icon: string;
}

function SocialAccountLink(props: SocialAccountLinkProps): JSX.Element {
const svgWrapperRef = React.useRef();
const {
children, className, href, icon,
} = props;

React.useEffect(() => {
if (icon) {
// eslint-disable-next-line no-unsanitized/property
svgWrapperRef.current.innerHTML = icon;
}
}, [icon, svgWrapperRef.current]);

return (
<Row>
<Col flex='auto'>
Expand All @@ -28,7 +35,10 @@ function SocialAccountLink(props: SocialAccountLinkProps): JSX.Element {
>
<Row align='middle' style={{ width: '100%' }}>
<Col>
<Icon component={icon} />
<div
ref={svgWrapperRef as any}
className='cvat-social-authentication-icon'
/>
</Col>
<Col flex='auto'>
{children}
Expand Down
Loading

0 comments on commit b00bc65

Please sign in to comment.