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 20f6f96612..a66ef4f6b5 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); }, @@ -137,16 +134,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 b26c42a032..f6176463bb 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; @@ -37,11 +39,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/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 9e0028684a..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 @@ -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', () => ({ @@ -79,12 +67,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, @@ -117,7 +99,7 @@ describe('create-user', () => { ); await expect( - createUser({ ...baseParams, password: 'abc' }), + createUser({ ...baseParams, password: 'P@ssw0rd' }), ).rejects.toMatchObject({ error: { code: ErrorCode.EXISTING_USER, @@ -138,7 +120,7 @@ describe('create-user', () => { createTablesUserMock.mockRejectedValue(new Error('tables down')); await expect( - createUser({ ...baseParams, password: 'abc' }), + createUser({ ...baseParams, password: 'P@ssw0rd' }), ).rejects.toBeInstanceOf(Error); expect(deleteUserServiceMock).toHaveBeenCalledWith({ @@ -159,12 +141,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' }), ); 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', +}