Merge pull request #10704 from strapi/migration/sso-providers-view

Migration SSO providers view - To merge after login view
This commit is contained in:
ELABBASSI Hicham 2021-08-12 18:18:56 +02:00 committed by GitHub
commit ec1d8e6829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 266 additions and 159 deletions

View File

@ -1,3 +1,7 @@
'use strict';
const GoogleStrategy = require('passport-google-oauth2');
module.exports = ({ env }) => ({
host: env('HOST', '0.0.0.0'),
port: env.int('PORT', 1337),
@ -5,6 +9,32 @@ module.exports = ({ env }) => ({
// autoOpen: true,
auth: {
secret: env('ADMIN_JWT_SECRET', 'example-token'),
providers: [
{
uid: 'google',
displayName: 'Google',
icon: 'https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Google-512.png',
createStrategy: strapi =>
new GoogleStrategy(
{
clientID: env('GOOGLE_CLIENT_ID'),
clientSecret: env('GOOGLE_CLIENT_SECRET'),
scope: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
],
callbackURL: strapi.admin.services.passport.getStrategyCallbackURL('google'),
},
(request, accessToken, refreshToken, profile, done) => {
done(null, {
email: profile.email,
firstname: profile.given_name,
lastname: profile.family_name,
});
}
),
},
],
},
},
});

View File

@ -28,6 +28,7 @@
"@strapi/utils": "3.6.6",
"lodash": "4.17.21",
"mysql": "2.18.1",
"passport-google-oauth2": "0.2.0",
"pg": "8.6.0",
"sqlite3": "5.0.2",
"strapi-middleware-views": "3.6.6"

View File

