-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(*): allow to select auth providers + add multiworkspace with subdomain management #8656
Conversation
Replaced the 'IS_SIGN_UP_DISABLED' flag with 'IS_MULTIWORKSPACE_ENABLED' to control workspace creation. This change allows for conditional workspace setup based on the new environment variable. Also updated related service, resolver, and entity files.
Updated the IS_MULTIWORKSPACE_ENABLED setting description to indicate support for multiple workspaces. This update clarifies that a web server capable of managing subdomain wildcards is required.
Updated the .env.example file to replace the IS_SIGN_UP_DISABLED variable with IS_MULTIWORKSPACE_ENABLED. Removed the IS_SIGN_UP_DISABLED property from environment-variables.ts as it is no longer needed.
…nabled Removed isSignUpDisabledState and associated references, and replaced them with isMultiworkspaceEnabledState. Updated GraphQL queries and related components to reflect this change, and ensured multi-workspace functionality is enabled where applicable.
Corrected the casing of the `isMultiWorkspaceEnabled` variable in the NavigationDrawerHeader component. This ensures consistent naming conventions and avoids potential bugs related to variable misnaming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR introduces multi-workspace functionality by replacing the sign-up disabled flag with a multi-workspace enabled flag across the application.
- Inconsistent casing in state key naming (
isMultiworkspaceEnabled
vsisMultiWorkspaceEnabled
) in/packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts
- Default value mismatch between frontend (true) and server (false) for
isMultiWorkspaceEnabled
could cause inconsistent behavior - Removal of
signUpDisabled
state without clear alternative mechanism needs review for impact on user registration flows - Added note in documentation about wildcard subdomain requirement for multi-workspace feature
- Server-side logic in
sign-in-up.service.ts
now blocks creation of additional workspaces when multi-workspace is disabled
12 file(s) reviewed, 4 comment(s)
Edit PR Review Bot Settings | Greptile
packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts
Outdated
Show resolved
Hide resolved
packages/twenty-front/src/modules/client-config/states/isMultiWorkspaceEnabledState.ts
Outdated
Show resolved
Hide resolved
packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, one comment about default value
packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts
Show resolved
Hide resolved
Corrected the typo from isMultiworkspaceEnabled to isMultiWorkspaceEnabled. This change affects multiple files to ensure consistency across the codebase, including GraphQL queries, state management, and server configurations.
packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
Outdated
Show resolved
Hide resolved
Removed unused `appTokenRepository` injection and renamed variable `numberOfWorkspaces` to `workspacesCount` for clarity. This cleanup helps improve code readability and maintainability.
…kspace-flag # Conflicts: # packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx
Updated various utility file paths and refactored enums to a separate file. Renamed utility files for consistency and moved 'SignInUpMode' enum to its own type file. Also, removed deprecated workspace invitation and URL-related utility files.
…kspace-flag # Conflicts: # packages/twenty-front/src/generated/graphql.tsx # packages/twenty-front/src/modules/ui/navigation/navigation-drawer/hooks/useWorkspaceSwitching.ts # packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx
|
||
const { signInUpStep, continueWithEmail } = useSignInUp(form); | ||
|
||
const checkAuthProviders = useCallback(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to SignInUpWorkspaceScopeFormEffect
|
||
useEffect(() => { | ||
try { | ||
if (isDefined(workspacePublicData?.logo)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see if can leverage react-helmet
@@ -0,0 +1,4 @@ | |||
export enum SignInUpMode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filenaming
import { IconKey } from 'twenty-ui'; | ||
import { useListSsoIdentityProvidersByWorkspaceIdQuery } from '~/generated/graphql'; | ||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; | ||
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProviders.state'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no .state
|
||
const [updateWorkspace] = useUpdateWorkspaceMutation(); | ||
|
||
const isValidAuthProvider = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract this to util so we can test it easily and we the typing complexity is "hidden"
@@ -0,0 +1,4 @@ | |||
export type AuthProvidersKeys = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GOOGLE
MICROSOFT
isGoogleAuthEnabled
isMicrosoftAuthEnabled
map(authEnablekyey: AuthProvidersKeys): GOOGLE | MICROSOFT (
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not mandatory but I would have "avoided the complexity like this"
return url.toString(); | ||
}; | ||
|
||
export const redirectToHome = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file naming + split into multi files
picture?: string | null; | ||
fromSSO: boolean; | ||
isAuthEnabled?: ReturnType<(typeof workspaceValidator)['isAuthEnabled']>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isAuthProviderEnabled
@@ -53,6 +58,13 @@ export class GoogleAuthController { | |||
workspaceInviteHash, | |||
workspacePersonalInviteToken, | |||
fromSSO: true, | |||
isAuthEnabled: workspaceValidator.isAuthEnabled( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
authProvider: 'google' | 'microsoft'
); | ||
|
||
if (isAuthEnabled) | ||
workspaceValidator.validateAuth(isAuthEnabled, workspace); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
workspaceValidator.validateAuth(() => { isValid(authProvider), ${authProvider} not enabled)
import { User } from 'src/engine/core-modules/user/user.entity'; | ||
import { CustomException } from 'src/utils/custom-exception'; | ||
|
||
const assertIsExist = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertIsDefinedOrThrow
}; | ||
|
||
export const userValidator: { | ||
assertIsExist: typeof assertIsExist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we would avoid typeof usage usually to lower complexity
}, | ||
}); | ||
|
||
return appTokens.map(castAppTokenToWorkspaceInvitationUtil); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move cast to an util
@@ -144,4 +152,26 @@ export class WorkspaceResolver { | |||
hasValidEntrepriseKey(): boolean { | |||
return isDefined(this.environmentService.get('ENTERPRISE_KEY')); | |||
} | |||
|
|||
@Query(() => PublicWorkspaceDataOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could extract that to another resolver
exceptionToThrowCustom?: CustomException, | ||
) => boolean; | ||
|
||
const isAuthEnabled: IsAuthEnabled = (provider, exceptionToThrow) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see comments above
…kspace-flag # Conflicts: # packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUpForm.ts # packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx # packages/twenty-server/src/engine/core-modules/workspace-invitation/services/workspace-invitation.service.ts
…kspace-flag # Conflicts: # packages/twenty-front/src/generated-metadata/graphql.ts # packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
Refactored GraphQL types to include new structures such as ActivateWorkspaceOutput and AvailableWorkspaceOutput, and updated IdentityProviderType enum. Added GraphQL mutations for workspace activation and switching, enhancing workspace management functionality. Revised authentication-related structures for improved SSO integration and user management.
Thanks @AMoreaux for your contribution! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
(updates since last review)
This PR significantly expands the multi-workspace functionality with subdomain management and authentication provider configuration.
- Added workspace subdomain support in
/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts
with unique constraint and auth provider flags - Introduced new
DomainManagerService
in/packages/twenty-server/src/engine/core-modules/domain-manager/service/domain-manager.service.ts
for handling workspace URLs and subdomain management - Refactored authentication flow in
/packages/twenty-front/src/modules/auth/hooks/useAuth.ts
to support workspace-specific auth providers and subdomain-based routing - Added workspace validation utilities in
/packages/twenty-server/src/engine/core-modules/workspace/workspace.validate.ts
for auth provider and activation checks - Replaced environment variable
FRONT_BASE_URL
with granular configuration (FRONT_DOMAIN
,FRONT_PROTOCOL
,FRONT_PORT
) for better multi-workspace support
The changes appear comprehensive but should be carefully tested for subdomain handling and authentication flows across workspaces.
166 file(s) reviewed, 136 comment(s)
Edit PR Review Bot Settings | Greptile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Removing the JWT generation mutation without a clear replacement could break authentication flows. Ensure there is a new mechanism in place for JWT generation that supports multi-workspace functionality.
query GetPublicWorkspaceDataBySubdomain { | ||
getPublicWorkspaceDataBySubdomain { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: query lacks parameters to specify which subdomain to query
params.workspacePersonalInviteToken, | ||
); | ||
} | ||
const subdomain = getWorkspaceSubdomain; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: getWorkspaceSubdomain is being used as a value but appears to be a function - should be getWorkspaceSubdomain()
if (isDefined(workspace) && isTwentyWorkspaceSubdomain) { | ||
setLastAuthenticateWorkspaceState({ | ||
id: workspace.id, | ||
subdomain: workspace.subdomain, | ||
cookieAttributes: { | ||
domain: `.${urlManager.frontDomain}`, | ||
}, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: consider adding error handling in case workspace.subdomain is undefined when setting cookie domain
onChange={(value: string) => { | ||
onChange(value); | ||
if (isDefined(onChangeFromProps)) onChangeFromProps(value); | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: onChange handler could potentially trigger multiple re-renders since it's recreated on each render. Consider memoizing this function with useCallback.
if (provider === 'google' && workspace.isGoogleAuthEnabled) return true; | ||
if (provider === 'microsoft' && workspace.isMicrosoftAuthEnabled) | ||
return true; | ||
if (provider === 'password' && workspace.isPasswordAuthEnabled) return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: consider using a switch statement or object lookup for better maintainability with multiple providers
(data: unknown, ctx: ExecutionContext) => { | ||
const request = getRequest(ctx); | ||
|
||
return request.headers['origin']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: headers['origin'] could be undefined - consider adding type safety checks and default value
(data: unknown, ctx: ExecutionContext) => { | ||
const request = getRequest(ctx); | ||
|
||
return request.headers['origin']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: origin header should be validated against expected format to prevent header injection attacks
async validateToken(req: Request) { | ||
try { | ||
return await this.accessTokenService.validateToken(req); | ||
return await this.accessTokenService.validateTokenByRequest(req); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: validateToken method name doesn't match the actual call to validateTokenByRequest
it('should return false for a personal email', () => { | ||
expect(isWorkEmail('[email protected]')).toBe(false); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: should test multiple personal email providers (yahoo, hotmail, outlook etc) since implementation uses a set of providers
Summary
Add support for multi-workspace feature and adjust configurations and states accordingly.