mirror of
https://github.com/twentyhq/twenty.git
synced 2024-09-11 23:37:21 +03:00
D gamer007/add microsoft oauth (#5103)
Need to create a new branch because original branch name is `main` and we cannot push additional commits Linked to https://github.com/twentyhq/twenty/pull/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>
This commit is contained in:
parent
b3e1d6becf
commit
87a9ecee28
@ -61,6 +61,7 @@
|
|||||||
"@types/lodash.pick": "^4.3.7",
|
"@types/lodash.pick": "^4.3.7",
|
||||||
"@types/mailparser": "^3.4.4",
|
"@types/mailparser": "^3.4.4",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
|
"@types/passport-microsoft": "^1.0.3",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"afterframe": "^1.0.2",
|
"afterframe": "^1.0.2",
|
||||||
"apollo-server-express": "^3.12.0",
|
"apollo-server-express": "^3.12.0",
|
||||||
@ -136,6 +137,7 @@
|
|||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"passport-microsoft": "^2.0.0",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pg-boss": "^9.0.3",
|
"pg-boss": "^9.0.3",
|
||||||
|
@ -70,10 +70,16 @@ import TabItem from '@theme/TabItem';
|
|||||||
['MESSAGING_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Gmail API connection'],
|
['MESSAGING_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Gmail API connection'],
|
||||||
['CALENDAR_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Google Calendar API connection'],
|
['CALENDAR_PROVIDER_GMAIL_ENABLED', 'false', 'Enable Google Calendar API connection'],
|
||||||
['AUTH_GOOGLE_APIS_CALLBACK_URL', '', 'Google APIs auth callback'],
|
['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_ID', '', 'Google client ID'],
|
||||||
['AUTH_GOOGLE_CLIENT_SECRET', '', 'Google client secret'],
|
['AUTH_GOOGLE_CLIENT_SECRET', '', 'Google client secret'],
|
||||||
['AUTH_GOOGLE_CALLBACK_URL', '', 'Google auth callback'],
|
['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'],
|
['FRONT_AUTH_CALLBACK_URL', 'http://localhost:3001/verify ', 'Callback used for Login page'],
|
||||||
['IS_SIGN_UP_DISABLED', 'false', 'Disable sign-up'],
|
['IS_SIGN_UP_DISABLED', 'false', 'Disable sign-up'],
|
||||||
['PASSWORD_RESET_TOKEN_EXPIRES_IN', '5m', 'Password reset token expiration time'],
|
['PASSWORD_RESET_TOKEN_EXPIRES_IN', '5m', 'Password reset token expiration time'],
|
||||||
|
@ -56,6 +56,7 @@ export type AuthProviders = {
|
|||||||
__typename?: 'AuthProviders';
|
__typename?: 'AuthProviders';
|
||||||
google: Scalars['Boolean'];
|
google: Scalars['Boolean'];
|
||||||
magicLink: Scalars['Boolean'];
|
magicLink: Scalars['Boolean'];
|
||||||
|
microsoft: Scalars['Boolean'];
|
||||||
password: Scalars['Boolean'];
|
password: Scalars['Boolean'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -562,6 +563,7 @@ export type RemoteServer = {
|
|||||||
|
|
||||||
export type RemoteTable = {
|
export type RemoteTable = {
|
||||||
__typename?: 'RemoteTable';
|
__typename?: 'RemoteTable';
|
||||||
|
id?: Maybe<Scalars['UUID']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
schema: Scalars['String'];
|
schema: Scalars['String'];
|
||||||
status: RemoteTableStatus;
|
status: RemoteTableStatus;
|
||||||
@ -1111,7 +1113,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
|
|||||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
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<{
|
export type UploadFileMutationVariables = Exact<{
|
||||||
file: Scalars['Upload'];
|
file: Scalars['Upload'];
|
||||||
@ -2140,6 +2142,7 @@ export const GetClientConfigDocument = gql`
|
|||||||
authProviders {
|
authProviders {
|
||||||
google
|
google
|
||||||
password
|
password
|
||||||
|
microsoft
|
||||||
}
|
}
|
||||||
billing {
|
billing {
|
||||||
isBillingEnabled
|
isBillingEnabled
|
||||||
|
@ -116,8 +116,9 @@ describe('useAuth', () => {
|
|||||||
expect(state.icons).toEqual({});
|
expect(state.icons).toEqual({});
|
||||||
expect(state.authProviders).toEqual({
|
expect(state.authProviders).toEqual({
|
||||||
google: false,
|
google: false,
|
||||||
|
microsoft: false,
|
||||||
magicLink: false,
|
magicLink: false,
|
||||||
password: true,
|
password: false,
|
||||||
});
|
});
|
||||||
expect(state.billing).toBeNull();
|
expect(state.billing).toBeNull();
|
||||||
expect(state.isSignInPrefilled).toBe(false);
|
expect(state.isSignInPrefilled).toBe(false);
|
||||||
|
@ -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 {
|
return {
|
||||||
challenge: handleChallenge,
|
challenge: handleChallenge,
|
||||||
verify: handleVerify,
|
verify: handleVerify,
|
||||||
@ -244,5 +252,6 @@ export const useAuth = () => {
|
|||||||
signUpWithCredentials: handleCredentialsSignUp,
|
signUpWithCredentials: handleCredentialsSignUp,
|
||||||
signInWithCredentials: handleCrendentialsSignIn,
|
signInWithCredentials: handleCrendentialsSignIn,
|
||||||
signInWithGoogle: handleGoogleLogin,
|
signInWithGoogle: handleGoogleLogin,
|
||||||
|
signInWithMicrosoft: handleMicrosoftLogin,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
import { JSX } from 'react';
|
import { JSX } from 'react';
|
||||||
import styled from '@emotion/styled';
|
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};
|
background-color: ${({ theme }) => theme.border.color.medium};
|
||||||
height: 1px;
|
height: ${({ visible }) => (visible ? '1px' : 0)};
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(3)};
|
margin-bottom: ${({ theme }) => theme.spacing(3)};
|
||||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const HorizontalSeparator = (): JSX.Element => <StyledSeparator />;
|
export const HorizontalSeparator = ({
|
||||||
|
visible = true,
|
||||||
|
}: HorizontalSeparatorProps): JSX.Element => (
|
||||||
|
<StyledSeparator visible={visible} />
|
||||||
|
);
|
||||||
|
@ -4,11 +4,12 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useRecoilState } from 'recoil';
|
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 { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
|
||||||
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
import { useSignInUpForm } from '@/auth/sign-in-up/hooks/useSignInUpForm';
|
||||||
import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle';
|
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 { useWorkspaceFromInviteHash } from '@/auth/sign-in-up/hooks/useWorkspaceFromInviteHash';
|
||||||
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
import { authProvidersState } from '@/client-config/states/authProvidersState';
|
||||||
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
import { Loader } from '@/ui/feedback/loader/components/Loader';
|
||||||
@ -27,7 +28,6 @@ import { HorizontalSeparator } from './HorizontalSeparator';
|
|||||||
const StyledContentContainer = styled.div`
|
const StyledContentContainer = styled.div`
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
||||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||||
width: 200px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledForm = styled.form`
|
const StyledForm = styled.form`
|
||||||
@ -51,6 +51,7 @@ export const SignInUpForm = () => {
|
|||||||
const { handleResetPassword } = useHandleResetPassword();
|
const { handleResetPassword } = useHandleResetPassword();
|
||||||
const workspace = useWorkspaceFromInviteHash();
|
const workspace = useWorkspaceFromInviteHash();
|
||||||
const { signInWithGoogle } = useSignInWithGoogle();
|
const { signInWithGoogle } = useSignInWithGoogle();
|
||||||
|
const { signInWithMicrosoft } = useSignInWithMicrosoft();
|
||||||
const { form } = useSignInUpForm();
|
const { form } = useSignInUpForm();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -125,119 +126,133 @@ export const SignInUpForm = () => {
|
|||||||
onClick={signInWithGoogle}
|
onClick={signInWithGoogle}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<HorizontalSeparator />
|
<HorizontalSeparator visible={!authProviders.microsoft} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StyledForm
|
{authProviders.microsoft && (
|
||||||
onSubmit={(event) => {
|
<>
|
||||||
event.preventDefault();
|
<MainButton
|
||||||
}}
|
Icon={() => <IconMicrosoft size={theme.icon.size.lg} />}
|
||||||
>
|
title="Continue with Microsoft"
|
||||||
{signInUpStep !== SignInUpStep.Init && (
|
onClick={signInWithMicrosoft}
|
||||||
<StyledFullWidthMotionDiv
|
fullWidth
|
||||||
initial={{ opacity: 0, height: 0 }}
|
/>
|
||||||
animate={{ opacity: 1, height: 'auto' }}
|
<HorizontalSeparator visible={authProviders.password} />
|
||||||
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
|
{authProviders.password && (
|
||||||
variant="secondary"
|
<StyledForm
|
||||||
title={buttonTitle}
|
onSubmit={(event) => {
|
||||||
type="submit"
|
event.preventDefault();
|
||||||
onClick={() => {
|
|
||||||
if (signInUpStep === SignInUpStep.Init) {
|
|
||||||
continueWithEmail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (signInUpStep === SignInUpStep.Email) {
|
|
||||||
continueWithCredentials();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setShowErrors(true);
|
|
||||||
form.handleSubmit(submitCredentials)();
|
|
||||||
}}
|
}}
|
||||||
Icon={() => form.formState.isSubmitting && <Loader />}
|
>
|
||||||
disabled={
|
{signInUpStep !== SignInUpStep.Init && (
|
||||||
signInUpStep === SignInUpStep.Init
|
<StyledFullWidthMotionDiv
|
||||||
? false
|
initial={{ opacity: 0, height: 0 }}
|
||||||
: signInUpStep === SignInUpStep.Email
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
? !form.watch('email')
|
transition={{
|
||||||
: !form.watch('email') ||
|
type: 'spring',
|
||||||
!form.watch('password') ||
|
stiffness: 800,
|
||||||
form.formState.isSubmitting
|
damping: 35,
|
||||||
}
|
}}
|
||||||
fullWidth
|
>
|
||||||
/>
|
<Controller
|
||||||
</StyledForm>
|
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)();
|
||||||
|
}}
|
||||||
|
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>
|
||||||
|
)}
|
||||||
</StyledContentContainer>
|
</StyledContentContainer>
|
||||||
{signInUpStep === SignInUpStep.Password && (
|
{signInUpStep === SignInUpStep.Password && (
|
||||||
<ActionLink onClick={handleResetPassword(form.getValues('email'))}>
|
<ActionLink onClick={handleResetPassword(form.getValues('email'))}>
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useAuth } from '@/auth/hooks/useAuth.ts';
|
||||||
|
|
||||||
|
export const useSignInWithMicrosoft = () => {
|
||||||
|
const workspaceInviteHash = useParams().workspaceInviteHash;
|
||||||
|
const { signInWithMicrosoft } = useAuth();
|
||||||
|
return {
|
||||||
|
signInWithMicrosoft: () => signInWithMicrosoft(workspaceInviteHash),
|
||||||
|
};
|
||||||
|
};
|
@ -38,6 +38,7 @@ export const ClientConfigProviderEffect = () => {
|
|||||||
setIsClientConfigLoaded(true);
|
setIsClientConfigLoaded(true);
|
||||||
setAuthProviders({
|
setAuthProviders({
|
||||||
google: data?.clientConfig.authProviders.google,
|
google: data?.clientConfig.authProviders.google,
|
||||||
|
microsoft: data?.clientConfig.authProviders.microsoft,
|
||||||
password: data?.clientConfig.authProviders.password,
|
password: data?.clientConfig.authProviders.password,
|
||||||
magicLink: false,
|
magicLink: false,
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ export const GET_CLIENT_CONFIG = gql`
|
|||||||
authProviders {
|
authProviders {
|
||||||
google
|
google
|
||||||
password
|
password
|
||||||
|
microsoft
|
||||||
}
|
}
|
||||||
billing {
|
billing {
|
||||||
isBillingEnabled
|
isBillingEnabled
|
||||||
|
@ -4,5 +4,10 @@ import { AuthProviders } from '~/generated/graphql';
|
|||||||
|
|
||||||
export const authProvidersState = createState<AuthProviders>({
|
export const authProvidersState = createState<AuthProviders>({
|
||||||
key: 'authProvidersState',
|
key: 'authProvidersState',
|
||||||
defaultValue: { google: false, magicLink: false, password: true },
|
defaultValue: {
|
||||||
|
google: false,
|
||||||
|
magicLink: false,
|
||||||
|
password: false,
|
||||||
|
microsoft: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -21,12 +21,18 @@ SIGN_IN_PREFILLED=true
|
|||||||
# REFRESH_TOKEN_EXPIRES_IN=90d
|
# REFRESH_TOKEN_EXPIRES_IN=90d
|
||||||
# FILE_TOKEN_EXPIRES_IN=1d
|
# FILE_TOKEN_EXPIRES_IN=1d
|
||||||
# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
|
# FRONT_AUTH_CALLBACK_URL=http://localhost:3001/verify
|
||||||
# AUTH_GOOGLE_ENABLED=false
|
|
||||||
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
|
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
|
||||||
# CALENDAR_PROVIDER_GOOGLE_ENABLED=false
|
# CALENDAR_PROVIDER_GOOGLE_ENABLED=false
|
||||||
# IS_BILLING_ENABLED=false
|
# IS_BILLING_ENABLED=false
|
||||||
# BILLING_PLAN_REQUIRED_LINK=https://twenty.com/stripe-redirection
|
# BILLING_PLAN_REQUIRED_LINK=https://twenty.com/stripe-redirection
|
||||||
|
# AUTH_PASSWORD_ENABLED=false
|
||||||
# IS_SIGN_UP_DISABLED=false
|
# IS_SIGN_UP_DISABLED=false
|
||||||
|
# AUTH_MICROSOFT_ENABLED=false
|
||||||
|
# AUTH_MICROSOFT_CLIENT_ID=replace_me_with_azure_client_id
|
||||||
|
# AUTH_MICROSOFT_TENANT_ID=replace_me_with_azure_tenant_id
|
||||||
|
# AUTH_MICROSOFT_CLIENT_SECRET=replace_me_with_azure_client_secret
|
||||||
|
# AUTH_MICROSOFT_CALLBACK_URL=http://localhost:3000/auth/microsoft/redirect
|
||||||
|
# AUTH_GOOGLE_ENABLED=false
|
||||||
# AUTH_GOOGLE_CLIENT_ID=replace_me_with_google_client_id
|
# AUTH_GOOGLE_CLIENT_ID=replace_me_with_google_client_id
|
||||||
# AUTH_GOOGLE_CLIENT_SECRET=replace_me_with_google_client_secret
|
# AUTH_GOOGLE_CLIENT_SECRET=replace_me_with_google_client_secret
|
||||||
# AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/redirect
|
# AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/redirect
|
||||||
|
@ -21,6 +21,7 @@ import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user
|
|||||||
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||||
|
import { MicrosoftAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-auth.controller';
|
||||||
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
|
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
|
||||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||||
import { ConnectedAccountObjectMetadata } from 'src/modules/connected-account/standard-objects/connected-account.object-metadata';
|
import { ConnectedAccountObjectMetadata } from 'src/modules/connected-account/standard-objects/connected-account.object-metadata';
|
||||||
@ -65,6 +66,7 @@ const jwtModule = JwtModule.registerAsync({
|
|||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
GoogleAuthController,
|
GoogleAuthController,
|
||||||
|
MicrosoftAuthController,
|
||||||
GoogleAPIsAuthController,
|
GoogleAPIsAuthController,
|
||||||
VerifyAuthController,
|
VerifyAuthController,
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
|
import { MicrosoftOAuthGuard } from 'src/engine/core-modules/auth/guards/microsoft-oauth.guard';
|
||||||
|
import { MicrosoftProviderEnabledGuard } from 'src/engine/core-modules/auth/guards/microsoft-provider-enabled.guard';
|
||||||
|
import { AuthService } from 'src/engine/core-modules/auth/services/auth.service';
|
||||||
|
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||||
|
import { MicrosoftRequest } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||||
|
|
||||||
|
@Controller('auth/microsoft')
|
||||||
|
export class MicrosoftAuthController {
|
||||||
|
constructor(
|
||||||
|
private readonly tokenService: TokenService,
|
||||||
|
private readonly typeORMService: TypeORMService,
|
||||||
|
private readonly authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
||||||
|
async microsoftAuth() {
|
||||||
|
// As this method is protected by Microsoft Auth guard, it will trigger Microsoft SSO flow
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('redirect')
|
||||||
|
@UseGuards(MicrosoftProviderEnabledGuard, MicrosoftOAuthGuard)
|
||||||
|
async microsoftAuthRedirect(
|
||||||
|
@Req() req: MicrosoftRequest,
|
||||||
|
@Res() res: Response,
|
||||||
|
) {
|
||||||
|
const { firstName, lastName, email, picture, workspaceInviteHash } =
|
||||||
|
req.user;
|
||||||
|
|
||||||
|
const user = await this.authService.signInUp({
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
picture,
|
||||||
|
workspaceInviteHash,
|
||||||
|
fromSSO: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginToken = await this.tokenService.generateLoginToken(user.email);
|
||||||
|
|
||||||
|
return res.redirect(this.tokenService.computeRedirectURI(loginToken.token));
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,7 @@ export class GoogleOauthGuard extends AuthGuard('google') {
|
|||||||
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||||
request.params.workspaceInviteHash = workspaceInviteHash;
|
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||||
}
|
}
|
||||||
const activate = (await super.canActivate(context)) as boolean;
|
|
||||||
|
|
||||||
return activate;
|
return (await super.canActivate(context)) as boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MicrosoftOAuthGuard extends AuthGuard('microsoft') {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
prompt: 'select_account',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext) {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const workspaceInviteHash = request.query.inviteHash;
|
||||||
|
|
||||||
|
if (workspaceInviteHash && typeof workspaceInviteHash === 'string') {
|
||||||
|
request.params.workspaceInviteHash = workspaceInviteHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await super.canActivate(context)) as boolean;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable, CanActivate, NotFoundException } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
|
import { MicrosoftStrategy } from 'src/engine/core-modules/auth/strategies/microsoft.auth.strategy';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MicrosoftProviderEnabledGuard implements CanActivate {
|
||||||
|
constructor(private readonly environmentService: EnvironmentService) {}
|
||||||
|
|
||||||
|
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
|
if (!this.environmentService.get('AUTH_MICROSOFT_ENABLED')) {
|
||||||
|
throw new NotFoundException('Microsoft auth is not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
new MicrosoftStrategy(this.environmentService);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { VerifyCallback } from 'passport-google-oauth20';
|
||||||
|
import { Strategy } from 'passport-microsoft';
|
||||||
|
|
||||||
|
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
|
|
||||||
|
export type MicrosoftRequest = Omit<
|
||||||
|
Request,
|
||||||
|
'user' | 'workspace' | 'cacheVersion'
|
||||||
|
> & {
|
||||||
|
user: {
|
||||||
|
firstName?: string | null;
|
||||||
|
lastName?: string | null;
|
||||||
|
email: string;
|
||||||
|
picture: string | null;
|
||||||
|
workspaceInviteHash?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MicrosoftStrategy extends PassportStrategy(Strategy, 'microsoft') {
|
||||||
|
constructor(environmentService: EnvironmentService) {
|
||||||
|
super({
|
||||||
|
clientID: environmentService.get('AUTH_MICROSOFT_CLIENT_ID'),
|
||||||
|
clientSecret: environmentService.get('AUTH_MICROSOFT_CLIENT_SECRET'),
|
||||||
|
callbackURL: environmentService.get('AUTH_MICROSOFT_CALLBACK_URL'),
|
||||||
|
tenant: environmentService.get('AUTH_MICROSOFT_TENANT_ID'),
|
||||||
|
scope: ['user.read'],
|
||||||
|
passReqToCallback: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(req: any, options: any) {
|
||||||
|
options = {
|
||||||
|
...options,
|
||||||
|
state: JSON.stringify({
|
||||||
|
workspaceInviteHash: req.params.workspaceInviteHash,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return super.authenticate(req, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(
|
||||||
|
request: MicrosoftRequest,
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string,
|
||||||
|
profile: any,
|
||||||
|
done: VerifyCallback,
|
||||||
|
): Promise<void> {
|
||||||
|
const { name, emails, photos } = profile;
|
||||||
|
|
||||||
|
const state =
|
||||||
|
typeof request.query.state === 'string'
|
||||||
|
? JSON.parse(request.query.state)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const email = emails?.[0]?.value ?? null;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new BadRequestException('No email found in your Microsoft profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
const user: MicrosoftRequest['user'] = {
|
||||||
|
email,
|
||||||
|
firstName: name.givenName,
|
||||||
|
lastName: name.familyName,
|
||||||
|
picture: photos?.[0]?.value,
|
||||||
|
workspaceInviteHash: state.workspaceInviteHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
done(null, user);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,9 @@ class AuthProviders {
|
|||||||
|
|
||||||
@Field(() => Boolean)
|
@Field(() => Boolean)
|
||||||
password: boolean;
|
password: boolean;
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
microsoft: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
|
@ -14,7 +14,8 @@ export class ClientConfigResolver {
|
|||||||
authProviders: {
|
authProviders: {
|
||||||
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
|
google: this.environmentService.get('AUTH_GOOGLE_ENABLED'),
|
||||||
magicLink: false,
|
magicLink: false,
|
||||||
password: true,
|
password: this.environmentService.get('AUTH_PASSWORD_ENABLED'),
|
||||||
|
microsoft: this.environmentService.get('AUTH_MICROSOFT_ENABLED'),
|
||||||
},
|
},
|
||||||
telemetry: {
|
telemetry: {
|
||||||
enabled: this.environmentService.get('TELEMETRY_ENABLED'),
|
enabled: this.environmentService.get('TELEMETRY_ENABLED'),
|
||||||
|
@ -52,11 +52,6 @@ export class EnvironmentVariables {
|
|||||||
@Max(65535)
|
@Max(65535)
|
||||||
DEBUG_PORT = 9000;
|
DEBUG_PORT = 9000;
|
||||||
|
|
||||||
@CastToBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
SIGN_IN_PREFILLED = false;
|
|
||||||
|
|
||||||
@CastToBoolean()
|
@CastToBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@ -156,21 +151,53 @@ export class EnvironmentVariables {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
FRONT_AUTH_CALLBACK_URL: string;
|
FRONT_AUTH_CALLBACK_URL: string;
|
||||||
|
|
||||||
|
@CastToBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
AUTH_PASSWORD_ENABLED = true;
|
||||||
|
|
||||||
|
@CastToBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
@ValidateIf((env) => env.AUTH_PASSWORD_ENABLED)
|
||||||
|
SIGN_IN_PREFILLED = false;
|
||||||
|
|
||||||
|
@CastToBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
AUTH_MICROSOFT_ENABLED = false;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||||
|
AUTH_MICROSOFT_CLIENT_ID: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||||
|
AUTH_MICROSOFT_TENANT_ID: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||||
|
AUTH_MICROSOFT_CLIENT_SECRET: string;
|
||||||
|
|
||||||
|
@IsUrl({ require_tld: false })
|
||||||
|
@ValidateIf((env) => env.AUTH_MICROSOFT_ENABLED)
|
||||||
|
AUTH_MICROSOFT_CALLBACK_URL: string;
|
||||||
|
|
||||||
@CastToBoolean()
|
@CastToBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
AUTH_GOOGLE_ENABLED = false;
|
AUTH_GOOGLE_ENABLED = false;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||||
AUTH_GOOGLE_CLIENT_ID: string;
|
AUTH_GOOGLE_CLIENT_ID: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||||
AUTH_GOOGLE_CLIENT_SECRET: string;
|
AUTH_GOOGLE_CLIENT_SECRET: string;
|
||||||
|
|
||||||
@IsUrl({ require_tld: false })
|
@IsUrl({ require_tld: false })
|
||||||
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED === true)
|
@ValidateIf((env) => env.AUTH_GOOGLE_ENABLED)
|
||||||
AUTH_GOOGLE_CALLBACK_URL: string;
|
AUTH_GOOGLE_CALLBACK_URL: string;
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
|
7
packages/twenty-ui/src/display/icon/assets/microsoft.svg
Normal file
7
packages/twenty-ui/src/display/icon/assets/microsoft.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||||
|
<path fill="#F35325" d="M1 1h6.5v6.5H1V1z"/>
|
||||||
|
<path fill="#81BC06" d="M8.5 1H15v6.5H8.5V1z"/>
|
||||||
|
<path fill="#05A6F0" d="M1 8.5h6.5V15H1V8.5z"/>
|
||||||
|
<path fill="#FFBA08" d="M8.5 8.5H15V15H8.5V8.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 355 B |
@ -0,0 +1,14 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
|
import IconMicrosoftRaw from '../assets/microsoft.svg?react';
|
||||||
|
|
||||||
|
interface IconMicrosoftProps {
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconMicrosoft = (props: IconMicrosoftProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const size = props.size ?? theme.icon.size.lg;
|
||||||
|
|
||||||
|
return <IconMicrosoftRaw height={size} width={size} />;
|
||||||
|
};
|
@ -8,6 +8,7 @@ export * from './icon/components/IconAddressBook';
|
|||||||
export * from './icon/components/IconGmail';
|
export * from './icon/components/IconGmail';
|
||||||
export * from './icon/components/IconGoogle';
|
export * from './icon/components/IconGoogle';
|
||||||
export * from './icon/components/IconGoogleCalendar';
|
export * from './icon/components/IconGoogleCalendar';
|
||||||
|
export * from './icon/components/IconMicrosoft';
|
||||||
export * from './icon/components/IconTwentyStar';
|
export * from './icon/components/IconTwentyStar';
|
||||||
export * from './icon/components/IconTwentyStarFilled';
|
export * from './icon/components/IconTwentyStarFilled';
|
||||||
export * from './icon/components/TablerIcons';
|
export * from './icon/components/TablerIcons';
|
||||||
|
40
yarn.lock
40
yarn.lock
@ -17093,6 +17093,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/passport-microsoft@npm:^1.0.3":
|
||||||
|
version: 1.0.3
|
||||||
|
resolution: "@types/passport-microsoft@npm:1.0.3"
|
||||||
|
dependencies:
|
||||||
|
"@types/passport-oauth2": "npm:*"
|
||||||
|
checksum: d177f63ae4976253242966146030b9913dc3b77c4eb192f5491d397f9036b64f258325157c244960ef4539e9c46807d0bba53497630253f4ef98d1318142b3d8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/passport-oauth2@npm:*":
|
"@types/passport-oauth2@npm:*":
|
||||||
version: 1.4.15
|
version: 1.4.15
|
||||||
resolution: "@types/passport-oauth2@npm:1.4.15"
|
resolution: "@types/passport-oauth2@npm:1.4.15"
|
||||||
@ -38165,6 +38174,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"oauth@npm:0.10.x":
|
||||||
|
version: 0.10.0
|
||||||
|
resolution: "oauth@npm:0.10.0"
|
||||||
|
checksum: 76f3e186cfd76cb33e5d5d442861c86680a5c3b71b2db1b854212087532c265a69de1a2ab9db683e6c6df733e17cfc67476527b81b224a19c1917de2bc3f75fa
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"oauth@npm:0.9.x":
|
"oauth@npm:0.9.x":
|
||||||
version: 0.9.15
|
version: 0.9.15
|
||||||
resolution: "oauth@npm:0.9.15"
|
resolution: "oauth@npm:0.9.15"
|
||||||
@ -39110,6 +39126,28 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"passport-microsoft@npm:^2.0.0":
|
||||||
|
version: 2.0.0
|
||||||
|
resolution: "passport-microsoft@npm:2.0.0"
|
||||||
|
dependencies:
|
||||||
|
passport-oauth2: "npm:1.8.0"
|
||||||
|
checksum: 6b7d053673d5af6cbbce3812a628afff2435aa321d70c9feaa3309d7a610a2e61c753a7cde8c0febee2cacf74b971fcdc7699fa1c073c72052bc4334f1b203ec
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"passport-oauth2@npm:1.8.0":
|
||||||
|
version: 1.8.0
|
||||||
|
resolution: "passport-oauth2@npm:1.8.0"
|
||||||
|
dependencies:
|
||||||
|
base64url: "npm:3.x.x"
|
||||||
|
oauth: "npm:0.10.x"
|
||||||
|
passport-strategy: "npm:1.x.x"
|
||||||
|
uid2: "npm:0.0.x"
|
||||||
|
utils-merge: "npm:1.x.x"
|
||||||
|
checksum: 16b431bd856b84dfe0c9c913dcbea6ff54875befac1035171b0dce1c77f79072dc5e26d785b13c2e62c034c8174a1a47571751d1066bdbcdb9108de217c0b19b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"passport-oauth2@npm:1.x.x":
|
"passport-oauth2@npm:1.x.x":
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
resolution: "passport-oauth2@npm:1.7.0"
|
resolution: "passport-oauth2@npm:1.7.0"
|
||||||
@ -46629,6 +46667,7 @@ __metadata:
|
|||||||
"@types/nodemailer": "npm:^6.4.14"
|
"@types/nodemailer": "npm:^6.4.14"
|
||||||
"@types/passport-google-oauth20": "npm:^2.0.11"
|
"@types/passport-google-oauth20": "npm:^2.0.11"
|
||||||
"@types/passport-jwt": "npm:^3.0.8"
|
"@types/passport-jwt": "npm:^3.0.8"
|
||||||
|
"@types/passport-microsoft": "npm:^1.0.3"
|
||||||
"@types/react": "npm:^18.2.39"
|
"@types/react": "npm:^18.2.39"
|
||||||
"@types/react-datepicker": "npm:^6.2.0"
|
"@types/react-datepicker": "npm:^6.2.0"
|
||||||
"@types/react-dom": "npm:^18.2.15"
|
"@types/react-dom": "npm:^18.2.15"
|
||||||
@ -46745,6 +46784,7 @@ __metadata:
|
|||||||
passport-google-oauth20: "npm:^2.0.0"
|
passport-google-oauth20: "npm:^2.0.0"
|
||||||
passport-jwt: "npm:^4.0.1"
|
passport-jwt: "npm:^4.0.1"
|
||||||
passport-local: "npm:^1.0.0"
|
passport-local: "npm:^1.0.0"
|
||||||
|
passport-microsoft: "npm:^2.0.0"
|
||||||
patch-package: "npm:^8.0.0"
|
patch-package: "npm:^8.0.0"
|
||||||
pg: "npm:^8.11.3"
|
pg: "npm:^8.11.3"
|
||||||
pg-boss: "npm:^9.0.3"
|
pg-boss: "npm:^9.0.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user