HomeServerUsersController.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import { ControllerContainerInterface, Username } from '@standardnotes/domain-core'
  2. import { Request, Response } from 'express'
  3. import { BaseHttpController, results } from 'inversify-express-utils'
  4. import { ChangeCredentials } from '../../../Domain/UseCase/ChangeCredentials/ChangeCredentials'
  5. import { ClearLoginAttempts } from '../../../Domain/UseCase/ClearLoginAttempts'
  6. import { DeleteAccount } from '../../../Domain/UseCase/DeleteAccount/DeleteAccount'
  7. import { GetUserKeyParams } from '../../../Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
  8. import { GetUserSubscription } from '../../../Domain/UseCase/GetUserSubscription/GetUserSubscription'
  9. import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts'
  10. import { UpdateUser } from '../../../Domain/UseCase/UpdateUser'
  11. import { ErrorTag } from '@standardnotes/responses'
  12. export class HomeServerUsersController extends BaseHttpController {
  13. constructor(
  14. protected updateUser: UpdateUser,
  15. protected getUserKeyParams: GetUserKeyParams,
  16. protected doDeleteAccount: DeleteAccount,
  17. protected doGetUserSubscription: GetUserSubscription,
  18. protected clearLoginAttempts: ClearLoginAttempts,
  19. protected increaseLoginAttempts: IncreaseLoginAttempts,
  20. protected changeCredentialsUseCase: ChangeCredentials,
  21. private controllerContainer?: ControllerContainerInterface,
  22. ) {
  23. super()
  24. if (this.controllerContainer !== undefined) {
  25. this.controllerContainer.register('auth.users.update', this.update.bind(this))
  26. this.controllerContainer.register('auth.users.getKeyParams', this.keyParams.bind(this))
  27. this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this))
  28. this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
  29. this.controllerContainer.register('auth.users.delete', this.deleteAccount.bind(this))
  30. }
  31. }
  32. async update(request: Request, response: Response): Promise<results.JsonResult> {
  33. if (response.locals.readOnlyAccess) {
  34. return this.json(
  35. {
  36. error: {
  37. tag: ErrorTag.ReadOnlyAccess,
  38. message: 'Session has read-only access.',
  39. },
  40. },
  41. 401,
  42. )
  43. }
  44. if (request.params.userId !== response.locals.user.uuid) {
  45. return this.json(
  46. {
  47. error: {
  48. message: 'Operation not allowed.',
  49. },
  50. },
  51. 401,
  52. )
  53. }
  54. const updateResult = await this.updateUser.execute({
  55. user: response.locals.user,
  56. updatedWithUserAgent: <string>request.headers['user-agent'],
  57. apiVersion: request.body.api,
  58. })
  59. if (updateResult.success) {
  60. response.setHeader('x-invalidate-cache', response.locals.user.uuid)
  61. return this.json(updateResult.authResponse)
  62. }
  63. return this.json(
  64. {
  65. error: {
  66. message: 'Could not update user.',
  67. },
  68. },
  69. 400,
  70. )
  71. }
  72. async keyParams(request: Request): Promise<results.JsonResult> {
  73. const email = 'email' in request.query ? <string>request.query.email : undefined
  74. const userUuid = 'uuid' in request.query ? <string>request.query.uuid : undefined
  75. if (!email && !userUuid) {
  76. return this.json(
  77. {
  78. error: {
  79. message: 'Missing mandatory request query parameters.',
  80. },
  81. },
  82. 400,
  83. )
  84. }
  85. const result = await this.getUserKeyParams.execute({
  86. email,
  87. userUuid,
  88. authenticated: request.query.authenticated === 'true',
  89. })
  90. return this.json(result.keyParams)
  91. }
  92. async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
  93. if (request.params.userUuid !== response.locals.user.uuid) {
  94. return this.json(
  95. {
  96. error: {
  97. message: 'Operation not allowed.',
  98. },
  99. },
  100. 401,
  101. )
  102. }
  103. const result = await this.doDeleteAccount.execute({
  104. userUuid: request.params.userUuid,
  105. })
  106. return this.json({ message: result.message }, result.responseCode)
  107. }
  108. async getSubscription(request: Request, response: Response): Promise<results.JsonResult> {
  109. if (request.params.userUuid !== response.locals.user.uuid) {
  110. return this.json(
  111. {
  112. error: {
  113. message: 'Operation not allowed.',
  114. },
  115. },
  116. 401,
  117. )
  118. }
  119. const result = await this.doGetUserSubscription.execute({
  120. userUuid: request.params.userUuid,
  121. })
  122. if (result.success) {
  123. return this.json(result)
  124. }
  125. return this.json(result, 400)
  126. }
  127. async changeCredentials(request: Request, response: Response): Promise<results.JsonResult> {
  128. if (response.locals.readOnlyAccess) {
  129. return this.json(
  130. {
  131. error: {
  132. tag: ErrorTag.ReadOnlyAccess,
  133. message: 'Session has read-only access.',
  134. },
  135. },
  136. 401,
  137. )
  138. }
  139. if (!request.body.current_password) {
  140. return this.json(
  141. {
  142. error: {
  143. message:
  144. 'Your current password is required to change your password. Please update your application if you do not see this option.',
  145. },
  146. },
  147. 400,
  148. )
  149. }
  150. if (!request.body.new_password) {
  151. return this.json(
  152. {
  153. error: {
  154. message: 'Your new password is required to change your password. Please try again.',
  155. },
  156. },
  157. 400,
  158. )
  159. }
  160. if (!request.body.pw_nonce) {
  161. return this.json(
  162. {
  163. error: {
  164. message: 'The change password request is missing new auth parameters. Please try again.',
  165. },
  166. },
  167. 400,
  168. )
  169. }
  170. const usernameOrError = Username.create(response.locals.user.email)
  171. if (usernameOrError.isFailed()) {
  172. return this.json(
  173. {
  174. error: {
  175. message: 'Invalid username.',
  176. },
  177. },
  178. 400,
  179. )
  180. }
  181. const username = usernameOrError.getValue()
  182. const changeCredentialsResult = await this.changeCredentialsUseCase.execute({
  183. username,
  184. apiVersion: request.body.api,
  185. currentPassword: request.body.current_password,
  186. newPassword: request.body.new_password,
  187. newEmail: request.body.new_email,
  188. pwNonce: request.body.pw_nonce,
  189. kpCreated: request.body.created,
  190. kpOrigination: request.body.origination,
  191. updatedWithUserAgent: <string>request.headers['user-agent'],
  192. protocolVersion: request.body.version,
  193. })
  194. if (!changeCredentialsResult.success) {
  195. await this.increaseLoginAttempts.execute({ email: response.locals.user.email })
  196. return this.json(
  197. {
  198. error: {
  199. message: changeCredentialsResult.errorMessage,
  200. },
  201. },
  202. 401,
  203. )
  204. }
  205. await this.clearLoginAttempts.execute({ email: response.locals.user.email })
  206. response.setHeader('x-invalidate-cache', response.locals.user.uuid)
  207. return this.json(changeCredentialsResult.authResponse)
  208. }
  209. }