Ver código fonte

feat: add creating shared vault file valet tokens. (#635)

Co-authored-by: Mo <mo@standardnotes.com>
Karol Sójko 2 anos atrás
pai
commit
04b3bb034f

+ 8 - 1
packages/syncing-server/src/Domain/SharedVault/User/SharedVaultUserPermission.ts

@@ -3,6 +3,12 @@ import { Result, ValueObject } from '@standardnotes/domain-core'
 import { SharedVaultUserPermissionProps } from './SharedVaultUserPermissionProps'
 
 export class SharedVaultUserPermission extends ValueObject<SharedVaultUserPermissionProps> {
+  static readonly PERMISSIONS = {
+    Read: 'read',
+    Write: 'write',
+    Admin: 'admin',
+  }
+
   get value(): string {
     return this.props.value
   }
@@ -12,7 +18,8 @@ export class SharedVaultUserPermission extends ValueObject<SharedVaultUserPermis
   }
 
   static create(sharedVaultUserPermission: string): Result<SharedVaultUserPermission> {
-    if (!['read', 'write', 'admin'].includes(sharedVaultUserPermission)) {
+    const isValidPermission = Object.values(this.PERMISSIONS).includes(sharedVaultUserPermission)
+    if (!isValidPermission) {
       return Result.fail<SharedVaultUserPermission>(`Invalid shared vault user permission ${sharedVaultUserPermission}`)
     } else {
       return Result.ok<SharedVaultUserPermission>(new SharedVaultUserPermission({ value: sharedVaultUserPermission }))

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

@@ -6,4 +6,5 @@ export interface SharedVaultUserRepositoryInterface {
   findByUuid(sharedVaultUserUuid: Uuid): Promise<SharedVaultUser | null>
   save(sharedVaultUser: SharedVaultUser): Promise<void>
   remove(sharedVault: SharedVaultUser): Promise<void>
+  findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
 }

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

@@ -0,0 +1,363 @@
+import { SharedVaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
+import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
+import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
+import { CreateSharedVaultFileValetToken } from './CreateSharedVaultFileValetToken'
+import { SharedVault } from '../../SharedVault/SharedVault'
+import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
+import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
+import { Timestamps, Uuid } from '@standardnotes/domain-core'
+
+describe('CreateSharedVaultFileValetToken', () => {
+  let sharedVaultRepository: SharedVaultRepositoryInterface
+  let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
+  let tokenEncoder: TokenEncoderInterface<SharedVaultValetTokenData>
+  const valetTokenTTL = 3600
+  let sharedVault: SharedVault
+  let sharedVaultUser: SharedVaultUser
+
+  const createUseCase = () =>
+    new CreateSharedVaultFileValetToken(sharedVaultRepository, sharedVaultUserRepository, tokenEncoder, valetTokenTTL)
+
+  beforeEach(() => {
+    sharedVault = SharedVault.create({
+      fileUploadBytesLimit: 100,
+      fileUploadBytesUsed: 2,
+      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      timestamps: Timestamps.create(123, 123).getValue(),
+    }).getValue()
+
+    sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
+    sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
+
+    sharedVaultUser = SharedVaultUser.create({
+      permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
+      sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      timestamps: Timestamps.create(123, 123).getValue(),
+    }).getValue()
+
+    sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
+    sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
+
+    tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<SharedVaultValetTokenData>>
+    tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('encoded-token')
+  })
+
+  it('should return error when shared vault uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: 'invalid-uuid',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Read,
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
+  })
+
+  it('should return error when user uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: 'invalid-uuid',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Read,
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
+  })
+
+  it('should return error when shared vault is not found', async () => {
+    sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null)
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Read,
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Shared vault not found')
+  })
+
+  it('should return error when shared vault user is not found', async () => {
+    sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(null)
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Read,
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Shared vault user not found')
+  })
+
+  it('should return error when shared vault user does not have permission', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Write,
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('User does not have permission to perform this operation')
+  })
+
+  it('should create a shared vault file valet token', async () => {
+    sharedVaultUser = SharedVaultUser.create({
+      permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+      sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+      timestamps: Timestamps.create(123, 123).getValue(),
+    }).getValue()
+    sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
+
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+      remoteIdentifier: 'remote-identifier',
+      operation: ValetTokenOperation.Write,
+    })
+
+    expect(result.isFailed()).toBe(false)
+    expect(result.getValue()).toBe('encoded-token')
+  })
+
+  describe('move operation', () => {
+    beforeEach(() => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+    })
+
+    it('should return error when move operation type is not specified', async () => {
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+      })
+
+      expect(result.isFailed()).toBe(true)
+      expect(result.getError()).toBe('Move operation type is required')
+    })
+
+    it('should return error when target uuid is missing on a shared-vault-to-shared-vault move operation', async () => {
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-shared-vault',
+      })
+
+      expect(result.isFailed()).toBe(true)
+      expect(result.getError()).toBe('Shared vault to shared vault move target uuid is required')
+    })
+
+    it('should return error when target uuid is invalid on a shared-vault-to-shared-vault move operation', async () => {
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-shared-vault',
+        sharedVaultToSharedVaultMoveTargetUuid: 'invalid-uuid',
+      })
+
+      expect(result.isFailed()).toBe(true)
+      expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
+    })
+
+    it('should return error when target shared vault user is not found on a shared-vault-to-shared-vault move operation', async () => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(null)
+
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-shared-vault',
+        sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBe(true)
+      expect(result.getError()).toBe('Shared vault target user not found')
+    })
+
+    it('should return error when target shared vault user does not have permission on a shared-vault-to-shared-vault move operation', async () => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-shared-vault',
+        sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBe(true)
+      expect(result.getError()).toBe('User does not have permission to perform this operation')
+    })
+
+    it('should create move valet token for shared-vault-to-shared-vault operation', async () => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-shared-vault',
+        sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBe(false)
+      expect(result.getValue()).toBe('encoded-token')
+    })
+
+    it('should create move valet token for shared-vault-to-user operation', async () => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'shared-vault-to-user',
+        sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBe(false)
+      expect(result.getValue()).toBe('encoded-token')
+    })
+
+    it('should create move valet token for user-to-shared-vault operation', async () => {
+      sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
+        .fn()
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+        .mockReturnValueOnce(
+          SharedVaultUser.create({
+            permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
+            sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
+            timestamps: Timestamps.create(123, 123).getValue(),
+          }).getValue(),
+        )
+
+      const useCase = createUseCase()
+      const result = await useCase.execute({
+        userUuid: '00000000-0000-0000-0000-000000000000',
+        sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
+        remoteIdentifier: 'remote-identifier',
+        operation: ValetTokenOperation.Move,
+        moveOperationType: 'user-to-shared-vault',
+        sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
+      })
+
+      expect(result.isFailed()).toBe(false)
+      expect(result.getValue()).toBe('encoded-token')
+    })
+  })
+})

