CreateCrossServiceToken.spec.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import 'reflect-metadata'
  2. import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
  3. import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
  4. import { Session } from '../../Session/Session'
  5. import { User } from '../../User/User'
  6. import { Role } from '../../Role/Role'
  7. import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
  8. import { CreateCrossServiceToken } from './CreateCrossServiceToken'
  9. import { GetSetting } from '../GetSetting/GetSetting'
  10. import {
  11. Result,
  12. SharedVaultUser,
  13. SharedVaultUserPermission,
  14. Timestamps,
  15. TransitionStatus,
  16. Uuid,
  17. } from '@standardnotes/domain-core'
  18. import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
  19. import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
  20. describe('CreateCrossServiceToken', () => {
  21. let userProjector: ProjectorInterface<User>
  22. let sessionProjector: ProjectorInterface<Session>
  23. let roleProjector: ProjectorInterface<Role>
  24. let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
  25. let userRepository: UserRepositoryInterface
  26. let getSettingUseCase: GetSetting
  27. let transitionStatusRepository: TransitionStatusRepositoryInterface
  28. let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
  29. const jwtTTL = 60
  30. let session: Session
  31. let user: User
  32. let role: Role
  33. const createUseCase = () =>
  34. new CreateCrossServiceToken(
  35. userProjector,
  36. sessionProjector,
  37. roleProjector,
  38. tokenEncoder,
  39. userRepository,
  40. jwtTTL,
  41. getSettingUseCase,
  42. transitionStatusRepository,
  43. sharedVaultUserRepository,
  44. )
  45. beforeEach(() => {
  46. session = {} as jest.Mocked<Session>
  47. user = {
  48. uuid: '00000000-0000-0000-0000-000000000000',
  49. email: 'test@test.te',
  50. } as jest.Mocked<User>
  51. user.roles = Promise.resolve([role])
  52. userProjector = {} as jest.Mocked<ProjectorInterface<User>>
  53. userProjector.projectSimple = jest
  54. .fn()
  55. .mockReturnValue({ uuid: '00000000-0000-0000-0000-000000000000', email: 'test@test.te' })
  56. roleProjector = {} as jest.Mocked<ProjectorInterface<Role>>
  57. roleProjector.projectSimple = jest.fn().mockReturnValue({ name: 'role1', uuid: '1-3-4' })
  58. sessionProjector = {} as jest.Mocked<ProjectorInterface<Session>>
  59. sessionProjector.projectCustom = jest.fn().mockReturnValue({ foo: 'bar' })
  60. sessionProjector.projectSimple = jest.fn().mockReturnValue({ test: 'test' })
  61. tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<CrossServiceTokenData>>
  62. tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
  63. userRepository = {} as jest.Mocked<UserRepositoryInterface>
  64. userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
  65. getSettingUseCase = {} as jest.Mocked<GetSetting>
  66. getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ setting: { value: '100' } }))
  67. transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
  68. transitionStatusRepository.getStatus = jest
  69. .fn()
  70. .mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
  71. sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
  72. sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([
  73. SharedVaultUser.create({
  74. permission: SharedVaultUserPermission.create('read').getValue(),
  75. sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
  76. timestamps: Timestamps.create(123456789, 123456789).getValue(),
  77. userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
  78. }).getValue(),
  79. ])
  80. })
  81. it('should create a cross service token for user', async () => {
  82. await createUseCase().execute({
  83. user,
  84. session,
  85. })
  86. expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
  87. {
  88. roles: [
  89. {
  90. name: 'role1',
  91. uuid: '1-3-4',
  92. },
  93. ],
  94. belongs_to_shared_vaults: [
  95. {
  96. shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
  97. permission: 'read',
  98. },
  99. ],
  100. session: {
  101. test: 'test',
  102. },
  103. user: {
  104. email: 'test@test.te',
  105. uuid: '00000000-0000-0000-0000-000000000000',
  106. },
  107. ongoing_transition: false,
  108. ongoing_revisions_transition: false,
  109. },
  110. 60,
  111. )
  112. })
  113. it('should create a cross service token for user that has an ongoing transaction', async () => {
  114. transitionStatusRepository.getStatus = jest
  115. .fn()
  116. .mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.InProgress).getValue())
  117. await createUseCase().execute({
  118. user,
  119. session,
  120. })
  121. expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
  122. {
  123. roles: [
  124. {
  125. name: 'role1',
  126. uuid: '1-3-4',
  127. },
  128. ],
  129. belongs_to_shared_vaults: [
  130. {
  131. shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
  132. permission: 'read',
  133. },
  134. ],
  135. session: {
  136. test: 'test',
  137. },
  138. user: {
  139. email: 'test@test.te',
  140. uuid: '00000000-0000-0000-0000-000000000000',
  141. },
  142. ongoing_transition: true,
  143. ongoing_revisions_transition: true,
  144. },
  145. 60,
  146. )
  147. })
  148. it('should create a cross service token for user without a session', async () => {
  149. await createUseCase().execute({
  150. user,
  151. })
  152. expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
  153. {
  154. roles: [
  155. {
  156. name: 'role1',
  157. uuid: '1-3-4',
  158. },
  159. ],
  160. belongs_to_shared_vaults: [
  161. {
  162. shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
  163. permission: 'read',
  164. },
  165. ],
  166. user: {
  167. email: 'test@test.te',
  168. uuid: '00000000-0000-0000-0000-000000000000',
  169. },
  170. ongoing_transition: false,
  171. ongoing_revisions_transition: false,
  172. },
  173. 60,
  174. )
  175. })
  176. it('should create a cross service token for user by user uuid', async () => {
  177. await createUseCase().execute({
  178. userUuid: '00000000-0000-0000-0000-000000000000',
  179. })
  180. expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
  181. {
  182. roles: [
  183. {
  184. name: 'role1',
  185. uuid: '1-3-4',
  186. },
  187. ],
  188. belongs_to_shared_vaults: [
  189. {
  190. shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
  191. permission: 'read',
  192. },
  193. ],
  194. user: {
  195. email: 'test@test.te',
  196. uuid: '00000000-0000-0000-0000-000000000000',
  197. },
  198. ongoing_transition: false,
  199. ongoing_revisions_transition: false,
  200. },
  201. 60,
  202. )
  203. })
  204. it('should throw an error if user does not exist', async () => {
  205. userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
  206. const result = await createUseCase().execute({
  207. userUuid: '00000000-0000-0000-0000-000000000000',
  208. })
  209. expect(result.isFailed()).toBeTruthy()
  210. })
  211. it('should throw an error if user uuid is invalid', async () => {
  212. const result = await createUseCase().execute({
  213. userUuid: 'invalid',
  214. })
  215. expect(result.isFailed()).toBeTruthy()
  216. })
  217. describe('shared vault context', () => {
  218. it('should add shared vault context if shared vault owner uuid is provided', async () => {
  219. await createUseCase().execute({
  220. user,
  221. session,
  222. sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
  223. })
  224. expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
  225. {
  226. roles: [
  227. {
  228. name: 'role1',
  229. uuid: '1-3-4',
  230. },
  231. ],
  232. belongs_to_shared_vaults: [
  233. {
  234. shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
  235. permission: 'read',
  236. },
  237. ],
  238. session: {
  239. test: 'test',
  240. },
  241. shared_vault_owner_context: {
  242. upload_bytes_limit: 100,
  243. },
  244. user: {
  245. email: 'test@test.te',
  246. uuid: '00000000-0000-0000-0000-000000000000',
  247. },
  248. ongoing_revisions_transition: false,
  249. ongoing_transition: false,
  250. },
  251. 60,
  252. )
  253. })
  254. it('should throw an error if shared vault owner context is sensitive', async () => {
  255. getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ sensitive: true }))
  256. const result = await createUseCase().execute({
  257. user,
  258. session,
  259. sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
  260. })
  261. expect(result.isFailed()).toBeTruthy()
  262. })
  263. it('should throw an error if it fails to retrieve shared vault owner setting', async () => {
  264. getSettingUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
  265. const result = await createUseCase().execute({
  266. user,
  267. session,
  268. sharedVaultOwnerContext: '00000000-0000-0000-0000-000000000000',
  269. })
  270. expect(result.isFailed()).toBeTruthy()
  271. })
  272. })
  273. })