admin.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. package api
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "github.com/ente-io/museum/pkg/controller/family"
  9. "github.com/ente-io/museum/pkg/repo/storagebonus"
  10. gTime "time"
  11. "github.com/ente-io/museum/pkg/controller"
  12. "github.com/ente-io/museum/pkg/controller/discord"
  13. "github.com/ente-io/museum/pkg/controller/user"
  14. "github.com/ente-io/museum/pkg/utils/auth"
  15. "github.com/ente-io/museum/pkg/utils/time"
  16. "github.com/gin-contrib/requestid"
  17. "github.com/sirupsen/logrus"
  18. "github.com/ente-io/museum/pkg/utils/crypto"
  19. "github.com/ente-io/stacktrace"
  20. "github.com/ente-io/museum/ente"
  21. "github.com/ente-io/museum/pkg/repo"
  22. emailUtil "github.com/ente-io/museum/pkg/utils/email"
  23. "github.com/ente-io/museum/pkg/utils/handler"
  24. "github.com/gin-gonic/gin"
  25. )
  26. // AdminHandler exposes request handlers for all admin related requests
  27. type AdminHandler struct {
  28. QueueRepo *repo.QueueRepository
  29. UserRepo *repo.UserRepository
  30. CollectionRepo *repo.CollectionRepository
  31. UserAuthRepo *repo.UserAuthRepository
  32. FileRepo *repo.FileRepository
  33. BillingRepo *repo.BillingRepository
  34. StorageBonusRepo *storagebonus.Repository
  35. BillingController *controller.BillingController
  36. UserController *user.UserController
  37. FamilyController *family.Controller
  38. ObjectCleanupController *controller.ObjectCleanupController
  39. MailingListsController *controller.MailingListsController
  40. DiscordController *discord.DiscordController
  41. HashingKey []byte
  42. PasskeyController *controller.PasskeyController
  43. }
  44. // Duration for which an admin's token is considered valid
  45. const AdminTokenValidityInMinutes = 10
  46. func (h *AdminHandler) SendMail(c *gin.Context) {
  47. var req ente.SendEmailRequest
  48. err := c.ShouldBindJSON(&req)
  49. if err != nil {
  50. handler.Error(c, stacktrace.Propagate(err, ""))
  51. return
  52. }
  53. err = emailUtil.Send(req.To, req.FromName, req.FromEmail, req.Subject, req.Body, nil)
  54. if err != nil {
  55. handler.Error(c, stacktrace.Propagate(err, ""))
  56. return
  57. }
  58. c.JSON(http.StatusOK, gin.H{})
  59. }
  60. func (h *AdminHandler) SubscribeMail(c *gin.Context) {
  61. email := c.Query("email")
  62. err := h.MailingListsController.Subscribe(email)
  63. if err != nil {
  64. handler.Error(c, stacktrace.Propagate(err, ""))
  65. return
  66. }
  67. c.Status(http.StatusOK)
  68. }
  69. func (h *AdminHandler) UnsubscribeMail(c *gin.Context) {
  70. email := c.Query("email")
  71. err := h.MailingListsController.Unsubscribe(email)
  72. if err != nil {
  73. handler.Error(c, stacktrace.Propagate(err, ""))
  74. return
  75. }
  76. c.Status(http.StatusOK)
  77. }
  78. func (h *AdminHandler) GetUsers(c *gin.Context) {
  79. err := h.isFreshAdminToken(c)
  80. if err != nil {
  81. handler.Error(c, stacktrace.Propagate(err, ""))
  82. return
  83. }
  84. sinceTime, err := strconv.ParseInt(c.Query("sinceTime"), 10, 64)
  85. if err != nil {
  86. handler.Error(c, stacktrace.Propagate(err, ""))
  87. return
  88. }
  89. users, err := h.UserRepo.GetAll(sinceTime, time.Microseconds())
  90. if err != nil {
  91. handler.Error(c, stacktrace.Propagate(err, ""))
  92. return
  93. }
  94. c.JSON(http.StatusOK, gin.H{"users": users})
  95. }
  96. func (h *AdminHandler) GetUser(c *gin.Context) {
  97. e := c.Query("email")
  98. if e == "" {
  99. id, err := strconv.ParseInt(c.Query("id"), 10, 64)
  100. if err != nil {
  101. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, ""))
  102. return
  103. }
  104. user, err := h.UserRepo.GetUserByIDInternal(id)
  105. if err != nil {
  106. handler.Error(c, stacktrace.Propagate(err, ""))
  107. return
  108. }
  109. response := gin.H{
  110. "user": user,
  111. }
  112. h.attachSubscription(c, user.ID, response)
  113. c.JSON(http.StatusOK, response)
  114. return
  115. }
  116. emailHash, err := crypto.GetHash(e, h.HashingKey)
  117. if err != nil {
  118. handler.Error(c, stacktrace.Propagate(err, ""))
  119. return
  120. }
  121. user, err := h.UserRepo.GetUserByEmailHash(emailHash)
  122. if err != nil {
  123. handler.Error(c, stacktrace.Propagate(err, ""))
  124. return
  125. }
  126. user.Email = e
  127. response := gin.H{
  128. "user": user,
  129. }
  130. h.attachSubscription(c, user.ID, response)
  131. c.JSON(http.StatusOK, response)
  132. }
  133. func (h *AdminHandler) DeleteUser(c *gin.Context) {
  134. err := h.isFreshAdminToken(c)
  135. if err != nil {
  136. handler.Error(c, stacktrace.Propagate(err, ""))
  137. return
  138. }
  139. email := c.Query("email")
  140. email = strings.TrimSpace(email)
  141. if email == "" {
  142. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "email id is missing"))
  143. return
  144. }
  145. emailHash, err := crypto.GetHash(email, h.HashingKey)
  146. if err != nil {
  147. handler.Error(c, stacktrace.Propagate(err, ""))
  148. return
  149. }
  150. user, err := h.UserRepo.GetUserByEmailHash(emailHash)
  151. if err != nil {
  152. handler.Error(c, stacktrace.Propagate(err, ""))
  153. return
  154. }
  155. adminID := auth.GetUserID(c.Request.Header)
  156. logger := logrus.WithFields(logrus.Fields{
  157. "user_id": user.ID,
  158. "admin_id": adminID,
  159. "user_email": email,
  160. "req_id": requestid.Get(c),
  161. "req_ctx": "account_deletion",
  162. })
  163. response, err := h.UserController.HandleAccountDeletion(c, user.ID, logger)
  164. if err != nil {
  165. handler.Error(c, stacktrace.Propagate(err, ""))
  166. return
  167. }
  168. go h.DiscordController.NotifyAdminAction(
  169. fmt.Sprintf("Admin (%d) deleting account for %d", adminID, user.ID))
  170. c.JSON(http.StatusOK, response)
  171. }
  172. func (h *AdminHandler) isFreshAdminToken(c *gin.Context) error {
  173. token := auth.GetToken(c)
  174. creationTime, err := h.UserAuthRepo.GetTokenCreationTime(token)
  175. if err != nil {
  176. return err
  177. }
  178. if (creationTime + time.MicroSecondsInOneMinute*AdminTokenValidityInMinutes) < time.Microseconds() {
  179. err = ente.NewBadRequestError(&ente.ApiErrorParams{
  180. Message: "Token is too old",
  181. })
  182. return err
  183. }
  184. return nil
  185. }
  186. func (h *AdminHandler) DisableTwoFactor(c *gin.Context) {
  187. err := h.isFreshAdminToken(c)
  188. if err != nil {
  189. handler.Error(c, stacktrace.Propagate(err, ""))
  190. return
  191. }
  192. var request ente.DisableTwoFactorRequest
  193. if err := c.ShouldBindJSON(&request); err != nil {
  194. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  195. return
  196. }
  197. go h.DiscordController.NotifyAdminAction(
  198. fmt.Sprintf("Admin (%d) disabling 2FA for account %d", auth.GetUserID(c.Request.Header), request.UserID))
  199. logger := logrus.WithFields(logrus.Fields{
  200. "user_id": request.UserID,
  201. "admin_id": auth.GetUserID(c.Request.Header),
  202. "req_id": requestid.Get(c),
  203. "req_ctx": "disable_2fa",
  204. })
  205. logger.Info("Initiate disable 2FA")
  206. err = h.UserController.DisableTwoFactor(request.UserID)
  207. if err != nil {
  208. logger.WithError(err).Error("Failed to disable 2FA")
  209. handler.Error(c, stacktrace.Propagate(err, ""))
  210. return
  211. }
  212. logger.Info("2FA successfully disabled")
  213. c.JSON(http.StatusOK, gin.H{})
  214. }
  215. // RemovePasskeys is an admin API request to disable passkey 2FA for a user account by removing its passkeys.
  216. // This is used when we get a user request to reset their passkeys 2FA when they might've lost access to their devices or synced stores. We verify their identity out of band.
  217. // BY DEFAULT, IF THE USER HAS TOTP BASED 2FA ENABLED, REMOVING PASSKEYS WILL NOT DISABLE TOTP 2FA.
  218. func (h *AdminHandler) RemovePasskeys(c *gin.Context) {
  219. var request ente.AdminOpsForUserRequest
  220. if err := c.ShouldBindJSON(&request); err != nil {
  221. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  222. return
  223. }
  224. go h.DiscordController.NotifyAdminAction(
  225. fmt.Sprintf("Admin (%d) removing passkeys for account %d", auth.GetUserID(c.Request.Header), request.UserID))
  226. logger := logrus.WithFields(logrus.Fields{
  227. "user_id": request.UserID,
  228. "admin_id": auth.GetUserID(c.Request.Header),
  229. "req_id": requestid.Get(c),
  230. "req_ctx": "remove_passkeys",
  231. })
  232. logger.Info("Initiate remove passkeys")
  233. err := h.PasskeyController.RemovePasskey2FA(request.UserID)
  234. if err != nil {
  235. logger.WithError(err).Error("Failed to remove passkeys")
  236. handler.Error(c, stacktrace.Propagate(err, ""))
  237. return
  238. }
  239. logger.Info("Passkeys successfully removed")
  240. c.JSON(http.StatusOK, gin.H{})
  241. }
  242. func (h *AdminHandler) CloseFamily(c *gin.Context) {
  243. var request ente.AdminOpsForUserRequest
  244. if err := c.ShouldBindJSON(&request); err != nil {
  245. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  246. return
  247. }
  248. go h.DiscordController.NotifyAdminAction(
  249. fmt.Sprintf("Admin (%d) closing family for account %d", auth.GetUserID(c.Request.Header), request.UserID))
  250. logger := logrus.WithFields(logrus.Fields{
  251. "user_id": request.UserID,
  252. "admin_id": auth.GetUserID(c.Request.Header),
  253. "req_id": requestid.Get(c),
  254. "req_ctx": "close_family",
  255. })
  256. logger.Info("Start close family")
  257. err := h.FamilyController.CloseFamily(c, request.UserID)
  258. if err != nil {
  259. logger.WithError(err).Error("Failed to close family")
  260. handler.Error(c, stacktrace.Propagate(err, ""))
  261. return
  262. }
  263. logger.Info("Finished close family")
  264. c.JSON(http.StatusOK, gin.H{})
  265. }
  266. func (h *AdminHandler) UpdateSubscription(c *gin.Context) {
  267. var r ente.UpdateSubscriptionRequest
  268. if err := c.ShouldBindJSON(&r); err != nil {
  269. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  270. return
  271. }
  272. r.AdminID = auth.GetUserID(c.Request.Header)
  273. go h.DiscordController.NotifyAdminAction(
  274. fmt.Sprintf("Admin (%d) updating subscription for user: %d", r.AdminID, r.UserID))
  275. err := h.BillingController.UpdateSubscription(r)
  276. if err != nil {
  277. logrus.WithError(err).Error("Failed to update subscription")
  278. handler.Error(c, stacktrace.Propagate(err, ""))
  279. return
  280. }
  281. logrus.Info("Updated subscription")
  282. c.JSON(http.StatusOK, gin.H{})
  283. }
  284. func (h *AdminHandler) ReQueueItem(c *gin.Context) {
  285. var r ente.ReQueueItemRequest
  286. if err := c.ShouldBindJSON(&r); err != nil {
  287. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  288. return
  289. }
  290. adminID := auth.GetUserID(c.Request.Header)
  291. go h.DiscordController.NotifyAdminAction(
  292. fmt.Sprintf("Admin (%d) requeueing item %d for queue: %s", adminID, r.ID, r.QueueName))
  293. err := h.QueueRepo.RequeueItem(c, r.QueueName, r.ID)
  294. if err != nil {
  295. logrus.WithError(err).Error("Failed to re-queue item")
  296. handler.Error(c, stacktrace.Propagate(err, ""))
  297. return
  298. }
  299. c.JSON(http.StatusOK, gin.H{})
  300. }
  301. func (h *AdminHandler) UpdateBFDeal(c *gin.Context) {
  302. var r ente.UpdateBlackFridayDeal
  303. if err := c.ShouldBindJSON(&r); err != nil {
  304. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "Bad request"))
  305. return
  306. }
  307. if err := r.Validate(); err != nil {
  308. handler.Error(c, stacktrace.Propagate(ente.NewBadRequestWithMessage(err.Error()), "Bad request"))
  309. return
  310. }
  311. adminID := auth.GetUserID(c.Request.Header)
  312. var storage, validTill int64
  313. if r.Testing {
  314. storage = r.StorageInMB * 1024 * 1024
  315. validTill = gTime.Now().Add(gTime.Duration(r.Minute) * gTime.Minute).UnixMicro()
  316. } else {
  317. storage = r.StorageInGB * 1024 * 1024 * 1024
  318. validTill = gTime.Now().AddDate(r.Year, 0, 0).UnixMicro()
  319. }
  320. var err error
  321. switch r.Action {
  322. case ente.ADD:
  323. err = h.StorageBonusRepo.InsertBFBonus(c, r.UserID, validTill, storage)
  324. case ente.UPDATE:
  325. err = h.StorageBonusRepo.UpdateBFBonus(c, r.UserID, validTill, storage)
  326. case ente.REMOVE:
  327. _, err = h.StorageBonusRepo.RemoveBFBonus(c, r.UserID)
  328. }
  329. if err != nil {
  330. handler.Error(c, stacktrace.Propagate(err, ""))
  331. return
  332. }
  333. go h.DiscordController.NotifyAdminAction(
  334. fmt.Sprintf("Admin (%d) : User %d %s", adminID, r.UserID, r.UpdateLog()))
  335. c.JSON(http.StatusOK, gin.H{})
  336. }
  337. func (h *AdminHandler) RecoverAccount(c *gin.Context) {
  338. err := h.isFreshAdminToken(c)
  339. if err != nil {
  340. handler.Error(c, stacktrace.Propagate(err, ""))
  341. return
  342. }
  343. var request ente.RecoverAccountRequest
  344. if err := c.ShouldBindJSON(&request); err != nil {
  345. handler.Error(c, stacktrace.Propagate(err, "Bad request"))
  346. return
  347. }
  348. if request.EmailID == "" || !strings.Contains(request.EmailID, "@") {
  349. handler.Error(c, stacktrace.Propagate(errors.New("invalid email"), "Bad request"))
  350. return
  351. }
  352. go h.DiscordController.NotifyAdminAction(
  353. fmt.Sprintf("Admin (%d) recovering account for %d", auth.GetUserID(c.Request.Header), request.UserID))
  354. logger := logrus.WithFields(logrus.Fields{
  355. "user_id": request.UserID,
  356. "admin_id": auth.GetUserID(c.Request.Header),
  357. "user_email": request.EmailID,
  358. "req_id": requestid.Get(c),
  359. "req_ctx": "account_recovery",
  360. })
  361. logger.Info("Initiate account recovery")
  362. err = h.UserController.HandleAccountRecovery(c, request)
  363. if err != nil {
  364. logger.WithError(err).Error("Failed to recover account")
  365. handler.Error(c, stacktrace.Propagate(err, ""))
  366. return
  367. }
  368. logger.Info("Account successfully recovered")
  369. c.JSON(http.StatusOK, gin.H{})
  370. }
  371. func (h *AdminHandler) GetEmailHash(c *gin.Context) {
  372. e := c.Query("email")
  373. hash, err := crypto.GetHash(e, h.HashingKey)
  374. if err != nil {
  375. handler.Error(c, stacktrace.Propagate(err, ""))
  376. return
  377. }
  378. c.JSON(http.StatusOK, gin.H{"hash": hash})
  379. }
  380. func (h *AdminHandler) GetEmailsFromHashes(c *gin.Context) {
  381. var request ente.GetEmailsFromHashesRequest
  382. if err := c.ShouldBindJSON(&request); err != nil {
  383. handler.Error(c, stacktrace.Propagate(err, ""))
  384. return
  385. }
  386. emails, err := h.UserRepo.GetEmailsFromHashes(request.Hashes)
  387. if err != nil {
  388. handler.Error(c, stacktrace.Propagate(err, ""))
  389. return
  390. }
  391. c.JSON(http.StatusOK, gin.H{"emails": emails})
  392. }
  393. func (h *AdminHandler) attachSubscription(ctx *gin.Context, userID int64, response gin.H) {
  394. subscription, err := h.BillingRepo.GetUserSubscription(userID)
  395. if err == nil {
  396. response["subscription"] = subscription
  397. }
  398. details, err := h.UserController.GetDetailsV2(ctx, userID, false, ente.Photos)
  399. if err == nil {
  400. response["details"] = details
  401. }
  402. }
  403. func (h *AdminHandler) ClearOrphanObjects(c *gin.Context) {
  404. var req ente.ClearOrphanObjectsRequest
  405. err := c.ShouldBindJSON(&req)
  406. if err != nil {
  407. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, ""))
  408. return
  409. }
  410. if !h.ObjectCleanupController.IsValidClearOrphanObjectsDC(req.DC) {
  411. handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "unsupported dc %s", req.DC))
  412. return
  413. }
  414. go h.ObjectCleanupController.ClearOrphanObjects(req.DC, req.Prefix, req.ForceTaskLock)
  415. c.JSON(http.StatusOK, gin.H{})
  416. }