+ 124 - 0
packages/syncing-server/src/Domain/UseCase/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken.ts

@@ -0,0 +1,124 @@
+import { SharedVaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
+
+import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
+import { CreateSharedVaultFileValetTokenDTO } from './CreateSharedVaultFileValetTokenDTO'
+import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
+
+export class CreateSharedVaultFileValetToken implements UseCaseInterface<string> {
+  constructor(
+    private sharedVaultRepository: SharedVaultRepositoryInterface,
+    private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
+    private tokenEncoder: TokenEncoderInterface<SharedVaultValetTokenData>,
+    private valetTokenTTL: number,
+  ) {}
+
+  async execute(dto: CreateSharedVaultFileValetTokenDTO): Promise<Result<string>> {
+    const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
+    if (sharedVaultUuidOrError.isFailed()) {
+      return Result.fail(sharedVaultUuidOrError.getError())
+    }
+    const sharedVaultUuid = sharedVaultUuidOrError.getValue()
+
+    const userUuidOrError = Uuid.create(dto.userUuid)
+    if (userUuidOrError.isFailed()) {
+      return Result.fail(userUuidOrError.getError())
+    }
+    const userUuid = userUuidOrError.getValue()
+
+    const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
+    if (!sharedVault) {
+      return Result.fail('Shared vault not found')
+    }
+
+    const sharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
+      userUuid: userUuid,
+      sharedVaultUuid: sharedVaultUuid,
+    })
+    if (!sharedVaultUser) {
+      return Result.fail('Shared vault user not found')
+    }
+
+    if (
+      sharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read &&
+      dto.operation !== ValetTokenOperation.Read
+    ) {
+      return Result.fail('User does not have permission to perform this operation')
+    }
+
+    if (dto.operation === ValetTokenOperation.Move) {
+      if (!dto.moveOperationType) {
+        return Result.fail('Move operation type is required')
+      }
+
+      if (dto.moveOperationType === 'shared-vault-to-shared-vault') {
+        if (!dto.sharedVaultToSharedVaultMoveTargetUuid) {
+          return Result.fail('Shared vault to shared vault move target uuid is required')
+        }
+
+        const sharedVaultTargetUuidOrError = Uuid.create(dto.sharedVaultToSharedVaultMoveTargetUuid)
+        if (sharedVaultTargetUuidOrError.isFailed()) {
+          return Result.fail(sharedVaultTargetUuidOrError.getError())
+        }
+        const sharedVaultTargetUuid = sharedVaultTargetUuidOrError.getValue()
+
+        const toSharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
+          userUuid: userUuid,
+          sharedVaultUuid: sharedVaultTargetUuid,
+        })
+
+        if (!toSharedVaultUser) {
+          return Result.fail('Shared vault target user not found')
+        }
+
+        if (toSharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read) {
+          return Result.fail('User does not have permission to perform this operation')
+        }
+      }
+    }
+
+    const tokenData: SharedVaultValetTokenData = {
+      sharedVaultUuid: dto.sharedVaultUuid,
+      permittedOperation: dto.operation,
+      remoteIdentifier: dto.remoteIdentifier,
+      uploadBytesUsed: sharedVault.props.fileUploadBytesUsed,
+      uploadBytesLimit: sharedVault.props.fileUploadBytesLimit,
+      unencryptedFileSize: dto.unencryptedFileSize,
+      moveOperation: this.createMoveOperationData(dto),
+    }
+
+    const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
+
+    return Result.ok(valetToken)
+  }
+
+  private createMoveOperationData(dto: CreateSharedVaultFileValetTokenDTO): SharedVaultValetTokenData['moveOperation'] {
+    if (!dto.moveOperationType) {
+      return undefined
+    }
+
+    let fromUuid: string
+    let toUuid: string
+    switch (dto.moveOperationType) {
+      case 'shared-vault-to-user':
+        fromUuid = dto.sharedVaultUuid
+        toUuid = dto.userUuid
+        break
+      case 'user-to-shared-vault':
+        fromUuid = dto.userUuid
+        toUuid = dto.sharedVaultUuid
+        break
+      case 'shared-vault-to-shared-vault':
+        fromUuid = dto.sharedVaultUuid
+        toUuid = dto.sharedVaultToSharedVaultMoveTargetUuid as string
+        break
+    }
+
+    return {
+      type: dto.moveOperationType,
+      fromUuid,
+      toUuid,
+    }
+  }
+}

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

