BREAKING - feat(auth): refactor tokens logic & enhance email verification flow (#13487)
- Replaced `getAuthTokensFromLoginToken` with `getAccessTokensFromLoginToken` for clarity. - Introduced `getWorkspaceAgnosticTokenFromEmailVerificationToken`. - Extended mutation inputs to include `locale` and `verifyEmailNextPath`. - Added email verification check and sending to various handlers. - Updated GraphQL types and hooks to reflect these changes. Fix #13412 --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@@ -62,7 +62,7 @@ const setTokenStateFromCookie = (cookie: string) => {
|
||||
if (isDefined(tokenPair)) {
|
||||
chrome.storage.local.set({
|
||||
isAuthenticated: true,
|
||||
accessToken: tokenPair.accessToken,
|
||||
accessToken: tokenPair.accessOrWorkspaceAgnosticToken,
|
||||
refreshToken: tokenPair.refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export type ExchangeAuthCodeInput = {
|
||||
|
||||
export type Tokens = {
|
||||
loginToken: AuthToken;
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
refreshToken: AuthToken;
|
||||
};
|
||||
|
||||
|
||||
@@ -826,7 +826,7 @@ export type AuthToken = {
|
||||
};
|
||||
|
||||
export type AuthTokenPair = {
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
refreshToken: AuthToken;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const EXCHANGE_AUTHORIZATION_CODE = gql`
|
||||
token
|
||||
expiresAt
|
||||
}
|
||||
accessToken {
|
||||
accessOrWorkspaceAgnosticToken {
|
||||
token
|
||||
expiresAt
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export const RENEW_TOKEN = gql`
|
||||
mutation RenewToken($appToken: String!) {
|
||||
renewToken(appToken: $appToken) {
|
||||
tokens {
|
||||
accessToken {
|
||||
accessOrWorkspaceAgnosticToken {
|
||||
token
|
||||
expiresAt
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const getAuthToken = async (page: Page) => {
|
||||
if (!authCookie) {
|
||||
throw new Error('No auth cookie found');
|
||||
}
|
||||
const token = JSON.parse(decodeURIComponent(authCookie.value)).accessToken
|
||||
const token = JSON.parse(decodeURIComponent(authCookie.value)).accessOrWorkspaceAgnosticToken
|
||||
.token;
|
||||
|
||||
return { authToken: token };
|
||||
|
||||
@@ -74,7 +74,7 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
cookie: {
|
||||
tokenPair: `{%22accessToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-07-18T15:06:40.704Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22refreshToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-10-15T15:06:41.558Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22__typename%22:%22AuthTokenPair%22}`,
|
||||
tokenPair: `{%22accessOrWorkspaceAgnosticToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-07-18T15:06:40.704Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22refreshToken%22:{%22token%22:%22${mockedUserJWT}%22%2C%22expiresAt%22:%222023-10-15T15:06:41.558Z%22%2C%22__typename%22:%22AuthToken%22}%2C%22__typename%22:%22AuthTokenPair%22}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { formatter } from '@lingui/format-po';
|
||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
export default defineConfig({
|
||||
sourceLocale: 'en',
|
||||
sourceLocale: SOURCE_LOCALE,
|
||||
locales: Object.values(APP_LOCALES),
|
||||
pseudoLocale: 'pseudo-en',
|
||||
fallbackLocales: {
|
||||
|
||||
@@ -161,7 +161,7 @@ export type AuthToken = {
|
||||
|
||||
export type AuthTokenPair = {
|
||||
__typename?: 'AuthTokenPair';
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
refreshToken: AuthToken;
|
||||
};
|
||||
|
||||
@@ -1168,6 +1168,7 @@ export type Mutation = {
|
||||
getAuthorizationUrlForSSO: GetAuthorizationUrlForSsoOutput;
|
||||
getLoginTokenFromCredentials: LoginToken;
|
||||
getLoginTokenFromEmailVerificationToken: GetLoginTokenFromEmailVerificationTokenOutput;
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken: AvailableWorkspacesAndAccessTokensOutput;
|
||||
impersonate: ImpersonateOutput;
|
||||
initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
@@ -1461,8 +1462,10 @@ export type MutationGetAuthorizationUrlForSsoArgs = {
|
||||
export type MutationGetLoginTokenFromCredentialsArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
origin: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1474,6 +1477,13 @@ export type MutationGetLoginTokenFromEmailVerificationTokenArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationGetWorkspaceAgnosticTokenFromEmailVerificationTokenArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
emailVerificationToken: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationImpersonateArgs = {
|
||||
userId: Scalars['String'];
|
||||
workspaceId: Scalars['String'];
|
||||
@@ -1538,14 +1548,18 @@ export type MutationSendInvitationsArgs = {
|
||||
export type MutationSignInArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationSignUpArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1554,7 +1568,7 @@ export type MutationSignUpInWorkspaceArgs = {
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailNextPath?: InputMaybe<Scalars['String']>;
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
workspaceId?: InputMaybe<Scalars['String']>;
|
||||
workspaceInviteHash?: InputMaybe<Scalars['String']>;
|
||||
workspacePersonalInviteToken?: InputMaybe<Scalars['String']>;
|
||||
@@ -3152,7 +3166,7 @@ export type UploadImageMutation = { __typename?: 'Mutation', uploadImage: { __ty
|
||||
|
||||
export type AuthTokenFragmentFragment = { __typename?: 'AuthToken', token: string, expiresAt: string };
|
||||
|
||||
export type AuthTokensFragmentFragment = { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } };
|
||||
export type AuthTokenPairFragmentFragment = { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } };
|
||||
|
||||
export type AvailableWorkspaceFragmentFragment = { __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> };
|
||||
|
||||
@@ -3190,6 +3204,14 @@ export type GenerateTransientTokenMutationVariables = Exact<{ [key: string]: nev
|
||||
|
||||
export type GenerateTransientTokenMutation = { __typename?: 'Mutation', generateTransientToken: { __typename?: 'TransientToken', transientToken: { __typename?: 'AuthToken', token: string } } };
|
||||
|
||||
export type GetAuthTokensFromLoginTokenMutationVariables = Exact<{
|
||||
loginToken: Scalars['String'];
|
||||
origin: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAuthTokensFromLoginTokenMutation = { __typename?: 'Mutation', getAuthTokensFromLoginToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type GetAuthTokensFromOtpMutationVariables = Exact<{
|
||||
loginToken: Scalars['String'];
|
||||
otp: Scalars['String'];
|
||||
@@ -3198,15 +3220,7 @@ export type GetAuthTokensFromOtpMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAuthTokensFromOtpMutation = { __typename?: 'Mutation', getAuthTokensFromOTP: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type GetAuthTokensFromLoginTokenMutationVariables = Exact<{
|
||||
loginToken: Scalars['String'];
|
||||
origin: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAuthTokensFromLoginTokenMutation = { __typename?: 'Mutation', getAuthTokensFromLoginToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type GetAuthTokensFromOtpMutation = { __typename?: 'Mutation', getAuthTokensFromOTP: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type GetAuthorizationUrlForSsoMutationVariables = Exact<{
|
||||
input: GetAuthorizationUrlForSsoInput;
|
||||
@@ -3235,6 +3249,15 @@ export type GetLoginTokenFromEmailVerificationTokenMutationVariables = Exact<{
|
||||
|
||||
export type GetLoginTokenFromEmailVerificationTokenMutation = { __typename?: 'Mutation', getLoginTokenFromEmailVerificationToken: { __typename?: 'GetLoginTokenFromEmailVerificationTokenOutput', loginToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null } } };
|
||||
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationVariables = Exact<{
|
||||
emailVerificationToken: Scalars['String'];
|
||||
email: Scalars['String'];
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation = { __typename?: 'Mutation', getWorkspaceAgnosticTokenFromEmailVerificationToken: { __typename?: 'AvailableWorkspacesAndAccessTokensOutput', availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> }, tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type ImpersonateMutationVariables = Exact<{
|
||||
userId: Scalars['String'];
|
||||
workspaceId: Scalars['String'];
|
||||
@@ -3261,7 +3284,7 @@ export type RenewTokenMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type RenewTokenMutation = { __typename?: 'Mutation', renewToken: { __typename?: 'AuthTokens', tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type ResendEmailVerificationTokenMutationVariables = Exact<{
|
||||
email: Scalars['String'];
|
||||
@@ -3285,16 +3308,18 @@ export type SignInMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SignInMutation = { __typename?: 'Mutation', signIn: { __typename?: 'AvailableWorkspacesAndAccessTokensOutput', availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type SignInMutation = { __typename?: 'Mutation', signIn: { __typename?: 'AvailableWorkspacesAndAccessTokensOutput', availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> }, tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type SignUpMutationVariables = Exact<{
|
||||
email: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'AvailableWorkspacesAndAccessTokensOutput', availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
export type SignUpMutation = { __typename?: 'Mutation', signUp: { __typename?: 'AvailableWorkspacesAndAccessTokensOutput', availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> }, tokens: { __typename?: 'AuthTokenPair', accessOrWorkspaceAgnosticToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
|
||||
|
||||
export type SignUpInNewWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -3309,7 +3334,7 @@ export type SignUpInWorkspaceMutationVariables = Exact<{
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
workspaceId?: InputMaybe<Scalars['String']>;
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
verifyEmailNextPath?: InputMaybe<Scalars['String']>;
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -4045,9 +4070,9 @@ export const AuthTokenFragmentFragmentDoc = gql`
|
||||
expiresAt
|
||||
}
|
||||
`;
|
||||
export const AuthTokensFragmentFragmentDoc = gql`
|
||||
fragment AuthTokensFragment on AuthTokenPair {
|
||||
accessToken {
|
||||
export const AuthTokenPairFragmentFragmentDoc = gql`
|
||||
fragment AuthTokenPairFragment on AuthTokenPair {
|
||||
accessOrWorkspaceAgnosticToken {
|
||||
...AuthTokenFragment
|
||||
}
|
||||
refreshToken {
|
||||
@@ -4830,6 +4855,42 @@ export function useGenerateTransientTokenMutation(baseOptions?: Apollo.MutationH
|
||||
export type GenerateTransientTokenMutationHookResult = ReturnType<typeof useGenerateTransientTokenMutation>;
|
||||
export type GenerateTransientTokenMutationResult = Apollo.MutationResult<GenerateTransientTokenMutation>;
|
||||
export type GenerateTransientTokenMutationOptions = Apollo.BaseMutationOptions<GenerateTransientTokenMutation, GenerateTransientTokenMutationVariables>;
|
||||
export const GetAuthTokensFromLoginTokenDocument = gql`
|
||||
mutation getAuthTokensFromLoginToken($loginToken: String!, $origin: String!) {
|
||||
getAuthTokensFromLoginToken(loginToken: $loginToken, origin: $origin) {
|
||||
tokens {
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type GetAuthTokensFromLoginTokenMutationFn = Apollo.MutationFunction<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useGetAuthTokensFromLoginTokenMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useGetAuthTokensFromLoginTokenMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetAuthTokensFromLoginTokenMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [getAuthTokensFromLoginTokenMutation, { data, loading, error }] = useGetAuthTokensFromLoginTokenMutation({
|
||||
* variables: {
|
||||
* loginToken: // value for 'loginToken'
|
||||
* origin: // value for 'origin'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetAuthTokensFromLoginTokenMutation(baseOptions?: Apollo.MutationHookOptions<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>(GetAuthTokensFromLoginTokenDocument, options);
|
||||
}
|
||||
export type GetAuthTokensFromLoginTokenMutationHookResult = ReturnType<typeof useGetAuthTokensFromLoginTokenMutation>;
|
||||
export type GetAuthTokensFromLoginTokenMutationResult = Apollo.MutationResult<GetAuthTokensFromLoginTokenMutation>;
|
||||
export type GetAuthTokensFromLoginTokenMutationOptions = Apollo.BaseMutationOptions<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>;
|
||||
export const GetAuthTokensFromOtpDocument = gql`
|
||||
mutation getAuthTokensFromOTP($loginToken: String!, $otp: String!, $captchaToken: String, $origin: String!) {
|
||||
getAuthTokensFromOTP(
|
||||
@@ -4839,11 +4900,11 @@ export const GetAuthTokensFromOtpDocument = gql`
|
||||
origin: $origin
|
||||
) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AuthTokensFragmentFragmentDoc}`;
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type GetAuthTokensFromOtpMutationFn = Apollo.MutationFunction<GetAuthTokensFromOtpMutation, GetAuthTokensFromOtpMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -4873,42 +4934,6 @@ export function useGetAuthTokensFromOtpMutation(baseOptions?: Apollo.MutationHoo
|
||||
export type GetAuthTokensFromOtpMutationHookResult = ReturnType<typeof useGetAuthTokensFromOtpMutation>;
|
||||
export type GetAuthTokensFromOtpMutationResult = Apollo.MutationResult<GetAuthTokensFromOtpMutation>;
|
||||
export type GetAuthTokensFromOtpMutationOptions = Apollo.BaseMutationOptions<GetAuthTokensFromOtpMutation, GetAuthTokensFromOtpMutationVariables>;
|
||||
export const GetAuthTokensFromLoginTokenDocument = gql`
|
||||
mutation GetAuthTokensFromLoginToken($loginToken: String!, $origin: String!) {
|
||||
getAuthTokensFromLoginToken(loginToken: $loginToken, origin: $origin) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AuthTokensFragmentFragmentDoc}`;
|
||||
export type GetAuthTokensFromLoginTokenMutationFn = Apollo.MutationFunction<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useGetAuthTokensFromLoginTokenMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useGetAuthTokensFromLoginTokenMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetAuthTokensFromLoginTokenMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [getAuthTokensFromLoginTokenMutation, { data, loading, error }] = useGetAuthTokensFromLoginTokenMutation({
|
||||
* variables: {
|
||||
* loginToken: // value for 'loginToken'
|
||||
* origin: // value for 'origin'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetAuthTokensFromLoginTokenMutation(baseOptions?: Apollo.MutationHookOptions<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>(GetAuthTokensFromLoginTokenDocument, options);
|
||||
}
|
||||
export type GetAuthTokensFromLoginTokenMutationHookResult = ReturnType<typeof useGetAuthTokensFromLoginTokenMutation>;
|
||||
export type GetAuthTokensFromLoginTokenMutationResult = Apollo.MutationResult<GetAuthTokensFromLoginTokenMutation>;
|
||||
export type GetAuthTokensFromLoginTokenMutationOptions = Apollo.BaseMutationOptions<GetAuthTokensFromLoginTokenMutation, GetAuthTokensFromLoginTokenMutationVariables>;
|
||||
export const GetAuthorizationUrlForSsoDocument = gql`
|
||||
mutation GetAuthorizationUrlForSSO($input: GetAuthorizationUrlForSSOInput!) {
|
||||
getAuthorizationUrlForSSO(input: $input) {
|
||||
@@ -5034,6 +5059,51 @@ export function useGetLoginTokenFromEmailVerificationTokenMutation(baseOptions?:
|
||||
export type GetLoginTokenFromEmailVerificationTokenMutationHookResult = ReturnType<typeof useGetLoginTokenFromEmailVerificationTokenMutation>;
|
||||
export type GetLoginTokenFromEmailVerificationTokenMutationResult = Apollo.MutationResult<GetLoginTokenFromEmailVerificationTokenMutation>;
|
||||
export type GetLoginTokenFromEmailVerificationTokenMutationOptions = Apollo.BaseMutationOptions<GetLoginTokenFromEmailVerificationTokenMutation, GetLoginTokenFromEmailVerificationTokenMutationVariables>;
|
||||
export const GetWorkspaceAgnosticTokenFromEmailVerificationTokenDocument = gql`
|
||||
mutation GetWorkspaceAgnosticTokenFromEmailVerificationToken($emailVerificationToken: String!, $email: String!, $captchaToken: String) {
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken(
|
||||
emailVerificationToken: $emailVerificationToken
|
||||
email: $email
|
||||
captchaToken: $captchaToken
|
||||
) {
|
||||
availableWorkspaces {
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AvailableWorkspacesFragmentFragmentDoc}
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationFn = Apollo.MutationFunction<GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation, GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [getWorkspaceAgnosticTokenFromEmailVerificationTokenMutation, { data, loading, error }] = useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation({
|
||||
* variables: {
|
||||
* emailVerificationToken: // value for 'emailVerificationToken'
|
||||
* email: // value for 'email'
|
||||
* captchaToken: // value for 'captchaToken'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation(baseOptions?: Apollo.MutationHookOptions<GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation, GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation, GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationVariables>(GetWorkspaceAgnosticTokenFromEmailVerificationTokenDocument, options);
|
||||
}
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationHookResult = ReturnType<typeof useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation>;
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationResult = Apollo.MutationResult<GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation>;
|
||||
export type GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationOptions = Apollo.BaseMutationOptions<GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation, GetWorkspaceAgnosticTokenFromEmailVerificationTokenMutationVariables>;
|
||||
export const ImpersonateDocument = gql`
|
||||
mutation Impersonate($userId: String!, $workspaceId: String!) {
|
||||
impersonate(userId: $userId, workspaceId: $workspaceId) {
|
||||
@@ -5147,11 +5217,11 @@ export const RenewTokenDocument = gql`
|
||||
mutation RenewToken($appToken: String!) {
|
||||
renewToken(appToken: $appToken) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AuthTokensFragmentFragmentDoc}`;
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type RenewTokenMutationFn = Apollo.MutationFunction<RenewTokenMutation, RenewTokenMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -5254,12 +5324,12 @@ export const SignInDocument = gql`
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AvailableWorkspacesFragmentFragmentDoc}
|
||||
${AuthTokensFragmentFragmentDoc}`;
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type SignInMutationFn = Apollo.MutationFunction<SignInMutation, SignInMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -5289,18 +5359,24 @@ export type SignInMutationHookResult = ReturnType<typeof useSignInMutation>;
|
||||
export type SignInMutationResult = Apollo.MutationResult<SignInMutation>;
|
||||
export type SignInMutationOptions = Apollo.BaseMutationOptions<SignInMutation, SignInMutationVariables>;
|
||||
export const SignUpDocument = gql`
|
||||
mutation SignUp($email: String!, $password: String!, $captchaToken: String) {
|
||||
signUp(email: $email, password: $password, captchaToken: $captchaToken) {
|
||||
mutation SignUp($email: String!, $password: String!, $captchaToken: String, $locale: String, $verifyEmailRedirectPath: String) {
|
||||
signUp(
|
||||
email: $email
|
||||
password: $password
|
||||
captchaToken: $captchaToken
|
||||
locale: $locale
|
||||
verifyEmailRedirectPath: $verifyEmailRedirectPath
|
||||
) {
|
||||
availableWorkspaces {
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
${AvailableWorkspacesFragmentFragmentDoc}
|
||||
${AuthTokensFragmentFragmentDoc}`;
|
||||
${AuthTokenPairFragmentFragmentDoc}`;
|
||||
export type SignUpMutationFn = Apollo.MutationFunction<SignUpMutation, SignUpMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -5319,6 +5395,8 @@ export type SignUpMutationFn = Apollo.MutationFunction<SignUpMutation, SignUpMut
|
||||
* email: // value for 'email'
|
||||
* password: // value for 'password'
|
||||
* captchaToken: // value for 'captchaToken'
|
||||
* locale: // value for 'locale'
|
||||
* verifyEmailRedirectPath: // value for 'verifyEmailRedirectPath'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -5371,7 +5449,7 @@ export type SignUpInNewWorkspaceMutationHookResult = ReturnType<typeof useSignUp
|
||||
export type SignUpInNewWorkspaceMutationResult = Apollo.MutationResult<SignUpInNewWorkspaceMutation>;
|
||||
export type SignUpInNewWorkspaceMutationOptions = Apollo.BaseMutationOptions<SignUpInNewWorkspaceMutation, SignUpInNewWorkspaceMutationVariables>;
|
||||
export const SignUpInWorkspaceDocument = gql`
|
||||
mutation SignUpInWorkspace($email: String!, $password: String!, $workspaceInviteHash: String, $workspacePersonalInviteToken: String = null, $captchaToken: String, $workspaceId: String, $locale: String, $verifyEmailNextPath: String) {
|
||||
mutation SignUpInWorkspace($email: String!, $password: String!, $workspaceInviteHash: String, $workspacePersonalInviteToken: String = null, $captchaToken: String, $workspaceId: String, $locale: String, $verifyEmailRedirectPath: String) {
|
||||
signUpInWorkspace(
|
||||
email: $email
|
||||
password: $password
|
||||
@@ -5380,7 +5458,7 @@ export const SignUpInWorkspaceDocument = gql`
|
||||
captchaToken: $captchaToken
|
||||
workspaceId: $workspaceId
|
||||
locale: $locale
|
||||
verifyEmailNextPath: $verifyEmailNextPath
|
||||
verifyEmailRedirectPath: $verifyEmailRedirectPath
|
||||
) {
|
||||
loginToken {
|
||||
...AuthTokenFragment
|
||||
@@ -5417,7 +5495,7 @@ export type SignUpInWorkspaceMutationFn = Apollo.MutationFunction<SignUpInWorksp
|
||||
* captchaToken: // value for 'captchaToken'
|
||||
* workspaceId: // value for 'workspaceId'
|
||||
* locale: // value for 'locale'
|
||||
* verifyEmailNextPath: // value for 'verifyEmailNextPath'
|
||||
* verifyEmailRedirectPath: // value for 'verifyEmailRedirectPath'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@@ -161,7 +161,7 @@ export type AuthToken = {
|
||||
|
||||
export type AuthTokenPair = {
|
||||
__typename?: 'AuthTokenPair';
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
refreshToken: AuthToken;
|
||||
};
|
||||
|
||||
@@ -1123,6 +1123,7 @@ export type Mutation = {
|
||||
getAuthorizationUrlForSSO: GetAuthorizationUrlForSsoOutput;
|
||||
getLoginTokenFromCredentials: LoginToken;
|
||||
getLoginTokenFromEmailVerificationToken: GetLoginTokenFromEmailVerificationTokenOutput;
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken: AvailableWorkspacesAndAccessTokensOutput;
|
||||
impersonate: ImpersonateOutput;
|
||||
initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
@@ -1392,8 +1393,10 @@ export type MutationGetAuthorizationUrlForSsoArgs = {
|
||||
export type MutationGetLoginTokenFromCredentialsArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
origin: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1405,6 +1408,13 @@ export type MutationGetLoginTokenFromEmailVerificationTokenArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationGetWorkspaceAgnosticTokenFromEmailVerificationTokenArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
emailVerificationToken: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationImpersonateArgs = {
|
||||
userId: Scalars['String'];
|
||||
workspaceId: Scalars['String'];
|
||||
@@ -1469,14 +1479,18 @@ export type MutationSendInvitationsArgs = {
|
||||
export type MutationSignInArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationSignUpArgs = {
|
||||
captchaToken?: InputMaybe<Scalars['String']>;
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1485,7 +1499,7 @@ export type MutationSignUpInWorkspaceArgs = {
|
||||
email: Scalars['String'];
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
password: Scalars['String'];
|
||||
verifyEmailNextPath?: InputMaybe<Scalars['String']>;
|
||||
verifyEmailRedirectPath?: InputMaybe<Scalars['String']>;
|
||||
workspaceId?: InputMaybe<Scalars['String']>;
|
||||
workspaceInviteHash?: InputMaybe<Scalars['String']>;
|
||||
workspacePersonalInviteToken?: InputMaybe<Scalars['String']>;
|
||||
|
||||
+9
-8
@@ -66,12 +66,12 @@ const setupMockUseParams = (objectNamePlural?: string) => {
|
||||
jest.mock('recoil');
|
||||
const setupMockRecoil = (
|
||||
objectNamePlural?: string,
|
||||
verifyEmailNextPath?: string,
|
||||
verifyEmailRedirectPath?: string,
|
||||
) => {
|
||||
jest
|
||||
.mocked(useRecoilValue)
|
||||
.mockReturnValueOnce([{ namePlural: objectNamePlural ?? '' }])
|
||||
.mockReturnValueOnce(verifyEmailNextPath);
|
||||
.mockReturnValueOnce(verifyEmailRedirectPath);
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
@@ -83,7 +83,7 @@ const testCases: {
|
||||
res: string | undefined;
|
||||
objectNamePluralFromParams?: string;
|
||||
objectNamePluralFromMetadata?: string;
|
||||
verifyEmailNextPath?: string;
|
||||
verifyEmailRedirectPath?: string;
|
||||
}[] = [
|
||||
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
|
||||
{ loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' },
|
||||
@@ -126,9 +126,9 @@ const testCases: {
|
||||
{ loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined },
|
||||
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailNextPath: '/nextPath?key=value', res: '/nextPath?key=value' },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailNextPath: '/nextPath?key=value', res: undefined },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace },
|
||||
{ loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile },
|
||||
@@ -330,7 +330,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
|
||||
isLoggedIn,
|
||||
objectNamePluralFromParams,
|
||||
objectNamePluralFromMetadata,
|
||||
verifyEmailNextPath,
|
||||
verifyEmailRedirectPath,
|
||||
res,
|
||||
}) => {
|
||||
setupMockIsMatchingLocation(loc);
|
||||
@@ -338,7 +338,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
|
||||
setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended);
|
||||
setupMockIsLogged(isLoggedIn);
|
||||
setupMockUseParams(objectNamePluralFromParams);
|
||||
setupMockRecoil(objectNamePluralFromMetadata, verifyEmailNextPath);
|
||||
setupMockRecoil(objectNamePluralFromMetadata, verifyEmailRedirectPath);
|
||||
|
||||
expect(usePageChangeEffectNavigateLocation()).toEqual(res);
|
||||
},
|
||||
@@ -351,7 +351,8 @@ describe('usePageChangeEffectNavigateLocation', () => {
|
||||
['isWorkspaceSuspended:true', 'isWorkspaceSuspended:false']
|
||||
.length) +
|
||||
['nonExistingObjectInParam', 'existingObjectInParam:false'].length +
|
||||
['caseWithRedirectionToVerifyEmailNextPath', 'caseWithout'].length,
|
||||
['caseWithRedirectionToVerifyEmailRedirectPath', 'caseWithout']
|
||||
.length,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { verifyEmailNextPathState } from '@/app/states/verifyEmailNextPathState';
|
||||
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
|
||||
import { useIsLogged } from '@/auth/hooks/useIsLogged';
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
|
||||
@@ -48,7 +48,7 @@ export const usePageChangeEffectNavigateLocation = () => {
|
||||
const objectMetadataItem = objectMetadataItems?.find(
|
||||
(objectMetadataItem) => objectMetadataItem.namePlural === objectNamePlural,
|
||||
);
|
||||
const verifyEmailNextPath = useRecoilValue(verifyEmailNextPathState);
|
||||
const verifyEmailRedirectPath = useRecoilValue(verifyEmailRedirectPathState);
|
||||
|
||||
if (
|
||||
(!isLoggedIn || (isLoggedIn && !isOnAWorkspace)) &&
|
||||
@@ -71,9 +71,9 @@ export const usePageChangeEffectNavigateLocation = () => {
|
||||
) {
|
||||
if (
|
||||
isMatchingLocation(location, AppPath.VerifyEmail) &&
|
||||
isDefined(verifyEmailNextPath)
|
||||
isDefined(verifyEmailRedirectPath)
|
||||
) {
|
||||
return verifyEmailNextPath;
|
||||
return verifyEmailRedirectPath;
|
||||
}
|
||||
return AppPath.PlanRequired;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ jest.mock('@/auth/services/AuthService', () => {
|
||||
...initialAuthService,
|
||||
renewToken: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
accessToken: { token: 'newAccessToken', expiresAt: '' },
|
||||
accessOrWorkspaceAgnosticToken: {
|
||||
token: 'newAccessToken',
|
||||
expiresAt: '',
|
||||
},
|
||||
refreshToken: { token: 'newRefreshToken', expiresAt: '' },
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -109,8 +109,8 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
|
||||
headers: {
|
||||
...headers,
|
||||
...options.headers,
|
||||
authorization: tokenPair.accessToken.token
|
||||
? `Bearer ${tokenPair.accessToken.token}`
|
||||
authorization: tokenPair.accessOrWorkspaceAgnosticToken.token
|
||||
? `Bearer ${tokenPair.accessOrWorkspaceAgnosticToken.token}`
|
||||
: '',
|
||||
...(this.currentWorkspaceMember?.locale
|
||||
? { 'x-locale': this.currentWorkspaceMember.locale }
|
||||
|
||||
@@ -3,5 +3,8 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const hasTokenPair = () => {
|
||||
const tokenPair = getTokenPair();
|
||||
return isDefined(tokenPair) && isDefined(tokenPair.accessToken?.token);
|
||||
return (
|
||||
isDefined(tokenPair) &&
|
||||
isDefined(tokenPair.accessOrWorkspaceAgnosticToken?.token)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const verifyEmailNextPathState = createState<string | undefined>({
|
||||
key: 'verifyEmailNextPathState',
|
||||
defaultValue: undefined,
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const verifyEmailRedirectPathState = createState<string | undefined>({
|
||||
key: 'verifyEmailRedirectPathState',
|
||||
defaultValue: undefined,
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { AppPath } from '@/types/AppPath';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
|
||||
import { verifyEmailNextPathState } from '@/app/states/verifyEmailNextPathState';
|
||||
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
|
||||
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
|
||||
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
@@ -15,24 +15,31 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { EmailVerificationSent } from '../sign-in-up/components/EmailVerificationSent';
|
||||
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
|
||||
|
||||
export const VerifyEmailEffect = () => {
|
||||
const { getLoginTokenFromEmailVerificationToken } = useAuth();
|
||||
const {
|
||||
getLoginTokenFromEmailVerificationToken,
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken,
|
||||
} = useAuth();
|
||||
|
||||
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
const setVerifyEmailNextPath = useSetRecoilState(verifyEmailNextPathState);
|
||||
const setVerifyEmailRedirectPath = useSetRecoilState(
|
||||
verifyEmailRedirectPathState,
|
||||
);
|
||||
|
||||
const email = searchParams.get('email');
|
||||
const emailVerificationToken = searchParams.get('emailVerificationToken');
|
||||
const verifyEmailNextPath = searchParams.get('nextPath');
|
||||
const verifyEmailRedirectPath = searchParams.get('nextPath');
|
||||
|
||||
const navigate = useNavigateApp();
|
||||
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
|
||||
const { verifyLoginToken } = useVerifyLogin();
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
const { t } = useLingui();
|
||||
useEffect(() => {
|
||||
@@ -47,19 +54,30 @@ export const VerifyEmailEffect = () => {
|
||||
return navigate(AppPath.SignInUp);
|
||||
}
|
||||
|
||||
const successSnackbarParams = {
|
||||
message: t`Email verified.`,
|
||||
options: {
|
||||
dedupeKey: 'email-verification-dedupe-key',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
if (!isOnAWorkspace) {
|
||||
await getWorkspaceAgnosticTokenFromEmailVerificationToken(
|
||||
emailVerificationToken,
|
||||
email,
|
||||
);
|
||||
|
||||
return enqueueSuccessSnackBar(successSnackbarParams);
|
||||
}
|
||||
|
||||
const { loginToken, workspaceUrls } =
|
||||
await getLoginTokenFromEmailVerificationToken(
|
||||
emailVerificationToken,
|
||||
email,
|
||||
);
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Email verified.`,
|
||||
options: {
|
||||
dedupeKey: 'email-verification-dedupe-key',
|
||||
},
|
||||
});
|
||||
enqueueSuccessSnackBar(successSnackbarParams);
|
||||
|
||||
const workspaceUrl = getWorkspaceUrl(workspaceUrls);
|
||||
if (workspaceUrl.slice(0, -1) !== window.location.origin) {
|
||||
@@ -68,11 +86,11 @@ export const VerifyEmailEffect = () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(verifyEmailNextPath)) {
|
||||
setVerifyEmailNextPath(verifyEmailNextPath);
|
||||
if (isDefined(verifyEmailRedirectPath)) {
|
||||
setVerifyEmailRedirectPath(verifyEmailRedirectPath);
|
||||
}
|
||||
|
||||
verifyLoginToken(loginToken.token);
|
||||
return verifyLoginToken(loginToken.token);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
...(error instanceof ApolloError
|
||||
@@ -95,7 +113,6 @@ export const VerifyEmailEffect = () => {
|
||||
};
|
||||
|
||||
verifyEmailToken();
|
||||
|
||||
// Verify email only needs to run once at mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -7,9 +7,9 @@ export const AUTH_TOKEN = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const AUTH_TOKENS = gql`
|
||||
fragment AuthTokensFragment on AuthTokenPair {
|
||||
accessToken {
|
||||
export const AUTH_TOKEN_PAIR = gql`
|
||||
fragment AuthTokenPairFragment on AuthTokenPair {
|
||||
accessOrWorkspaceAgnosticToken {
|
||||
...AuthTokenFragment
|
||||
}
|
||||
refreshToken {
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_AUTH_TOKENS_FROM_LOGIN_TOKEN = gql`
|
||||
mutation GetAuthTokensFromLoginToken($loginToken: String!, $origin: String!) {
|
||||
mutation getAuthTokensFromLoginToken($loginToken: String!, $origin: String!) {
|
||||
getAuthTokensFromLoginToken(loginToken: $loginToken, origin: $origin) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export const GET_AUTH_TOKENS_FROM_OTP = gql`
|
||||
origin: $origin
|
||||
) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_WORKSPACE_AGNOSTIC_TOKEN_FROM_EMAIL_VERIFICATION_TOKEN = gql`
|
||||
mutation GetWorkspaceAgnosticTokenFromEmailVerificationToken(
|
||||
$emailVerificationToken: String!
|
||||
$email: String!
|
||||
$captchaToken: String
|
||||
) {
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken(
|
||||
emailVerificationToken: $emailVerificationToken
|
||||
email: $email
|
||||
captchaToken: $captchaToken
|
||||
) {
|
||||
availableWorkspaces {
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -4,7 +4,7 @@ export const RENEW_TOKEN = gql`
|
||||
mutation RenewToken($appToken: String!) {
|
||||
renewToken(appToken: $appToken) {
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const SIGN_IN = gql`
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const SIGN_UP = gql`
|
||||
mutation SignUp($email: String!, $password: String!, $captchaToken: String) {
|
||||
signUp(email: $email, password: $password, captchaToken: $captchaToken) {
|
||||
mutation SignUp(
|
||||
$email: String!
|
||||
$password: String!
|
||||
$captchaToken: String
|
||||
$locale: String
|
||||
$verifyEmailRedirectPath: String
|
||||
) {
|
||||
signUp(
|
||||
email: $email
|
||||
password: $password
|
||||
captchaToken: $captchaToken
|
||||
locale: $locale
|
||||
verifyEmailRedirectPath: $verifyEmailRedirectPath
|
||||
) {
|
||||
availableWorkspaces {
|
||||
...AvailableWorkspacesFragment
|
||||
}
|
||||
tokens {
|
||||
...AuthTokensFragment
|
||||
...AuthTokenPairFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const SIGN_UP_IN_WORKSPACE = gql`
|
||||
$captchaToken: String
|
||||
$workspaceId: String
|
||||
$locale: String
|
||||
$verifyEmailNextPath: String
|
||||
$verifyEmailRedirectPath: String
|
||||
) {
|
||||
signUpInWorkspace(
|
||||
email: $email
|
||||
@@ -19,7 +19,7 @@ export const SIGN_UP_IN_WORKSPACE = gql`
|
||||
captchaToken: $captchaToken
|
||||
workspaceId: $workspaceId
|
||||
locale: $locale
|
||||
verifyEmailNextPath: $verifyEmailNextPath
|
||||
verifyEmailRedirectPath: $verifyEmailRedirectPath
|
||||
) {
|
||||
loginToken {
|
||||
...AuthTokenFragment
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
GetAuthTokensFromLoginTokenDocument,
|
||||
GetCurrentUserDocument,
|
||||
GetLoginTokenFromCredentialsDocument,
|
||||
SignUpDocument,
|
||||
SignUpInWorkspaceDocument,
|
||||
GetAuthTokensFromLoginTokenDocument,
|
||||
GetCurrentUserDocument,
|
||||
GetLoginTokenFromCredentialsDocument,
|
||||
SignUpDocument,
|
||||
SignUpInWorkspaceDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const queries = {
|
||||
@@ -51,7 +51,7 @@ export const results = {
|
||||
},
|
||||
getAuthTokensFromLoginToken: {
|
||||
tokens: {
|
||||
accessToken: { token, expiresAt: 'expiresAt' },
|
||||
accessOrWorkspaceAgnosticToken: { token, expiresAt: 'expiresAt' },
|
||||
refreshToken: { token, expiresAt: 'expiresAt' },
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('useIsLogged', () => {
|
||||
|
||||
await act(async () => {
|
||||
result.current.setTokenPair({
|
||||
accessToken: {
|
||||
accessOrWorkspaceAgnosticToken: {
|
||||
expiresAt: '',
|
||||
token: 'testToken',
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
useGetCurrentUserLazyQuery,
|
||||
useGetLoginTokenFromCredentialsMutation,
|
||||
useGetLoginTokenFromEmailVerificationTokenMutation,
|
||||
useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation,
|
||||
useSignInMutation,
|
||||
useSignUpInWorkspaceMutation,
|
||||
useSignUpMutation,
|
||||
@@ -72,7 +73,7 @@ import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefres
|
||||
import { workspaceAuthProvidersState } from '@/workspace/states/workspaceAuthProvidersState';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { iconsState } from 'twenty-ui/display';
|
||||
import { AuthToken } from '~/generated/graphql';
|
||||
@@ -117,6 +118,8 @@ export const useAuth = () => {
|
||||
useGetAuthTokensFromLoginTokenMutation();
|
||||
const [getLoginTokenFromEmailVerificationToken] =
|
||||
useGetLoginTokenFromEmailVerificationTokenMutation();
|
||||
const [getWorkspaceAgnosticTokenFromEmailVerificationToken] =
|
||||
useGetWorkspaceAgnosticTokenFromEmailVerificationTokenMutation();
|
||||
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||
const [getAuthTokensFromOtp] = useGetAuthTokensFromOtpMutation();
|
||||
|
||||
@@ -204,6 +207,111 @@ export const useAuth = () => {
|
||||
[navigate, client, goToRecoilSnapshot, setLastAuthenticateWorkspaceDomain],
|
||||
);
|
||||
|
||||
const loadCurrentUser = useCallback(async () => {
|
||||
const currentUserResult = await getCurrentUser({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
if (isDefined(currentUserResult.error)) {
|
||||
throw new Error(currentUserResult.error.message);
|
||||
}
|
||||
|
||||
const user = currentUserResult.data?.currentUser;
|
||||
|
||||
if (!user) {
|
||||
throw new Error('No current user result');
|
||||
}
|
||||
|
||||
let workspaceMember = null;
|
||||
|
||||
setCurrentUser(user);
|
||||
|
||||
if (isDefined(user.workspaceMembers)) {
|
||||
const workspaceMembers = user.workspaceMembers.map((workspaceMember) => ({
|
||||
...workspaceMember,
|
||||
colorScheme: workspaceMember.colorScheme as ColorScheme,
|
||||
locale: workspaceMember.locale ?? SOURCE_LOCALE,
|
||||
}));
|
||||
|
||||
setCurrentWorkspaceMembers(workspaceMembers);
|
||||
}
|
||||
|
||||
if (isDefined(user.availableWorkspaces)) {
|
||||
setAvailableWorkspaces(user.availableWorkspaces);
|
||||
}
|
||||
|
||||
if (isDefined(user.currentUserWorkspace)) {
|
||||
setCurrentUserWorkspace(user.currentUserWorkspace);
|
||||
}
|
||||
|
||||
if (isDefined(user.workspaceMember)) {
|
||||
workspaceMember = {
|
||||
...user.workspaceMember,
|
||||
colorScheme: user.workspaceMember?.colorScheme as ColorScheme,
|
||||
locale: user.workspaceMember?.locale ?? SOURCE_LOCALE,
|
||||
};
|
||||
|
||||
setCurrentWorkspaceMember(workspaceMember);
|
||||
|
||||
// TODO: factorize with UserProviderEffect
|
||||
setDateTimeFormat({
|
||||
timeZone:
|
||||
workspaceMember.timeZone && workspaceMember.timeZone !== 'system'
|
||||
? workspaceMember.timeZone
|
||||
: detectTimeZone(),
|
||||
dateFormat: isDefined(user.workspaceMember.dateFormat)
|
||||
? getDateFormatFromWorkspaceDateFormat(
|
||||
user.workspaceMember.dateFormat,
|
||||
)
|
||||
: DateFormat[detectDateFormat()],
|
||||
timeFormat: isDefined(user.workspaceMember.timeFormat)
|
||||
? getTimeFormatFromWorkspaceTimeFormat(
|
||||
user.workspaceMember.timeFormat,
|
||||
)
|
||||
: TimeFormat[detectTimeFormat()],
|
||||
});
|
||||
dynamicActivate(
|
||||
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE,
|
||||
);
|
||||
}
|
||||
|
||||
const workspace = user.currentWorkspace ?? null;
|
||||
|
||||
setCurrentWorkspace(workspace);
|
||||
|
||||
if (isDefined(workspace) && isOnAWorkspace) {
|
||||
setLastAuthenticateWorkspaceDomain({
|
||||
workspaceId: workspace.id,
|
||||
workspaceUrl: getWorkspaceUrl(workspace.workspaceUrls),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
workspaceMember,
|
||||
workspace,
|
||||
};
|
||||
}, [
|
||||
getCurrentUser,
|
||||
isOnAWorkspace,
|
||||
setCurrentUser,
|
||||
setCurrentUserWorkspace,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
setCurrentWorkspaceMembers,
|
||||
setDateTimeFormat,
|
||||
setLastAuthenticateWorkspaceDomain,
|
||||
setAvailableWorkspaces,
|
||||
]);
|
||||
|
||||
const handleSetAuthTokens = useCallback(
|
||||
(tokens: AuthTokenPair) => {
|
||||
setTokenPair(tokens);
|
||||
cookieStorage.setItem('tokenPair', JSON.stringify(tokens));
|
||||
},
|
||||
[setTokenPair],
|
||||
);
|
||||
|
||||
const handleGetLoginTokenFromCredentials = useCallback(
|
||||
async (email: string, password: string, captchaToken?: string) => {
|
||||
try {
|
||||
@@ -268,109 +376,48 @@ export const useAuth = () => {
|
||||
[getLoginTokenFromEmailVerificationToken, origin],
|
||||
);
|
||||
|
||||
const loadCurrentUser = useCallback(async () => {
|
||||
const currentUserResult = await getCurrentUser({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const handleGetWorkspaceAgnosticTokenFromEmailVerificationToken = useCallback(
|
||||
async (
|
||||
emailVerificationToken: string,
|
||||
email: string,
|
||||
captchaToken?: string,
|
||||
) => {
|
||||
const { data, errors } =
|
||||
await getWorkspaceAgnosticTokenFromEmailVerificationToken({
|
||||
variables: {
|
||||
email,
|
||||
emailVerificationToken,
|
||||
captchaToken,
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(currentUserResult.error)) {
|
||||
throw new Error(currentUserResult.error.message);
|
||||
}
|
||||
if (isDefined(errors)) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
const user = currentUserResult.data?.currentUser;
|
||||
if (!data?.getWorkspaceAgnosticTokenFromEmailVerificationToken) {
|
||||
throw new Error('No workspace agnostic token in result');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new Error('No current user result');
|
||||
}
|
||||
|
||||
let workspaceMember = null;
|
||||
|
||||
setCurrentUser(user);
|
||||
|
||||
if (isDefined(user.workspaceMembers)) {
|
||||
const workspaceMembers = user.workspaceMembers.map((workspaceMember) => ({
|
||||
...workspaceMember,
|
||||
colorScheme: workspaceMember.colorScheme as ColorScheme,
|
||||
locale: workspaceMember.locale ?? 'en',
|
||||
}));
|
||||
|
||||
setCurrentWorkspaceMembers(workspaceMembers);
|
||||
}
|
||||
|
||||
if (isDefined(user.availableWorkspaces)) {
|
||||
setAvailableWorkspaces(user.availableWorkspaces);
|
||||
}
|
||||
|
||||
if (isDefined(user.currentUserWorkspace)) {
|
||||
setCurrentUserWorkspace(user.currentUserWorkspace);
|
||||
}
|
||||
|
||||
if (isDefined(user.workspaceMember)) {
|
||||
workspaceMember = {
|
||||
...user.workspaceMember,
|
||||
colorScheme: user.workspaceMember?.colorScheme as ColorScheme,
|
||||
locale: user.workspaceMember?.locale ?? 'en',
|
||||
};
|
||||
|
||||
setCurrentWorkspaceMember(workspaceMember);
|
||||
|
||||
// TODO: factorize with UserProviderEffect
|
||||
setDateTimeFormat({
|
||||
timeZone:
|
||||
workspaceMember.timeZone && workspaceMember.timeZone !== 'system'
|
||||
? workspaceMember.timeZone
|
||||
: detectTimeZone(),
|
||||
dateFormat: isDefined(user.workspaceMember.dateFormat)
|
||||
? getDateFormatFromWorkspaceDateFormat(
|
||||
user.workspaceMember.dateFormat,
|
||||
)
|
||||
: DateFormat[detectDateFormat()],
|
||||
timeFormat: isDefined(user.workspaceMember.timeFormat)
|
||||
? getTimeFormatFromWorkspaceTimeFormat(
|
||||
user.workspaceMember.timeFormat,
|
||||
)
|
||||
: TimeFormat[detectTimeFormat()],
|
||||
});
|
||||
dynamicActivate(
|
||||
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? 'en',
|
||||
handleSetAuthTokens(
|
||||
data.getWorkspaceAgnosticTokenFromEmailVerificationToken.tokens,
|
||||
);
|
||||
}
|
||||
|
||||
const workspace = user.currentWorkspace ?? null;
|
||||
const { user } = await loadCurrentUser();
|
||||
|
||||
setCurrentWorkspace(workspace);
|
||||
if (countAvailableWorkspaces(user.availableWorkspaces) === 0) {
|
||||
return await createWorkspace({ newTab: false });
|
||||
}
|
||||
|
||||
if (isDefined(workspace) && isOnAWorkspace) {
|
||||
setLastAuthenticateWorkspaceDomain({
|
||||
workspaceId: workspace.id,
|
||||
workspaceUrl: getWorkspaceUrl(workspace.workspaceUrls),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
workspaceMember,
|
||||
workspace,
|
||||
};
|
||||
}, [
|
||||
getCurrentUser,
|
||||
isOnAWorkspace,
|
||||
setCurrentUser,
|
||||
setCurrentUserWorkspace,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
setCurrentWorkspaceMembers,
|
||||
setDateTimeFormat,
|
||||
setLastAuthenticateWorkspaceDomain,
|
||||
setAvailableWorkspaces,
|
||||
]);
|
||||
|
||||
const handleSetAuthTokens = useCallback(
|
||||
(tokens: AuthTokenPair) => {
|
||||
setTokenPair(tokens);
|
||||
cookieStorage.setItem('tokenPair', JSON.stringify(tokens));
|
||||
setSignInUpStep(SignInUpStep.WorkspaceSelection);
|
||||
},
|
||||
[setTokenPair],
|
||||
[
|
||||
createWorkspace,
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken,
|
||||
handleSetAuthTokens,
|
||||
loadCurrentUser,
|
||||
setSignInUpStep,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSetLoginToken = useCallback(
|
||||
@@ -507,13 +554,24 @@ export const useAuth = () => {
|
||||
const handleCredentialsSignUp = useCallback(
|
||||
async (email: string, password: string, captchaToken?: string) => {
|
||||
const signUpResult = await signUp({
|
||||
variables: { email, password, captchaToken },
|
||||
variables: {
|
||||
email,
|
||||
password,
|
||||
captchaToken,
|
||||
locale: i18n.locale ?? SOURCE_LOCALE,
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(signUpResult.errors)) {
|
||||
throw signUpResult.errors;
|
||||
}
|
||||
|
||||
if (isEmailVerificationRequired) {
|
||||
setSearchParams({ email });
|
||||
setSignInUpStep(SignInUpStep.EmailVerification);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!signUpResult.data?.signUp) {
|
||||
throw new Error('No signUp result');
|
||||
}
|
||||
@@ -529,6 +587,8 @@ export const useAuth = () => {
|
||||
setSignInUpStep(SignInUpStep.WorkspaceSelection);
|
||||
},
|
||||
[
|
||||
isEmailVerificationRequired,
|
||||
setSearchParams,
|
||||
handleSetAuthTokens,
|
||||
signUp,
|
||||
loadCurrentUser,
|
||||
@@ -561,14 +621,14 @@ export const useAuth = () => {
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
captchaToken,
|
||||
verifyEmailNextPath,
|
||||
verifyEmailRedirectPath,
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
workspaceInviteHash?: string;
|
||||
workspacePersonalInviteToken?: string;
|
||||
captchaToken?: string;
|
||||
verifyEmailNextPath?: string;
|
||||
verifyEmailRedirectPath?: string;
|
||||
}) => {
|
||||
const signUpInWorkspaceResult = await signUpInWorkspace({
|
||||
variables: {
|
||||
@@ -577,11 +637,11 @@ export const useAuth = () => {
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
captchaToken,
|
||||
locale: i18n.locale ?? 'en',
|
||||
locale: i18n.locale ?? SOURCE_LOCALE,
|
||||
...(workspacePublicData?.id
|
||||
? { workspaceId: workspacePublicData.id }
|
||||
: {}),
|
||||
verifyEmailNextPath,
|
||||
verifyEmailRedirectPath,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -712,7 +772,7 @@ export const useAuth = () => {
|
||||
}
|
||||
|
||||
if (!getAuthTokensFromOtpResult.data?.getAuthTokensFromOTP) {
|
||||
throw new Error('No getAuthTokensFromLoginToken result');
|
||||
throw new Error('No getAuthTokensFromOTP result');
|
||||
}
|
||||
|
||||
await handleLoadWorkspaceAfterAuthentication(
|
||||
@@ -724,6 +784,8 @@ export const useAuth = () => {
|
||||
|
||||
return {
|
||||
getLoginTokenFromCredentials: handleGetLoginTokenFromCredentials,
|
||||
getWorkspaceAgnosticTokenFromEmailVerificationToken:
|
||||
handleGetWorkspaceAgnosticTokenFromEmailVerificationToken,
|
||||
getLoginTokenFromEmailVerificationToken:
|
||||
handleGetLoginTokenFromEmailVerificationToken,
|
||||
getAuthTokensFromLoginToken: handleGetAuthTokensFromLoginToken,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { renewToken } from '@/auth/services/AuthService';
|
||||
enableFetchMocks();
|
||||
|
||||
const tokens = {
|
||||
accessToken: {
|
||||
accessOrWorkspaceAgnosticToken: {
|
||||
token: 'accessToken',
|
||||
expiresAt: 'expiresAt',
|
||||
},
|
||||
|
||||
@@ -130,7 +130,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
);
|
||||
}
|
||||
|
||||
const verifyEmailNextPath = buildAppPathWithQueryParams(
|
||||
const verifyEmailRedirectPath = buildAppPathWithQueryParams(
|
||||
AppPath.PlanRequired,
|
||||
await buildSearchParamsFromUrlSyncedStates(),
|
||||
);
|
||||
@@ -141,7 +141,7 @@ export const useSignInUp = (form: UseFormReturn<Form>) => {
|
||||
workspaceInviteHash,
|
||||
workspacePersonalInviteToken,
|
||||
captchaToken: token,
|
||||
verifyEmailNextPath,
|
||||
verifyEmailRedirectPath,
|
||||
});
|
||||
} catch (error: any) {
|
||||
enqueueErrorSnackBar({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
import { AuthTokenPair } from '~/generated/graphql';
|
||||
import { cookieStorageEffect } from '~/utils/recoil-effects';
|
||||
import { createState } from 'twenty-ui/utilities';
|
||||
|
||||
export const tokenPairState = createState<AuthTokenPair | null>({
|
||||
key: 'tokenPairState',
|
||||
@@ -11,7 +11,7 @@ export const tokenPairState = createState<AuthTokenPair | null>({
|
||||
{},
|
||||
{
|
||||
validateInitFn: (payload: AuthTokenPair) =>
|
||||
Boolean(payload['accessToken']),
|
||||
Boolean(payload['accessOrWorkspaceAgnosticToken']),
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
+4
-1
@@ -8,7 +8,10 @@ import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
|
||||
import { OnboardingStatus } from '~/generated/graphql';
|
||||
|
||||
const tokenPair = {
|
||||
accessToken: { token: 'accessToken', expiresAt: 'expiresAt' },
|
||||
accessOrWorkspaceAgnosticToken: {
|
||||
token: 'accessToken',
|
||||
expiresAt: 'expiresAt',
|
||||
},
|
||||
refreshToken: { token: 'refreshToken', expiresAt: 'expiresAt' },
|
||||
};
|
||||
const currentUser = {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { createClient } from 'graphql-sse';
|
||||
import { ON_DB_EVENT } from '@/subscription/graphql/subscriptions/onDbEvent';
|
||||
import { Subscription, SubscriptionOnDbEventArgs } from '~/generated/graphql';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { getTokenPair } from '@/apollo/utils/getTokenPair';
|
||||
import { ON_DB_EVENT } from '@/subscription/graphql/subscriptions/onDbEvent';
|
||||
import { createClient } from 'graphql-sse';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { Subscription, SubscriptionOnDbEventArgs } from '~/generated/graphql';
|
||||
|
||||
type OnDbEventArgs = SubscriptionOnDbEventArgs & {
|
||||
skip?: boolean;
|
||||
@@ -25,12 +25,12 @@ export const useOnDbEvent = ({
|
||||
return createClient({
|
||||
url: `${REACT_APP_SERVER_BASE_URL}/graphql`,
|
||||
headers: {
|
||||
Authorization: tokenPair?.accessToken.token
|
||||
? `Bearer ${tokenPair?.accessToken.token}`
|
||||
Authorization: tokenPair?.accessOrWorkspaceAgnosticToken.token
|
||||
? `Bearer ${tokenPair?.accessOrWorkspaceAgnosticToken.token}`
|
||||
: '',
|
||||
},
|
||||
});
|
||||
}, [tokenPair?.accessToken.token]);
|
||||
}, [tokenPair?.accessOrWorkspaceAgnosticToken.token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (skip === true) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { verifyEmailNextPathState } from '@/app/states/verifyEmailNextPathState';
|
||||
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
|
||||
import { SubTitle } from '@/auth/components/SubTitle';
|
||||
import { Title } from '@/auth/components/Title';
|
||||
import { useAuth } from '@/auth/hooks/useAuth';
|
||||
@@ -101,11 +101,11 @@ export const ChooseYourPlan = () => {
|
||||
|
||||
const calendarBookingPageId = useRecoilValue(calendarBookingPageIdState);
|
||||
|
||||
const [verifyEmailNextPath, setVerifyEmailNextPath] = useRecoilState(
|
||||
verifyEmailNextPathState,
|
||||
const [verifyEmailRedirectPath, setVerifyEmailRedirectPath] = useRecoilState(
|
||||
verifyEmailRedirectPathState,
|
||||
);
|
||||
if (isDefined(verifyEmailNextPath)) {
|
||||
setVerifyEmailNextPath(undefined);
|
||||
if (isDefined(verifyEmailRedirectPath)) {
|
||||
setVerifyEmailRedirectPath(undefined);
|
||||
}
|
||||
const { data: plans } = useBillingBaseProductPricesQuery();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { formatter } from '@lingui/format-po';
|
||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
export default defineConfig({
|
||||
sourceLocale: 'en',
|
||||
sourceLocale: SOURCE_LOCALE,
|
||||
locales: Object.values(APP_LOCALES),
|
||||
pseudoLocale: 'pseudo-en',
|
||||
fallbackLocales: {
|
||||
|
||||
+3
-3
@@ -77,10 +77,10 @@ export class ApprovedAccessDomainService {
|
||||
lastName: sender.name.lastName,
|
||||
},
|
||||
serverUrl: this.twentyConfigService.get('SERVER_URL'),
|
||||
locale: 'en',
|
||||
locale: sender.locale,
|
||||
});
|
||||
const html = await render(emailTemplate);
|
||||
const text = await render(emailTemplate, {
|
||||
const html = render(emailTemplate);
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ import {
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AvailableWorkspacesAndAccessTokensOutput } from 'src/engine/core-modules/auth/dto/available-workspaces-and-access-tokens.output';
|
||||
import { GetAuthTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-auth-token-from-email-verification-token.input';
|
||||
import { GetAuthorizationUrlForSSOInput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.input';
|
||||
import { GetAuthorizationUrlForSSOOutput } from 'src/engine/core-modules/auth/dto/get-authorization-url-for-sso.output';
|
||||
import { GetLoginTokenFromEmailVerificationTokenInput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.input';
|
||||
import { GetLoginTokenFromEmailVerificationTokenOutput } from 'src/engine/core-modules/auth/dto/get-login-token-from-email-verification-token.output';
|
||||
import { SignUpOutput } from 'src/engine/core-modules/auth/dto/sign-up.output';
|
||||
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
|
||||
@@ -51,6 +51,7 @@ import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/re
|
||||
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
|
||||
import { SSOService } from 'src/engine/core-modules/sso/services/sso.service';
|
||||
import { TwoFactorAuthenticationVerificationInput } from 'src/engine/core-modules/two-factor-authentication/dto/two-factor-authentication-verification.input';
|
||||
import { TwoFactorAuthenticationExceptionFilter } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter';
|
||||
import { TwoFactorAuthenticationService } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication.service';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
@@ -67,7 +68,6 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { TwoFactorAuthenticationExceptionFilter } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter';
|
||||
|
||||
import { GetAuthTokensFromLoginTokenInput } from './dto/get-auth-tokens-from-login-token.input';
|
||||
import { LoginToken } from './dto/login-token.entity';
|
||||
@@ -213,7 +213,7 @@ export class AuthResolver {
|
||||
AuthProviderEnum.Password,
|
||||
),
|
||||
tokens: {
|
||||
accessToken:
|
||||
accessOrWorkspaceAgnosticToken:
|
||||
await this.workspaceAgnosticTokenService.generateWorkspaceAgnosticToken(
|
||||
{
|
||||
userId: user.id,
|
||||
@@ -233,13 +233,13 @@ export class AuthResolver {
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
async getLoginTokenFromEmailVerificationToken(
|
||||
@Args()
|
||||
getLoginTokenFromEmailVerificationTokenInput: GetLoginTokenFromEmailVerificationTokenInput,
|
||||
getAuthTokenFromEmailVerificationTokenInput: GetAuthTokenFromEmailVerificationTokenInput,
|
||||
@Args('origin') origin: string,
|
||||
@AuthProvider() authProvider: AuthProviderEnum,
|
||||
) {
|
||||
const appToken =
|
||||
await this.emailVerificationTokenService.validateEmailVerificationTokenOrThrow(
|
||||
getLoginTokenFromEmailVerificationTokenInput,
|
||||
getAuthTokenFromEmailVerificationTokenInput,
|
||||
);
|
||||
|
||||
const workspace =
|
||||
@@ -264,6 +264,50 @@ export class AuthResolver {
|
||||
return { loginToken, workspaceUrls };
|
||||
}
|
||||
|
||||
@Mutation(() => AvailableWorkspacesAndAccessTokensOutput)
|
||||
@UseGuards(PublicEndpointGuard)
|
||||
async getWorkspaceAgnosticTokenFromEmailVerificationToken(
|
||||
@Args()
|
||||
getAuthTokenFromEmailVerificationTokenInput: GetAuthTokenFromEmailVerificationTokenInput,
|
||||
@AuthProvider() authProvider: AuthProviderEnum,
|
||||
) {
|
||||
const appToken =
|
||||
await this.emailVerificationTokenService.validateEmailVerificationTokenOrThrow(
|
||||
getAuthTokenFromEmailVerificationTokenInput,
|
||||
);
|
||||
|
||||
await this.userService.markEmailAsVerified(appToken.user.id);
|
||||
await this.appTokenRepository.remove(appToken);
|
||||
|
||||
const availableWorkspaces =
|
||||
await this.userWorkspaceService.findAvailableWorkspacesByEmail(
|
||||
appToken.user.email,
|
||||
);
|
||||
|
||||
return {
|
||||
availableWorkspaces:
|
||||
await this.userWorkspaceService.setLoginTokenToAvailableWorkspacesWhenAuthProviderMatch(
|
||||
availableWorkspaces,
|
||||
appToken.user,
|
||||
authProvider,
|
||||
),
|
||||
tokens: {
|
||||
accessOrWorkspaceAgnosticToken:
|
||||
await this.workspaceAgnosticTokenService.generateWorkspaceAgnosticToken(
|
||||
{
|
||||
userId: appToken.user.id,
|
||||
authProvider: AuthProviderEnum.Password,
|
||||
},
|
||||
),
|
||||
refreshToken: await this.refreshTokenService.generateRefreshToken({
|
||||
userId: appToken.user.id,
|
||||
authProvider: AuthProviderEnum.Password,
|
||||
targetedTokenType: JwtTokenTypeEnum.WORKSPACE_AGNOSTIC,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation(() => AuthTokens)
|
||||
@UseGuards(CaptchaGuard, PublicEndpointGuard)
|
||||
async getAuthTokensFromOTP(
|
||||
@@ -321,6 +365,14 @@ export class AuthResolver {
|
||||
user.email,
|
||||
);
|
||||
|
||||
await this.emailVerificationService.sendVerificationEmail(
|
||||
user.id,
|
||||
user.email,
|
||||
undefined,
|
||||
signUpInput.locale ?? SOURCE_LOCALE,
|
||||
signUpInput.verifyEmailRedirectPath,
|
||||
);
|
||||
|
||||
return {
|
||||
availableWorkspaces:
|
||||
await this.userWorkspaceService.setLoginTokenToAvailableWorkspacesWhenAuthProviderMatch(
|
||||
@@ -329,7 +381,7 @@ export class AuthResolver {
|
||||
AuthProviderEnum.Password,
|
||||
),
|
||||
tokens: {
|
||||
accessToken:
|
||||
accessOrWorkspaceAgnosticToken:
|
||||
await this.workspaceAgnosticTokenService.generateWorkspaceAgnosticToken(
|
||||
{
|
||||
userId: user.id,
|
||||
@@ -402,7 +454,7 @@ export class AuthResolver {
|
||||
user.email,
|
||||
workspace,
|
||||
signUpInput.locale ?? SOURCE_LOCALE,
|
||||
signUpInput.verifyEmailNextPath,
|
||||
signUpInput.verifyEmailRedirectPath,
|
||||
);
|
||||
|
||||
const loginToken = await this.loginTokenService.generateLoginToken(
|
||||
@@ -508,6 +560,8 @@ export class AuthResolver {
|
||||
|
||||
const user = await this.userService.getUserByEmail(email);
|
||||
|
||||
await this.authService.checkIsEmailVerified(user.isEmailVerified);
|
||||
|
||||
const currentUserWorkspace =
|
||||
await this.userWorkspaceService.getUserWorkspaceForUserOrThrow({
|
||||
userId: user.id,
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
@ObjectType()
|
||||
export class ExchangeAuthCode {
|
||||
@Field(() => AuthToken)
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
|
||||
@Field(() => AuthToken)
|
||||
refreshToken: AuthToken;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { ArgsType, Field } from '@nestjs/graphql';
|
||||
import { IsNotEmpty, IsString, IsOptional } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class GetLoginTokenFromEmailVerificationTokenInput {
|
||||
export class GetAuthTokenFromEmailVerificationTokenInput {
|
||||
@Field(() => String)
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@@ -43,5 +43,5 @@ export class SignUpInput {
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
verifyEmailNextPath?: string;
|
||||
verifyEmailRedirectPath?: string;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class ApiKeyToken {
|
||||
@ObjectType()
|
||||
export class AuthTokenPair {
|
||||
@Field(() => AuthToken)
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
|
||||
@Field(() => AuthToken)
|
||||
refreshToken: AuthToken;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||
|
||||
@ArgsType()
|
||||
export class UserCredentialsInput {
|
||||
@@ -18,4 +19,14 @@ export class UserCredentialsInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
captchaToken?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
locale?: keyof typeof APP_LOCALES;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
verifyEmailRedirectPath?: string;
|
||||
}
|
||||
|
||||
@@ -174,18 +174,22 @@ export class AuthService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.checkIsEmailVerified(user.isEmailVerified);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async checkIsEmailVerified(isEmailVerified: boolean) {
|
||||
const isEmailVerificationRequired = this.twentyConfigService.get(
|
||||
'IS_EMAIL_VERIFICATION_REQUIRED',
|
||||
);
|
||||
|
||||
if (isEmailVerificationRequired && !user.isEmailVerified) {
|
||||
if (isEmailVerificationRequired && !isEmailVerified) {
|
||||
throw new AuthException(
|
||||
'Email is not verified',
|
||||
AuthExceptionCode.EMAIL_NOT_VERIFIED,
|
||||
);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private async validatePassword(
|
||||
@@ -296,7 +300,7 @@ export class AuthService {
|
||||
|
||||
return {
|
||||
tokens: {
|
||||
accessToken,
|
||||
accessOrWorkspaceAgnosticToken: accessToken,
|
||||
refreshToken,
|
||||
},
|
||||
};
|
||||
@@ -474,12 +478,12 @@ export class AuthService {
|
||||
locale: firstUserWorkspace.locale,
|
||||
});
|
||||
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
i18n.activate(firstUserWorkspace.locale);
|
||||
|
||||
this.emailService.send({
|
||||
await this.emailService.send({
|
||||
from: `${this.twentyConfigService.get(
|
||||
'EMAIL_FROM_NAME',
|
||||
)} <${this.twentyConfigService.get('EMAIL_FROM_ADDRESS')}>`,
|
||||
@@ -731,7 +735,7 @@ export class AuthService {
|
||||
pathname: '/welcome',
|
||||
searchParams: {
|
||||
tokenPair: JSON.stringify({
|
||||
accessToken:
|
||||
accessOrWorkspaceAgnosticToken:
|
||||
await this.workspaceAgnosticTokenService.generateWorkspaceAgnosticToken(
|
||||
{
|
||||
userId: user.id,
|
||||
|
||||
+2
-2
@@ -158,8 +158,8 @@ export class ResetPasswordService {
|
||||
|
||||
const emailTemplate = PasswordResetLinkEmail(emailData);
|
||||
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
i18n.activate(locale);
|
||||
|
||||
|
||||
+3
-3
@@ -3,13 +3,13 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
|
||||
import { RenewTokenService } from './renew-token.service';
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('RenewTokenService', () => {
|
||||
await service.generateTokensFromRefreshToken(mockRefreshToken);
|
||||
|
||||
expect(result).toEqual({
|
||||
accessToken: mockAccessToken,
|
||||
accessOrWorkspaceAgnosticToken: mockAccessToken,
|
||||
refreshToken: mockNewRefreshToken,
|
||||
});
|
||||
expect(refreshTokenService.verifyRefreshToken).toHaveBeenCalledWith(
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import {
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { RefreshTokenService } from 'src/engine/core-modules/auth/token/services/refresh-token.service';
|
||||
import { WorkspaceAgnosticTokenService } from 'src/engine/core-modules/auth/token/services/workspace-agnostic-token.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
|
||||
@Injectable()
|
||||
@@ -26,7 +26,7 @@ export class RenewTokenService {
|
||||
) {}
|
||||
|
||||
async generateTokensFromRefreshToken(token: string): Promise<{
|
||||
accessToken: AuthToken;
|
||||
accessOrWorkspaceAgnosticToken: AuthToken;
|
||||
refreshToken: AuthToken;
|
||||
}> {
|
||||
if (!token) {
|
||||
@@ -80,7 +80,7 @@ export class RenewTokenService {
|
||||
});
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
accessOrWorkspaceAgnosticToken: accessToken,
|
||||
refreshToken,
|
||||
};
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,8 +2,12 @@ import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class EmailVerificationException extends CustomException {
|
||||
declare code: EmailVerificationExceptionCode;
|
||||
constructor(message: string, code: EmailVerificationExceptionCode) {
|
||||
super(message, code);
|
||||
constructor(
|
||||
message: string,
|
||||
code: EmailVerificationExceptionCode,
|
||||
{ userFriendlyMessage }: { userFriendlyMessage?: string } = {},
|
||||
) {
|
||||
super(message, code, userFriendlyMessage ?? message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -46,7 +46,7 @@ export class EmailVerificationService {
|
||||
| WorkspaceSubdomainCustomDomainAndIsCustomDomainEnabledType
|
||||
| undefined,
|
||||
locale: keyof typeof APP_LOCALES,
|
||||
verifyEmailNextPath?: string,
|
||||
verifyEmailRedirectPath?: string,
|
||||
) {
|
||||
if (!this.twentyConfigService.get('IS_EMAIL_VERIFICATION_REQUIRED')) {
|
||||
return { success: false };
|
||||
@@ -60,8 +60,8 @@ export class EmailVerificationService {
|
||||
searchParams: {
|
||||
emailVerificationToken,
|
||||
email,
|
||||
...(isDefined(verifyEmailNextPath)
|
||||
? { nextPath: verifyEmailNextPath }
|
||||
...(isDefined(verifyEmailRedirectPath)
|
||||
? { nextPath: verifyEmailRedirectPath }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
@@ -79,13 +79,12 @@ export class EmailVerificationService {
|
||||
|
||||
const emailTemplate = SendEmailVerificationLinkEmail(emailData);
|
||||
|
||||
const html = await render(emailTemplate);
|
||||
const text = await render(emailTemplate, {
|
||||
const html = render(emailTemplate);
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
i18n.activate(locale);
|
||||
|
||||
await this.emailService.send({
|
||||
from: `${this.twentyConfigService.get(
|
||||
'EMAIL_FROM_NAME',
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
@@ -73,7 +73,7 @@ export class UserWorkspace {
|
||||
defaultAvatarUrl: string;
|
||||
|
||||
@Field(() => String, { nullable: false })
|
||||
@Column({ nullable: false, default: 'en', type: 'varchar' })
|
||||
@Column({ nullable: false, default: SOURCE_LOCALE, type: 'varchar' })
|
||||
locale: keyof typeof APP_LOCALES;
|
||||
|
||||
@Field()
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
import { SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
@@ -94,7 +95,7 @@ export class User {
|
||||
deletedAt: Date;
|
||||
|
||||
@Field(() => String, { nullable: false })
|
||||
@Column({ nullable: false, default: 'en' })
|
||||
@Column({ nullable: false, default: SOURCE_LOCALE })
|
||||
locale: string;
|
||||
|
||||
@OneToMany(() => AppToken, (appToken) => appToken.user, {
|
||||
|
||||
+2
-2
@@ -301,8 +301,8 @@ export class WorkspaceInvitationService {
|
||||
};
|
||||
|
||||
const emailTemplate = SendInviteLinkEmail(emailData);
|
||||
const html = await render(emailTemplate);
|
||||
const text = await render(emailTemplate, {
|
||||
const html = render(emailTemplate);
|
||||
const text = render(emailTemplate, {
|
||||
plainText: true,
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -122,8 +122,8 @@ export class CleanerWorkspaceService {
|
||||
locale: workspaceMember.locale,
|
||||
};
|
||||
const emailTemplate = WarnSuspendedWorkspaceEmail(emailData);
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
i18n.activate(workspaceMember.locale);
|
||||
|
||||
@@ -198,8 +198,8 @@ export class CleanerWorkspaceService {
|
||||
locale: workspaceMember.locale,
|
||||
};
|
||||
const emailTemplate = CleanSuspendedWorkspaceEmail(emailData);
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
const html = render(emailTemplate, { pretty: true });
|
||||
const text = render(emailTemplate, { plainText: true });
|
||||
|
||||
this.emailService.send({
|
||||
to: workspaceMember.userEmail,
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('AuthResolve (integration)', () => {
|
||||
mutation GetAuthTokensFromLoginToken {
|
||||
getAuthTokensFromLoginToken(loginToken: "${loginToken}", origin: "${ORIGIN.toString()}") {
|
||||
tokens {
|
||||
accessToken {
|
||||
accessOrWorkspaceAgnosticToken {
|
||||
token
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ describe('AuthResolve (integration)', () => {
|
||||
expect(data).toBeDefined();
|
||||
expect(data.tokens).toBeDefined();
|
||||
|
||||
const accessToken = data.tokens.accessToken;
|
||||
const accessToken = data.tokens.accessOrWorkspaceAgnosticToken;
|
||||
|
||||
expect(accessToken).toBeDefined();
|
||||
expect(accessToken.token).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user