@ -13,24 +13,34 @@ export const Column = styled(Row)`
flex-direction: column;
`;
export const LayoutContent = ({ children }) => (
<Wrapper
shadow="tableShadow"
hasRadius
paddingTop="9"
paddingBottom="9"
paddingLeft="10"
paddingRight="10"
background="neutral0"
justifyContent="center"
>
{children}
</Wrapper>
);
LayoutContent.propTypes = {
children: PropTypes.node.isRequired,
};
const UnauthenticatedLayout = ({ children }) => {
return (
<div>
<Row as="header" justifyContent="flex-end">
<Box paddingTop={6} paddingRight={8}>
<LocaleToggle isLogged />
<LocaleToggle />
</Box>
</Row>
<Box paddingTop="11" paddingBottom="11">
<Wrapper
shadow="tableShadow"
hasRadius
padding="10"
background="neutral0"
justifyContent="center"
>
{children}
</Wrapper>
{children}
</Box>
</div>
);

View File

@ -11,13 +11,15 @@ import {
TextInput,
Main,
FieldAction,
Row,
Link,
} from '@strapi/parts';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { useIntl } from 'react-intl';
import { Formik } from 'formik';
import { Column } from '../../../../layouts/UnauthenticatedLayout';
import { Column, LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
import Form from './Form';
import Logo from '../Logo';
@ -35,101 +37,113 @@ const FieldActionWrapper = styled(FieldAction)`
}
`;
const Login = ({ onSubmit, schema }) => {
const Login = ({ onSubmit, schema, children }) => {
const [passwordShown, setPasswordShown] = useState(false);
const { formatMessage } = useIntl();
return (
<Main labelledBy="welcome">
<Formik
enableReinitialize
initialValues={{
email: '',
password: '',
}}
onSubmit={onSubmit}
validationSchema={schema}
validateOnChange={false}
>
{({ values, errors, handleChange }) => (
<Form noValidate>
<Column>
<Logo />
<Box paddingTop="6" paddingBottom="1">
<H1 id="welcome">{formatMessage({ id: 'Auth.form.welcome.title' })}</H1>
</Box>
<Box paddingBottom="7">
<Subtitle textColor="neutral600">
{formatMessage({ id: 'Auth.form.welcome.subtitle' })}
</Subtitle>
</Box>
{errors.errorMessage && (
<Text id="global-form-error" role="alert" tabIndex={-1} textColor="danger600">
{errors.errorMessage}
</Text>
)}
</Column>
<LayoutContent>
<Formik
enableReinitialize
initialValues={{
email: '',
password: '',
}}
onSubmit={onSubmit}
validationSchema={schema}
validateOnChange={false}
>
{({ values, errors, handleChange }) => (
<Form noValidate>
<Column>
<Logo />
<Box paddingTop="6" paddingBottom="1">
<H1 id="welcome">{formatMessage({ id: 'Auth.form.welcome.title' })}</H1>
</Box>
<Box paddingBottom="7">
<Subtitle textColor="neutral600">
{formatMessage({ id: 'Auth.form.welcome.subtitle' })}
</Subtitle>
</Box>
{errors.errorMessage && (
<Text id="global-form-error" role="alert" tabIndex={-1} textColor="danger600">
{errors.errorMessage}
</Text>
)}
</Column>
<Stack size={6}>
<TextInput
error={errors.email ? formatMessage({ id: errors.email }) : ''}
value={values.email}
onChange={handleChange}
label={formatMessage({ id: 'Auth.form.email.label' })}
placeholder={formatMessage({ id: 'Auth.form.email.placeholder' })}
name="email"
required
/>
<TextInput
error={errors.password ? formatMessage({ id: errors.password }) : ''}
onChange={handleChange}
value={values.password}
label={formatMessage({ id: 'Auth.form.password.label' })}
name="password"
type={passwordShown ? 'text' : 'password'}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
e.stopPropagation();
setPasswordShown(prev => !prev);
}}
label={formatMessage({
id: passwordShown
? 'Auth.form.password.show-password'
: 'Auth.form.password.hide-password',
})}
>
{passwordShown ? <Show /> : <Hide />}
</FieldActionWrapper>
}
required
/>
<Checkbox
onValueChange={checked => {
handleChange({ target: { value: checked, name: 'rememberMe' } });
}}
value={values.rememberMe}
name="rememberMe"
>
{formatMessage({ id: 'Auth.form.rememberMe.label' })}
</Checkbox>
<AuthButton type="submit">
{formatMessage({ id: 'Auth.form.button.login' })}
</AuthButton>
</Stack>
</Form>
)}
</Formik>
<Stack size={6}>
<TextInput
error={errors.email ? formatMessage({ id: errors.email }) : ''}
value={values.email}
onChange={handleChange}
label={formatMessage({ id: 'Auth.form.email.label' })}
placeholder={formatMessage({ id: 'Auth.form.email.placeholder' })}
name="email"
required
/>
<TextInput
error={errors.password ? formatMessage({ id: errors.password }) : ''}
onChange={handleChange}
value={values.password}
label={formatMessage({ id: 'Auth.form.password.label' })}
name="password"
type={passwordShown ? 'text' : 'password'}
endAction={
// eslint-disable-next-line react/jsx-wrap-multilines
<FieldActionWrapper
onClick={e => {
e.stopPropagation();
setPasswordShown(prev => !prev);
}}
label={formatMessage({
id: passwordShown
? 'Auth.form.password.show-password'
: 'Auth.form.password.hide-password',
})}
>
{passwordShown ? <Show /> : <Hide />}
</FieldActionWrapper>
}
required
/>
<Checkbox
onValueChange={checked => {
handleChange({ target: { value: checked, name: 'rememberMe' } });
}}
value={values.rememberMe}
name="rememberMe"
>
{formatMessage({ id: 'Auth.form.rememberMe.label' })}
</Checkbox>
<AuthButton type="submit">
{formatMessage({ id: 'Auth.form.button.login' })}
</AuthButton>
</Stack>
</Form>
)}
</Formik>
{children}
</LayoutContent>
<Row justifyContent="center">
<Box paddingTop={4}>
<Link to="/auth/forgot-password">
<Text small>{formatMessage({ id: 'Auth.link.forgot-password' })}</Text>
</Link>
</Box>
</Row>
</Main>
);
};
Login.defaultProps = {
children: null,
onSubmit: () => {},
};
Login.propTypes = {
children: PropTypes.node,
onSubmit: PropTypes.func,
schema: PropTypes.shape({
type: PropTypes.string.isRequired,

View File

@ -6,13 +6,7 @@ const FormWithFocus = props => {
useEffect(() => {
if (isSubmitting && !isValidating) {
const errorNames = Object.keys(touched).reduce((prev, key) => {
if (getIn(errors, key)) {
prev.push(key);
}
return prev;
}, []);
const errorNames = Object.keys(touched).filter(error => getIn(errors, error));
if (errorNames.length) {
let errorEl;

View File

@ -6,7 +6,7 @@
"Auth.form.button.login": "Login",
"Auth.form.button.login.providers.error": "We cannot connect you through the selected provider.",
"Auth.form.button.login.providers.see-more": "See more",
"Auth.form.button.login.strapi": "LOG IN VIA STRAPI",
"Auth.form.button.login.strapi": "Log in via Strapi",
"Auth.form.button.register": "LET'S START",
"Auth.form.button.reset-password": "Change password",
"Auth.form.confirmPassword.label": "Confirmation Password",
@ -47,6 +47,8 @@
"Auth.link.signin": "Sign in",
"Auth.link.signin.account": "Already have an account?",
"Auth.login.sso.divider": "Or login with",
"Auth.login.sso.loading": "Loading providers...",
"Auth.login.sso.subtitle": "Login to your account via SSO",
"Auth.privacy-policy-agreement.policy": "privacy policy",
"Auth.privacy-policy-agreement.terms": "terms",
"Content Manager": "Content Manager",

View File

@ -27,21 +27,22 @@ const Login = loginProps => {
return (
<UnauthenticatedLayout>
<BaseLogin {...loginProps} />
<Box paddingTop={7}>
<Stack size={7}>
<Row>
<DividerFull />
<Box paddingLeft={3} paddingRight={3}>
<TableLabel textColor="neutral600">
{formatMessage({ id: 'Auth.login.sso.divider' })}
</TableLabel>
</Box>
<DividerFull />
</Row>
<SSOProviders providers={providers} displayAllProviders={false} />
</Stack>
</Box>
<BaseLogin {...loginProps}>
<Box paddingTop={7}>
<Stack size={7}>
<Row>
<DividerFull />
<Box paddingLeft={3} paddingRight={3}>
<TableLabel textColor="neutral600">
{formatMessage({ id: 'Auth.login.sso.divider' })}
</TableLabel>
</Box>
<DividerFull />
</Row>
<SSOProviders providers={providers} displayAllProviders={false} />
</Stack>
</Box>
</BaseLogin>
</UnauthenticatedLayout>
);
};

View File

@ -84,7 +84,7 @@ const SSOProviders = ({ providers, displayAllProviders }) => {
})}
>
<SSOButton as={Link} to="/auth/providers">
<span aroa-hidden></span>
<span aria-hidden></span>
</SSOButton>
</Tooltip>
</GridItem>

View File

@ -1,18 +1,35 @@
import React from 'react';
import { Button, Flex, Padded, Separator } from '@buffetjs/core';
import { LoadingIndicator } from '@buffetjs/styles';
import { Redirect, useHistory } from 'react-router-dom';
import styled from 'styled-components';
import {
Divider,
Stack,
Row,
Box,
TableLabel,
Button,
Main,
Subtitle,
H1,
Link,
Text,
Loader,
} from '@strapi/parts';
import { useIntl } from 'react-intl';
import { BaselineAlignment } from '@strapi/helper-plugin';
import Box from '../../../../../../admin/src/pages/AuthPage/components/Box';
import Logo from '../../../../../../admin/src/pages/AuthPage/components/Logo';
import Section from '../../../../../../admin/src/pages/AuthPage/components/Section';
import ProviderButton from '../../../../components/ProviderButton';
import { useAuthProviders } from '../../../../hooks';
import UnauthenticatedLayout, {
Column,
LayoutContent,
} from '../../../../../../admin/src/layouts/UnauthenticatedLayout';
import SSOProviders from './SSOProviders';
import Logo from '../../../../../../admin/src/pages/AuthPage/components/Logo';
const ProviderWrapper = styled.div`
padding: 5px 4px;
const DividerFull = styled(Divider)`
flex: 1;
`;
const AuthButton = styled(Button)`
display: inline-block;
width: 100%;
`;
const Providers = () => {
@ -31,44 +48,49 @@ const Providers = () => {
}
return (
<>
<Section textAlign="center">
<Logo />
</Section>
<Section withBackground textAlign="center">
<BaselineAlignment top size="25px">
<Box withoutError>
<UnauthenticatedLayout>
<Main labelledBy="welcome">
<LayoutContent>
<Column>
<Logo />
<Box paddingTop="6" paddingBottom="1">
<H1 id="welcome">{formatMessage({ id: 'Auth.form.welcome.title' })}</H1>
</Box>
<Box paddingBottom="7">
<Subtitle textColor="neutral600">
{formatMessage({ id: 'Auth.login.sso.subtitle' })}
</Subtitle>
</Box>
</Column>
<Stack size={7}>
{isLoading ? (
<LoadingIndicator />
<Row justifyContent="center">
<Loader>{formatMessage({ id: 'Auth.login.sso.loading' })}</Loader>
</Row>
) : (
<Flex flexWrap="wrap">
{providers.map(provider => (
<ProviderWrapper key={provider.uid}>
<ProviderButton provider={provider} />
</ProviderWrapper>
))}
</Flex>
<SSOProviders providers={providers} />
)}
<Padded top bottom size="smd">
<BaselineAlignment top size="3px" />
<Separator
label={formatMessage({
id: 'or',
defaultMessage: 'OR',
})}
/>
<BaselineAlignment top size="7px" />
</Padded>
<Button style={{ width: '100%' }} onClick={handleClick} type="submit" color="secondary">
{formatMessage({
id: 'Auth.form.button.login.strapi',
defaultMessage: 'LOG IN VIA STRAPI',
})}
</Button>
<Row>
<DividerFull />
<Box paddingLeft={3} paddingRight={3}>
<TableLabel textColor="neutral600">{formatMessage({ id: 'or' })}</TableLabel>
</Box>
<DividerFull />
</Row>
<AuthButton onClick={handleClick}>
{formatMessage({ id: 'Auth.form.button.login.strapi' })}
</AuthButton>
</Stack>
</LayoutContent>
<Row justifyContent="center">
<Box paddingTop={4}>
<Link to="/auth/forgot-password">
<Text small>{formatMessage({ id: 'Auth.link.forgot-password' })}</Text>
</Link>
</Box>
</BaselineAlignment>
</Section>
</>
</Row>
</Main>
</UnauthenticatedLayout>
);
};

View File

@ -5558,6 +5558,11 @@ base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
base64url@3.x.x:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@ -15115,6 +15120,11 @@ oauth-sign@^0.9.0, oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -15813,6 +15823,13 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
passport-google-oauth2@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.2.0.tgz#fc9ea59e7091f02e24fd16d6be9257ea982ebbc3"
integrity sha512-62EdPtbfVdc55nIXi0p1WOa/fFMM8v/M8uQGnbcXA4OexZWCnfsEi3wo2buag+Is5oqpuHzOtI64JpHk0Xi5RQ==
dependencies:
passport-oauth2 "^1.1.2"
passport-local@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
@ -15820,6 +15837,17 @@ passport-local@1.0.0:
dependencies:
passport-strategy "1.x.x"
passport-oauth2@^1.1.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.6.0.tgz#5f599735e0ea40ea3027643785f81a3a9b4feb50"
integrity sha512-emXPLqLcVEcLFR/QvQXZcwLmfK8e9CqvMgmOFJxcNT3okSFMtUbRRKpY20x5euD+01uHsjjCa07DYboEeLXYiw==
dependencies:
base64url "3.x.x"
oauth "0.9.x"
passport-strategy "1.x.x"
uid2 "0.0.x"
utils-merge "1.x.x"
passport-strategy@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
@ -20800,6 +20828,11 @@ uid-number@0.0.6:
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
uid2@0.0.x:
version "0.0.3"
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
integrity sha1-SDEm4Rd03y9xuLY53NeZw3YWK4I=
umask@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
@ -21139,7 +21172,7 @@ utila@~0.4:
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
utils-merge@1.0.1:
utils-merge@1.0.1, utils-merge@1.x.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=