Skip to content

Commit

Permalink
D gamer007/add microsoft oauth (#5103)
Browse files Browse the repository at this point in the history
Need to create a new branch because original branch name is `main` and
we cannot push additional commits
Linked to #4718


![image](https://github.com/twentyhq/twenty/assets/29927851/52b220e7-770a-4ffe-b6e9-468605c2b8fa)

![image](https://github.com/twentyhq/twenty/assets/29927851/7a7a4737-f09f-4d9b-8962-5a9b8c71edc1)

---------

Co-authored-by: DGamer007 <prajapatidhruv266@gmail.com>
  • Loading branch information
martmull and DGamer007 authored Apr 24, 2024
1 parent b3e1d6b commit 87a9ece
Show file tree
Hide file tree
Showing 25 changed files with 458 additions and 129 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@types/lodash.pick": "^4.3.7",
"@types/mailparser": "^3.4.4",
"@types/nodemailer": "^6.4.14",
"@types/passport-microsoft": "^1.0.3",
"add": "^2.0.6",
"afterframe": "^1.0.2",
"apollo-server-express": "^3.12.0",
Expand Down Expand Up @@ -136,6 +137,7 @@
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"passport-microsoft": "^2.0.0",
"patch-package": "^8.0.0",
"pg": "^8.11.3",
"pg-boss": "^9.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,16 @@ import TabItem from '@theme/TabItem';
['MESSAGING_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Gmail API connection'],
['CALENDAR_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Google Calendar API connection'],
['AUTH_GOOGLE_APIS_CALLBACK_URL', '', 'Google APIs auth callback'],
['AUTH_GOOGLE_ENABLED', 'false', 'Enable Goole SSO login'],
['AUTH_PASSWORD_ENABLED', 'false', 'Enable Email/Password login'],
['AUTH_GOOGLE_ENABLED', 'false', 'Enable Google SSO login'],
['AUTH_GOOGLE_CLIENT_ID', '', 'Google client ID'],
['AUTH_GOOGLE_CLIENT_SECRET', '', 'Google client secret'],
['AUTH_GOOGLE_CALLBACK_URL', '', 'Google auth callback'],
['AUTH_MICROSOFT_ENABLED', 'false', 'Enable Microsoft SSO login'],
['AUTH_MICROSOFT_CLIENT_ID', '', 'Microsoft client ID'],
['AUTH_MICROSOFT_TENANT_ID', '', 'Microsoft tenant ID'],
['AUTH_MICROSOFT_CLIENT_SECRET', '', 'Microsoft client secret'],
['AUTH_MICROSOFT_CALLBACK_URL', '', 'Microsoft auth callback'],
['FRONT_AUTH_CALLBACK_URL', 'http://localhost:3001/verify ', 'Callback used for Login page'],
['IS_SIGN_UP_DISABLED', 'false', 'Disable sign-up'],
['PASSWORD_RESET_TOKEN_EXPIRES_IN', '5m', 'Password reset token expiration time'],
Expand Down
5 changes: 4 additions & 1 deletion packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type AuthProviders = {
__typename?: 'AuthProviders';
google: Scalars['Boolean'];
magicLink: Scalars['Boolean'];
microsoft: Scalars['Boolean'];
password: Scalars['Boolean'];
};

Expand Down Expand Up @@ -562,6 +563,7 @@ export type RemoteServer = {

export type RemoteTable = {
__typename?: 'RemoteTable';
id?: Maybe<Scalars['UUID']>;
name: Scalars['String'];
schema: Scalars['String'];
status: RemoteTableStatus;
Expand Down Expand Up @@ -1111,7 +1113,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;


export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null } } };
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null } } };

export type UploadFileMutationVariables = Exact<{
file: Scalars['Upload'];
Expand Down Expand Up @@ -2140,6 +2142,7 @@ export const GetClientConfigDocument = gql`
authProviders {
google
password
microsoft
}
billing {
isBillingEnabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,9 @@ describe('useAuth', () => {
expect(state.icons).toEqual({});
expect(state.authProviders).toEqual({
google: false,
microsoft: false,
magicLink: false,
password: true,
password: false,
});
expect(state.billing).toBeNull();
expect(state.isSignInPrefilled).toBe(false);
Expand Down
9 changes: 9 additions & 0 deletions packages/twenty-front/src/modules/auth/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ export const useAuth = () => {
}` || '';
}, []);