@@ -0,0 +1,12 @@
+import { SharedVaultMoveType, ValetTokenOperation } from '@standardnotes/security'
+
+export interface CreateSharedVaultFileValetTokenDTO {
+  userUuid: string
+  sharedVaultUuid: string
+  fileUuid?: string
+  remoteIdentifier: string
+  operation: ValetTokenOperation
+  unencryptedFileSize?: number
+  moveOperationType?: SharedVaultMoveType
+  sharedVaultToSharedVaultMoveTargetUuid?: string
+}

+ 1 - 1
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultRepository.ts

@@ -21,7 +21,7 @@ export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterf
     const persistence = await this.ormRepository
       .createQueryBuilder('shared_vault')
       .where('shared_vault.uuid = :uuid', {
-        uuid: uuid.toString(),
+        uuid: uuid.value,
       })
       .getOne()
 

+ 22 - 1
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultUserRepository.ts

@@ -11,6 +11,27 @@ export class TypeORMSharedVaultUserRepository implements SharedVaultUserReposito
     private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
   ) {}
 
+  async findByUserUuidAndSharedVaultUuid(dto: {
+    userUuid: Uuid
+    sharedVaultUuid: Uuid
+  }): Promise<SharedVaultUser | null> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('shared_vault_user')
+      .where('shared_vault_user.user_uuid = :userUuid', {
+        userUuid: dto.userUuid.value,
+      })
+      .andWhere('shared_vault_user.shared_vault_uuid = :sharedVaultUuid', {
+        sharedVaultUuid: dto.sharedVaultUuid.value,
+      })
+      .getOne()
+
+    if (persistence === null) {
+      return null
+    }
+
+    return this.mapper.toDomain(persistence)
+  }
+
   async save(sharedVaultUser: SharedVaultUser): Promise<void> {
     const persistence = this.mapper.toProjection(sharedVaultUser)
 
@@ -21,7 +42,7 @@ export class TypeORMSharedVaultUserRepository implements SharedVaultUserReposito
     const persistence = await this.ormRepository
       .createQueryBuilder('shared_vault_user')
       .where('shared_vault_user.uuid = :uuid', {
-        uuid: uuid.toString(),
+        uuid: uuid.value,
       })
       .getOne()