AuthController.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
  2. import {
  3. ApiVersion,
  4. UserRegistrationRequestParams,
  5. UserServerInterface,
  6. UserDeletionResponseBody,
  7. UserRegistrationResponseBody,
  8. } from '@standardnotes/api'
  9. import { ErrorTag, HttpResponse, HttpStatusCode } from '@standardnotes/responses'
  10. import { ProtocolVersion } from '@standardnotes/common'
  11. import { ClearLoginAttempts } from '../Domain/UseCase/ClearLoginAttempts'
  12. import { Register } from '../Domain/UseCase/Register'
  13. import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
  14. import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
  15. import { SignInWithRecoveryCodesRequestParams } from '../Infra/Http/Request/SignInWithRecoveryCodesRequestParams'
  16. import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
  17. import { RecoveryKeyParamsRequestParams } from '../Infra/Http/Request/RecoveryKeyParamsRequestParams'
  18. import { SignInWithRecoveryCodesResponseBody } from '../Infra/Http/Response/SignInWithRecoveryCodesResponseBody'
  19. import { RecoveryKeyParamsResponseBody } from '../Infra/Http/Response/RecoveryKeyParamsResponseBody'
  20. import { GenerateRecoveryCodesResponseBody } from '../Infra/Http/Response/GenerateRecoveryCodesResponseBody'
  21. import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
  22. import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams'
  23. import { Logger } from 'winston'
  24. import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
  25. export class AuthController implements UserServerInterface {
  26. constructor(
  27. private clearLoginAttempts: ClearLoginAttempts,
  28. private registerUser: Register,
  29. private domainEventPublisher: DomainEventPublisherInterface,
  30. private domainEventFactory: DomainEventFactoryInterface,
  31. private doSignInWithRecoveryCodes: SignInWithRecoveryCodes,
  32. private getUserKeyParamsRecovery: GetUserKeyParamsRecovery,
  33. private doGenerateRecoveryCodes: GenerateRecoveryCodes,
  34. private logger: Logger,
  35. private sessionService: SessionServiceInterface,
  36. ) {}
  37. async deleteAccount(_params: never): Promise<HttpResponse<UserDeletionResponseBody>> {
  38. throw new Error('This method is implemented on the payments server.')
  39. }
  40. async register(params: UserRegistrationRequestParams): Promise<HttpResponse<UserRegistrationResponseBody>> {
  41. if (!params.email || !params.password) {
  42. return {
  43. status: HttpStatusCode.BadRequest,
  44. data: {
  45. error: {
  46. message: 'Please enter an email and a password to register.',
  47. },
  48. },
  49. }
  50. }
  51. const registerResult = await this.registerUser.execute({
  52. email: params.email,
  53. password: params.password,
  54. updatedWithUserAgent: params.userAgent as string,
  55. apiVersion: params.api,
  56. ephemeralSession: params.ephemeral,
  57. pwNonce: params.pw_nonce,
  58. kpOrigination: params.origination,
  59. kpCreated: params.created,
  60. version: params.version,
  61. })
  62. if (!registerResult.success) {
  63. return {
  64. status: HttpStatusCode.BadRequest,
  65. data: {
  66. error: {
  67. message: registerResult.errorMessage,
  68. },
  69. },
  70. }
  71. }
  72. await this.clearLoginAttempts.execute({ email: registerResult.authResponse.user.email as string })
  73. await this.domainEventPublisher.publish(
  74. this.domainEventFactory.createUserRegisteredEvent({
  75. userUuid: <string>registerResult.authResponse.user.uuid,
  76. email: <string>registerResult.authResponse.user.email,
  77. protocolVersion: (<string>registerResult.authResponse.user.protocolVersion) as ProtocolVersion,
  78. }),
  79. )
  80. return {
  81. status: HttpStatusCode.Success,
  82. data: registerResult.authResponse,
  83. }
  84. }
  85. async generateRecoveryCodes(
  86. params: GenerateRecoveryCodesRequestParams,
  87. ): Promise<HttpResponse<GenerateRecoveryCodesResponseBody>> {
  88. const result = await this.doGenerateRecoveryCodes.execute({
  89. userUuid: params.userUuid,
  90. })
  91. if (result.isFailed()) {
  92. return {
  93. status: HttpStatusCode.BadRequest,
  94. data: {
  95. error: {
  96. message: 'Could not generate recovery codes.',
  97. },
  98. },
  99. }
  100. }
  101. return {
  102. status: HttpStatusCode.Success,
  103. data: {
  104. recoveryCodes: result.getValue(),
  105. },
  106. }
  107. }
  108. async signInWithRecoveryCodes(
  109. params: SignInWithRecoveryCodesRequestParams,
  110. ): Promise<HttpResponse<SignInWithRecoveryCodesResponseBody>> {
  111. if (params.apiVersion !== ApiVersion.v0) {
  112. return {
  113. status: HttpStatusCode.BadRequest,
  114. data: {
  115. error: {
  116. message: 'Invalid API version.',
  117. },
  118. },
  119. }
  120. }
  121. const result = await this.doSignInWithRecoveryCodes.execute({
  122. userAgent: params.userAgent,
  123. username: params.username,
  124. password: params.password,
  125. codeVerifier: params.codeVerifier,
  126. recoveryCodes: params.recoveryCodes,
  127. })
  128. if (result.isFailed()) {
  129. this.logger.debug(`Failed to sign in with recovery codes: ${result.getError()}`)
  130. return {
  131. status: HttpStatusCode.Unauthorized,
  132. data: {
  133. error: {
  134. message: 'Invalid login credentials.',
  135. },
  136. },
  137. }
  138. }
  139. return {
  140. status: HttpStatusCode.Success,
  141. data: result.getValue(),
  142. }
  143. }
  144. async recoveryKeyParams(
  145. params: RecoveryKeyParamsRequestParams,
  146. ): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> {
  147. if (params.apiVersion !== ApiVersion.v0) {
  148. return {
  149. status: HttpStatusCode.BadRequest,
  150. data: {
  151. error: {
  152. message: 'Invalid API version.',
  153. },
  154. },
  155. }
  156. }
  157. const result = await this.getUserKeyParamsRecovery.execute({
  158. username: params.username,
  159. codeChallenge: params.codeChallenge,
  160. recoveryCodes: params.recoveryCodes,
  161. })
  162. if (result.isFailed()) {
  163. this.logger.debug(`Failed to get recovery key params: ${result.getError()}`)
  164. return {
  165. status: HttpStatusCode.Unauthorized,
  166. data: {
  167. error: {
  168. message: 'Invalid login credentials.',
  169. },
  170. },
  171. }
  172. }
  173. return {
  174. status: HttpStatusCode.Success,
  175. data: {
  176. keyParams: result.getValue(),
  177. },
  178. }
  179. }
  180. async signOut(params: Record<string, unknown>): Promise<HttpResponse> {
  181. if (params.readOnlyAccess) {
  182. return {
  183. status: HttpStatusCode.Unauthorized,
  184. data: {
  185. error: {
  186. tag: ErrorTag.ReadOnlyAccess,
  187. message: 'Session has read-only access.',
  188. },
  189. },
  190. }
  191. }
  192. const userUuid = await this.sessionService.deleteSessionByToken(
  193. (params.authorizationHeader as string).replace('Bearer ', ''),
  194. )
  195. let headers = undefined
  196. if (userUuid !== null) {
  197. headers = new Map([['x-invalidate-cache', userUuid]])
  198. }
  199. return {
  200. status: HttpStatusCode.NoContent,
  201. data: {},
  202. headers,
  203. }
  204. }
  205. }