user.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. package user
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/ente-io/museum/pkg/repo/two_factor_recovery"
  6. "strings"
  7. cache2 "github.com/ente-io/museum/ente/cache"
  8. "github.com/ente-io/museum/pkg/controller/discord"
  9. "github.com/ente-io/museum/pkg/controller/usercache"
  10. "github.com/ente-io/museum/ente"
  11. "github.com/ente-io/museum/pkg/controller"
  12. "github.com/ente-io/museum/pkg/controller/family"
  13. "github.com/ente-io/museum/pkg/repo"
  14. "github.com/ente-io/museum/pkg/repo/datacleanup"
  15. "github.com/ente-io/museum/pkg/repo/passkey"
  16. storageBonusRepo "github.com/ente-io/museum/pkg/repo/storagebonus"
  17. "github.com/ente-io/museum/pkg/utils/billing"
  18. "github.com/ente-io/museum/pkg/utils/crypto"
  19. "github.com/ente-io/museum/pkg/utils/email"
  20. "github.com/ente-io/stacktrace"
  21. "github.com/gin-gonic/gin"
  22. "github.com/golang-jwt/jwt"
  23. "github.com/patrickmn/go-cache"
  24. "github.com/sirupsen/logrus"
  25. "github.com/spf13/viper"
  26. )
  27. // UserController exposes request handlers for all user related requests
  28. type UserController struct {
  29. UserRepo *repo.UserRepository
  30. TwoFactorRecoveryRepo *two_factor_recovery.Repository
  31. UsageRepo *repo.UsageRepository
  32. UserAuthRepo *repo.UserAuthRepository
  33. TwoFactorRepo *repo.TwoFactorRepository
  34. PasskeyRepo *passkey.Repository
  35. StorageBonusRepo *storageBonusRepo.Repository
  36. FileRepo *repo.FileRepository
  37. CollectionRepo *repo.CollectionRepository
  38. DataCleanupRepo *datacleanup.Repository
  39. CollectionCtrl *controller.CollectionController
  40. BillingRepo *repo.BillingRepository
  41. BillingController *controller.BillingController
  42. FamilyController *family.Controller
  43. DiscordController *discord.DiscordController
  44. MailingListsController *controller.MailingListsController
  45. PushController *controller.PushController
  46. HashingKey []byte
  47. SecretEncryptionKey []byte
  48. JwtSecret []byte
  49. Cache *cache.Cache // refers to the auth token cache
  50. HardCodedOTT HardCodedOTT
  51. roadmapURLPrefix string
  52. roadmapSSOSecret string
  53. UserCache *cache2.UserCache
  54. UserCacheController *usercache.Controller
  55. }
  56. const (
  57. // OTTValidityDurationInMicroSeconds is the duration for which an OTT is valid
  58. // (60 minutes)
  59. OTTValidityDurationInMicroSeconds = 60 * 60 * 1000000
  60. // OTTWrongAttemptLimit is the max number of wrong attempt to verify OTT (to prevent bruteforce guessing)
  61. // When client hits this limit, they will need to trigger new OTT.
  62. OTTWrongAttemptLimit = 20
  63. // OTTActiveCodeLimit is the max number of active OTT a user can have in
  64. // a time window of OTTValidityDurationInMicroSeconds duration
  65. OTTActiveCodeLimit = 10
  66. // TwoFactorValidityDurationInMicroSeconds is the duration for which an OTT is valid
  67. // (10 minutes)
  68. TwoFactorValidityDurationInMicroSeconds = 10 * 60 * 1000000
  69. // TokenLength is the length of the token issued to a verified user
  70. TokenLength = 32
  71. // TwoFactorSessionIDLength is the length of the twoFactorSessionID issued to a verified user
  72. TwoFactorSessionIDLength = 32
  73. // PassKeySessionIDLength is the length of the passKey sessionID issued to a verified user
  74. PassKeySessionIDLength = 32
  75. CryptoPwhashMemLimitInteractive = 67108864
  76. CryptoPwhashOpsLimitInteractive = 2
  77. TOTPIssuerORG = "ente"
  78. // Template and subject for the mail that we send when the user deletes
  79. // their account.
  80. AccountDeletedEmailTemplate = "account_deleted.html"
  81. AccountDeletedWithActiveSubscriptionEmailTemplate = "account_deleted_active_sub.html"
  82. AccountDeletedEmailSubject = "Your ente account has been deleted"
  83. )
  84. func NewUserController(
  85. userRepo *repo.UserRepository,
  86. usageRepo *repo.UsageRepository,
  87. userAuthRepo *repo.UserAuthRepository,
  88. twoFactorRepo *repo.TwoFactorRepository,
  89. twoFactorRecoveryRepo *two_factor_recovery.Repository,
  90. passkeyRepo *passkey.Repository,
  91. storageBonusRepo *storageBonusRepo.Repository,
  92. fileRepo *repo.FileRepository,
  93. collectionController *controller.CollectionController,
  94. collectionRepo *repo.CollectionRepository,
  95. dataCleanupRepository *datacleanup.Repository,
  96. billingRepo *repo.BillingRepository,
  97. secretEncryptionKeyBytes []byte,
  98. hashingKeyBytes []byte,
  99. authCache *cache.Cache,
  100. jwtSecretBytes []byte,
  101. billingController *controller.BillingController,
  102. familyController *family.Controller,
  103. discordController *discord.DiscordController,
  104. mailingListsController *controller.MailingListsController,
  105. pushController *controller.PushController,
  106. userCache *cache2.UserCache,
  107. userCacheController *usercache.Controller,
  108. ) *UserController {
  109. return &UserController{
  110. UserRepo: userRepo,
  111. UsageRepo: usageRepo,
  112. TwoFactorRecoveryRepo: twoFactorRecoveryRepo,
  113. UserAuthRepo: userAuthRepo,
  114. StorageBonusRepo: storageBonusRepo,
  115. TwoFactorRepo: twoFactorRepo,
  116. PasskeyRepo: passkeyRepo,
  117. FileRepo: fileRepo,
  118. CollectionCtrl: collectionController,
  119. CollectionRepo: collectionRepo,
  120. DataCleanupRepo: dataCleanupRepository,
  121. BillingRepo: billingRepo,
  122. SecretEncryptionKey: secretEncryptionKeyBytes,
  123. HashingKey: hashingKeyBytes,
  124. Cache: authCache,
  125. JwtSecret: jwtSecretBytes,
  126. BillingController: billingController,
  127. FamilyController: familyController,
  128. DiscordController: discordController,
  129. MailingListsController: mailingListsController,
  130. PushController: pushController,
  131. HardCodedOTT: ReadHardCodedOTTFromConfig(),
  132. roadmapURLPrefix: viper.GetString("roadmap.url-prefix"),
  133. roadmapSSOSecret: viper.GetString("roadmap.sso-secret"),
  134. UserCache: userCache,
  135. UserCacheController: userCacheController,
  136. }
  137. }
  138. // GetAttributes returns the key attributes for a user
  139. func (c *UserController) GetAttributes(userID int64) (ente.KeyAttributes, error) {
  140. return c.UserRepo.GetKeyAttributes(userID)
  141. }
  142. // SetAttributes sets the attributes for a user. The request will fail if key attributes are already set
  143. func (c *UserController) SetAttributes(userID int64, request ente.SetUserAttributesRequest) error {
  144. _, err := c.UserRepo.GetKeyAttributes(userID)
  145. if err == nil { // If there are key attributes already set
  146. return stacktrace.Propagate(ente.ErrPermissionDenied, "key attributes are already set")
  147. }
  148. if request.KeyAttributes.MemLimit <= 0 || request.KeyAttributes.OpsLimit <= 0 {
  149. // note for curious soul in the future
  150. _ = fmt.Sprintf("Older clients were not passing these values, so server used %d & %d as ops and memLimit",
  151. CryptoPwhashOpsLimitInteractive, CryptoPwhashMemLimitInteractive)
  152. return stacktrace.Propagate(ente.ErrBadRequest, "mem or ops limit should be > 0")
  153. }
  154. err = c.UserRepo.SetKeyAttributes(userID, request.KeyAttributes)
  155. if err != nil {
  156. return stacktrace.Propagate(err, "")
  157. }
  158. return nil
  159. }
  160. // UpdateEmailMFA updates the email MFA for a user.
  161. func (c *UserController) UpdateEmailMFA(context *gin.Context, userID int64, isEnabled bool) error {
  162. if !isEnabled {
  163. isSrpSetupDone, err := c.UserAuthRepo.IsSRPSetupDone(context, userID)
  164. if err != nil {
  165. return stacktrace.Propagate(err, "")
  166. }
  167. // if SRP is not setup, then we can not disable email MFA
  168. if !isSrpSetupDone {
  169. return stacktrace.Propagate(ente.NewConflictError("SRP setup incomplete"), "can not disable email MFA before SRP is setup")
  170. }
  171. }
  172. return c.UserAuthRepo.UpdateEmailMFA(context, userID, isEnabled)
  173. }
  174. // UpdateKeys updates the user keys on password change
  175. func (c *UserController) UpdateKeys(context *gin.Context, userID int64,
  176. request ente.UpdateKeysRequest, token string) error {
  177. /*
  178. todo: send email to the user on password change and may be keep history of old keys for X days.
  179. History will allow easy recovery of the account when password is changed by a bad actor
  180. */
  181. isSRPSetupDone, err := c.UserAuthRepo.IsSRPSetupDone(context, userID)
  182. if err != nil {
  183. return err
  184. }
  185. if isSRPSetupDone {
  186. return stacktrace.Propagate(ente.NewBadRequestWithMessage("Need to upgrade client"), "can not use old API to change password after SRP is setup")
  187. }
  188. err = c.UserRepo.UpdateKeys(userID, request)
  189. if err != nil {
  190. return stacktrace.Propagate(err, "")
  191. }
  192. err = c.UserAuthRepo.RemoveAllOtherTokens(userID, token)
  193. if err != nil {
  194. return stacktrace.Propagate(err, "")
  195. }
  196. return nil
  197. }
  198. // SetRecoveryKey sets the recovery key attributes for a user, if not already set
  199. func (c *UserController) SetRecoveryKey(userID int64, request ente.SetRecoveryKeyRequest) error {
  200. keyAttr, keyErr := c.UserRepo.GetKeyAttributes(userID)
  201. if keyErr != nil {
  202. return stacktrace.Propagate(keyErr, "User keys setup is not completed")
  203. }
  204. if keyAttr.RecoveryKeyEncryptedWithMasterKey != "" {
  205. return stacktrace.Propagate(errors.New("recovery key is already set"), "")
  206. }
  207. err := c.UserRepo.SetRecoveryKeyAttributes(userID, request)
  208. if err != nil {
  209. return stacktrace.Propagate(err, "")
  210. }
  211. return nil
  212. }
  213. // GetPublicKey returns the public key of a user
  214. func (c *UserController) GetPublicKey(email string) (string, error) {
  215. userID, err := c.UserRepo.GetUserIDWithEmail(email)
  216. if err != nil {
  217. return "", stacktrace.Propagate(err, "")
  218. }
  219. key, err := c.UserRepo.GetPublicKey(userID)
  220. if err != nil {
  221. return "", stacktrace.Propagate(err, "")
  222. }
  223. return key, nil
  224. }
  225. // GetRoadmapURL redirects the user to the feedback page
  226. func (c *UserController) GetRoadmapURL(userID int64) (string, error) {
  227. // If SSO is not configured, redirect the user to the plain roadmap
  228. if c.roadmapURLPrefix == "" || c.roadmapSSOSecret == "" {
  229. return "https://roadmap.ente.io", nil
  230. }
  231. user, err := c.UserRepo.Get(userID)
  232. if err != nil {
  233. return "", stacktrace.Propagate(err, "")
  234. }
  235. userData := jwt.MapClaims{
  236. "full_name": "",
  237. "email": user.Hash + "@ente.io",
  238. }
  239. token := jwt.NewWithClaims(jwt.SigningMethodHS256, userData)
  240. signature, err := token.SignedString([]byte(c.roadmapSSOSecret))
  241. if err != nil {
  242. return "", stacktrace.Propagate(err, "")
  243. }
  244. return c.roadmapURLPrefix + signature, nil
  245. }
  246. // GetTwoFactorStatus returns a user's two factor status
  247. func (c *UserController) GetTwoFactorStatus(userID int64) (bool, error) {
  248. isTwoFactorEnabled, err := c.UserRepo.IsTwoFactorEnabled(userID)
  249. if err != nil {
  250. return false, stacktrace.Propagate(err, "")
  251. }
  252. return isTwoFactorEnabled, nil
  253. }
  254. func (c *UserController) HandleAccountDeletion(ctx *gin.Context, userID int64, logger *logrus.Entry) (*ente.DeleteAccountResponse, error) {
  255. isSubscriptionCancelled, err := c.BillingController.HandleAccountDeletion(ctx, userID, logger)
  256. if err != nil {
  257. return nil, stacktrace.Propagate(err, "")
  258. }
  259. err = c.CollectionCtrl.HandleAccountDeletion(ctx, userID, logger)
  260. if err != nil {
  261. return nil, stacktrace.Propagate(err, "")
  262. }
  263. err = c.FamilyController.HandleAccountDeletion(ctx, userID, logger)
  264. if err != nil {
  265. return nil, stacktrace.Propagate(err, "")
  266. }
  267. logger.Info("remove push tokens for user")
  268. c.PushController.RemoveTokensForUser(userID)
  269. logger.Info("remove active tokens for user")
  270. err = c.UserAuthRepo.RemoveAllTokens(userID)
  271. if err != nil {
  272. return nil, stacktrace.Propagate(err, "")
  273. }
  274. user, err := c.UserRepo.Get(userID)
  275. if err != nil {
  276. return nil, stacktrace.Propagate(err, "")
  277. }
  278. email := user.Email
  279. // See also: Do not block on mailing list errors
  280. go func() {
  281. _ = c.MailingListsController.Unsubscribe(email)
  282. }()
  283. logger.Info("mark user as deleted")
  284. err = c.UserRepo.Delete(userID)
  285. if err != nil {
  286. return nil, stacktrace.Propagate(err, "")
  287. }
  288. logger.Info("schedule data deletion")
  289. err = c.DataCleanupRepo.Insert(ctx, userID)
  290. if err != nil {
  291. return nil, stacktrace.Propagate(err, "")
  292. }
  293. go c.NotifyAccountDeletion(email, isSubscriptionCancelled)
  294. return &ente.DeleteAccountResponse{
  295. IsSubscriptionCancelled: isSubscriptionCancelled,
  296. UserID: userID,
  297. }, nil
  298. }
  299. func (c *UserController) NotifyAccountDeletion(userEmail string, isSubscriptionCancelled bool) {
  300. template := AccountDeletedEmailTemplate
  301. if !isSubscriptionCancelled {
  302. template = AccountDeletedWithActiveSubscriptionEmailTemplate
  303. }
  304. err := email.SendTemplatedEmail([]string{userEmail}, "ente", "team@ente.io",
  305. AccountDeletedEmailSubject, template, nil, nil)
  306. if err != nil {
  307. logrus.WithError(err).Errorf("Failed to send the account deletion email to %s", userEmail)
  308. }
  309. }
  310. func (c *UserController) HandleAccountRecovery(ctx *gin.Context, req ente.RecoverAccountRequest) error {
  311. _, err := c.UserRepo.Get(req.UserID)
  312. if err == nil {
  313. return stacktrace.Propagate(ente.NewBadRequestError(&ente.ApiErrorParams{
  314. Message: "User ID is linked to undeleted account",
  315. }), "")
  316. }
  317. if !errors.Is(err, ente.ErrUserDeleted) {
  318. return stacktrace.Propagate(err, "error while getting the user")
  319. }
  320. // check if the user keyAttributes are still available
  321. if _, keyErr := c.UserRepo.GetKeyAttributes(req.UserID); keyErr != nil {
  322. return stacktrace.Propagate(keyErr, "keyAttributes missing? Account can not be recovered")
  323. }
  324. email := strings.ToLower(req.EmailID)
  325. encryptedEmail, err := crypto.Encrypt(email, c.SecretEncryptionKey)
  326. if err != nil {
  327. return stacktrace.Propagate(err, "")
  328. }
  329. emailHash, err := crypto.GetHash(email, c.HashingKey)
  330. if err != nil {
  331. return stacktrace.Propagate(err, "")
  332. }
  333. err = c.UserRepo.UpdateEmail(req.UserID, encryptedEmail, emailHash)
  334. if err != nil {
  335. return stacktrace.Propagate(err, "failed to update email")
  336. }
  337. err = c.DataCleanupRepo.RemoveScheduledDelete(ctx, req.UserID)
  338. if err != nil {
  339. logrus.WithError(err).Error("failed to remove scheduled delete")
  340. return stacktrace.Propagate(err, "")
  341. }
  342. return stacktrace.Propagate(err, "")
  343. }
  344. func (c *UserController) attachFreeSubscription(userID int64) (ente.Subscription, error) {
  345. subscription := billing.GetFreeSubscription(userID)
  346. generatedID, err := c.BillingRepo.AddSubscription(subscription)
  347. if err != nil {
  348. return subscription, stacktrace.Propagate(err, "")
  349. }
  350. subscription.ID = generatedID
  351. return subscription, nil
  352. }
  353. func (c *UserController) createUser(email string, source *string) (int64, ente.Subscription, error) {
  354. encryptedEmail, err := crypto.Encrypt(email, c.SecretEncryptionKey)
  355. if err != nil {
  356. return -1, ente.Subscription{}, stacktrace.Propagate(err, "")
  357. }
  358. emailHash, err := crypto.GetHash(email, c.HashingKey)
  359. if err != nil {
  360. return -1, ente.Subscription{}, stacktrace.Propagate(err, "")
  361. }
  362. userID, err := c.UserRepo.Create(encryptedEmail, emailHash, source)
  363. if err != nil {
  364. return -1, ente.Subscription{}, stacktrace.Propagate(err, "")
  365. }
  366. err = c.UsageRepo.Create(userID)
  367. if err != nil {
  368. return -1, ente.Subscription{}, stacktrace.Propagate(err, "failed to add entry in usage")
  369. }
  370. subscription, err := c.attachFreeSubscription(userID)
  371. if err != nil {
  372. return -1, ente.Subscription{}, stacktrace.Propagate(err, "")
  373. }
  374. // Do not block on mailing list errors
  375. //
  376. // The mailing lists are hosted on a third party (Zoho), so we do not wish
  377. // to fail user creation in case Zoho is having temporary issues. So we
  378. // perform these actions async, and ignore errors that happen with them (a
  379. // notification will be sent to Discord for those).
  380. go func() {
  381. _ = c.MailingListsController.Subscribe(email)
  382. }()
  383. return userID, subscription, nil
  384. }