userauth.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. package user
  2. import (
  3. "database/sql"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "github.com/ente-io/museum/pkg/utils/random"
  8. "strings"
  9. "github.com/ente-io/museum/pkg/utils/config"
  10. "github.com/ente-io/museum/pkg/utils/network"
  11. "github.com/gin-contrib/requestid"
  12. "github.com/spf13/viper"
  13. "github.com/ente-io/museum/ente"
  14. "github.com/ente-io/museum/pkg/utils/auth"
  15. "github.com/ente-io/museum/pkg/utils/crypto"
  16. emailUtil "github.com/ente-io/museum/pkg/utils/email"
  17. "github.com/ente-io/museum/pkg/utils/time"
  18. "github.com/ente-io/stacktrace"
  19. "github.com/gin-gonic/gin"
  20. log "github.com/sirupsen/logrus"
  21. )
  22. type HardCodedOTTEmail struct {
  23. Email string
  24. Value string
  25. }
  26. type HardCodedOTT struct {
  27. Emails []HardCodedOTTEmail
  28. LocalDomainSuffix string
  29. LocalDomainValue string
  30. }
  31. func ReadHardCodedOTTFromConfig() HardCodedOTT {
  32. emails := make([]HardCodedOTTEmail, 0)
  33. emailsSlice := viper.GetStringSlice("internal.hardcoded-ott.emails")
  34. for _, entry := range emailsSlice {
  35. xs := strings.Split(entry, ",")
  36. if len(xs) == 2 && xs[0] != "" && xs[1] != "" {
  37. emails = append(emails, HardCodedOTTEmail{
  38. Email: xs[0],
  39. Value: xs[1],
  40. })
  41. } else {
  42. log.Errorf("Ignoring malformed internal.hardcoded-ott.emails entry %s", entry)
  43. }
  44. }
  45. localDomainSuffix := ""
  46. localDomainValue := ""
  47. if config.IsLocalEnvironment() {
  48. localDomainSuffix = viper.GetString("internal.hardcoded-ott.local-domain-suffix")
  49. localDomainValue = viper.GetString("internal.hardcoded-ott.local-domain-value")
  50. }
  51. return HardCodedOTT{
  52. Emails: emails,
  53. LocalDomainSuffix: localDomainSuffix,
  54. LocalDomainValue: localDomainValue,
  55. }
  56. }
  57. func hardcodedOTTForEmail(hardCodedOTT HardCodedOTT, email string) string {
  58. for _, entry := range hardCodedOTT.Emails {
  59. if email == entry.Email {
  60. return entry.Value
  61. }
  62. }
  63. if hardCodedOTT.LocalDomainSuffix != "" && strings.HasSuffix(email, hardCodedOTT.LocalDomainSuffix) {
  64. return hardCodedOTT.LocalDomainValue
  65. }
  66. return ""
  67. }
  68. // SendEmailOTT generates and sends an OTT to the provided email address
  69. func (c *UserController) SendEmailOTT(context *gin.Context, email string, client string, purpose string) error {
  70. if purpose == ente.ChangeEmailOTTPurpose {
  71. _, err := c.UserRepo.GetUserIDWithEmail(email)
  72. if err == nil {
  73. // email already owned by a user
  74. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  75. }
  76. if !errors.Is(err, sql.ErrNoRows) {
  77. // unknown error, rethrow
  78. return stacktrace.Propagate(err, "")
  79. }
  80. }
  81. ott, err := random.GenerateSixDigitOtp()
  82. if err != nil {
  83. return stacktrace.Propagate(err, "")
  84. }
  85. // for hard-coded ott, adding same OTT in db can throw error
  86. hasHardcodedOTT := false
  87. if purpose != ente.ChangeEmailOTTPurpose {
  88. hardCodedOTT := hardcodedOTTForEmail(c.HardCodedOTT, email)
  89. if hardCodedOTT != "" {
  90. log.Warn(fmt.Sprintf("returning hardcoded ott for %s", email))
  91. hasHardcodedOTT = true
  92. ott = hardCodedOTT
  93. }
  94. }
  95. emailHash, err := crypto.GetHash(email, c.HashingKey)
  96. if err != nil {
  97. return stacktrace.Propagate(err, "")
  98. }
  99. // check if user has already requested for more than 10 codes in last 10mins
  100. otts, _ := c.UserAuthRepo.GetValidOTTs(emailHash, auth.GetApp(context))
  101. if len(otts) >= OTTActiveCodeLimit {
  102. msg := "Too many ott requests in a short duration"
  103. go c.DiscordController.NotifyPotentialAbuse(msg)
  104. return stacktrace.Propagate(ente.ErrTooManyBadRequest, msg)
  105. }
  106. err = c.UserAuthRepo.AddOTT(emailHash, auth.GetApp(context), ott, time.Microseconds()+OTTValidityDurationInMicroSeconds)
  107. if !hasHardcodedOTT {
  108. // ignore error for AddOTT for hardcode OTT. This is to avoid error when unique OTT check fails at db layer
  109. if err != nil {
  110. return stacktrace.Propagate(err, "")
  111. }
  112. log.Info("Added ott for " + emailHash + ": " + ott)
  113. err = emailOTT(context, email, ott, client, purpose)
  114. if err != nil {
  115. return stacktrace.Propagate(err, "")
  116. }
  117. } else {
  118. log.Info("Added hard coded ott for " + email + " : " + ott)
  119. }
  120. return nil
  121. }
  122. // verifyEmailOtt should be deprecated in favor of verifyEmailOttWithSession once clients are updated.
  123. func (c *UserController) verifyEmailOtt(context *gin.Context, email string, ott string) error {
  124. ott = strings.TrimSpace(ott)
  125. app := auth.GetApp(context)
  126. emailHash, err := crypto.GetHash(email, c.HashingKey)
  127. if err != nil {
  128. return stacktrace.Propagate(err, "")
  129. }
  130. wrongAttempt, err := c.UserAuthRepo.GetMaxWrongAttempts(emailHash, app)
  131. if err != nil {
  132. return stacktrace.Propagate(err, "")
  133. }
  134. if wrongAttempt >= OTTWrongAttemptLimit {
  135. msg := fmt.Sprintf("Too many wrong ott verification attemp for app %s", app)
  136. go c.DiscordController.NotifyPotentialAbuse(msg)
  137. return stacktrace.Propagate(ente.ErrTooManyBadRequest, "User needs to wait before active ott are expired")
  138. }
  139. otts, err := c.UserAuthRepo.GetValidOTTs(emailHash, app)
  140. log.Infof("Valid ott (app: %s) for %s are %s", app, emailHash, strings.Join(otts, ","))
  141. if err != nil {
  142. return stacktrace.Propagate(err, "")
  143. }
  144. if len(otts) < 1 {
  145. return stacktrace.Propagate(ente.ErrExpiredOTT, "")
  146. }
  147. isValidOTT := false
  148. for _, validOTT := range otts {
  149. if ott == validOTT {
  150. isValidOTT = true
  151. }
  152. }
  153. if !isValidOTT {
  154. if err = c.UserAuthRepo.RecordWrongAttemptForActiveOtt(emailHash, app); err != nil {
  155. log.WithError(err).Warn("Failed to track wrong attempt")
  156. }
  157. return stacktrace.Propagate(ente.ErrIncorrectOTT, "")
  158. }
  159. err = c.UserAuthRepo.RemoveOTT(emailHash, ott, app)
  160. if err != nil {
  161. return stacktrace.Propagate(err, "")
  162. }
  163. return nil
  164. }
  165. // VerifyEmail validates that the OTT provided in the request is valid for the
  166. // provided email address and if yes returns the users credentials
  167. func (c *UserController) VerifyEmail(context *gin.Context, request ente.EmailVerificationRequest) (ente.EmailAuthorizationResponse, error) {
  168. email := strings.ToLower(request.Email)
  169. err := c.verifyEmailOtt(context, email, request.OTT)
  170. if err != nil {
  171. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  172. }
  173. return c.onVerificationSuccess(context, email, request.Source)
  174. }
  175. // ChangeEmail validates that the OTT provided in the request is valid for the
  176. // provided email address and if yes updates the user's existing email address
  177. func (c *UserController) ChangeEmail(ctx *gin.Context, request ente.EmailVerificationRequest) error {
  178. email := strings.ToLower(request.Email)
  179. err := c.verifyEmailOtt(ctx, email, request.OTT)
  180. if err != nil {
  181. return stacktrace.Propagate(err, "")
  182. }
  183. return c.UpdateEmail(ctx, auth.GetUserID(ctx.Request.Header), email)
  184. }
  185. // UpdateEmail updates the email address of the user with the provided userID
  186. func (c *UserController) UpdateEmail(ctx *gin.Context, userID int64, email string) error {
  187. _, err := c.UserRepo.GetUserIDWithEmail(email)
  188. if err == nil {
  189. // email already owned by a user
  190. return stacktrace.Propagate(ente.ErrPermissionDenied, "")
  191. }
  192. if !errors.Is(err, sql.ErrNoRows) {
  193. // unknown error, rethrow
  194. return stacktrace.Propagate(err, "")
  195. }
  196. user, err := c.UserRepo.Get(userID)
  197. if err != nil {
  198. return stacktrace.Propagate(err, "")
  199. }
  200. oldEmail := user.Email
  201. encryptedEmail, err := crypto.Encrypt(email, c.SecretEncryptionKey)
  202. if err != nil {
  203. return stacktrace.Propagate(err, "")
  204. }
  205. emailHash, err := crypto.GetHash(email, c.HashingKey)
  206. if err != nil {
  207. return stacktrace.Propagate(err, "")
  208. }
  209. err = c.UserRepo.UpdateEmail(userID, encryptedEmail, emailHash)
  210. if err != nil {
  211. return stacktrace.Propagate(err, "")
  212. }
  213. _ = emailUtil.SendTemplatedEmail([]string{user.Email}, "ente", "team@ente.io",
  214. ente.EmailChangedSubject, ente.EmailChangedTemplate, map[string]interface{}{
  215. "NewEmail": email,
  216. }, nil)
  217. err = c.BillingController.UpdateBillingEmail(userID, email)
  218. if err != nil {
  219. log.WithError(err).
  220. WithFields(log.Fields{
  221. "req_id": requestid.Get(ctx),
  222. "user_id": userID,
  223. }).Error("stripe update email failed")
  224. }
  225. // Unsubscribe the old email, subscribe the new one.
  226. //
  227. // Note that resubscribing the same email after it has been unsubscribed
  228. // once works fine.
  229. //
  230. // See also: Do not block on mailing list errors
  231. go func() {
  232. _ = c.MailingListsController.Unsubscribe(oldEmail)
  233. _ = c.MailingListsController.Subscribe(email)
  234. }()
  235. return nil
  236. }
  237. // Logout removes the token from the cache and database.
  238. // known issue: the token may be still cached in other instances till the expiry time (10min), JWTs might remain too
  239. func (c *UserController) Logout(ctx *gin.Context) error {
  240. token := auth.GetToken(ctx)
  241. userID := auth.GetUserID(ctx.Request.Header)
  242. return c.TerminateSession(userID, token)
  243. }
  244. // GetActiveSessions returns the list of active tokens for userID
  245. func (c *UserController) GetActiveSessions(context *gin.Context, userID int64) ([]ente.Session, error) {
  246. tokens, err := c.UserAuthRepo.GetActiveSessions(userID, auth.GetApp(context))
  247. if err != nil {
  248. return nil, stacktrace.Propagate(err, "")
  249. }
  250. return tokens, nil
  251. }
  252. // TerminateSession removes the token for a user from cache and database
  253. func (c *UserController) TerminateSession(userID int64, token string) error {
  254. c.Cache.Delete(fmt.Sprintf("%s:%s", ente.Photos, token))
  255. c.Cache.Delete(fmt.Sprintf("%s:%s", ente.Auth, token))
  256. return stacktrace.Propagate(c.UserAuthRepo.RemoveToken(userID, token), "")
  257. }
  258. func emailOTT(c *gin.Context, to string, ott string, client string, purpose string) error {
  259. var templateName string
  260. if auth.GetApp(c) == ente.Auth {
  261. templateName = ente.AuthOTTTemplate
  262. } else {
  263. templateName = ente.PhotosOTTTemplate
  264. }
  265. if purpose == ente.ChangeEmailOTTPurpose {
  266. templateName = ente.ChangeEmailOTTTemplate
  267. }
  268. var inlineImages []map[string]interface{}
  269. inlineImage := make(map[string]interface{})
  270. inlineImage["cid"] = "img-email-verification-header"
  271. inlineImage["mime_type"] = "image/png"
  272. if auth.GetApp(c) == ente.Photos {
  273. inlineImage["content"] = "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"
  274. } else {
  275. inlineImage["content"] = "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"
  276. }
  277. inlineImages = append(inlineImages, inlineImage)
  278. err := emailUtil.SendTemplatedEmail([]string{to}, "ente", "verification@ente.io",
  279. ente.OTTEmailSubject, templateName, map[string]interface{}{
  280. "VerificationCode": ott,
  281. }, inlineImages)
  282. if err != nil {
  283. return stacktrace.Propagate(err, "")
  284. }
  285. return nil
  286. }
  287. // onVerificationSuccess is called when the user has successfully verified their email address.
  288. // source indicates where the user came from. It can be nil.
  289. func (c *UserController) onVerificationSuccess(context *gin.Context, email string, source *string) (ente.EmailAuthorizationResponse, error) {
  290. isTwoFactorEnabled := false
  291. userID, err := c.UserRepo.GetUserIDWithEmail(email)
  292. if err != nil {
  293. if errors.Is(err, sql.ErrNoRows) {
  294. userID, _, err = c.createUser(email, source)
  295. if err != nil {
  296. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  297. }
  298. } else {
  299. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  300. }
  301. } else {
  302. isTwoFactorEnabled, err = c.UserRepo.IsTwoFactorEnabled(userID)
  303. if err != nil {
  304. return ente.EmailAuthorizationResponse{}, err
  305. }
  306. }
  307. hasPasskeys, err := c.UserRepo.HasPasskeys(userID)
  308. if err != nil {
  309. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  310. }
  311. // if the user has passkeys, we will prioritize that over secret TOTP
  312. if hasPasskeys {
  313. passKeySessionID, err := auth.GenerateURLSafeRandomString(PassKeySessionIDLength)
  314. if err != nil {
  315. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  316. }
  317. err = c.PasskeyRepo.AddPasskeyTwoFactorSession(userID, passKeySessionID, time.Microseconds()+TwoFactorValidityDurationInMicroSeconds)
  318. if err != nil {
  319. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  320. }
  321. return ente.EmailAuthorizationResponse{ID: userID, PasskeySessionID: passKeySessionID}, nil
  322. } else {
  323. if isTwoFactorEnabled {
  324. twoFactorSessionID, err := auth.GenerateURLSafeRandomString(TwoFactorSessionIDLength)
  325. if err != nil {
  326. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  327. }
  328. err = c.TwoFactorRepo.AddTwoFactorSession(userID, twoFactorSessionID, time.Microseconds()+TwoFactorValidityDurationInMicroSeconds)
  329. if err != nil {
  330. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  331. }
  332. return ente.EmailAuthorizationResponse{ID: userID, TwoFactorSessionID: twoFactorSessionID}, nil
  333. }
  334. }
  335. token, err := auth.GenerateURLSafeRandomString(TokenLength)
  336. if err != nil {
  337. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  338. }
  339. keyAttributes, err := c.UserRepo.GetKeyAttributes(userID)
  340. if err != nil {
  341. if errors.Is(err, sql.ErrNoRows) {
  342. err = c.UserAuthRepo.AddToken(userID, auth.GetApp(context), token,
  343. network.GetClientIP(context), context.Request.UserAgent())
  344. if err != nil {
  345. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  346. }
  347. return ente.EmailAuthorizationResponse{ID: userID, Token: token}, nil
  348. } else {
  349. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  350. }
  351. }
  352. encryptedToken, err := crypto.GetEncryptedToken(token, keyAttributes.PublicKey)
  353. if err != nil {
  354. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  355. }
  356. err = c.UserAuthRepo.AddToken(userID, auth.GetApp(context), token,
  357. network.GetClientIP(context), context.Request.UserAgent())
  358. if err != nil {
  359. return ente.EmailAuthorizationResponse{}, stacktrace.Propagate(err, "")
  360. }
  361. return ente.EmailAuthorizationResponse{
  362. ID: userID,
  363. KeyAttributes: &keyAttributes,
  364. EncryptedToken: encryptedToken,
  365. }, nil
  366. }
  367. func convertStringToBytes(s string) []byte {
  368. b, err := base64.StdEncoding.DecodeString(s)
  369. if err != nil {
  370. log.Fatal(err)
  371. }
  372. return b
  373. }
  374. func convertBytesToString(b []byte) string {
  375. return base64.StdEncoding.EncodeToString(b)
  376. }