mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 19:27:52 +01:00
Add default OAuths (#259)
This commit is contained in:
committed by
GitHub
parent
0828c9c879
commit
653e139a9e
@@ -3,11 +3,11 @@ import I18n from 'i18n-js';
|
||||
|
||||
import { IOAuth } from '../../../interfaces/IOAuth';
|
||||
import Switch from '../../common/Switch';
|
||||
import Separator from '../../common/Separator';
|
||||
import { AuthenticationPages } from './AuthenticationSiteSettingsP';
|
||||
import CopyToClipboardButton from '../../common/CopyToClipboardButton';
|
||||
import ActionLink from '../../common/ActionLink';
|
||||
import { DeleteIcon, EditIcon, TestIcon } from '../../common/Icons';
|
||||
import { MutedText } from '../../common/CustomTexts';
|
||||
|
||||
interface Props {
|
||||
oAuth: IOAuth;
|
||||
@@ -30,52 +30,60 @@ const OAuthProviderItem = ({
|
||||
|
||||
<div className="oAuthNameAndEnabled">
|
||||
<span className="oAuthName">{oAuth.name}</span>
|
||||
<div className="oAuthIsEnabled">
|
||||
<Switch
|
||||
label={I18n.t(`common.${oAuth.isEnabled ? 'enabled' : 'disabled'}`)}
|
||||
onClick={() => handleToggleEnabledOAuth(oAuth.id, !oAuth.isEnabled)}
|
||||
checked={oAuth.isEnabled}
|
||||
htmlId={`oAuth${oAuth.name}EnabledSwitch`}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
oAuth.tenantId ?
|
||||
<div className="oAuthIsEnabled">
|
||||
<Switch
|
||||
label={I18n.t(`common.${oAuth.isEnabled ? 'enabled' : 'disabled'}`)}
|
||||
onClick={() => handleToggleEnabledOAuth(oAuth.id, !oAuth.isEnabled)}
|
||||
checked={oAuth.isEnabled}
|
||||
htmlId={`oAuth${oAuth.name}EnabledSwitch`}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div><MutedText>{I18n.t('site_settings.authentication.default_oauth')}</MutedText></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="oAuthActions">
|
||||
<CopyToClipboardButton
|
||||
label={I18n.t('site_settings.authentication.copy_url')}
|
||||
textToCopy={oAuth.callbackUrl}
|
||||
/>
|
||||
|
||||
<ActionLink
|
||||
onClick={() =>
|
||||
window.open(`/o_auths/${oAuth.id}/start?reason=test`, '', 'width=640, height=640')
|
||||
}
|
||||
icon={<TestIcon />}
|
||||
customClass='testAction'
|
||||
>
|
||||
{I18n.t('common.buttons.test')}
|
||||
</ActionLink>
|
||||
|
||||
<ActionLink
|
||||
onClick={() => {
|
||||
setSelectedOAuth(oAuth.id);
|
||||
setPage('edit');
|
||||
}}
|
||||
icon={<EditIcon />}
|
||||
customClass='editAction'
|
||||
>
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</ActionLink>
|
||||
|
||||
<ActionLink
|
||||
onClick={() => confirm(I18n.t('common.confirmation')) && handleDeleteOAuth(oAuth.id)}
|
||||
icon={<DeleteIcon />}
|
||||
customClass='deleteAction'
|
||||
>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</ActionLink>
|
||||
</div>
|
||||
{
|
||||
oAuth.tenantId &&
|
||||
<div className="oAuthActions">
|
||||
<CopyToClipboardButton
|
||||
label={I18n.t('site_settings.authentication.copy_url')}
|
||||
textToCopy={oAuth.callbackUrl}
|
||||
/>
|
||||
|
||||
<ActionLink
|
||||
onClick={() =>
|
||||
window.open(`/o_auths/${oAuth.id}/start?reason=test`, '', 'width=640, height=640')
|
||||
}
|
||||
icon={<TestIcon />}
|
||||
customClass='testAction'
|
||||
>
|
||||
{I18n.t('common.buttons.test')}
|
||||
</ActionLink>
|
||||
|
||||
<ActionLink
|
||||
onClick={() => {
|
||||
setSelectedOAuth(oAuth.id);
|
||||
setPage('edit');
|
||||
}}
|
||||
icon={<EditIcon />}
|
||||
customClass='editAction'
|
||||
>
|
||||
{I18n.t('common.buttons.edit')}
|
||||
</ActionLink>
|
||||
|
||||
<ActionLink
|
||||
onClick={() => confirm(I18n.t('common.confirmation')) && handleDeleteOAuth(oAuth.id)}
|
||||
icon={<DeleteIcon />}
|
||||
customClass='deleteAction'
|
||||
>
|
||||
{I18n.t('common.buttons.delete')}
|
||||
</ActionLink>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
);
|
||||
|
||||
|
||||
@@ -47,9 +47,13 @@ const TenantSignUpForm = ({
|
||||
<input
|
||||
{...register('subdomain', {
|
||||
required: true,
|
||||
validate: async (newSubdomain) => {
|
||||
const res = await fetch(`/is_available?new_subdomain=${newSubdomain}`);
|
||||
return res.status === HttpStatus.OK;
|
||||
pattern: /^[a-zA-Z0-9-]+$/,
|
||||
validate: {
|
||||
noSpaces: (value) => !/\s/.test(value),
|
||||
notAlreadyTaken: async (newSubdomain) => {
|
||||
const res = await fetch(`/is_available?new_subdomain=${newSubdomain}`);
|
||||
return res.status === HttpStatus.OK;
|
||||
},
|
||||
},
|
||||
})}
|
||||
placeholder={getLabel('tenant', 'subdomain')}
|
||||
@@ -64,7 +68,10 @@ const TenantSignUpForm = ({
|
||||
{errors.subdomain?.type === 'required' && getValidationMessage('required', 'tenant', 'subdomain')}
|
||||
</DangerText>
|
||||
<DangerText>
|
||||
{errors.subdomain?.type === 'validate' && I18n.t('signup.step2.validations.subdomain_already_taken')}
|
||||
{errors.subdomain?.type === 'pattern' && I18n.t('signup.step2.validations.subdomain_only_letters_and_numbers')}
|
||||
</DangerText>
|
||||
<DangerText>
|
||||
{errors.subdomain?.type === 'notAlreadyTaken' && I18n.t('signup.step2.validations.subdomain_already_taken')}
|
||||
</DangerText>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,8 +5,14 @@ import ConfirmSignUpPage from './ConfirmSignUpPage';
|
||||
|
||||
import TenantSignUpForm from './TenantSignUpForm';
|
||||
import UserSignUpForm from './UserSignUpForm';
|
||||
import { IOAuth } from '../../interfaces/IOAuth';
|
||||
|
||||
interface Props {
|
||||
oAuthLoginCompleted: boolean;
|
||||
oauthUserEmail?: string;
|
||||
oauthUserName?: string;
|
||||
oAuths: Array<IOAuth>;
|
||||
|
||||
isSubmitting: boolean;
|
||||
error: string;
|
||||
|
||||
@@ -16,9 +22,11 @@ interface Props {
|
||||
userPassword: string,
|
||||
siteName: string,
|
||||
subdomain: string,
|
||||
isOAuthLogin: boolean,
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
|
||||
baseUrl: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
@@ -35,9 +43,14 @@ export interface ITenantSignUpTenantForm {
|
||||
}
|
||||
|
||||
const TenantSignUpP = ({
|
||||
oAuths,
|
||||
oAuthLoginCompleted,
|
||||
oauthUserEmail,
|
||||
oauthUserName,
|
||||
isSubmitting,
|
||||
error,
|
||||
handleSubmit,
|
||||
baseUrl,
|
||||
authenticityToken
|
||||
}: Props) => {
|
||||
const [userData, setUserData] = useState({
|
||||
@@ -52,19 +65,27 @@ const TenantSignUpP = ({
|
||||
subdomain: '',
|
||||
});
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [currentStep, setCurrentStep] = useState(oAuthLoginCompleted ? 2 : 1);
|
||||
|
||||
const [emailAuth, setEmailAuth] = useState(false);
|
||||
|
||||
const handleSignUpSubmit = (siteName: string, subdomain: string) => {
|
||||
handleSubmit(
|
||||
userData.fullName,
|
||||
userData.email,
|
||||
oAuthLoginCompleted ? oauthUserName : userData.fullName,
|
||||
oAuthLoginCompleted ? oauthUserEmail : userData.email,
|
||||
userData.password,
|
||||
siteName,
|
||||
subdomain,
|
||||
oAuthLoginCompleted,
|
||||
authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.Created) return;
|
||||
if (oAuthLoginCompleted) {
|
||||
let redirectUrl = new URL(baseUrl);
|
||||
redirectUrl.hostname = `${subdomain}.${redirectUrl.hostname}`;
|
||||
window.location.href = `${redirectUrl.toString()}users/sign_in`;
|
||||
return;
|
||||
}
|
||||
|
||||
setTenantData({ siteName, subdomain });
|
||||
setCurrentStep(currentStep + 1);
|
||||
@@ -80,6 +101,10 @@ const TenantSignUpP = ({
|
||||
setCurrentStep={setCurrentStep}
|
||||
emailAuth={emailAuth}
|
||||
setEmailAuth={setEmailAuth}
|
||||
oAuths={oAuths}
|
||||
oAuthLoginCompleted={oAuthLoginCompleted}
|
||||
oauthUserEmail={oauthUserEmail}
|
||||
oauthUserName={oauthUserName}
|
||||
userData={userData}
|
||||
setUserData={setUserData}
|
||||
/>
|
||||
|
||||
@@ -4,16 +4,24 @@ import I18n from 'i18n-js';
|
||||
|
||||
import Box from '../common/Box';
|
||||
import Button from '../common/Button';
|
||||
import OAuthProviderLink from '../common/OAuthProviderLink';
|
||||
import { ITenantSignUpUserForm } from './TenantSignUpP';
|
||||
import { DangerText } from '../common/CustomTexts';
|
||||
import { getLabel, getValidationMessage } from '../../helpers/formUtils';
|
||||
import { EMAIL_REGEX } from '../../constants/regex';
|
||||
import { IOAuth } from '../../interfaces/IOAuth';
|
||||
import ActionLink from '../common/ActionLink';
|
||||
import { BackIcon } from '../common/Icons';
|
||||
|
||||
interface Props {
|
||||
currentStep: number;
|
||||
setCurrentStep(step: number): void;
|
||||
emailAuth: boolean;
|
||||
setEmailAuth(enabled: boolean): void;
|
||||
oAuths: Array<IOAuth>;
|
||||
oAuthLoginCompleted: boolean;
|
||||
oauthUserEmail?: string;
|
||||
oauthUserName?: string;
|
||||
userData: ITenantSignUpUserForm;
|
||||
setUserData({}: ITenantSignUpUserForm): void;
|
||||
}
|
||||
@@ -23,6 +31,10 @@ const UserSignUpForm = ({
|
||||
setCurrentStep,
|
||||
emailAuth,
|
||||
setEmailAuth,
|
||||
oAuths,
|
||||
oAuthLoginCompleted,
|
||||
oauthUserEmail,
|
||||
oauthUserName,
|
||||
userData,
|
||||
setUserData,
|
||||
}: Props) => {
|
||||
@@ -49,14 +61,37 @@ const UserSignUpForm = ({
|
||||
|
||||
{
|
||||
currentStep === 1 && !emailAuth &&
|
||||
<>
|
||||
<Button className="emailAuth" onClick={() => setEmailAuth(true)}>
|
||||
{ I18n.t('signup.step1.email_auth') }
|
||||
</Button>
|
||||
|
||||
{
|
||||
oAuths.filter(oAuth => oAuth.isEnabled).map((oAuth, i) =>
|
||||
<OAuthProviderLink
|
||||
oAuthId={oAuth.id}
|
||||
oAuthName={oAuth.name}
|
||||
oAuthLogo={oAuth.logo}
|
||||
oAuthReason='tenantsignup'
|
||||
isSignUp
|
||||
key={i}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
currentStep === 1 && emailAuth &&
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<ActionLink
|
||||
onClick={() => setEmailAuth(false)}
|
||||
icon={<BackIcon />}
|
||||
customClass="backButton"
|
||||
>
|
||||
{I18n.t('common.buttons.back')}
|
||||
</ActionLink>
|
||||
|
||||
<div className="formRow">
|
||||
<input
|
||||
{...register('fullName', { required: true, minLength: 2 })}
|
||||
@@ -116,9 +151,14 @@ const UserSignUpForm = ({
|
||||
}
|
||||
|
||||
{
|
||||
currentStep === 2 &&
|
||||
currentStep === 2 && !oAuthLoginCompleted &&
|
||||
<p><b>{userData.fullName}</b> ({userData.email})</p>
|
||||
}
|
||||
|
||||
{
|
||||
currentStep === 2 && oAuthLoginCompleted &&
|
||||
<p><b>{oauthUserName}</b> ({oauthUserEmail})</p>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
|
||||
import createStoreHelper from '../../helpers/createStore';
|
||||
|
||||
import TenantSignUp from '../../containers/TenantSignUp';
|
||||
|
||||
import { Store } from 'redux';
|
||||
import { State } from '../../reducers/rootReducer';
|
||||
import { IOAuthJSON, oAuthJSON2JS } from '../../interfaces/IOAuth';
|
||||
|
||||
interface Props {
|
||||
oAuths: Array<IOAuthJSON>;
|
||||
oAuthLoginCompleted: boolean;
|
||||
oauthUserEmail?: string;
|
||||
oauthUserName?: string;
|
||||
baseUrl: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
@@ -22,11 +26,23 @@ class TenantSignUpRoot extends React.Component<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { authenticityToken } = this.props;
|
||||
const {
|
||||
oAuths,
|
||||
oAuthLoginCompleted,
|
||||
oauthUserEmail,
|
||||
oauthUserName,
|
||||
baseUrl,
|
||||
authenticityToken,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<TenantSignUp
|
||||
oAuthLoginCompleted={oAuthLoginCompleted}
|
||||
oauthUserEmail={oauthUserEmail}
|
||||
oauthUserName={oauthUserName}
|
||||
oAuths={oAuths.map(oAuth => oAuthJSON2JS(oAuth))}
|
||||
baseUrl={baseUrl}
|
||||
authenticityToken={authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -21,6 +21,10 @@ export const MutedText = ({ children }: Props) => (
|
||||
<span className="mutedText">{children}</span>
|
||||
);
|
||||
|
||||
export const CenteredText = ({ children }: Props) => (
|
||||
<p className="centeredText">{children}</p>
|
||||
);
|
||||
|
||||
export const CenteredMutedText = ({ children }: Props) => (
|
||||
<p className="centeredText"><span className="mutedText">{children}</span></p>
|
||||
);
|
||||
|
||||
29
app/javascript/components/common/OAuthProviderLink.tsx
Normal file
29
app/javascript/components/common/OAuthProviderLink.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
interface Props {
|
||||
oAuthId: number;
|
||||
oAuthName: string;
|
||||
oAuthLogo?: string;
|
||||
oAuthReason: string;
|
||||
isSignUp?: boolean;
|
||||
}
|
||||
|
||||
const OAuthProviderLink = ({ oAuthId, oAuthName, oAuthLogo, oAuthReason, isSignUp = false }: Props) => (
|
||||
<button
|
||||
onClick={() => window.location.href = `/o_auths/${oAuthId}/start?reason=${oAuthReason}`}
|
||||
className={`oauthProviderBtn oauthProvider${oAuthName.replace(' ', '')}`}
|
||||
>
|
||||
<img src={oAuthLogo} alt={oAuthName} width={28} height={28} />
|
||||
<span className='oauthProviderText'>
|
||||
{
|
||||
isSignUp ?
|
||||
I18n.t('common.forms.auth.sign_up_with', { o_auth: oAuthName })
|
||||
:
|
||||
I18n.t('common.forms.auth.log_in_with', { o_auth: oAuthName })
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
export default OAuthProviderLink;
|
||||
Reference in New Issue
Block a user