From d84ff7321f80c2765d2a9332c4b541d685ab12d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 11:09:30 +0000 Subject: [PATCH 1/8] WIP --- .../hooks/authentication-service-hooks.ts | 19 +- .../hooks/community-authentication-hooks.ts | 10 +- .../authentication-service/index.ts | 152 ++-------------- .../authentication/new-user/create-user.ts | 168 ++++++++++++++++++ .../server/api/src/app/user/user-service.ts | 14 +- 5 files changed, 202 insertions(+), 161 deletions(-) create mode 100644 packages/server/api/src/app/authentication/new-user/create-user.ts diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts index 1bff4d6720..81caf7daee 100644 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts +++ b/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts @@ -1,4 +1,4 @@ -import { Project, ProjectMemberRole, User } from '@openops/shared'; +import { User } from '@openops/shared'; export enum Provider { EMAIL = 'EMAIL', @@ -8,16 +8,14 @@ export enum Provider { export type AuthenticationServiceHooks = { preSignIn(p: PreParams): Promise; preSignUp(p: PreSignUpParams): Promise; - postSignUp(p: PostParams): Promise; - postSignIn(p: PostParams): Promise; + postSignUp(p: PostParams): Promise; + postSignIn(p: PostParams): Promise; }; type PreSignUpParams = { name: string; email: string; password: string; - organizationId: string | null; - provider: Provider; }; type PreParams = { @@ -28,15 +26,4 @@ type PreParams = { type PostParams = { user: User; - tablesAccessToken: string; - tablesRefreshToken: string; - referringUserId?: string; -}; - -type PostResult = { - user: User; - project: Project; - token: string; - tablesRefreshToken: string; - projectRole: ProjectMemberRole | null; }; diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts index 054d78f660..49a1cbe64a 100644 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts +++ b/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts @@ -37,7 +37,7 @@ export const communityAuthenticationServiceHooks: AuthenticationServiceHooks = { }); } }, - async postSignUp({ user, tablesRefreshToken }) { + async postSignUp({ user }) { let organization = await organizationService.getOldestOrganization(); const adminUser = await userService.getUserByEmailOrFail({ @@ -66,16 +66,14 @@ export const communityAuthenticationServiceHooks: AuthenticationServiceHooks = { email: user.email, workspaceId: organization.tablesWorkspaceId, }); - - return getProjectAndToken(user, tablesRefreshToken); }, - async postSignIn({ user, tablesRefreshToken }) { - return getProjectAndToken(user, tablesRefreshToken); + async postSignIn() { + // Empty }, }; -async function getProjectAndToken( +export async function getProjectAndToken( user: User, tablesRefreshToken: string, ): Promise<{ diff --git a/packages/server/api/src/app/authentication/authentication-service/index.ts b/packages/server/api/src/app/authentication/authentication-service/index.ts index 17e37c5e30..b06a170123 100644 --- a/packages/server/api/src/app/authentication/authentication-service/index.ts +++ b/packages/server/api/src/app/authentication/authentication-service/index.ts @@ -1,52 +1,32 @@ import { authenticateUserInOpenOpsTables } from '@openops/common'; -import { logger, SharedSystemProp, system } from '@openops/server-shared'; import { ApplicationError, AuthenticationResponse, - EnvironmentType, ErrorCode, isNil, - OrganizationRole, User, UserId, UserStatus, } from '@openops/shared'; -import { QueryFailedError } from 'typeorm'; -import { flagService } from '../../flags/flag.service'; -import { openopsTables } from '../../openops-tables'; import { userService } from '../../user/user-service'; import { passwordHasher } from '../basic/password-hasher'; +import { createUser } from '../new-user/create-user'; import { authenticationServiceHooks as hooks } from './hooks'; import { Provider } from './hooks/authentication-service-hooks'; +import { getProjectAndToken } from './hooks/community-authentication-hooks'; export const authenticationService = { async signUp(params: SignUpParams): Promise { - const name = `${params.firstName} ${params.lastName}`.trim(); - await hooks.get().preSignUp({ - ...params, - name, - }); - - const { token, refresh_token } = await openopsTables.createUser({ - name, - email: params.email, - password: params.password, - authenticate: true, - }); + const { user, tablesRefreshToken } = await createUser(params); - const user = await createUser(params); - - return this.signUpResponse({ + await hooks.get().postSignUp({ user, - tablesAccessToken: token, - tablesRefreshToken: refresh_token, - referringUserId: params.referringUserId, }); + + return this.authResponse(user, tablesRefreshToken); }, async signIn(request: SignInParams): Promise { - await hooks.get().preSignIn(request); - const user = await userService.getByOrganizationAndEmail({ organizationId: request.organizationId, email: request.email, @@ -59,98 +39,30 @@ export const authenticationService = { userPassword: user.password, }); - const { token, refresh_token } = await authenticateUserInOpenOpsTables( + const { refresh_token } = await authenticateUserInOpenOpsTables( request.email, request.password, ); - return this.signInResponse({ - user, - tablesAccessToken: token, - tablesRefreshToken: refresh_token, - }); + return this.authResponse(user, refresh_token); }, - async signUpResponse({ - user, - tablesAccessToken, - tablesRefreshToken, - referringUserId, - }: SignUpResponseParams): Promise { - const authnResponse = await hooks.get().postSignUp({ - user, - tablesAccessToken, - tablesRefreshToken, - referringUserId, - }); - - const userWithoutPassword = removePasswordPropFromUser(authnResponse.user); + async authResponse( + user: User, + tablesRefreshToken: string, + ): Promise { + const projectContext = await getProjectAndToken(user, tablesRefreshToken); - await saveNewsLetterSubscriber(user); + const userWithoutPassword = removePasswordPropFromUser(projectContext.user); return { ...userWithoutPassword, - token: authnResponse.token, - projectId: authnResponse.project.id, - projectRole: authnResponse.projectRole, - tablesRefreshToken: authnResponse.tablesRefreshToken, + token: projectContext.token, + projectId: projectContext.project.id, + projectRole: projectContext.projectRole, + tablesRefreshToken: projectContext.tablesRefreshToken, }; }, - - async signInResponse({ - user, - tablesAccessToken, - tablesRefreshToken, - }: SignInResponseParams): Promise { - const authnResponse = await hooks.get().postSignIn({ - user, - tablesAccessToken, - tablesRefreshToken, - }); - - const userWithoutPassword = removePasswordPropFromUser(authnResponse.user); - - return { - ...userWithoutPassword, - token: authnResponse.token, - projectId: authnResponse.project.id, - projectRole: authnResponse.projectRole, - tablesRefreshToken: authnResponse.tablesRefreshToken, - }; - }, -}; - -const createUser = async (params: SignUpParams): Promise => { - try { - const newUser: NewUser = { - email: params.email, - organizationRole: OrganizationRole.MEMBER, - verified: params.verified, - status: UserStatus.ACTIVE, - firstName: params.firstName, - lastName: params.lastName, - trackEvents: params.trackEvents, - newsLetter: params.newsLetter, - password: params.password, - organizationId: params.organizationId, - }; - - const user = await userService.create(newUser); - - return user; - } catch (e: unknown) { - if (e instanceof QueryFailedError) { - throw new ApplicationError({ - code: ErrorCode.EXISTING_USER, - params: { - email: params.email, - organizationId: params.organizationId, - }, - }); - } - - throw e; - } }; const assertUserIsAllowedToSignIn: ( @@ -202,34 +114,6 @@ const removePasswordPropFromUser = (user: User): Omit => { return filteredUser; }; -async function saveNewsLetterSubscriber(user: User): Promise { - const isOrganizationUserOrNotSubscribed = - (!isNil(user.organizationId) && - !flagService.isCloudOrganization(user.organizationId)) || - !user.newsLetter; - const environment = system.get(SharedSystemProp.ENVIRONMENT); - if ( - isOrganizationUserOrNotSubscribed || - environment !== EnvironmentType.PRODUCTION - ) { - return; - } - try { - const response = await fetch('/addContact', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email: user.email }), - }); - return await response.json(); - } catch (error) { - logger.warn(error); - } -} - -type NewUser = Omit; - type SignUpParams = { email: string; password: string; diff --git a/packages/server/api/src/app/authentication/new-user/create-user.ts b/packages/server/api/src/app/authentication/new-user/create-user.ts new file mode 100644 index 0000000000..16239b98fd --- /dev/null +++ b/packages/server/api/src/app/authentication/new-user/create-user.ts @@ -0,0 +1,168 @@ +import { cryptoUtils } from '@openops/server-shared'; +import { + ApplicationError, + ErrorCode, + OrganizationRole, + User, + UserStatus, +} from '@openops/shared'; +import { QueryFailedError } from 'typeorm'; +import { openopsTables } from '../../openops-tables'; +import { userService } from '../../user/user-service'; +import { authenticationServiceHooks as hooks } from '../authentication-service/hooks'; +import { Provider } from '../authentication-service/hooks/authentication-service-hooks'; + +type NewUserParams = { + email: string; + firstName: string; + lastName: string; + trackEvents: boolean; + newsLetter: boolean; + verified: boolean; + organizationId: string | null; + referringUserId?: string; + provider: Provider; +}; + +type NewUserResponse = { + user: User; + tablesRefreshToken: string; +}; + +const assertValidSignUpParams = async ({ + email, + password, + name, +}: { + name: string; + email: string; + password: string; +}): Promise => { + await hooks.get().preSignUp({ + name, + email, + password, + }); +}; + +const createEditorUser = async ( + params: NewUserParams & { + password: string; + }, +): Promise => { + try { + const newUser = { + email: params.email, + organizationRole: OrganizationRole.MEMBER, + verified: params.verified, + status: UserStatus.ACTIVE, + firstName: params.firstName, + lastName: params.lastName, + trackEvents: params.trackEvents, + newsLetter: params.newsLetter, + password: params.password, + organizationId: params.organizationId, + }; + + return await userService.create(newUser); + } catch (e: unknown) { + if (e instanceof QueryFailedError) { + throw new ApplicationError({ + code: ErrorCode.EXISTING_USER, + params: { + email: params.email, + organizationId: params.organizationId, + }, + }); + } + + throw e; + } +}; + +async function createTablesUser( + name: string, + email: string, + password: string, +): Promise { + const { refresh_token } = await openopsTables.createUser({ + name, + email, + password, + authenticate: true, + }); + + return refresh_token; +} + +export async function createUser( + params: NewUserParams & { + password: string; + }, +): Promise { + const name = `${params.firstName} ${params.lastName}`.trim(); + await assertValidSignUpParams({ + ...params, + name, + }); + + const user = await createEditorUser(params); + + try { + const tablesRefreshToken = await createTablesUser( + name, + params.email, + params.password, + ); + + return { + user, + tablesRefreshToken, + }; + } catch (e: unknown) { + await userService.delete({ + id: user.id, + organizationId: user.organizationId, + }); + + throw e; + } +} + +export async function createUserWithRandomPassword( + params: NewUserParams, +): Promise { + const randomPassword = await cryptoUtils.generateRandomPassword(); + + const name = `${params.firstName} ${params.lastName}`.trim(); + await assertValidSignUpParams({ + ...params, + password: randomPassword, + name, + }); + + const user = await createEditorUser({ + ...params, + password: randomPassword, + }); + + try { + const tablesRefreshToken = await createTablesUser( + name, + params.email, + randomPassword, + ); + + return { + user, + tablesRefreshToken, + }; + } catch (e: unknown) { + await userService.delete({ + id: user.id, + organizationId: user.organizationId, + }); + + throw e; + } +} diff --git a/packages/server/api/src/app/user/user-service.ts b/packages/server/api/src/app/user/user-service.ts index 342fbe86d5..05b79a44cc 100644 --- a/packages/server/api/src/app/user/user-service.ts +++ b/packages/server/api/src/app/user/user-service.ts @@ -139,10 +139,14 @@ export const userService = { }, async delete({ id, organizationId }: DeleteParams): Promise { - await userRepo().delete({ - id, - organizationId, - }); + const organizationWhereQuery = organizationId ? { organizationId } : {}; + + await userRepo() + .createQueryBuilder() + .delete() + .where({ id }) + .andWhere(organizationWhereQuery) + .execute(); }, async getUserByEmailOrFail({ email }: { email: string }): Promise { @@ -266,7 +270,7 @@ export const userService = { type DeleteParams = { id: UserId; - organizationId: OrganizationId; + organizationId: OrganizationId | null; }; type ListParams = { From 754c252c57a6456b1974cfc002c96eb702c2cf34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 11:46:21 +0000 Subject: [PATCH 2/8] Add unit test --- .../new-user/create-user.test.ts | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 packages/server/api/test/unit/authentication/new-user/create-user.test.ts diff --git a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts new file mode 100644 index 0000000000..8bbda17b49 --- /dev/null +++ b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts @@ -0,0 +1,197 @@ +import { ErrorCode, OrganizationRole, UserStatus } from '@openops/shared'; +import { QueryFailedError } from 'typeorm'; + +const preSignUpMock = jest.fn(); +jest.mock( + '../../../../src/app/authentication/authentication-service/hooks', + () => { + return { + authenticationServiceHooks: { + get: () => ({ preSignUp: preSignUpMock }), + }, + }; + }, +); + +const createUserServiceMock = jest.fn(); +const deleteUserServiceMock = jest.fn(); +jest.mock('../../../../src/app/user/user-service', () => ({ + userService: { + create: createUserServiceMock, + delete: deleteUserServiceMock, + }, +})); + +const createTablesUserMock = jest.fn(); +jest.mock('../../../../src/app/openops-tables', () => ({ + openopsTables: { + createUser: createTablesUserMock, + }, +})); + +const generateRandomPasswordMock = jest.fn(); +jest.mock('@openops/server-shared', () => ({ + cryptoUtils: { + generateRandomPassword: generateRandomPasswordMock, + }, +})); + +import { + createUser, + createUserWithRandomPassword, +} from '../../../../src/app/authentication/new-user/create-user'; + +describe('create-user', () => { + const baseParams = { + email: 'john.doe@example.com', + firstName: 'John', + lastName: 'Doe', + trackEvents: true, + newsLetter: false, + verified: true, + organizationId: 'org-1', + referringUserId: 'user-x', + provider: 'email' as any, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates user and tables user successfully', async () => { + const createdUser = { + id: 'u1', + email: baseParams.email, + organizationId: baseParams.organizationId, + firstName: baseParams.firstName, + lastName: baseParams.lastName, + status: UserStatus.ACTIVE, + organizationRole: OrganizationRole.MEMBER, + verified: baseParams.verified, + trackEvents: baseParams.trackEvents, + newsLetter: baseParams.newsLetter, + }; + + createUserServiceMock.mockResolvedValue(createdUser); + createTablesUserMock.mockResolvedValue({ + refresh_token: 'tables-token', + }); + + const res = await createUser({ ...baseParams, password: 'P@ssw0rd' }); + + expect(preSignUpMock).toHaveBeenCalledWith({ + name: 'John Doe', + email: baseParams.email, + password: 'P@ssw0rd', + }); + + expect(createUserServiceMock).toHaveBeenCalledWith({ + email: baseParams.email, + organizationRole: OrganizationRole.MEMBER, + verified: baseParams.verified, + status: UserStatus.ACTIVE, + firstName: baseParams.firstName, + lastName: baseParams.lastName, + trackEvents: baseParams.trackEvents, + newsLetter: baseParams.newsLetter, + password: 'P@ssw0rd', + organizationId: baseParams.organizationId, + }); + + expect(createTablesUserMock).toHaveBeenCalledWith({ + name: 'John Doe', + email: baseParams.email, + password: 'P@ssw0rd', + authenticate: true, + }); + + expect(res).toEqual({ + user: createdUser, + tablesRefreshToken: 'tables-token', + }); + }); + + it('maps duplicate user error to ApplicationError with EXISTING_USER', async () => { + createUserServiceMock.mockRejectedValue( + new QueryFailedError('insert into users', [], new Error('duplicate')), + ); + + await expect( + createUser({ ...baseParams, password: 'abc' }), + ).rejects.toMatchObject({ + error: { + code: ErrorCode.EXISTING_USER, + params: { + email: baseParams.email, + organizationId: baseParams.organizationId, + }, + }, + }); + }); + + it('rolls back created user if tables creation fails', async () => { + const createdUser = { + id: 'u2', + organizationId: baseParams.organizationId, + }; + createUserServiceMock.mockResolvedValue(createdUser); + createTablesUserMock.mockRejectedValue(new Error('tables down')); + + await expect( + createUser({ ...baseParams, password: 'abc' }), + ).rejects.toBeInstanceOf(Error); + + expect(deleteUserServiceMock).toHaveBeenCalledWith({ + id: 'u2', + organizationId: baseParams.organizationId, + }); + }); + + it('creates user with random password successfully', async () => { + generateRandomPasswordMock.mockResolvedValue('Rand#123'); + + const createdUser = { + id: 'u3', + organizationId: baseParams.organizationId, + }; + createUserServiceMock.mockResolvedValue(createdUser); + createTablesUserMock.mockResolvedValue({ refresh_token: 't2' }); + + const res = await createUserWithRandomPassword(baseParams); + + expect(preSignUpMock).toHaveBeenCalledWith({ + name: 'John Doe', + email: baseParams.email, + password: 'Rand#123', + }); + + expect(createUserServiceMock).toHaveBeenCalledWith( + expect.objectContaining({ password: 'Rand#123' }), + ); + + expect(createTablesUserMock).toHaveBeenCalledWith( + expect.objectContaining({ password: 'Rand#123' }), + ); + + expect(res).toEqual({ user: createdUser, tablesRefreshToken: 't2' }); + }); + + it('rolls back user when tables creation fails with random password flow', async () => { + generateRandomPasswordMock.mockResolvedValue('Rand#XYZ'); + const createdUser = { + id: 'u4', + organizationId: baseParams.organizationId, + }; + createUserServiceMock.mockResolvedValue(createdUser); + createTablesUserMock.mockRejectedValue(new Error('tables err')); + + await expect( + createUserWithRandomPassword(baseParams), + ).rejects.toBeInstanceOf(Error); + + expect(deleteUserServiceMock).toHaveBeenCalledWith({ + id: 'u4', + organizationId: baseParams.organizationId, + }); + }); +}); From 899729d7585fc7273cfdbc24c040b599b16dbc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 11:54:26 +0000 Subject: [PATCH 3/8] Fix lint --- .../api/test/unit/authentication/new-user/create-user.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts index 8bbda17b49..262528ad67 100644 --- a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts +++ b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts @@ -51,6 +51,7 @@ describe('create-user', () => { verified: true, organizationId: 'org-1', referringUserId: 'user-x', + // eslint-disable-next-line @typescript-eslint/no-explicit-any provider: 'email' as any, }; From 26df2428f4807450ba47b8390d7045e9c48576fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 16:53:00 +0000 Subject: [PATCH 4/8] Move hooks to proper files --- .../hooks/authentication-service-hooks.ts | 29 ---- .../hooks/community-authentication-hooks.ts | 130 ------------------ .../authentication-service/hooks/index.ts | 14 -- .../authentication-service/index.ts | 24 +--- .../authentication.controller.ts | 2 +- .../context/create-project-auth-context.ts | 57 ++++++++ .../authentication/new-user/create-user.ts | 23 +++- .../new-user/organization-assignment.ts | 49 +++++++ .../api/src/app/database/seeds/dev-seeds.ts | 3 +- .../api/src/app/database/seeds/seed-admin.ts | 3 +- .../ce/authentication/signup.test.ts | 2 - packages/shared/src/index.ts | 1 + .../model/authentication-type.ts | 4 + 13 files changed, 134 insertions(+), 207 deletions(-) delete mode 100644 packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts delete mode 100644 packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts delete mode 100644 packages/server/api/src/app/authentication/authentication-service/hooks/index.ts create mode 100644 packages/server/api/src/app/authentication/context/create-project-auth-context.ts create mode 100644 packages/server/api/src/app/authentication/new-user/organization-assignment.ts create mode 100644 packages/shared/src/lib/authentication/model/authentication-type.ts diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts deleted file mode 100644 index 81caf7daee..0000000000 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { User } from '@openops/shared'; - -export enum Provider { - EMAIL = 'EMAIL', - FEDERATED = 'FEDERATED', -} - -export type AuthenticationServiceHooks = { - preSignIn(p: PreParams): Promise; - preSignUp(p: PreSignUpParams): Promise; - postSignUp(p: PostParams): Promise; - postSignIn(p: PostParams): Promise; -}; - -type PreSignUpParams = { - name: string; - email: string; - password: string; -}; - -type PreParams = { - email: string; - organizationId: string | null; - provider: Provider; -}; - -type PostParams = { - user: User; -}; diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts deleted file mode 100644 index 49a1cbe64a..0000000000 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { authenticateDefaultUserInOpenOpsTables } from '@openops/common'; -import { AppSystemProp, system } from '@openops/server-shared'; -import { - ApplicationError, - assertValidEmail, - assertValidPassword, - ErrorCode, - isEmpty, - isNil, - PrincipalType, - Project, - ProjectMemberRole, - User, -} from '@openops/shared'; -import { openopsTables } from '../../../openops-tables'; -import { organizationService } from '../../../organization/organization.service'; -import { projectService } from '../../../project/project-service'; -import { userService } from '../../../user/user-service'; -import { accessTokenManager } from '../../context/access-token-manager'; -import { AuthenticationServiceHooks } from './authentication-service-hooks'; - -export const communityAuthenticationServiceHooks: AuthenticationServiceHooks = { - async preSignIn() { - // Empty - }, - async preSignUp({ email, password, name }) { - assertValidEmail(email); - assertValidPassword(password); - - if (isEmpty(name)) { - throw new ApplicationError({ - code: ErrorCode.INVALID_NAME_FOR_USER, - params: { - name, - message: 'First name and last name were not provided correctly.', - }, - }); - } - }, - async postSignUp({ user }) { - let organization = await organizationService.getOldestOrganization(); - - const adminUser = await userService.getUserByEmailOrFail({ - email: system.getOrThrow(AppSystemProp.OPENOPS_ADMIN_EMAIL), - }); - - organization = !isNil(adminUser.organizationId) - ? await organizationService.getOne(adminUser.organizationId) - : organization; - - if (!organization) { - throw new ApplicationError({ - code: ErrorCode.ENTITY_NOT_FOUND, - params: { - message: 'Admin organization not found', - }, - }); - } - - await userService.addUserToOrganization({ - id: user.id, - organizationId: organization.id, - }); - - await addUserToDefaultWorkspace({ - email: user.email, - workspaceId: organization.tablesWorkspaceId, - }); - }, - - async postSignIn() { - // Empty - }, -}; - -export async function getProjectAndToken( - user: User, - tablesRefreshToken: string, -): Promise<{ - user: User; - project: Project; - token: string; - tablesRefreshToken: string; - projectRole: ProjectMemberRole; -}> { - const updatedUser = await userService.getOneOrFail({ id: user.id }); - - const project = await projectService.getOneForUser(updatedUser); - if (isNil(project)) { - throw new ApplicationError({ - code: ErrorCode.INVITATION_ONLY_SIGN_UP, - params: { - message: 'No project found for user', - }, - }); - } - - const organization = await organizationService.getOneOrThrow( - project.organizationId, - ); - - const token = await accessTokenManager.generateToken({ - id: user.id, - type: PrincipalType.USER, - projectId: project.id, - organization: { - id: organization.id, - }, - }); - - return { - user: updatedUser, - token, - project, - tablesRefreshToken, - projectRole: ProjectMemberRole.ADMIN, - }; -} - -async function addUserToDefaultWorkspace(values: { - email: string; - workspaceId: number; -}): Promise { - const { token: defaultToken } = - await authenticateDefaultUserInOpenOpsTables(); - - await openopsTables.addUserToWorkspace(defaultToken, { - ...values, - }); -} diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/index.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/index.ts deleted file mode 100644 index 385c282653..0000000000 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AuthenticationServiceHooks } from './authentication-service-hooks'; -import { communityAuthenticationServiceHooks } from './community-authentication-hooks'; - -let hooks = communityAuthenticationServiceHooks; - -export const authenticationServiceHooks = { - set(newHooks: AuthenticationServiceHooks): void { - hooks = newHooks; - }, - - get(): AuthenticationServiceHooks { - return hooks; - }, -}; diff --git a/packages/server/api/src/app/authentication/authentication-service/index.ts b/packages/server/api/src/app/authentication/authentication-service/index.ts index b06a170123..dc08a44c0e 100644 --- a/packages/server/api/src/app/authentication/authentication-service/index.ts +++ b/packages/server/api/src/app/authentication/authentication-service/index.ts @@ -4,24 +4,21 @@ import { AuthenticationResponse, ErrorCode, isNil, + Provider, User, - UserId, UserStatus, } from '@openops/shared'; import { userService } from '../../user/user-service'; import { passwordHasher } from '../basic/password-hasher'; +import { getProjectAndToken } from '../context/create-project-auth-context'; import { createUser } from '../new-user/create-user'; -import { authenticationServiceHooks as hooks } from './hooks'; -import { Provider } from './hooks/authentication-service-hooks'; -import { getProjectAndToken } from './hooks/community-authentication-hooks'; +import { assignDefaultOrganization } from '../new-user/organization-assignment'; export const authenticationService = { async signUp(params: SignUpParams): Promise { const { user, tablesRefreshToken } = await createUser(params); - await hooks.get().postSignUp({ - user, - }); + await assignDefaultOrganization(user); return this.authResponse(user, tablesRefreshToken); }, @@ -138,16 +135,3 @@ type AssertPasswordsMatchParams = { requestPassword: string; userPassword: string; }; - -type SignUpResponseParams = { - user: User; - tablesAccessToken: string; - tablesRefreshToken: string; - referringUserId?: UserId; -}; - -type SignInResponseParams = { - user: User; - tablesAccessToken: string; - tablesRefreshToken: string; -}; diff --git a/packages/server/api/src/app/authentication/authentication.controller.ts b/packages/server/api/src/app/authentication/authentication.controller.ts index 4bb96f64ee..9e9523d45d 100644 --- a/packages/server/api/src/app/authentication/authentication.controller.ts +++ b/packages/server/api/src/app/authentication/authentication.controller.ts @@ -8,6 +8,7 @@ import { ALL_PRINCIPAL_TYPES, OpsEdition, PrincipalType, + Provider, SignInRequest, SignUpRequest, } from '@openops/shared'; @@ -16,7 +17,6 @@ import { resolveOrganizationIdForAuthnRequest } from '../organization/organizati import { userService } from '../user/user-service'; import { analyticsAuthenticationService } from './analytics-authentication-service'; import { authenticationService } from './authentication-service'; -import { Provider } from './authentication-service/hooks/authentication-service-hooks'; import { removeAuthCookiesAndReply, setAuthCookiesAndReply, diff --git a/packages/server/api/src/app/authentication/context/create-project-auth-context.ts b/packages/server/api/src/app/authentication/context/create-project-auth-context.ts new file mode 100644 index 0000000000..61457aa16d --- /dev/null +++ b/packages/server/api/src/app/authentication/context/create-project-auth-context.ts @@ -0,0 +1,57 @@ +import { + ApplicationError, + ErrorCode, + isNil, + PrincipalType, + Project, + ProjectMemberRole, + User, +} from '@openops/shared'; +import { organizationService } from '../../organization/organization.service'; +import { projectService } from '../../project/project-service'; +import { userService } from '../../user/user-service'; +import { accessTokenManager } from './access-token-manager'; + +export async function getProjectAndToken( + user: User, + tablesRefreshToken: string, +): Promise<{ + user: User; + project: Project; + token: string; + tablesRefreshToken: string; + projectRole: ProjectMemberRole; +}> { + const updatedUser = await userService.getOneOrFail({ id: user.id }); + + const project = await projectService.getOneForUser(updatedUser); + if (isNil(project)) { + throw new ApplicationError({ + code: ErrorCode.INVITATION_ONLY_SIGN_UP, + params: { + message: 'No project found for user', + }, + }); + } + + const organization = await organizationService.getOneOrThrow( + project.organizationId, + ); + + const token = await accessTokenManager.generateToken({ + id: user.id, + type: PrincipalType.USER, + projectId: project.id, + organization: { + id: organization.id, + }, + }); + + return { + user: updatedUser, + token, + project, + tablesRefreshToken, + projectRole: ProjectMemberRole.ADMIN, + }; +} diff --git a/packages/server/api/src/app/authentication/new-user/create-user.ts b/packages/server/api/src/app/authentication/new-user/create-user.ts index 16239b98fd..a7a7f72c6e 100644 --- a/packages/server/api/src/app/authentication/new-user/create-user.ts +++ b/packages/server/api/src/app/authentication/new-user/create-user.ts @@ -1,16 +1,18 @@ import { cryptoUtils } from '@openops/server-shared'; import { ApplicationError, + assertValidEmail, + assertValidPassword, ErrorCode, + isEmpty, OrganizationRole, + Provider, User, UserStatus, } from '@openops/shared'; import { QueryFailedError } from 'typeorm'; import { openopsTables } from '../../openops-tables'; import { userService } from '../../user/user-service'; -import { authenticationServiceHooks as hooks } from '../authentication-service/hooks'; -import { Provider } from '../authentication-service/hooks/authentication-service-hooks'; type NewUserParams = { email: string; @@ -38,11 +40,18 @@ const assertValidSignUpParams = async ({ email: string; password: string; }): Promise => { - await hooks.get().preSignUp({ - name, - email, - password, - }); + assertValidEmail(email); + assertValidPassword(password); + + if (isEmpty(name)) { + throw new ApplicationError({ + code: ErrorCode.INVALID_NAME_FOR_USER, + params: { + name, + message: 'First name and last name were not provided correctly.', + }, + }); + } }; const createEditorUser = async ( diff --git a/packages/server/api/src/app/authentication/new-user/organization-assignment.ts b/packages/server/api/src/app/authentication/new-user/organization-assignment.ts new file mode 100644 index 0000000000..42d588d2aa --- /dev/null +++ b/packages/server/api/src/app/authentication/new-user/organization-assignment.ts @@ -0,0 +1,49 @@ +import { authenticateDefaultUserInOpenOpsTables } from '@openops/common'; +import { AppSystemProp, system } from '@openops/server-shared'; +import { ApplicationError, ErrorCode, isNil, User } from '@openops/shared'; +import { openopsTables } from '../../openops-tables'; +import { organizationService } from '../../organization/organization.service'; +import { userService } from '../../user/user-service'; + +export async function assignDefaultOrganization(user: User): Promise { + let organization = await organizationService.getOldestOrganization(); + + const adminUser = await userService.getUserByEmailOrFail({ + email: system.getOrThrow(AppSystemProp.OPENOPS_ADMIN_EMAIL), + }); + + organization = !isNil(adminUser.organizationId) + ? await organizationService.getOne(adminUser.organizationId) + : organization; + + if (!organization) { + throw new ApplicationError({ + code: ErrorCode.ENTITY_NOT_FOUND, + params: { + message: 'Admin organization not found', + }, + }); + } + + await userService.addUserToOrganization({ + id: user.id, + organizationId: organization.id, + }); + + await addUserToDefaultWorkspace({ + email: user.email, + workspaceId: organization.tablesWorkspaceId, + }); +} + +async function addUserToDefaultWorkspace(values: { + email: string; + workspaceId: number; +}): Promise { + const { token: defaultToken } = + await authenticateDefaultUserInOpenOpsTables(); + + await openopsTables.addUserToWorkspace(defaultToken, { + ...values, + }); +} diff --git a/packages/server/api/src/app/database/seeds/dev-seeds.ts b/packages/server/api/src/app/database/seeds/dev-seeds.ts index 29dcf176d6..a9152e1abd 100644 --- a/packages/server/api/src/app/database/seeds/dev-seeds.ts +++ b/packages/server/api/src/app/database/seeds/dev-seeds.ts @@ -1,7 +1,6 @@ import { logger, SharedSystemProp, system } from '@openops/server-shared'; -import { EnvironmentType } from '@openops/shared'; +import { EnvironmentType, Provider } from '@openops/shared'; import { authenticationService } from '../../authentication/authentication-service'; -import { Provider } from '../../authentication/authentication-service/hooks/authentication-service-hooks'; import { FlagEntity } from '../../flags/flag.entity'; import { databaseConnection } from '../database-connection'; diff --git a/packages/server/api/src/app/database/seeds/seed-admin.ts b/packages/server/api/src/app/database/seeds/seed-admin.ts index a2ec9e3aa8..9aecc98cef 100644 --- a/packages/server/api/src/app/database/seeds/seed-admin.ts +++ b/packages/server/api/src/app/database/seeds/seed-admin.ts @@ -1,8 +1,7 @@ import { authenticateDefaultUserInOpenOpsTables } from '@openops/common'; import { AppSystemProp, logger, system } from '@openops/server-shared'; -import { OrganizationRole, User } from '@openops/shared'; +import { OrganizationRole, Provider, User } from '@openops/shared'; import { authenticationService } from '../../authentication/authentication-service'; -import { Provider } from '../../authentication/authentication-service/hooks/authentication-service-hooks'; import { openopsTables } from '../../openops-tables'; import { organizationService } from '../../organization/organization.service'; import { projectService } from '../../project/project-service'; diff --git a/packages/server/api/test/integration/ce/authentication/signup.test.ts b/packages/server/api/test/integration/ce/authentication/signup.test.ts index f149be1e80..a7cba08f6e 100644 --- a/packages/server/api/test/integration/ce/authentication/signup.test.ts +++ b/packages/server/api/test/integration/ce/authentication/signup.test.ts @@ -38,8 +38,6 @@ jest.mock('../../../../src/app/openops-tables/index', () => ({ import { PrincipalType, UserStatus } from '@openops/shared'; import { FastifyInstance } from 'fastify'; import { StatusCodes } from 'http-status-codes'; -import { authenticationService } from '../../../../src/app/authentication/authentication-service'; -import { Provider } from '../../../../src/app/authentication/authentication-service/hooks/authentication-service-hooks'; import { databaseConnection } from '../../../../src/app/database/database-connection'; import { setupServer } from '../../../../src/app/server'; import { generateMockToken } from '../../../helpers/auth'; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 0b89286704..c3aa177a2e 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -10,6 +10,7 @@ export * from './lib/app-connection/dto/upsert-app-connection-request'; export * from './lib/authentication/dto/authentication-response'; export * from './lib/authentication/dto/sign-in-request'; export * from './lib/authentication/dto/sign-up-request'; +export * from './lib/authentication/model/authentication-type'; export * from './lib/authentication/model/principal'; export * from './lib/authentication/model/principal-type'; export * from './lib/blocks'; diff --git a/packages/shared/src/lib/authentication/model/authentication-type.ts b/packages/shared/src/lib/authentication/model/authentication-type.ts new file mode 100644 index 0000000000..707e0a881f --- /dev/null +++ b/packages/shared/src/lib/authentication/model/authentication-type.ts @@ -0,0 +1,4 @@ +export enum Provider { + EMAIL = 'EMAIL', + FEDERATED = 'FEDERATED', +} From e251183d71615e9d038ea350f7cb161eb274ca59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 17:22:22 +0000 Subject: [PATCH 5/8] Fix tests --- .../new-user/create-user.test.ts | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts index 262528ad67..d5dbd1eb36 100644 --- a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts +++ b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts @@ -1,18 +1,6 @@ import { ErrorCode, OrganizationRole, UserStatus } from '@openops/shared'; import { QueryFailedError } from 'typeorm'; -const preSignUpMock = jest.fn(); -jest.mock( - '../../../../src/app/authentication/authentication-service/hooks', - () => { - return { - authenticationServiceHooks: { - get: () => ({ preSignUp: preSignUpMock }), - }, - }; - }, -); - const createUserServiceMock = jest.fn(); const deleteUserServiceMock = jest.fn(); jest.mock('../../../../src/app/user/user-service', () => ({ @@ -80,12 +68,6 @@ describe('create-user', () => { const res = await createUser({ ...baseParams, password: 'P@ssw0rd' }); - expect(preSignUpMock).toHaveBeenCalledWith({ - name: 'John Doe', - email: baseParams.email, - password: 'P@ssw0rd', - }); - expect(createUserServiceMock).toHaveBeenCalledWith({ email: baseParams.email, organizationRole: OrganizationRole.MEMBER, @@ -118,7 +100,7 @@ describe('create-user', () => { ); await expect( - createUser({ ...baseParams, password: 'abc' }), + createUser({ ...baseParams, password: 'abcas2esf' }), ).rejects.toMatchObject({ error: { code: ErrorCode.EXISTING_USER, @@ -139,7 +121,7 @@ describe('create-user', () => { createTablesUserMock.mockRejectedValue(new Error('tables down')); await expect( - createUser({ ...baseParams, password: 'abc' }), + createUser({ ...baseParams, password: 'ab122rwerc' }), ).rejects.toBeInstanceOf(Error); expect(deleteUserServiceMock).toHaveBeenCalledWith({ @@ -160,12 +142,6 @@ describe('create-user', () => { const res = await createUserWithRandomPassword(baseParams); - expect(preSignUpMock).toHaveBeenCalledWith({ - name: 'John Doe', - email: baseParams.email, - password: 'Rand#123', - }); - expect(createUserServiceMock).toHaveBeenCalledWith( expect.objectContaining({ password: 'Rand#123' }), ); From f212757f963c643f9833780e161fd025ee267b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 20:13:44 +0000 Subject: [PATCH 6/8] Remove referringUserId --- .../api/src/app/authentication/authentication-service/index.ts | 1 - .../server/api/src/app/authentication/new-user/create-user.ts | 1 - packages/server/api/test/helpers/mocks/authn.ts | 1 - .../api/test/unit/authentication/new-user/create-user.test.ts | 1 - packages/shared/src/lib/authentication/dto/sign-up-request.ts | 2 -- 5 files changed, 6 deletions(-) diff --git a/packages/server/api/src/app/authentication/authentication-service/index.ts b/packages/server/api/src/app/authentication/authentication-service/index.ts index b06a170123..20f6f96612 100644 --- a/packages/server/api/src/app/authentication/authentication-service/index.ts +++ b/packages/server/api/src/app/authentication/authentication-service/index.ts @@ -123,7 +123,6 @@ type SignUpParams = { newsLetter: boolean; verified: boolean; organizationId: string | null; - referringUserId?: string; provider: Provider; }; diff --git a/packages/server/api/src/app/authentication/new-user/create-user.ts b/packages/server/api/src/app/authentication/new-user/create-user.ts index 16239b98fd..b26c42a032 100644 --- a/packages/server/api/src/app/authentication/new-user/create-user.ts +++ b/packages/server/api/src/app/authentication/new-user/create-user.ts @@ -20,7 +20,6 @@ type NewUserParams = { newsLetter: boolean; verified: boolean; organizationId: string | null; - referringUserId?: string; provider: Provider; }; diff --git a/packages/server/api/test/helpers/mocks/authn.ts b/packages/server/api/test/helpers/mocks/authn.ts index 886acee534..f968c7734f 100644 --- a/packages/server/api/test/helpers/mocks/authn.ts +++ b/packages/server/api/test/helpers/mocks/authn.ts @@ -11,7 +11,6 @@ export const createMockSignUpRequest = ( lastName: signUpRequest?.lastName ?? faker.person.lastName(), trackEvents: signUpRequest?.trackEvents ?? faker.datatype.boolean(), newsLetter: signUpRequest?.newsLetter ?? faker.datatype.boolean(), - referringUserId: signUpRequest?.referringUserId, }; }; diff --git a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts index 262528ad67..9e0028684a 100644 --- a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts +++ b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts @@ -50,7 +50,6 @@ describe('create-user', () => { newsLetter: false, verified: true, organizationId: 'org-1', - referringUserId: 'user-x', // eslint-disable-next-line @typescript-eslint/no-explicit-any provider: 'email' as any, }; diff --git a/packages/shared/src/lib/authentication/dto/sign-up-request.ts b/packages/shared/src/lib/authentication/dto/sign-up-request.ts index bc01a8d439..8623337025 100644 --- a/packages/shared/src/lib/authentication/dto/sign-up-request.ts +++ b/packages/shared/src/lib/authentication/dto/sign-up-request.ts @@ -1,5 +1,4 @@ import { Static, Type } from '@sinclair/typebox'; -import { OpenOpsId } from '../../common/id-generator'; import { EmailType, PasswordType } from '../../user/user'; export const SignUpRequest = Type.Object({ @@ -9,7 +8,6 @@ export const SignUpRequest = Type.Object({ lastName: Type.String(), trackEvents: Type.Boolean(), newsLetter: Type.Boolean(), - referringUserId: Type.Optional(OpenOpsId), }); export type SignUpRequest = Static; From ba5241dff81f3705a68edb15ce6dd62a53155402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Wed, 12 Nov 2025 20:20:01 +0000 Subject: [PATCH 7/8] Update test --- .../api/test/unit/authentication/new-user/create-user.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts index c6cde06b73..2a48b00e31 100644 --- a/packages/server/api/test/unit/authentication/new-user/create-user.test.ts +++ b/packages/server/api/test/unit/authentication/new-user/create-user.test.ts @@ -99,7 +99,7 @@ describe('create-user', () => { ); await expect( - createUser({ ...baseParams, password: 'abcas2esf' }), + createUser({ ...baseParams, password: 'P@ssw0rd' }), ).rejects.toMatchObject({ error: { code: ErrorCode.EXISTING_USER, @@ -120,7 +120,7 @@ describe('create-user', () => { createTablesUserMock.mockRejectedValue(new Error('tables down')); await expect( - createUser({ ...baseParams, password: 'ab122rwerc' }), + createUser({ ...baseParams, password: 'P@ssw0rd' }), ).rejects.toBeInstanceOf(Error); expect(deleteUserServiceMock).toHaveBeenCalledWith({ From bf05acfab5f2cc93e007d8518198c13b0c4664db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20Gon=C3=A7alves?= Date: Thu, 13 Nov 2025 08:35:10 +0000 Subject: [PATCH 8/8] Merge --- .../hooks/authentication-service-hooks.ts | 29 ---- .../hooks/community-authentication-hooks.ts | 130 ------------------ 2 files changed, 159 deletions(-) delete mode 100644 packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts delete mode 100644 packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts deleted file mode 100644 index 81caf7daee..0000000000 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/authentication-service-hooks.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { User } from '@openops/shared'; - -export enum Provider { - EMAIL = 'EMAIL', - FEDERATED = 'FEDERATED', -} - -export type AuthenticationServiceHooks = { - preSignIn(p: PreParams): Promise; - preSignUp(p: PreSignUpParams): Promise; - postSignUp(p: PostParams): Promise; - postSignIn(p: PostParams): Promise; -}; - -type PreSignUpParams = { - name: string; - email: string; - password: string; -}; - -type PreParams = { - email: string; - organizationId: string | null; - provider: Provider; -}; - -type PostParams = { - user: User; -}; diff --git a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts b/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts deleted file mode 100644 index 49a1cbe64a..0000000000 --- a/packages/server/api/src/app/authentication/authentication-service/hooks/community-authentication-hooks.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { authenticateDefaultUserInOpenOpsTables } from '@openops/common'; -import { AppSystemProp, system } from '@openops/server-shared'; -import { - ApplicationError, - assertValidEmail, - assertValidPassword, - ErrorCode, - isEmpty, - isNil, - PrincipalType, - Project, - ProjectMemberRole, - User, -} from '@openops/shared'; -import { openopsTables } from '../../../openops-tables'; -import { organizationService } from '../../../organization/organization.service'; -import { projectService } from '../../../project/project-service'; -import { userService } from '../../../user/user-service'; -import { accessTokenManager } from '../../context/access-token-manager'; -import { AuthenticationServiceHooks } from './authentication-service-hooks'; - -export const communityAuthenticationServiceHooks: AuthenticationServiceHooks = { - async preSignIn() { - // Empty - }, - async preSignUp({ email, password, name }) { - assertValidEmail(email); - assertValidPassword(password); - - if (isEmpty(name)) { - throw new ApplicationError({ - code: ErrorCode.INVALID_NAME_FOR_USER, - params: { - name, - message: 'First name and last name were not provided correctly.', - }, - }); - } - }, - async postSignUp({ user }) { - let organization = await organizationService.getOldestOrganization(); - - const adminUser = await userService.getUserByEmailOrFail({ - email: system.getOrThrow(AppSystemProp.OPENOPS_ADMIN_EMAIL), - }); - - organization = !isNil(adminUser.organizationId) - ? await organizationService.getOne(adminUser.organizationId) - : organization; - - if (!organization) { - throw new ApplicationError({ - code: ErrorCode.ENTITY_NOT_FOUND, - params: { - message: 'Admin organization not found', - }, - }); - } - - await userService.addUserToOrganization({ - id: user.id, - organizationId: organization.id, - }); - - await addUserToDefaultWorkspace({ - email: user.email, - workspaceId: organization.tablesWorkspaceId, - }); - }, - - async postSignIn() { - // Empty - }, -}; - -export async function getProjectAndToken( - user: User, - tablesRefreshToken: string, -): Promise<{ - user: User; - project: Project; - token: string; - tablesRefreshToken: string; - projectRole: ProjectMemberRole; -}> { - const updatedUser = await userService.getOneOrFail({ id: user.id }); - - const project = await projectService.getOneForUser(updatedUser); - if (isNil(project)) { - throw new ApplicationError({ - code: ErrorCode.INVITATION_ONLY_SIGN_UP, - params: { - message: 'No project found for user', - }, - }); - } - - const organization = await organizationService.getOneOrThrow( - project.organizationId, - ); - - const token = await accessTokenManager.generateToken({ - id: user.id, - type: PrincipalType.USER, - projectId: project.id, - organization: { - id: organization.id, - }, - }); - - return { - user: updatedUser, - token, - project, - tablesRefreshToken, - projectRole: ProjectMemberRole.ADMIN, - }; -} - -async function addUserToDefaultWorkspace(values: { - email: string; - workspaceId: number; -}): Promise { - const { token: defaultToken } = - await authenticateDefaultUserInOpenOpsTables(); - - await openopsTables.addUserToWorkspace(defaultToken, { - ...values, - }); -}