const handleMicrosoftLogin = useCallback((workspaceInviteHash?: string) => {
const authServerUrl = REACT_APP_SERVER_BASE_URL;
window.location.href =
`${authServerUrl}/auth/microsoft/${
workspaceInviteHash ? '?inviteHash=' + workspaceInviteHash : ''
}` || '';
}, []);

return {
challenge: handleChallenge,
verify: handleVerify,
Expand All @@ -244,5 +252,6 @@ export const useAuth = () => {
signUpWithCredentials: handleCredentialsSignUp,
signInWithCredentials: handleCrendentialsSignIn,
signInWithGoogle: handleGoogleLogin,
signInWithMicrosoft: handleMicrosoftLogin,
};
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { JSX } from 'react';
import styled from '@emotion/styled';

const StyledSeparator = styled.div`
type HorizontalSeparatorProps = {
visible?: boolean;
};
const StyledSeparator = styled.div<HorizontalSeparatorProps>`
background-color: ${({ theme }) => theme.border.color.medium};
height: 1px;
height: ${({ visible }) => (visible ? '1px' : 0)};
margin-bottom: ${({ theme }) => theme.spacing(3)};
margin-top: ${({ theme }) => theme.spacing(3)};
width: 100%;
`;

export const HorizontalSeparator = (): JSX.Element => <StyledSeparator />;
export const HorizontalSeparator = ({
visible = true,
}: HorizontalSeparatorProps): JSX.Element => (
<StyledSeparator visible={visible} />
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { useRecoilState } from 'recoil';
import { IconGoogle } from 'twenty-ui';
import { IconGoogle, IconMicrosoft } from 'twenty-ui';

import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft';
import { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { Loader } from '@/ui/feedback/loader/components/Loader';
Expand All @@ -27,7 +28,6 @@ import { HorizontalSeparator } from './HorizontalSeparator';
const StyledContentContainer = styled.div`
margin-bottom: ${({ theme }) => theme.spacing(8)};
margin-top: ${({ theme }) => theme.spacing(4)};
width: 200px;
`;

const StyledForm = styled.form`
Expand All @@ -51,6 +51,7 @@ export const SignInUpForm = () => {
const { handleResetPassword } = useHandleResetPassword();
const workspace = useWorkspaceFromInviteHash();
const { signInWithGoogle } = useSignInWithGoogle();
const { signInWithMicrosoft } = useSignInWithMicrosoft();
const { form } = useSignInUpForm();

const {
Expand Down Expand Up @@ -125,119 +126,133 @@ export const SignInUpForm = () => {
onClick={signInWithGoogle}
fullWidth
/>
<HorizontalSeparator />
<HorizontalSeparator visible={!authProviders.microsoft} />
</>
)}

<StyledForm
onSubmit={(event) => {
event.preventDefault();
}}
>
{signInUpStep !== SignInUpStep.Init && (
<StyledFullWidthMotionDiv
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
transition={{
type: 'spring',
stiffness: 800,
damping: 35,
}}
>
<Controller
name="email"
control={form.control}
render={({
field: { onChange, onBlur, value },
fieldState: { error },
}) => (
<StyledInputContainer>
<TextInput
autoFocus
value={value}
placeholder="Email"
onBlur={onBlur}
onChange={(value: string) => {
onChange(value);
if (signInUpStep === SignInUpStep.Password) {
continueWithEmail();
}
}}
error={showErrors ? error?.message : undefined}
fullWidth
disableHotkeys
onKeyDown={handleKeyDown}
/>
</StyledInputContainer>
)}
/>
</StyledFullWidthMotionDiv>
)}
{signInUpStep === SignInUpStep.Password && (
<StyledFullWidthMotionDiv
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
transition={{
type: 'spring',
stiffness: 800,
damping: 35,
{authProviders.microsoft && (
<>
<MainButton
Icon={() => <IconMicrosoft size={theme.icon.size.lg} />}
title="Continue with Microsoft"
onClick={signInWithMicrosoft}
fullWidth
/>
<HorizontalSeparator visible={authProviders.password} />
</>
)}

{authProviders.password && (
<StyledForm
onSubmit={(event) => {
event.preventDefault();
}}
>
{signInUpStep !== SignInUpStep.Init && (
<StyledFullWidthMotionDiv
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
transition={{
type: 'spring',
stiffness: 800,
damping: 35,
}}
>
<Controller
name="email"
control={form.control}
render={({
field: { onChange, onBlur, value },
fieldState: { error },
}) => (
<StyledInputContainer>
<TextInput
autoFocus
value={value}
placeholder="Email"
onBlur={onBlur}
onChange={(value: string) => {
onChange(value);
if (signInUpStep === SignInUpStep.Password) {
continueWithEmail();
}
}}
error={showErrors ? error?.message : undefined}
fullWidth
disableHotkeys
onKeyDown={handleKeyDown}
/>
</StyledInputContainer>
)}
/>
</StyledFullWidthMotionDiv>
)}
{signInUpStep === SignInUpStep.Password && (
<StyledFullWidthMotionDiv
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
transition={{
type: 'spring',
stiffness: 800,
damping: 35,
}}
>
<Controller
name="password"
control={form.control}
render={({
field: { onChange, onBlur, value },
fieldState: { error },
}) => (
<StyledInputContainer>
<TextInput
autoFocus
value={value}
type="password"
placeholder="Password"
onBlur={onBlur}
onChange={onChange}
error={showErrors ? error?.message : undefined}
fullWidth
disableHotkeys
onKeyDown={handleKeyDown}
/>
</StyledInputContainer>
)}
/>
</StyledFullWidthMotionDiv>
)}

<MainButton
variant="secondary"
title={buttonTitle}
type="submit"
onClick={() => {
if (signInUpStep === SignInUpStep.Init) {
continueWithEmail();
return;
}
if (signInUpStep === SignInUpStep.Email) {
continueWithCredentials();
return;
}
setShowErrors(true);
form.handleSubmit(submitCredentials)();
}}
>
<Controller
name="password"
control={form.control}
render={({
field: { onChange, onBlur, value },
fieldState: { error },
}) => (
<StyledInputContainer>
<TextInput
autoFocus
value={value}
type="password"
placeholder="Password"
onBlur={onBlur}
onChange={onChange}
error={showErrors ? error?.message : undefined}
fullWidth
disableHotkeys
onKeyDown={handleKeyDown}
/>
</StyledInputContainer>
)}
/>
</StyledFullWidthMotionDiv>
)}

<MainButton
variant="secondary"
title={buttonTitle}
type="submit"
onClick={() => {
if (signInUpStep === SignInUpStep.Init) {
continueWithEmail();
return;
}
if (signInUpStep === SignInUpStep.Email) {
continueWithCredentials();
return;
Icon={() => form.formState.isSubmitting && <Loader />}
disabled={
signInUpStep === SignInUpStep.Init
? false
: signInUpStep === SignInUpStep.Email
? !form.watch('email')
: !form.watch('email') ||
!form.watch('password') ||
form.formState.isSubmitting
}
setShowErrors(true);
form.handleSubmit(submitCredentials)();
}}
Icon={() => form.formState.isSubmitting && <Loader />}
disabled={
signInUpStep === SignInUpStep.Init
? false
: signInUpStep === SignInUpStep.Email
? !form.watch('email')
: !form.watch('email') ||
!form.watch('password') ||
form.formState.isSubmitting
}
fullWidth
/>
</StyledForm>
fullWidth
/>
</StyledForm>
)}
</StyledContentContainer>
{signInUpStep === SignInUpStep.Password && (
<ActionLink onClick={handleResetPassword(form.getValues('email'))}>
Expand Down
Loading

0 comments on commit 87a9ece

Please sign in to comment.