Przeglądaj źródła

feat: consider shared vault owner quota when uploading files to shared vault (#704)

* fix(auth): updating storage quota on shared subscriptions

* fix(syncing-server): turn shared vault and key associations into value objects

* feat: consider shared vault owner quota when uploading files to shared vault

* fix: add passing x-shared-vault-owner-context value

* fix: refactor creating cross service token to not throw errors

* fix: caching cross service token

* fix: missing header in http service proxy
Karol Sójko 1 rok temu
rodzic
commit
34085ac6fb
63 zmienionych plików z 460 dodań i 564 usunięć
  1. 11 3
      packages/api-gateway/src/Controller/AuthMiddleware.ts
  2. 18 18
      packages/api-gateway/src/Infra/InMemory/InMemoryCrossServiceTokenCache.ts
  3. 9 9
      packages/api-gateway/src/Infra/Redis/RedisCrossServiceTokenCache.ts
  4. 2 2
      packages/api-gateway/src/Service/Cache/CrossServiceTokenCacheInterface.ts
  5. 6 4
      packages/api-gateway/src/Service/Http/HttpServiceProxy.ts
  6. 1 1
      packages/api-gateway/src/Service/Http/ServiceProxyInterface.ts
  7. 6 4
      packages/api-gateway/src/Service/Proxy/DirectCallServiceProxy.ts
  8. 77 17
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.spec.ts
  9. 30 10
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.ts
  10. 1 0
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceTokenDTO.ts
  11. 0 3
      packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceTokenResponse.ts
  12. 54 57
      packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts
  13. 15 39
      packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts
  14. 11 16
      packages/auth/src/Domain/UseCase/GetSetting/GetSettingResponse.ts
  15. 6 10
      packages/auth/src/Domain/UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser.ts
  16. 3 2
      packages/auth/src/Infra/InversifyExpressUtils/AnnotatedInternalController.spec.ts
  17. 14 4
      packages/auth/src/Infra/InversifyExpressUtils/AnnotatedInternalController.ts
  18. 2 1
      packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSessionsController.spec.ts
  19. 3 2
      packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSettingsController.spec.ts
  20. 3 2
      packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSubscriptionSettingsController.spec.ts
  21. 15 2
      packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSessionsController.ts
  22. 14 5
      packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSettingsController.ts
  23. 14 4
      packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSubscriptionSettingsController.ts
  24. 12 2
      packages/auth/src/Infra/InversifyExpressUtils/Base/BaseWebSocketsController.ts
  25. 4 0
      packages/files/src/Infra/InversifyExpress/AnnotatedSharedVaultFilesController.ts
  26. 40 1
      packages/files/src/Infra/InversifyExpress/Middleware/SharedVaultValetTokenAuthMiddleware.ts
  27. 3 0
      packages/security/src/Domain/Token/CrossServiceTokenData.ts
  28. 1 1
      packages/security/src/Domain/Token/SharedVaultValetTokenData.ts
  29. 13 0
      packages/syncing-server/migrations/mysql/1692619430384-remove-shared-vault-limit.ts
  30. 20 0
      packages/syncing-server/migrations/sqlite/1692619677621-remove-shared-vault-limit.ts
  31. 0 8
      packages/syncing-server/src/Bootstrap/Container.ts
  32. 0 4
      packages/syncing-server/src/Bootstrap/DataSource.ts
  33. 0 2
      packages/syncing-server/src/Bootstrap/Types.ts
  34. 1 103
      packages/syncing-server/src/Domain/Item/Item.spec.ts
  35. 1 57
      packages/syncing-server/src/Domain/Item/Item.ts
  36. 8 5
      packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts
  37. 11 6
      packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.ts
  38. 0 2
      packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts
  39. 1 2
      packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.spec.ts
  40. 6 6
      packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.ts
  41. 0 1
      packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts
  42. 2 4
      packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts
  43. 0 1
      packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken.spec.ts
  44. 1 1
      packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken.ts
  45. 1 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetTokenDTO.ts
  46. 0 2
      packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts
  47. 0 2
      packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts
  48. 0 1
      packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts
  49. 0 2
      packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts
  50. 0 3
      packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts
  51. 0 1
      packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts
  52. 3 5
      packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts
  53. 5 15
      packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts
  54. 9 26
      packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts
  55. 1 0
      packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseSharedVaultsController.ts
  56. 1 0
      packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.ts
  57. 0 33
      packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts
  58. 0 6
      packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVault.ts
  59. 0 40
      packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociation.ts
  60. 0 1
      packages/syncing-server/src/Mapping/Http/SharedVaultHttpMapper.ts
  61. 0 1
      packages/syncing-server/src/Mapping/Http/SharedVaultHttpRepresentation.ts
  62. 1 3
      packages/syncing-server/src/Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper.ts
  63. 0 2
      packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts

+ 11 - 3
packages/api-gateway/src/Controller/AuthMiddleware.ts

@@ -27,16 +27,23 @@ export abstract class AuthMiddleware extends BaseMiddleware {
     }
 
     const authHeaderValue = request.headers.authorization as string
+    const sharedVaultOwnerContextHeaderValue = request.headers['x-shared-vault-owner-context'] as string | undefined
+    const cacheKey = `${authHeaderValue}${
+      sharedVaultOwnerContextHeaderValue ? `:${sharedVaultOwnerContextHeaderValue}` : ''
+    }`
 
     try {
       let crossServiceTokenFetchedFromCache = true
       let crossServiceToken = null
       if (this.crossServiceTokenCacheTTL) {
-        crossServiceToken = await this.crossServiceTokenCache.get(authHeaderValue)
+        crossServiceToken = await this.crossServiceTokenCache.get(cacheKey)
       }
 
       if (crossServiceToken === null) {
-        const authResponse = await this.serviceProxy.validateSession(authHeaderValue)
+        const authResponse = await this.serviceProxy.validateSession({
+          authorization: authHeaderValue,
+          sharedVaultOwnerContext: sharedVaultOwnerContextHeaderValue,
+        })
 
         if (!this.handleSessionValidationResponse(authResponse, response, next)) {
           return
@@ -52,7 +59,7 @@ export abstract class AuthMiddleware extends BaseMiddleware {
 
       if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
         await this.crossServiceTokenCache.set({
-          authorizationHeaderValue: authHeaderValue,
+          key: cacheKey,
           encodedCrossServiceToken: crossServiceToken,
           expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken),
           userUuid: decodedToken.user.uuid,
@@ -62,6 +69,7 @@ export abstract class AuthMiddleware extends BaseMiddleware {
       response.locals.user = decodedToken.user
       response.locals.session = decodedToken.session
       response.locals.roles = decodedToken.roles
+      response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
     } catch (error) {
       const errorMessage = (error as AxiosError).isAxiosError
         ? JSON.stringify((error as AxiosError).response?.data)

+ 18 - 18
packages/api-gateway/src/Infra/InMemory/InMemoryCrossServiceTokenCache.ts

@@ -12,29 +12,29 @@ export class InMemoryCrossServiceTokenCache implements CrossServiceTokenCacheInt
   constructor(private timer: TimerInterface) {}
 
   async set(dto: {
-    authorizationHeaderValue: string
+    key: string
     encodedCrossServiceToken: string
     expiresAtInSeconds: number
     userUuid: string
   }): Promise<void> {
-    let userAuthHeaders = []
-    const userAuthHeadersJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${dto.userUuid}`)
-    if (userAuthHeadersJSON) {
-      userAuthHeaders = JSON.parse(userAuthHeadersJSON)
+    let userKeys = []
+    const userKeysJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${dto.userUuid}`)
+    if (userKeysJSON) {
+      userKeys = JSON.parse(userKeysJSON)
     }
-    userAuthHeaders.push(dto.authorizationHeaderValue)
+    userKeys.push(dto.key)
 
-    this.crossServiceTokenCache.set(`${this.USER_CST_PREFIX}:${dto.userUuid}`, JSON.stringify(userAuthHeaders))
+    this.crossServiceTokenCache.set(`${this.USER_CST_PREFIX}:${dto.userUuid}`, JSON.stringify(userKeys))
     this.crossServiceTokenTTLCache.set(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.expiresAtInSeconds)
 
-    this.crossServiceTokenCache.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.encodedCrossServiceToken)
-    this.crossServiceTokenTTLCache.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.expiresAtInSeconds)
+    this.crossServiceTokenCache.set(`${this.PREFIX}:${dto.key}`, dto.encodedCrossServiceToken)
+    this.crossServiceTokenTTLCache.set(`${this.PREFIX}:${dto.key}`, dto.expiresAtInSeconds)
   }
 
-  async get(authorizationHeaderValue: string): Promise<string | null> {
+  async get(key: string): Promise<string | null> {
     this.invalidateExpiredTokens()
 
-    const cachedToken = this.crossServiceTokenCache.get(`${this.PREFIX}:${authorizationHeaderValue}`)
+    const cachedToken = this.crossServiceTokenCache.get(`${this.PREFIX}:${key}`)
     if (!cachedToken) {
       return null
     }
@@ -43,15 +43,15 @@ export class InMemoryCrossServiceTokenCache implements CrossServiceTokenCacheInt
   }
 
   async invalidate(userUuid: string): Promise<void> {
-    let userAuthorizationHeaderValues = []
-    const userAuthHeadersJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${userUuid}`)
-    if (userAuthHeadersJSON) {
-      userAuthorizationHeaderValues = JSON.parse(userAuthHeadersJSON)
+    let userKeyValues = []
+    const userKeysJSON = this.crossServiceTokenCache.get(`${this.USER_CST_PREFIX}:${userUuid}`)
+    if (userKeysJSON) {
+      userKeyValues = JSON.parse(userKeysJSON)
     }
 
-    for (const authorizationHeaderValue of userAuthorizationHeaderValues) {
-      this.crossServiceTokenCache.delete(`${this.PREFIX}:${authorizationHeaderValue}`)
-      this.crossServiceTokenTTLCache.delete(`${this.PREFIX}:${authorizationHeaderValue}`)
+    for (const key of userKeyValues) {
+      this.crossServiceTokenCache.delete(`${this.PREFIX}:${key}`)
+      this.crossServiceTokenTTLCache.delete(`${this.PREFIX}:${key}`)
     }
     this.crossServiceTokenCache.delete(`${this.USER_CST_PREFIX}:${userUuid}`)
     this.crossServiceTokenTTLCache.delete(`${this.USER_CST_PREFIX}:${userUuid}`)

+ 9 - 9
packages/api-gateway/src/Infra/Redis/RedisCrossServiceTokenCache.ts

@@ -12,32 +12,32 @@ export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterf
   constructor(@inject(TYPES.ApiGateway_Redis) private redisClient: IORedis.Redis) {}
 
   async set(dto: {
-    authorizationHeaderValue: string
+    key: string
     encodedCrossServiceToken: string
     expiresAtInSeconds: number
     userUuid: string
   }): Promise<void> {
     const pipeline = this.redisClient.pipeline()
 
-    pipeline.sadd(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.authorizationHeaderValue)
+    pipeline.sadd(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.key)
     pipeline.expireat(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.expiresAtInSeconds)
 
-    pipeline.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.encodedCrossServiceToken)
-    pipeline.expireat(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.expiresAtInSeconds)
+    pipeline.set(`${this.PREFIX}:${dto.key}`, dto.encodedCrossServiceToken)
+    pipeline.expireat(`${this.PREFIX}:${dto.key}`, dto.expiresAtInSeconds)
 
     await pipeline.exec()
   }
 
-  async get(authorizationHeaderValue: string): Promise<string | null> {
-    return this.redisClient.get(`${this.PREFIX}:${authorizationHeaderValue}`)
+  async get(key: string): Promise<string | null> {
+    return this.redisClient.get(`${this.PREFIX}:${key}`)
   }
 
   async invalidate(userUuid: string): Promise<void> {
-    const userAuthorizationHeaderValues = await this.redisClient.smembers(`${this.USER_CST_PREFIX}:${userUuid}`)
+    const userKeyValues = await this.redisClient.smembers(`${this.USER_CST_PREFIX}:${userUuid}`)
 
     const pipeline = this.redisClient.pipeline()
-    for (const authorizationHeaderValue of userAuthorizationHeaderValues) {
-      pipeline.del(`${this.PREFIX}:${authorizationHeaderValue}`)
+    for (const key of userKeyValues) {
+      pipeline.del(`${this.PREFIX}:${key}`)
     }
     pipeline.del(`${this.USER_CST_PREFIX}:${userUuid}`)
 

+ 2 - 2
packages/api-gateway/src/Service/Cache/CrossServiceTokenCacheInterface.ts

@@ -1,10 +1,10 @@
 export interface CrossServiceTokenCacheInterface {
   set(dto: {
-    authorizationHeaderValue: string
+    key: string
     encodedCrossServiceToken: string
     expiresAtInSeconds: number
     userUuid: string
   }): Promise<void>
-  get(authorizationHeaderValue: string): Promise<string | null>
+  get(key: string): Promise<string | null>
   invalidate(userUuid: string): Promise<void>
 }

+ 6 - 4
packages/api-gateway/src/Service/Http/HttpServiceProxy.ts

@@ -24,14 +24,16 @@ export class HttpServiceProxy implements ServiceProxyInterface {
     @inject(TYPES.ApiGateway_Logger) private logger: Logger,
   ) {}
 
-  async validateSession(
-    authorizationHeaderValue: string,
-  ): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
+  async validateSession(headers: {
+    authorization: string
+    sharedVaultOwnerContext?: string
+  }): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
     const authResponse = await this.httpClient.request({
       method: 'POST',
       headers: {
-        Authorization: authorizationHeaderValue,
+        Authorization: headers.authorization,
         Accept: 'application/json',
+        'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
       },
       validateStatus: (status: number) => {
         return status >= 200 && status < 500

+ 1 - 1
packages/api-gateway/src/Service/Http/ServiceProxyInterface.ts

@@ -50,7 +50,7 @@ export interface ServiceProxyInterface {
     endpointOrMethodIdentifier: string,
     payload?: Record<string, unknown> | string,
   ): Promise<void>
-  validateSession(authorizationHeaderValue: string): Promise<{
+  validateSession(headers: { authorization: string; sharedVaultOwnerContext?: string }): Promise<{
     status: number
     data: unknown
     headers: {

+ 6 - 4
packages/api-gateway/src/Service/Proxy/DirectCallServiceProxy.ts

@@ -6,9 +6,10 @@ import { ServiceProxyInterface } from '../Http/ServiceProxyInterface'
 export class DirectCallServiceProxy implements ServiceProxyInterface {
   constructor(private serviceContainer: ServiceContainerInterface, private filesServerUrl: string) {}
 
-  async validateSession(
-    authorizationHeaderValue: string,
-  ): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
+  async validateSession(headers: {
+    authorization: string
+    sharedVaultOwnerContext?: string
+  }): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
     const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
     if (!authService) {
       throw new Error('Auth service not found')
@@ -17,7 +18,8 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
     const serviceResponse = (await authService.handleRequest(
       {
         headers: {
-          authorization: authorizationHeaderValue,
+          authorization: headers.authorization,
+          'x-shared-vault-owner-context': headers.sharedVaultOwnerContext,
         },
       } as never,
       {} as never,

+ 77 - 17
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.spec.ts

@@ -8,6 +8,8 @@ import { Role } from '../../Role/Role'
 import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
 
 import { CreateCrossServiceToken } from './CreateCrossServiceToken'
+import { GetSetting } from '../GetSetting/GetSetting'
+import { Result } from '@standardnotes/domain-core'
 
 describe('CreateCrossServiceToken', () => {
   let userProjector: ProjectorInterface<User>
@@ -15,6 +17,7 @@ describe('CreateCrossServiceToken', () => {
   let roleProjector: ProjectorInterface<Role>
   let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
   let userRepository: UserRepositoryInterface
+  let getSettingUseCase: GetSetting
   const jwtTTL = 60
 
   let session: Session
@@ -22,7 +25,15 @@ describe('CreateCrossServiceToken', () => {
   let role: Role
 
   const createUseCase = () =>
-    new CreateCrossServiceToken(userProjector, sessionProjector, roleProjector, tokenEncoder, userRepository, jwtTTL)
+    new CreateCrossServiceToken(
+      userProjector,
+      sessionProjector,
+      roleProjector,
+      tokenEncoder,
+      userRepository,
+      jwtTTL,
+      getSettingUseCase,
+    )
 
   beforeEach(() => {
     session = {} as jest.Mocked<Session>
@@ -50,6 +61,9 @@ describe('CreateCrossServiceToken', () => {
 
     userRepository = {} as jest.Mocked<UserRepositoryInterface>
     userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
+
+    getSettingUseCase = {} as jest.Mocked<GetSetting>
+    getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ setting: { value: '100' } }))
   })
 
   it('should create a cross service token for user', async () => {
@@ -125,28 +139,74 @@ describe('CreateCrossServiceToken', () => {
   it('should throw an error if user does not exist', async () => {
     userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
 
-    let caughtError = null
-    try {
-      await createUseCase().execute({
-        userUuid: '00000000-0000-0000-0000-000000000000',
-      })
-    } catch (error) {
-      caughtError = error
-    }
+    const result = await createUseCase().execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+    })
 
-    expect(caughtError).not.toBeNull()
+    expect(result.isFailed()).toBeTruthy()
   })
 
   it('should throw an error if user uuid is invalid', async () => {
-    let caughtError = null
-    try {
+    const result = await createUseCase().execute({
+      userUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  describe('shared vault context', () => {
+    it('should add shared vault context if shared vault owner uuid is provided', async () => {
       await createUseCase().execute({
-        userUuid: 'invalid',
+        user,
+        session,
+        sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
       })
-    } catch (error) {
-      caughtError = error
-    }
 
-    expect(caughtError).not.toBeNull()
+      expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
+        {
+          roles: [
+            {
+              name: 'role1',
+              uuid: '1-3-4',
+            },
+          ],
+          session: {
+            test: 'test',
+          },
+          shared_vault_owner_context: {
+            upload_bytes_limit: 100,
+          },
+          user: {
+            email: 'test@test.te',
+            uuid: '00000000-0000-0000-0000-000000000000',
+          },
+        },
+        60,
+      )
+    })
+
+    it('should throw an error if shared vault owner context is sensitive', async () => {
+      getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ sensitive: true }))
+
+      const result = await createUseCase().execute({
+        user,
+        session,
+        sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBeTruthy()
+    })
+
+    it('should throw an error if it fails to retrieve shared vault owner setting', async () => {
+      getSettingUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
+
+      const result = await createUseCase().execute({
+        user,
+        session,
+        sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBeTruthy()
+    })
   })
 })

+ 30 - 10
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken.ts

@@ -1,5 +1,6 @@
 import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
 import { inject, injectable } from 'inversify'
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
 
 import TYPES from '../../../Bootstrap/Types'
 import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
@@ -7,14 +8,13 @@ import { Role } from '../../Role/Role'
 import { Session } from '../../Session/Session'
 import { User } from '../../User/User'
 import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
-import { UseCaseInterface } from '../UseCaseInterface'
 
 import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
-import { CreateCrossServiceTokenResponse } from './CreateCrossServiceTokenResponse'
-import { Uuid } from '@standardnotes/domain-core'
+import { GetSetting } from '../GetSetting/GetSetting'
+import { SettingName } from '@standardnotes/settings'
 
 @injectable()
-export class CreateCrossServiceToken implements UseCaseInterface {
+export class CreateCrossServiceToken implements UseCaseInterface<string> {
   constructor(
     @inject(TYPES.Auth_UserProjector) private userProjector: ProjectorInterface<User>,
     @inject(TYPES.Auth_SessionProjector) private sessionProjector: ProjectorInterface<Session>,
@@ -22,14 +22,16 @@ export class CreateCrossServiceToken implements UseCaseInterface {
     @inject(TYPES.Auth_CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>,
     @inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
     @inject(TYPES.Auth_AUTH_JWT_TTL) private jwtTTL: number,
+    @inject(TYPES.Auth_GetSetting)
+    private getSettingUseCase: GetSetting,
   ) {}
 
-  async execute(dto: CreateCrossServiceTokenDTO): Promise<CreateCrossServiceTokenResponse> {
+  async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> {
     let user: User | undefined | null = dto.user
     if (user === undefined && dto.userUuid !== undefined) {
       const userUuidOrError = Uuid.create(dto.userUuid)
       if (userUuidOrError.isFailed()) {
-        throw new Error(userUuidOrError.getError())
+        return Result.fail(userUuidOrError.getError())
       }
       const userUuid = userUuidOrError.getValue()
 
@@ -37,7 +39,7 @@ export class CreateCrossServiceToken implements UseCaseInterface {
     }
 
     if (!user) {
-      throw new Error(`Could not find user with uuid ${dto.userUuid}`)
+      return Result.fail(`Could not find user with uuid ${dto.userUuid}`)
     }
 
     const roles = await user.roles
@@ -45,15 +47,33 @@ export class CreateCrossServiceToken implements UseCaseInterface {
     const authTokenData: CrossServiceTokenData = {
       user: this.projectUser(user),
       roles: this.projectRoles(roles),
+      shared_vault_owner_context: undefined,
+    }
+
+    if (dto.sharedVaultOwnerContext !== undefined) {
+      const uploadBytesLimitSettingOrError = await this.getSettingUseCase.execute({
+        settingName: SettingName.NAMES.FileUploadBytesLimit,
+        userUuid: dto.sharedVaultOwnerContext,
+      })
+      if (uploadBytesLimitSettingOrError.isFailed()) {
+        return Result.fail(uploadBytesLimitSettingOrError.getError())
+      }
+      const uploadBytesLimitSetting = uploadBytesLimitSettingOrError.getValue()
+      if (uploadBytesLimitSetting.sensitive) {
+        return Result.fail('Shared vault owner upload bytes limit setting is sensitive!')
+      }
+      const uploadBytesLimit = parseInt(uploadBytesLimitSetting.setting.value as string)
+
+      authTokenData.shared_vault_owner_context = {
+        upload_bytes_limit: uploadBytesLimit,
+      }
     }
 
     if (dto.session !== undefined) {
       authTokenData.session = this.projectSession(dto.session)
     }
 
-    return {
-      token: this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL),
-    }
+    return Result.ok(this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL))
   }
 
   private projectUser(user: User): { uuid: string; email: string } {

+ 1 - 0
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceTokenDTO.ts

@@ -6,6 +6,7 @@ export type CreateCrossServiceTokenDTO = Either<
   {
     user: User
     session?: Session
+    sharedVaultOwnerContext?: string
   },
   {
     userUuid: string

+ 0 - 3
packages/auth/src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceTokenResponse.ts

@@ -1,3 +0,0 @@
-export type CreateCrossServiceTokenResponse = {
-  token: string
-}

+ 54 - 57
packages/auth/src/Domain/UseCase/GetSetting/GetSetting.spec.ts

@@ -73,35 +73,30 @@ describe('GetSetting', () => {
 
   describe('no subscription', () => {
     it('should find a setting for user', async () => {
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.DropboxBackupFrequency }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.DropboxBackupFrequency,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         userUuid: '1-2-3',
         setting: { foo: 'bar' },
       })
     })
 
     it('should not find a setting if the setting name is invalid', async () => {
-      expect(await createUseCase().execute({ userUuid: '1-2-3', settingName: 'invalid' })).toEqual({
-        success: false,
-        error: {
-          message: 'Invalid setting name: invalid',
-        },
-      })
+      const result = await createUseCase().execute({ userUuid: '1-2-3', settingName: 'invalid' })
+      expect(result.isFailed()).toBeTruthy()
     })
 
     it('should not get a setting for user if it does not exist', async () => {
       settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
 
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.DropboxBackupFrequency }),
-      ).toEqual({
-        success: false,
-        error: {
-          message: 'Setting DROPBOX_BACKUP_FREQUENCY for user 1-2-3 not found!',
-        },
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.DropboxBackupFrequency,
       })
+      expect(result.isFailed()).toBeTruthy()
     })
 
     it('should not retrieve a sensitive setting for user', async () => {
@@ -112,21 +107,19 @@ describe('GetSetting', () => {
 
       settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
 
-      expect(await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MfaSecret })).toEqual({
-        success: true,
+      const result = await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MfaSecret })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         sensitive: true,
       })
     })
 
     it('should not retrieve a subscription setting for user', async () => {
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MuteSignInEmails }),
-      ).toEqual({
-        success: false,
-        error: {
-          message: 'No subscription found.',
-        },
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MuteSignInEmails,
       })
+      expect(result.isFailed()).toBeTruthy()
     })
 
     it('should retrieve a sensitive setting for user if explicitly told to', async () => {
@@ -137,14 +130,13 @@ describe('GetSetting', () => {
 
       settingService.findSettingWithDecryptedValue = jest.fn().mockReturnValue(setting)
 
-      expect(
-        await createUseCase().execute({
-          userUuid: '1-2-3',
-          settingName: SettingName.NAMES.MfaSecret,
-          allowSensitiveRetrieval: true,
-        }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MfaSecret,
+        allowSensitiveRetrieval: true,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         userUuid: '1-2-3',
         setting: { foo: 'bar' },
       })
@@ -159,10 +151,12 @@ describe('GetSetting', () => {
     })
 
     it('should find a setting for user', async () => {
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MuteSignInEmails }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MuteSignInEmails,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         userUuid: '1-2-3',
         setting: { foo: 'sub-bar' },
       })
@@ -171,14 +165,11 @@ describe('GetSetting', () => {
     it('should not get a suscription setting for user if it does not exist', async () => {
       subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
 
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MuteSignInEmails }),
-      ).toEqual({
-        success: false,
-        error: {
-          message: 'Subscription setting MUTE_SIGN_IN_EMAILS for user 1-2-3 not found!',
-        },
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MuteSignInEmails,
       })
+      expect(result.isFailed()).toBeTruthy()
     })
 
     it('should not retrieve a sensitive subscription setting for user', async () => {
@@ -188,10 +179,12 @@ describe('GetSetting', () => {
         .fn()
         .mockReturnValue(subscriptionSetting)
 
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MuteSignInEmails }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MuteSignInEmails,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         sensitive: true,
       })
     })
@@ -205,10 +198,12 @@ describe('GetSetting', () => {
     })
 
     it('should find a setting for user', async () => {
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.MuteSignInEmails }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.MuteSignInEmails,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         userUuid: '1-2-3',
         setting: { foo: 'sub-bar' },
       })
@@ -221,10 +216,12 @@ describe('GetSetting', () => {
     })
 
     it('should find a regular subscription only setting for user', async () => {
-      expect(
-        await createUseCase().execute({ userUuid: '1-2-3', settingName: SettingName.NAMES.FileUploadBytesLimit }),
-      ).toEqual({
-        success: true,
+      const result = await createUseCase().execute({
+        userUuid: '1-2-3',
+        settingName: SettingName.NAMES.FileUploadBytesLimit,
+      })
+      expect(result.isFailed()).toBeFalsy()
+      expect(result.getValue()).toEqual({
         userUuid: '1-2-3',
         setting: { foo: 'sub-bar' },
       })

+ 15 - 39
packages/auth/src/Domain/UseCase/GetSetting/GetSetting.ts

@@ -1,7 +1,7 @@
 import { SettingName } from '@standardnotes/settings'
 import { inject, injectable } from 'inversify'
+import { Result, UseCaseInterface } from '@standardnotes/domain-core'
 
-import { UseCaseInterface } from '../UseCaseInterface'
 import TYPES from '../../../Bootstrap/Types'
 import { SettingProjector } from '../../../Projection/SettingProjector'
 import { SettingServiceInterface } from '../../Setting/SettingServiceInterface'
@@ -14,7 +14,7 @@ import { GetSettingResponse } from './GetSettingResponse'
 import { UserSubscription } from '../../Subscription/UserSubscription'
 
 @injectable()
-export class GetSetting implements UseCaseInterface {
+export class GetSetting implements UseCaseInterface<GetSettingResponse> {
   constructor(
     @inject(TYPES.Auth_SettingProjector) private settingProjector: SettingProjector,
     @inject(TYPES.Auth_SubscriptionSettingProjector) private subscriptionSettingProjector: SubscriptionSettingProjector,
@@ -24,15 +24,10 @@ export class GetSetting implements UseCaseInterface {
     @inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
   ) {}
 
-  async execute(dto: GetSettingDto): Promise<GetSettingResponse> {
+  async execute(dto: GetSettingDto): Promise<Result<GetSettingResponse>> {
     const settingNameOrError = SettingName.create(dto.settingName)
     if (settingNameOrError.isFailed()) {
-      return {
-        success: false,
-        error: {
-          message: settingNameOrError.getError(),
-        },
-      }
+      return Result.fail(settingNameOrError.getError())
     }
     const settingName = settingNameOrError.getValue()
 
@@ -47,12 +42,7 @@ export class GetSetting implements UseCaseInterface {
       }
 
       if (!subscription) {
-        return {
-          success: false,
-          error: {
-            message: 'No subscription found.',
-          },
-        }
+        return Result.fail('No subscription found.')
       }
 
       const subscriptionSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
@@ -62,28 +52,21 @@ export class GetSetting implements UseCaseInterface {
       })
 
       if (subscriptionSetting === null) {
-        return {
-          success: false,
-          error: {
-            message: `Subscription setting ${settingName.value} for user ${dto.userUuid} not found!`,
-          },
-        }
+        return Result.fail(`Subscription setting ${settingName.value} for user ${dto.userUuid} not found!`)
       }
 
       if (subscriptionSetting.sensitive && !dto.allowSensitiveRetrieval) {
-        return {
-          success: true,
+        return Result.ok({
           sensitive: true,
-        }
+        })
       }
 
       const simpleSubscriptionSetting = await this.subscriptionSettingProjector.projectSimple(subscriptionSetting)
 
-      return {
-        success: true,
+      return Result.ok({
         userUuid: dto.userUuid,
         setting: simpleSubscriptionSetting,
-      }
+      })
     }
 
     const setting = await this.settingService.findSettingWithDecryptedValue({
@@ -92,27 +75,20 @@ export class GetSetting implements UseCaseInterface {
     })
 
     if (setting === null) {
-      return {
-        success: false,
-        error: {
-          message: `Setting ${settingName.value} for user ${dto.userUuid} not found!`,
-        },
-      }
+      return Result.fail(`Setting ${settingName.value} for user ${dto.userUuid} not found!`)
     }
 
     if (setting.sensitive && !dto.allowSensitiveRetrieval) {
-      return {
-        success: true,
+      return Result.ok({
         sensitive: true,
-      }
+      })
     }
 
     const simpleSetting = await this.settingProjector.projectSimple(setting)
 
-    return {
-      success: true,
+    return Result.ok({
       userUuid: dto.userUuid,
       setting: simpleSetting,
-    }
+    })
   }
 }

+ 11 - 16
packages/auth/src/Domain/UseCase/GetSetting/GetSettingResponse.ts

@@ -1,18 +1,13 @@
+import { Either } from '@standardnotes/common'
+
 import { SimpleSetting } from '../../Setting/SimpleSetting'
 
-export type GetSettingResponse =
-  | {
-      success: true
-      userUuid: string
-      setting: SimpleSetting
-    }
-  | {
-      success: true
-      sensitive: true
-    }
-  | {
-      success: false
-      error: {
-        message: string
-      }
-    }
+export type GetSettingResponse = Either<
+  {
+    userUuid: string
+    setting: SimpleSetting
+  },
+  {
+    sensitive: true
+  }
+>

+ 6 - 10
packages/auth/src/Domain/UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser.ts

@@ -7,7 +7,6 @@ import { UserSubscription } from '../../Subscription/UserSubscription'
 import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
 import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
 import { UpdateStorageQuotaUsedForUserDTO } from './UpdateStorageQuotaUsedForUserDTO'
-import { User } from '../../User/User'
 
 export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
   constructor(
@@ -34,23 +33,20 @@ export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
       return Result.fail(`Could not find regular user subscription for user with uuid: ${userUuid.value}`)
     }
 
-    await this.updateUploadBytesUsedSetting(regularSubscription, user, dto.bytesUsed)
+    await this.updateUploadBytesUsedSetting(regularSubscription, dto.bytesUsed)
 
     if (sharedSubscription !== null) {
-      await this.updateUploadBytesUsedSetting(sharedSubscription, user, dto.bytesUsed)
+      await this.updateUploadBytesUsedSetting(sharedSubscription, dto.bytesUsed)
     }
 
     return Result.ok()
   }
 
-  private async updateUploadBytesUsedSetting(
-    subscription: UserSubscription,
-    user: User,
-    bytesUsed: number,
-  ): Promise<void> {
+  private async updateUploadBytesUsedSetting(subscription: UserSubscription, bytesUsed: number): Promise<void> {
     let bytesAlreadyUsed = '0'
+    const subscriptionUser = await subscription.user
     const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
-      userUuid: (await subscription.user).uuid,
+      userUuid: subscriptionUser.uuid,
       userSubscriptionUuid: subscription.uuid,
       subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
     })
@@ -60,7 +56,7 @@ export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
 
     await this.subscriptionSettingService.createOrReplace({
       userSubscription: subscription,
-      user,
+      user: subscriptionUser,
       props: {
         name: SettingName.NAMES.FileUploadBytesUsed,
         unencryptedValue: (+bytesAlreadyUsed + bytesUsed).toString(),

+ 3 - 2
packages/auth/src/Infra/InversifyExpressUtils/AnnotatedInternalController.spec.ts

@@ -7,6 +7,7 @@ import { results } from 'inversify-express-utils'
 import { User } from '../../Domain/User/User'
 import { GetUserFeatures } from '../../Domain/UseCase/GetUserFeatures/GetUserFeatures'
 import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting'
+import { Result } from '@standardnotes/domain-core'
 
 describe('AnnotatedInternalController', () => {
   let getUserFeatures: GetUserFeatures
@@ -73,7 +74,7 @@ describe('AnnotatedInternalController', () => {
     request.params.userUuid = '1-2-3'
     request.params.settingName = 'foobar'
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: true })
+    getSetting.execute = jest.fn().mockReturnValue(Result.ok())
 
     const httpResponse = <results.JsonResult>await createController().getSetting(request)
     const result = await httpResponse.executeAsync()
@@ -91,7 +92,7 @@ describe('AnnotatedInternalController', () => {
     request.params.userUuid = '1-2-3'
     request.params.settingName = 'foobar'
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: false })
+    getSetting.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
 
     const httpResponse = <results.JsonResult>await createController().getSetting(request)
     const result = await httpResponse.executeAsync()

+ 14 - 4
packages/auth/src/Infra/InversifyExpressUtils/AnnotatedInternalController.ts

@@ -36,16 +36,26 @@ export class AnnotatedInternalController extends BaseHttpController {
 
   @httpGet('/users/:userUuid/settings/:settingName')
   async getSetting(request: Request): Promise<results.JsonResult> {
-    const result = await this.doGetSetting.execute({
+    const resultOrError = await this.doGetSetting.execute({
       userUuid: request.params.userUuid,
       settingName: request.params.settingName,
       allowSensitiveRetrieval: true,
     })
 
-    if (result.success) {
-      return this.json(result)
+    if (resultOrError.isFailed()) {
+      return this.json(
+        {
+          error: {
+            message: resultOrError.getError(),
+          },
+        },
+        400,
+      )
     }
 
-    return this.json(result, 400)
+    return this.json({
+      success: true,
+      ...resultOrError.getValue(),
+    })
   }
 }

+ 2 - 1
packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSessionsController.spec.ts

@@ -11,6 +11,7 @@ import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossService
 import { GetActiveSessionsForUser } from '../../Domain/UseCase/GetActiveSessionsForUser'
 import { ProjectorInterface } from '../../Projection/ProjectorInterface'
 import { Session } from '../../Domain/Session/Session'
+import { Result } from '@standardnotes/domain-core'
 
 describe('AnnotatedSessionsController', () => {
   let getActiveSessionsForUser: GetActiveSessionsForUser
@@ -45,7 +46,7 @@ describe('AnnotatedSessionsController', () => {
     sessionProjector.projectCustom = jest.fn().mockReturnValue({ foo: 'bar' })
 
     createCrossServiceToken = {} as jest.Mocked<CreateCrossServiceToken>
-    createCrossServiceToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
+    createCrossServiceToken.execute = jest.fn().mockReturnValue(Result.ok('foobar'))
 
     request = {
       params: {},

+ 3 - 2
packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSettingsController.spec.ts

@@ -10,6 +10,7 @@ import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting'
 import { GetSettings } from '../../Domain/UseCase/GetSettings/GetSettings'
 import { UpdateSetting } from '../../Domain/UseCase/UpdateSetting/UpdateSetting'
 import { User } from '../../Domain/User/User'
+import { Result } from '@standardnotes/domain-core'
 
 describe('AnnotatedSettingsController', () => {
   let deleteSetting: DeleteSetting
@@ -85,7 +86,7 @@ describe('AnnotatedSettingsController', () => {
       uuid: '1-2-3',
     }
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: true })
+    getSetting.execute = jest.fn().mockReturnValue(Result.ok())
 
     const httpResponse = <results.JsonResult>await createController().getSetting(request, response)
     const result = await httpResponse.executeAsync()
@@ -119,7 +120,7 @@ describe('AnnotatedSettingsController', () => {
       uuid: '1-2-3',
     }
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: false })
+    getSetting.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
 
     const httpResponse = <results.JsonResult>await createController().getSetting(request, response)
     const result = await httpResponse.executeAsync()

+ 3 - 2
packages/auth/src/Infra/InversifyExpressUtils/AnnotatedSubscriptionSettingsController.spec.ts

@@ -6,6 +6,7 @@ import { results } from 'inversify-express-utils'
 import { AnnotatedSubscriptionSettingsController } from './AnnotatedSubscriptionSettingsController'
 import { User } from '../../Domain/User/User'
 import { GetSetting } from '../../Domain/UseCase/GetSetting/GetSetting'
+import { Result } from '@standardnotes/domain-core'
 
 describe('AnnotatedSubscriptionSettingsController', () => {
   let getSetting: GetSetting
@@ -41,7 +42,7 @@ describe('AnnotatedSubscriptionSettingsController', () => {
       uuid: '1-2-3',
     }
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: true })
+    getSetting.execute = jest.fn().mockReturnValue(Result.ok())
 
     const httpResponse = <results.JsonResult>await createController().getSubscriptionSetting(request, response)
     const result = await httpResponse.executeAsync()
@@ -58,7 +59,7 @@ describe('AnnotatedSubscriptionSettingsController', () => {
       uuid: '1-2-3',
     }
 
-    getSetting.execute = jest.fn().mockReturnValue({ success: false })
+    getSetting.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
 
     const httpResponse = <results.JsonResult>await createController().getSubscriptionSetting(request, response)
     const result = await httpResponse.executeAsync()

+ 15 - 2
packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSessionsController.ts

@@ -45,12 +45,25 @@ export class BaseSessionsController extends BaseHttpController {
 
     const user = authenticateRequestResponse.user as User
 
-    const result = await this.createCrossServiceToken.execute({
+    const sharedVaultOwnerContext = request.headers['x-shared-vault-owner-context'] as string | undefined
+
+    const resultOrError = await this.createCrossServiceToken.execute({
       user,
       session: authenticateRequestResponse.session,
+      sharedVaultOwnerContext,
     })
+    if (resultOrError.isFailed()) {
+      return this.json(
+        {
+          error: {
+            message: resultOrError.getError(),
+          },
+        },
+        400,
+      )
+    }
 
-    return this.json({ authToken: result.token })
+    return this.json({ authToken: resultOrError.getValue() })
   }
 
   async getSessions(_request: Request, response: Response): Promise<results.JsonResult> {

+ 14 - 5
packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSettingsController.ts

@@ -58,13 +58,22 @@ export class BaseSettingsController extends BaseHttpController {
     }
 
     const { userUuid, settingName } = request.params
-    const result = await this.doGetSetting.execute({ userUuid, settingName: settingName.toUpperCase() })
-
-    if (result.success) {
-      return this.json(result)
+    const resultOrError = await this.doGetSetting.execute({ userUuid, settingName: settingName.toUpperCase() })
+    if (resultOrError.isFailed()) {
+      return this.json(
+        {
+          error: {
+            message: resultOrError.getError(),
+          },
+        },
+        400,
+      )
     }
 
-    return this.json(result, 400)
+    return this.json({
+      success: true,
+      ...resultOrError.getValue(),
+    })
   }
 
   async updateSetting(request: Request, response: Response): Promise<results.JsonResult | results.StatusCodeResult> {

+ 14 - 4
packages/auth/src/Infra/InversifyExpressUtils/Base/BaseSubscriptionSettingsController.ts

@@ -14,15 +14,25 @@ export class BaseSubscriptionSettingsController extends BaseHttpController {
   }
 
   async getSubscriptionSetting(request: Request, response: Response): Promise<results.JsonResult> {
-    const result = await this.doGetSetting.execute({
+    const resultOrError = await this.doGetSetting.execute({
       userUuid: response.locals.user.uuid,
       settingName: request.params.subscriptionSettingName.toUpperCase(),
     })
 
-    if (result.success) {
-      return this.json(result)
+    if (resultOrError.isFailed()) {
+      return this.json(
+        {
+          error: {
+            message: resultOrError.getError(),
+          },
+        },
+        400,
+      )
     }
 
-    return this.json(result, 400)
+    return this.json({
+      success: true,
+      ...resultOrError.getValue(),
+    })
   }
 }

+ 12 - 2
packages/auth/src/Infra/InversifyExpressUtils/Base/BaseWebSocketsController.ts

@@ -46,10 +46,20 @@ export class BaseWebSocketsController extends BaseHttpController {
       )
     }
 
-    const result = await this.createCrossServiceToken.execute({
+    const resultOrError = await this.createCrossServiceToken.execute({
       userUuid: token.userUuid,
     })
+    if (resultOrError.isFailed()) {
+      return this.json(
+        {
+          error: {
+            message: resultOrError.getError(),
+          },
+        },
+        400,
+      )
+    }
 
-    return this.json({ authToken: result.token })
+    return this.json({ authToken: resultOrError.getValue() })
   }
 }

+ 4 - 0
packages/files/src/Infra/InversifyExpress/AnnotatedSharedVaultFilesController.ts

@@ -121,6 +121,10 @@ export class AnnotatedSharedVaultFilesController extends BaseHttpController {
       return this.badRequest('Not permitted for this operation')
     }
 
+    if (locals.uploadBytesLimit === undefined) {
+      return this.badRequest('Missing upload bytes limit')
+    }
+
     const result = await this.finishUploadSession.execute({
       userUuid: locals.vaultOwnerUuid,
       sharedVaultUuid: locals.sharedVaultUuid,

+ 40 - 1
packages/files/src/Infra/InversifyExpress/Middleware/SharedVaultValetTokenAuthMiddleware.ts

@@ -1,4 +1,4 @@
-import { SharedVaultValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
+import { SharedVaultValetTokenData, TokenDecoderInterface, ValetTokenOperation } from '@standardnotes/security'
 import { Uuid } from '@standardnotes/domain-core'
 import { NextFunction, Request, Response } from 'express'
 import { inject, injectable } from 'inversify'
@@ -61,6 +61,17 @@ export class SharedVaultValetTokenAuthMiddleware extends BaseMiddleware {
         return
       }
 
+      if (this.userHasNoSpaceToUpload(valetTokenData)) {
+        response.status(403).send({
+          error: {
+            tag: 'no-space',
+            message: 'The file you are trying to upload is too big. Please ask the vault owner to upgrade subscription',
+          },
+        })
+
+        return
+      }
+
       const whitelistedData: SharedVaultValetTokenData = {
         sharedVaultUuid: valetTokenData.sharedVaultUuid,
         vaultOwnerUuid: valetTokenData.vaultOwnerUuid,
@@ -79,4 +90,32 @@ export class SharedVaultValetTokenAuthMiddleware extends BaseMiddleware {
       return next(error)
     }
   }
+
+  private userHasNoSpaceToUpload(valetTokenData: SharedVaultValetTokenData) {
+    if (![ValetTokenOperation.Write, ValetTokenOperation.Move].includes(valetTokenData.permittedOperation)) {
+      return false
+    }
+
+    if (valetTokenData.uploadBytesLimit === -1) {
+      return false
+    }
+
+    const isMovingToNonSharedVault =
+      valetTokenData.permittedOperation === ValetTokenOperation.Move &&
+      valetTokenData.moveOperation?.type === 'shared-vault-to-user'
+
+    if (isMovingToNonSharedVault) {
+      return false
+    }
+
+    if (valetTokenData.uploadBytesLimit === undefined) {
+      return true
+    }
+
+    const remainingUploadSpace = valetTokenData.uploadBytesLimit - valetTokenData.uploadBytesUsed
+
+    const consideredUploadSize = valetTokenData.unencryptedFileSize as number
+
+    return remainingUploadSpace - consideredUploadSize <= 0
+  }
 }

+ 3 - 0
packages/security/src/Domain/Token/CrossServiceTokenData.ts

@@ -5,6 +5,9 @@ export type CrossServiceTokenData = {
     uuid: string
     email: string
   }
+  shared_vault_owner_context?: {
+    upload_bytes_limit: number
+  }
   roles: Array<Role>
   session?: {
     uuid: string

+ 1 - 1
packages/security/src/Domain/Token/SharedVaultValetTokenData.ts

@@ -8,7 +8,7 @@ export interface SharedVaultValetTokenData {
   remoteIdentifier: string
   unencryptedFileSize?: number
   uploadBytesUsed: number
-  uploadBytesLimit: number
+  uploadBytesLimit?: number
   moveOperation?: {
     type: SharedVaultMoveType
     fromUuid: string

+ 13 - 0
packages/syncing-server/migrations/mysql/1692619430384-remove-shared-vault-limit.ts

@@ -0,0 +1,13 @@
+import { MigrationInterface, QueryRunner } from 'typeorm'
+
+export class RemoveSharedVaultLimit1692619430384 implements MigrationInterface {
+  name = 'RemoveSharedVaultLimit1692619430384'
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query('ALTER TABLE `shared_vaults` DROP COLUMN `file_upload_bytes_limit`')
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query('ALTER TABLE `shared_vaults` ADD `file_upload_bytes_limit` int NOT NULL')
+  }
+}

+ 20 - 0
packages/syncing-server/migrations/sqlite/1692619677621-remove-shared-vault-limit.ts

@@ -0,0 +1,20 @@
+import { MigrationInterface, QueryRunner } from 'typeorm'
+
+export class RemoveSharedVaultLimit1692619677621 implements MigrationInterface {
+  name = 'RemoveSharedVaultLimit1692619677621'
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query('ALTER TABLE "shared_vaults" RENAME TO "temporary_shared_vaults"')
+    await queryRunner.query(
+      'CREATE TABLE "shared_vaults" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "file_upload_bytes_used" integer NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
+    )
+    await queryRunner.query(
+      'INSERT INTO "shared_vaults"("uuid", "user_uuid", "file_upload_bytes_used", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "user_uuid", "file_upload_bytes_used", "created_at_timestamp", "updated_at_timestamp" FROM "temporary_shared_vaults"',
+    )
+    await queryRunner.query('DROP TABLE "temporary_shared_vaults"')
+  }
+
+  public async down(): Promise<void> {
+    return
+  }
+}

+ 0 - 8
packages/syncing-server/src/Bootstrap/Container.ts

@@ -78,8 +78,6 @@ import { SaveItems } from '../Domain/UseCase/Syncing/SaveItems/SaveItems'
 import { ItemHashHttpMapper } from '../Mapping/Http/ItemHashHttpMapper'
 import { ItemHash } from '../Domain/Item/ItemHash'
 import { ItemHashHttpRepresentation } from '../Mapping/Http/ItemHashHttpRepresentation'
-import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation'
-import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation'
 import { BaseSharedVaultInvitesController } from '../Infra/InversifyExpressUtils/Base/BaseSharedVaultInvitesController'
 import { InviteUserToSharedVault } from '../Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault'
 import { TypeORMSharedVaultRepository } from '../Infra/TypeORM/TypeORMSharedVaultRepository'
@@ -361,12 +359,6 @@ export class ContainerConfigLoader {
     container
       .bind<Repository<TypeORMItem>>(TYPES.Sync_ORMItemRepository)
       .toDynamicValue(() => appDataSource.getRepository(TypeORMItem))
-    container
-      .bind<Repository<TypeORMSharedVaultAssociation>>(TYPES.Sync_ORMSharedVaultAssociationRepository)
-      .toConstantValue(appDataSource.getRepository(TypeORMSharedVaultAssociation))
-    container
-      .bind<Repository<TypeORMKeySystemAssociation>>(TYPES.Sync_ORMKeySystemAssociationRepository)
-      .toConstantValue(appDataSource.getRepository(TypeORMKeySystemAssociation))
     container
       .bind<Repository<TypeORMSharedVault>>(TYPES.Sync_ORMSharedVaultRepository)
       .toConstantValue(appDataSource.getRepository(TypeORMSharedVault))

+ 0 - 4
packages/syncing-server/src/Bootstrap/DataSource.ts

@@ -4,8 +4,6 @@ import { Env } from './Env'
 import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
 import { TypeORMItem } from '../Infra/TypeORM/TypeORMItem'
 import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
-import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation'
-import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation'
 import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
 import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
 import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
@@ -79,8 +77,6 @@ export class AppDataSource {
       entities: [
         TypeORMItem,
         TypeORMNotification,
-        TypeORMSharedVaultAssociation,
-        TypeORMKeySystemAssociation,
         TypeORMSharedVault,
         TypeORMSharedVaultUser,
         TypeORMSharedVaultInvite,

+ 0 - 2
packages/syncing-server/src/Bootstrap/Types.ts

@@ -17,8 +17,6 @@ const TYPES = {
   Sync_MessageRepository: Symbol.for('Sync_MessageRepository'),
   // ORM
   Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
-  Sync_ORMSharedVaultAssociationRepository: Symbol.for('Sync_ORMSharedVaultAssociationRepository'),
-  Sync_ORMKeySystemAssociationRepository: Symbol.for('Sync_ORMKeySystemAssociationRepository'),
   Sync_ORMSharedVaultRepository: Symbol.for('Sync_ORMSharedVaultRepository'),
   Sync_ORMSharedVaultInviteRepository: Symbol.for('Sync_ORMSharedVaultInviteRepository'),
   Sync_ORMSharedVaultUserRepository: Symbol.for('Sync_ORMSharedVaultUserRepository'),

+ 1 - 103
packages/syncing-server/src/Domain/Item/Item.spec.ts

@@ -110,9 +110,7 @@ describe('Item', () => {
       updatedWithSession: null,
       dates: Dates.create(new Date(123), new Date(123)).getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
-      keySystemAssociation: KeySystemAssociation.create({
-        keySystemIdentifier: 'key-system-identifier',
-      }).getValue(),
+      keySystemAssociation: KeySystemAssociation.create('key-system-identifier').getValue(),
     })
 
     expect(entityOrError.isFailed()).toBeFalsy()
@@ -138,106 +136,6 @@ describe('Item', () => {
     expect(entityOrError.getValue().isAssociatedWithKeySystem('key-system-identifier')).toBeFalsy()
   })
 
-  it('should set shared vault association', () => {
-    const sharedVaultAssociation = SharedVaultAssociation.create({
-      sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-    }).getValue()
-
-    const entity = Item.create({
-      duplicateOf: null,
-      itemsKeyId: 'items-key-id',
-      content: 'content',
-      contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
-      encItemKey: 'enc-item-key',
-      authHash: 'auth-hash',
-      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      deleted: false,
-      updatedWithSession: null,
-      dates: Dates.create(new Date(123), new Date(123)).getValue(),
-      timestamps: Timestamps.create(123, 123).getValue(),
-    }).getValue()
-
-    entity.setSharedVaultAssociation(sharedVaultAssociation)
-
-    expect(entity.props.sharedVaultAssociation).toEqual(sharedVaultAssociation)
-    expect(entity.getChanges()).toHaveLength(1)
-  })
-
-  it('should unset a shared vault association', () => {
-    const entity = Item.create({
-      duplicateOf: null,
-      itemsKeyId: 'items-key-id',
-      content: 'content',
-      contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
-      encItemKey: 'enc-item-key',
-      authHash: 'auth-hash',
-      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      deleted: false,
-      updatedWithSession: null,
-      dates: Dates.create(new Date(123), new Date(123)).getValue(),
-      timestamps: Timestamps.create(123, 123).getValue(),
-      sharedVaultAssociation: SharedVaultAssociation.create({
-        sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-        lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      }).getValue(),
-    }).getValue()
-
-    entity.unsetSharedVaultAssociation()
-
-    expect(entity.props.sharedVaultAssociation).toBeUndefined()
-    expect(entity.getChanges()).toHaveLength(1)
-  })
-
-  it('should set key system association', () => {
-    const keySystemAssociation = KeySystemAssociation.create({
-      keySystemIdentifier: 'key-system-identifier',
-    }).getValue()
-
-    const entity = Item.create({
-      duplicateOf: null,
-      itemsKeyId: 'items-key-id',
-      content: 'content',
-      contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
-      encItemKey: 'enc-item-key',
-      authHash: 'auth-hash',
-      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      deleted: false,
-      updatedWithSession: null,
-      dates: Dates.create(new Date(123), new Date(123)).getValue(),
-      timestamps: Timestamps.create(123, 123).getValue(),
-    }).getValue()
-
-    entity.setKeySystemAssociation(keySystemAssociation)
-
-    expect(entity.props.keySystemAssociation).toEqual(keySystemAssociation)
-    expect(entity.getChanges()).toHaveLength(1)
-  })
-
-  it('should unset a key system association', () => {
-    const entity = Item.create({
-      duplicateOf: null,
-      itemsKeyId: 'items-key-id',
-      content: 'content',
-      contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
-      encItemKey: 'enc-item-key',
-      authHash: 'auth-hash',
-      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-      deleted: false,
-      updatedWithSession: null,
-      dates: Dates.create(new Date(123), new Date(123)).getValue(),
-      timestamps: Timestamps.create(123, 123).getValue(),
-      keySystemAssociation: KeySystemAssociation.create({
-        keySystemIdentifier: 'key-system-identifier',
-      }).getValue(),
-    }).getValue()
-
-    entity.unsetKeySystemAssociation()
-
-    expect(entity.props.keySystemAssociation).toBeUndefined()
-    expect(entity.getChanges()).toHaveLength(1)
-  })
-
   it('should tell if an item is identical to another item', () => {
     const entity = Item.create(
       {

+ 1 - 57
packages/syncing-server/src/Domain/Item/Item.ts

@@ -1,8 +1,6 @@
-import { Aggregate, Change, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
+import { Aggregate, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
 
 import { ItemProps } from './ItemProps'
-import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
-import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
 
 export class Item extends Aggregate<ItemProps> {
   private constructor(props: ItemProps, id?: UniqueEntityId) {
@@ -55,60 +53,6 @@ export class Item extends Aggregate<ItemProps> {
     return this.props.keySystemAssociation.props.keySystemIdentifier === keySystemIdentifier
   }
 
-  setSharedVaultAssociation(sharedVaultAssociation: SharedVaultAssociation): void {
-    this.addChange(
-      Change.create({
-        aggregateRootUuid: this.uuid.value,
-        changeType: this.props.sharedVaultAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
-        changeData: sharedVaultAssociation,
-      }).getValue(),
-    )
-
-    this.props.sharedVaultAssociation = sharedVaultAssociation
-  }
-
-  unsetSharedVaultAssociation(): void {
-    if (!this.props.sharedVaultAssociation) {
-      return
-    }
-
-    this.addChange(
-      Change.create({
-        aggregateRootUuid: this.uuid.value,
-        changeType: Change.TYPES.Remove,
-        changeData: this.props.sharedVaultAssociation,
-      }).getValue(),
-    )
-    this.props.sharedVaultAssociation = undefined
-  }
-
-  setKeySystemAssociation(keySystemAssociation: KeySystemAssociation): void {
-    this.addChange(
-      Change.create({
-        aggregateRootUuid: this.uuid.value,
-        changeType: this.props.keySystemAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
-        changeData: keySystemAssociation,
-      }).getValue(),
-    )
-
-    this.props.keySystemAssociation = keySystemAssociation
-  }
-
-  unsetKeySystemAssociation(): void {
-    if (!this.props.keySystemAssociation) {
-      return
-    }
-
-    this.addChange(
-      Change.create({
-        aggregateRootUuid: this.uuid.value,
-        changeType: Change.TYPES.Remove,
-        changeData: this.props.keySystemAssociation,
-      }).getValue(),
-    )
-    this.props.keySystemAssociation = undefined
-  }
-
   isIdenticalTo(item: Item): boolean {
     if (this._id.toString() !== item._id.toString()) {
       return false

+ 8 - 5
packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.spec.ts

@@ -1,12 +1,15 @@
 import { KeySystemAssociation } from './KeySystemAssociation'
 
 describe('KeySystemAssociation', () => {
-  it('should create an entity', () => {
-    const entityOrError = KeySystemAssociation.create({
-      keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
-    })
+  it('should create a value object', () => {
+    const entityOrError = KeySystemAssociation.create('00000000-0000-0000-0000-000000000000')
 
     expect(entityOrError.isFailed()).toBeFalsy()
-    expect(entityOrError.getValue().id).not.toBeNull()
+  })
+
+  it('should fail to create a value object with an empty key system identifier', () => {
+    const entityOrError = KeySystemAssociation.create('')
+
+    expect(entityOrError.isFailed()).toBeTruthy()
   })
 })

+ 11 - 6
packages/syncing-server/src/Domain/KeySystem/KeySystemAssociation.ts

@@ -1,13 +1,18 @@
-import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
+import { Result, Validator, ValueObject } from '@standardnotes/domain-core'
 
 import { KeySystemAssociationProps } from './KeySystemAssocationProps'
 
-export class KeySystemAssociation extends Entity<KeySystemAssociationProps> {
-  private constructor(props: KeySystemAssociationProps, id?: UniqueEntityId) {
-    super(props, id)
+export class KeySystemAssociation extends ValueObject<KeySystemAssociationProps> {
+  private constructor(props: KeySystemAssociationProps) {
+    super(props)
   }
 
-  static create(props: KeySystemAssociationProps, id?: UniqueEntityId): Result<KeySystemAssociation> {
-    return Result.ok<KeySystemAssociation>(new KeySystemAssociation(props, id))
+  static create(keySystemIdentifier: string): Result<KeySystemAssociation> {
+    const validationResult = Validator.isNotEmptyString(keySystemIdentifier)
+    if (validationResult.isFailed()) {
+      return Result.fail<KeySystemAssociation>(validationResult.getError())
+    }
+
+    return Result.ok<KeySystemAssociation>(new KeySystemAssociation({ keySystemIdentifier }))
   }
 }

+ 0 - 2
packages/syncing-server/src/Domain/SharedVault/SharedVault.spec.ts

@@ -5,7 +5,6 @@ import { SharedVault } from './SharedVault'
 describe('SharedVault', () => {
   it('should create an entity', () => {
     const entityOrError = SharedVault.create({
-      fileUploadBytesLimit: 1_000_000,
       fileUploadBytesUsed: 0,
       timestamps: Timestamps.create(123456789, 123456789).getValue(),
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
@@ -19,7 +18,6 @@ describe('SharedVault', () => {
   it('should throw an error if id cannot be cast to uuid', () => {
     const entityOrError = SharedVault.create(
       {
-        fileUploadBytesLimit: 1_000_000,
         fileUploadBytesUsed: 0,
         timestamps: Timestamps.create(123456789, 123456789).getValue(),
         userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),

+ 1 - 2
packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.spec.ts

@@ -3,13 +3,12 @@ import { Uuid } from '@standardnotes/domain-core'
 import { SharedVaultAssociation } from './SharedVaultAssociation'
 
 describe('SharedVaultAssociation', () => {
-  it('should create an entity', () => {
+  it('should create a value object', () => {
     const entityOrError = SharedVaultAssociation.create({
       lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
     })
 
     expect(entityOrError.isFailed()).toBeFalsy()
-    expect(entityOrError.getValue().id).not.toBeNull()
   })
 })

+ 6 - 6
packages/syncing-server/src/Domain/SharedVault/SharedVaultAssociation.ts

@@ -1,13 +1,13 @@
-import { Aggregate, Result, UniqueEntityId } from '@standardnotes/domain-core'
+import { Result, ValueObject } from '@standardnotes/domain-core'
 
 import { SharedVaultAssociationProps } from './SharedVaultAssociationProps'
 
-export class SharedVaultAssociation extends Aggregate<SharedVaultAssociationProps> {
-  private constructor(props: SharedVaultAssociationProps, id?: UniqueEntityId) {
-    super(props, id)
+export class SharedVaultAssociation extends ValueObject<SharedVaultAssociationProps> {
+  private constructor(props: SharedVaultAssociationProps) {
+    super(props)
   }
 
-  static create(props: SharedVaultAssociationProps, id?: UniqueEntityId): Result<SharedVaultAssociation> {
-    return Result.ok<SharedVaultAssociation>(new SharedVaultAssociation(props, id))
+  static create(props: SharedVaultAssociationProps): Result<SharedVaultAssociation> {
+    return Result.ok<SharedVaultAssociation>(new SharedVaultAssociation(props))
   }
 }

+ 0 - 1
packages/syncing-server/src/Domain/SharedVault/SharedVaultProps.ts

@@ -3,6 +3,5 @@ import { Uuid, Timestamps } from '@standardnotes/domain-core'
 export interface SharedVaultProps {
   userUuid: Uuid
   fileUploadBytesUsed: number
-  fileUploadBytesLimit: number
   timestamps: Timestamps
 }

+ 2 - 4
packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVault/CreateSharedVault.ts

@@ -7,16 +7,15 @@ import {
   Uuid,
   Validator,
 } from '@standardnotes/domain-core'
+import { TimerInterface } from '@standardnotes/time'
+
 import { CreateSharedVaultResult } from './CreateSharedVaultResult'
 import { CreateSharedVaultDTO } from './CreateSharedVaultDTO'
-import { TimerInterface } from '@standardnotes/time'
 import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
 import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
 import { SharedVault } from '../../../SharedVault/SharedVault'
 
 export class CreateSharedVault implements UseCaseInterface<CreateSharedVaultResult> {
-  private readonly FILE_UPLOAD_BYTES_LIMIT = 1_000_000_000
-
   constructor(
     private addUserToSharedVault: AddUserToSharedVault,
     private sharedVaultRepository: SharedVaultRepositoryInterface,
@@ -49,7 +48,6 @@ export class CreateSharedVault implements UseCaseInterface<CreateSharedVaultResu
     ).getValue()
 
     const sharedVaultOrError = SharedVault.create({
-      fileUploadBytesLimit: this.FILE_UPLOAD_BYTES_LIMIT,
       fileUploadBytesUsed: 0,
       userUuid,
       timestamps,

+ 0 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken.spec.ts

@@ -19,7 +19,6 @@ describe('CreateSharedVaultFileValetToken', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 1 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken.ts

@@ -89,7 +89,7 @@ export class CreateSharedVaultFileValetToken implements UseCaseInterface<string>
       permittedOperation: dto.operation,
       remoteIdentifier: dto.remoteIdentifier,
       uploadBytesUsed: sharedVault.props.fileUploadBytesUsed,
-      uploadBytesLimit: sharedVault.props.fileUploadBytesLimit,
+      uploadBytesLimit: dto.sharedVaultOwnerUploadBytesLimit,
       unencryptedFileSize: dto.unencryptedFileSize,
       moveOperation: this.createMoveOperationData(dto),
     }

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetTokenDTO.ts

@@ -3,6 +3,7 @@ import { SharedVaultMoveType, ValetTokenOperation } from '@standardnotes/securit
 export interface CreateSharedVaultFileValetTokenDTO {
   userUuid: string
   sharedVaultUuid: string
+  sharedVaultOwnerUploadBytesLimit?: number
   fileUuid?: string
   remoteIdentifier: string
   operation: ValetTokenOperation

+ 0 - 2
packages/syncing-server/src/Domain/UseCase/SharedVaults/DeleteSharedVault/DeleteSharedVault.spec.ts

@@ -26,7 +26,6 @@ describe('DeleteSharedVault', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
@@ -110,7 +109,6 @@ describe('DeleteSharedVault', () => {
 
   it('should return error when originator of the delete request is not the owner of the shared vault', async () => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 0 - 2
packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultUsers/GetSharedVaultUsers.spec.ts

@@ -15,7 +15,6 @@ describe('GetSharedVaultUsers', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
@@ -61,7 +60,6 @@ describe('GetSharedVaultUsers', () => {
 
   it('returns error when originator is not the owner of the shared vault', async () => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 0 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaults/GetSharedVaults.spec.ts

@@ -26,7 +26,6 @@ describe('GetSharedVaults', () => {
     sharedVault = SharedVault.create({
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
-      fileUploadBytesLimit: 123,
       fileUploadBytesUsed: 123,
     }).getValue()
     sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>

+ 0 - 2
packages/syncing-server/src/Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault.spec.ts

@@ -22,7 +22,6 @@ describe('InviteUserToSharedVault', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
@@ -179,7 +178,6 @@ describe('InviteUserToSharedVault', () => {
     const useCase = createUseCase()
 
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('10000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 0 - 3
packages/syncing-server/src/Domain/UseCase/SharedVaults/RemoveUserFromSharedVault/RemoveUserFromSharedVault.spec.ts

@@ -19,7 +19,6 @@ describe('RemoveUserFromSharedVault', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
@@ -84,7 +83,6 @@ describe('RemoveUserFromSharedVault', () => {
 
   it('should return error when user is not owner of shared vault', async () => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),
@@ -104,7 +102,6 @@ describe('RemoveUserFromSharedVault', () => {
 
   it('should remove shared vault user if user is owner and is being force removed', async () => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 0 - 1
packages/syncing-server/src/Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault.spec.ts

@@ -11,7 +11,6 @@ describe('UpdateStorageQuotaUsedInSharedVault', () => {
 
   beforeEach(() => {
     sharedVault = SharedVault.create({
-      fileUploadBytesLimit: 100,
       fileUploadBytesUsed: 2,
       userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
       timestamps: Timestamps.create(123, 123).getValue(),

+ 3 - 5
packages/syncing-server/src/Domain/UseCase/Syncing/SaveNewItem/SaveNewItem.ts

@@ -123,7 +123,7 @@ export class SaveNewItem implements UseCaseInterface<Item> {
       if (sharedVaultAssociationOrError.isFailed()) {
         return Result.fail(sharedVaultAssociationOrError.getError())
       }
-      newItem.setSharedVaultAssociation(sharedVaultAssociationOrError.getValue())
+      newItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
     }
 
     if (dto.itemHash.hasDedicatedKeySystemAssociation()) {
@@ -133,13 +133,11 @@ export class SaveNewItem implements UseCaseInterface<Item> {
       }
       const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
 
-      const keySystemAssociationOrError = KeySystemAssociation.create({
-        keySystemIdentifier,
-      })
+      const keySystemAssociationOrError = KeySystemAssociation.create(keySystemIdentifier)
       if (keySystemAssociationOrError.isFailed()) {
         return Result.fail(keySystemAssociationOrError.getError())
       }
-      newItem.setKeySystemAssociation(keySystemAssociationOrError.getValue())
+      newItem.props.keySystemAssociation = keySystemAssociationOrError.getValue()
     }
 
     const itemRepository = this.itemRepositoryResolver.resolve(roleNames)

+ 5 - 15
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.spec.ts

@@ -382,13 +382,10 @@ describe('UpdateExistingItem', () => {
         shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
       }).getValue()
 
-      item1.setSharedVaultAssociation(
-        SharedVaultAssociation.create({
-          sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-          lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
-        }).getValue(),
-      )
-      const idBefore = item1.props.sharedVaultAssociation?.id.toString()
+      item1.props.sharedVaultAssociation = SharedVaultAssociation.create({
+        sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+        lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      }).getValue()
 
       const result = await useCase.execute({
         existingItem: item1,
@@ -401,7 +398,6 @@ describe('UpdateExistingItem', () => {
       expect(result.isFailed()).toBeFalsy()
 
       expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
-      expect((item1.props.sharedVaultAssociation as SharedVaultAssociation).id.toString()).toEqual(idBefore)
     })
 
     it('should return error if shared vault association could not be created', async () => {
@@ -567,12 +563,7 @@ describe('UpdateExistingItem', () => {
         key_system_identifier: '00000000-0000-0000-0000-000000000000',
       }).getValue()
 
-      item1.setKeySystemAssociation(
-        KeySystemAssociation.create({
-          keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
-        }).getValue(),
-      )
-      const idBefore = item1.props.keySystemAssociation?.id.toString()
+      item1.props.keySystemAssociation = KeySystemAssociation.create('00000000-0000-0000-0000-000000000000').getValue()
 
       const result = await useCase.execute({
         existingItem: item1,
@@ -585,7 +576,6 @@ describe('UpdateExistingItem', () => {
       expect(result.isFailed()).toBeFalsy()
 
       expect(item1.props.keySystemAssociation).not.toBeUndefined()
-      expect((item1.props.keySystemAssociation as KeySystemAssociation).id.toString()).toEqual(idBefore)
     })
 
     it('should return error if key system identifier is invalid', async () => {

+ 9 - 26
packages/syncing-server/src/Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem.ts

@@ -6,7 +6,6 @@ import {
   Result,
   RoleNameCollection,
   Timestamps,
-  UniqueEntityId,
   UseCaseInterface,
   Uuid,
   Validator,
@@ -131,23 +130,16 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
 
     let sharedVaultOperation: SharedVaultOperationOnItem | null = null
     if (dto.itemHash.representsASharedVaultItem()) {
-      const sharedVaultAssociationOrError = SharedVaultAssociation.create(
-        {
-          lastEditedBy: userUuid,
-          sharedVaultUuid: dto.itemHash.sharedVaultUuid as Uuid,
-        },
-        new UniqueEntityId(
-          dto.existingItem.props.sharedVaultAssociation
-            ? dto.existingItem.props.sharedVaultAssociation.id.toString()
-            : undefined,
-        ),
-      )
+      const sharedVaultAssociationOrError = SharedVaultAssociation.create({
+        lastEditedBy: userUuid,
+        sharedVaultUuid: dto.itemHash.sharedVaultUuid as Uuid,
+      })
 
       if (sharedVaultAssociationOrError.isFailed()) {
         return Result.fail(sharedVaultAssociationOrError.getError())
       }
 
-      dto.existingItem.setSharedVaultAssociation(sharedVaultAssociationOrError.getValue())
+      dto.existingItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
 
       const sharedVaultOperationOrError = await this.determineSharedVaultOperationOnItem.execute({
         existingItem: dto.existingItem,
@@ -159,7 +151,7 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
       }
       sharedVaultOperation = sharedVaultOperationOrError.getValue()
     } else {
-      dto.existingItem.unsetSharedVaultAssociation()
+      dto.existingItem.props.sharedVaultAssociation = undefined
     }
 
     if (dto.itemHash.hasDedicatedKeySystemAssociation()) {
@@ -169,23 +161,14 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
       }
       const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
 
-      const keySystemAssociationOrError = KeySystemAssociation.create(
-        {
-          keySystemIdentifier,
-        },
-        new UniqueEntityId(
-          dto.existingItem.props.keySystemAssociation
-            ? dto.existingItem.props.keySystemAssociation.id.toString()
-            : undefined,
-        ),
-      )
+      const keySystemAssociationOrError = KeySystemAssociation.create(keySystemIdentifier)
       if (keySystemAssociationOrError.isFailed()) {
         return Result.fail(keySystemAssociationOrError.getError())
       }
 
-      dto.existingItem.setKeySystemAssociation(keySystemAssociationOrError.getValue())
+      dto.existingItem.props.keySystemAssociation = keySystemAssociationOrError.getValue()
     } else {
-      dto.existingItem.unsetKeySystemAssociation()
+      dto.existingItem.props.keySystemAssociation = undefined
     }
 
     if (dto.itemHash.props.deleted === true) {

+ 1 - 0
packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseSharedVaultsController.ts

@@ -104,6 +104,7 @@ export class BaseSharedVaultsController extends BaseHttpController {
     const result = await this.createSharedVaultFileValetTokenUseCase.execute({
       userUuid: response.locals.user.uuid,
       sharedVaultUuid: request.params.sharedVaultUuid,
+      sharedVaultOwnerUploadBytesLimit: response.locals.sharedVaultOwnerContext?.upload_bytes_limit,
       fileUuid: request.body.file_uuid,
       remoteIdentifier: request.body.remote_identifier,
       operation: request.body.operation,

+ 1 - 0
packages/syncing-server/src/Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware.ts

@@ -25,6 +25,7 @@ export class InversifyExpressAuthMiddleware extends BaseMiddleware {
       response.locals.roles = decodedToken.roles
       response.locals.session = decodedToken.session
       response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
+      response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
 
       return next()
     } catch (error) {

+ 0 - 33
packages/syncing-server/src/Infra/TypeORM/TypeORMKeySystemAssociation.ts

@@ -1,33 +0,0 @@
-import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
-
-@Entity({ name: 'key_system_associations' })
-export class TypeORMKeySystemAssociation {
-  @PrimaryGeneratedColumn('uuid')
-  declare uuid: string
-
-  @Column({
-    name: 'key_system_identifier',
-    length: 36,
-  })
-  @Index('key_system_identifier_on_key_system_associations')
-  declare keySystemIdentifier: string
-
-  @Column({
-    name: 'item_uuid',
-    length: 36,
-  })
-  @Index('item_uuid_on_key_system_associations')
-  declare itemUuid: string
-
-  @Column({
-    name: 'created_at_timestamp',
-    type: 'bigint',
-  })
-  declare createdAtTimestamp: number
-
-  @Column({
-    name: 'updated_at_timestamp',
-    type: 'bigint',
-  })
-  declare updatedAtTimestamp: number
-}

+ 0 - 6
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVault.ts

@@ -18,12 +18,6 @@ export class TypeORMSharedVault {
   })
   declare fileUploadBytesUsed: number
 
-  @Column({
-    name: 'file_upload_bytes_limit',
-    type: 'int',
-  })
-  declare fileUploadBytesLimit: number
-
   @Column({
     name: 'created_at_timestamp',
     type: 'bigint',

+ 0 - 40
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultAssociation.ts

@@ -1,40 +0,0 @@
-import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
-
-@Entity({ name: 'shared_vault_associations' })
-export class TypeORMSharedVaultAssociation {
-  @PrimaryGeneratedColumn('uuid')
-  declare uuid: string
-
-  @Column({
-    name: 'shared_vault_uuid',
-    length: 36,
-  })
-  @Index('shared_vault_uuid_on_shared_vault_associations')
-  declare sharedVaultUuid: string
-
-  @Column({
-    name: 'item_uuid',
-    length: 36,
-  })
-  @Index('item_uuid_on_shared_vault_associations')
-  declare itemUuid: string
-
-  @Column({
-    name: 'last_edited_by',
-    type: 'varchar',
-    length: 36,
-  })
-  declare lastEditedBy: string
-
-  @Column({
-    name: 'created_at_timestamp',
-    type: 'bigint',
-  })
-  declare createdAtTimestamp: number
-
-  @Column({
-    name: 'updated_at_timestamp',
-    type: 'bigint',
-  })
-  declare updatedAtTimestamp: number
-}

+ 0 - 1
packages/syncing-server/src/Mapping/Http/SharedVaultHttpMapper.ts

@@ -12,7 +12,6 @@ export class SharedVaultHttpMapper implements MapperInterface<SharedVault, Share
     return {
       uuid: domain.id.toString(),
       user_uuid: domain.props.userUuid.value,
-      file_upload_bytes_limit: domain.props.fileUploadBytesLimit,
       file_upload_bytes_used: domain.props.fileUploadBytesUsed,
       created_at_timestamp: domain.props.timestamps.createdAt,
       updated_at_timestamp: domain.props.timestamps.updatedAt,

+ 0 - 1
packages/syncing-server/src/Mapping/Http/SharedVaultHttpRepresentation.ts

@@ -2,7 +2,6 @@ export interface SharedVaultHttpRepresentation {
   uuid: string
   user_uuid: string
   file_upload_bytes_used: number
-  file_upload_bytes_limit: number
   created_at_timestamp: number
   updated_at_timestamp: number
 }

+ 1 - 3
packages/syncing-server/src/Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper.ts

@@ -73,9 +73,7 @@ export class MongoDBItemPersistenceMapper implements MapperInterface<Item, Mongo
 
     let keySystemAssociation: KeySystemAssociation | undefined = undefined
     if (projection.keySystemIdentifier) {
-      const keySystemAssociationOrError = KeySystemAssociation.create({
-        keySystemIdentifier: projection.keySystemIdentifier,
-      })
+      const keySystemAssociationOrError = KeySystemAssociation.create(projection.keySystemIdentifier)
       if (keySystemAssociationOrError.isFailed()) {
         throw new Error(`Failed to create item from projection: ${keySystemAssociationOrError.getError()}`)
       }

+ 0 - 2
packages/syncing-server/src/Mapping/Persistence/SharedVaultPersistenceMapper.ts

@@ -21,7 +21,6 @@ export class SharedVaultPersistenceMapper implements MapperInterface<SharedVault
       {
         userUuid,
         fileUploadBytesUsed: projection.fileUploadBytesUsed,
-        fileUploadBytesLimit: projection.fileUploadBytesLimit,
         timestamps,
       },
       new UniqueEntityId(projection.uuid),
@@ -40,7 +39,6 @@ export class SharedVaultPersistenceMapper implements MapperInterface<SharedVault
     typeorm.uuid = domain.id.toString()
     typeorm.userUuid = domain.props.userUuid.value
     typeorm.fileUploadBytesUsed = domain.props.fileUploadBytesUsed
-    typeorm.fileUploadBytesLimit = domain.props.fileUploadBytesLimit
     typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
     typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt