[server] Add remaining mails for legacy
This commit is contained in:
@@ -474,6 +474,7 @@ func main() {
|
||||
UserRepo: userRepo,
|
||||
UserCtrl: userController,
|
||||
PasskeyController: passkeyCtrl,
|
||||
LockCtrl: lockController,
|
||||
}
|
||||
userHandler := &api.UserHandler{
|
||||
UserController: userController,
|
||||
@@ -766,7 +767,7 @@ func main() {
|
||||
setupAndStartBackgroundJobs(objectCleanupController, replicationController3, fileDataCtrl)
|
||||
setupAndStartCrons(
|
||||
userAuthRepo, publicCollectionRepo, twoFactorRepo, passkeysRepo, fileController, taskLockingRepo, emailNotificationCtrl,
|
||||
trashController, pushController, objectController, dataCleanupController, storageBonusCtrl,
|
||||
trashController, pushController, objectController, dataCleanupController, storageBonusCtrl, emergencyCtrl,
|
||||
embeddingController, healthCheckHandler, kexCtrl, castDb)
|
||||
|
||||
// Create a new collector, the name will be used as a label on the metrics
|
||||
@@ -901,6 +902,7 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
objectController *controller.ObjectController,
|
||||
dataCleanupCtrl *dataCleanupCtrl.DeleteUserCleanupController,
|
||||
storageBonusCtrl *storagebonus.Controller,
|
||||
emergencyCtrl *emergency.Controller,
|
||||
embeddingCtrl *embeddingCtrl.Controller,
|
||||
healthCheckHandler *api.HealthCheckHandler,
|
||||
kexCtrl *kexCtrl.Controller,
|
||||
@@ -994,6 +996,7 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
})
|
||||
|
||||
scheduleAndRun(c, "@every 60m", func() {
|
||||
emergencyCtrl.SendRecoveryReminder()
|
||||
kexCtrl.DeleteOldKeys()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{define "content"}}
|
||||
<p>Hello,</p>
|
||||
|
||||
<p>{{.TrustedContact}} has initiated recovery on your account. After 2 days, they will be able to change the password and access your account. </p>
|
||||
<p>{{.TrustedContact}} has initiated recovery on your account. After {{.DaysLeft}} days, they will be able to change the password and access your account. </p>
|
||||
|
||||
<p>If you want to block the recovery, please navigate to <strong>Settings > Account > Legacy </strong> in the Ente Photos app.</p>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package emergency
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/controller/lock"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/controller/user"
|
||||
@@ -14,10 +15,12 @@ import (
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Repo *emergency.Repository
|
||||
UserRepo *repo.UserRepository
|
||||
UserCtrl *user.UserController
|
||||
PasskeyController *controller.PasskeyController
|
||||
Repo *emergency.Repository
|
||||
UserRepo *repo.UserRepository
|
||||
UserCtrl *user.UserController
|
||||
PasskeyController *controller.PasskeyController
|
||||
LockCtrl *lock.LockController
|
||||
isReminderCronRunning bool
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateContact(ctx *gin.Context,
|
||||
|
||||
@@ -3,6 +3,7 @@ package emergency
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
emailUtil "github.com/ente-io/museum/pkg/utils/email"
|
||||
"github.com/ente-io/stacktrace"
|
||||
@@ -131,11 +132,14 @@ func (c *Controller) sendContactNotification(ctx context.Context, legacyUserID i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) createRecoveryEmailData(legacyUser, trustedUser ente.User, newStatus ente.RecoveryStatus) ([]emailData, error) {
|
||||
func (c *Controller) createRecoveryEmailData(legacyUser, trustedUser ente.User, newStatus ente.RecoveryStatus, daysLeft *int64) ([]emailData, error) {
|
||||
templateData := map[string]interface{}{
|
||||
"LegacyContact": legacyUser.Email,
|
||||
"TrustedContact": trustedUser.Email,
|
||||
}
|
||||
if daysLeft != nil {
|
||||
templateData["DaysLeft"] = *daysLeft
|
||||
}
|
||||
|
||||
var emailDatas []emailData
|
||||
|
||||
@@ -210,7 +214,7 @@ func (c *Controller) createRecoveryEmailData(legacyUser, trustedUser ente.User,
|
||||
return emailDatas, nil
|
||||
}
|
||||
|
||||
func (c *Controller) sendRecoveryNotification(ctx context.Context, legacyUserID int64, trustedUserID int64, newStatus ente.RecoveryStatus) error {
|
||||
func (c *Controller) sendRecoveryNotification(ctx context.Context, legacyUserID int64, trustedUserID int64, newStatus ente.RecoveryStatus, daysLeft *int64) error {
|
||||
legacyUser, err := c.UserRepo.Get(legacyUserID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
@@ -220,7 +224,7 @@ func (c *Controller) sendRecoveryNotification(ctx context.Context, legacyUserID
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
emailDatas, err := c.createRecoveryEmailData(legacyUser, trustedUser, newStatus)
|
||||
emailDatas, err := c.createRecoveryEmailData(legacyUser, trustedUser, newStatus, daysLeft)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
package emergency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/repo/emergency"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
_recoveryReminderLock = "recoveryReminderLock"
|
||||
)
|
||||
|
||||
func (c *Controller) GetRecoveryInfo(ctx *gin.Context,
|
||||
userID int64,
|
||||
sessionID uuid.UUID,
|
||||
@@ -67,7 +74,7 @@ func (c *Controller) ChangePassword(ctx *gin.Context, userID int64, request ente
|
||||
log.WithField("userID", userID).WithField("req", request).
|
||||
Warn("no row updated while rejecting recovery")
|
||||
} else {
|
||||
go c.sendRecoveryNotification(ctx, contact.UserID, contact.EmergencyContactID, ente.RecoveryStatusRecovered)
|
||||
go c.sendRecoveryNotification(ctx, contact.UserID, contact.EmergencyContactID, ente.RecoveryStatusRecovered, nil)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
@@ -95,3 +102,75 @@ func (c *Controller) checkRecoveryAndGetContact(ctx *gin.Context,
|
||||
}
|
||||
return contact, nil
|
||||
}
|
||||
|
||||
func (c *Controller) SendRecoveryReminder() {
|
||||
if c.isReminderCronRunning {
|
||||
return
|
||||
}
|
||||
c.isReminderCronRunning = true
|
||||
defer func() {
|
||||
c.isReminderCronRunning = false
|
||||
}()
|
||||
lockStatus := c.LockCtrl.TryLock(_recoveryReminderLock, time.MicrosecondsAfterHours(1))
|
||||
if !lockStatus {
|
||||
log.Error("Could not acquire lock to send storage limit exceeded mails")
|
||||
return
|
||||
}
|
||||
defer c.LockCtrl.ReleaseLock(_recoveryReminderLock)
|
||||
|
||||
rows, err := c.Repo.GetActiveRecoveryForNotification()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to get recovery rows")
|
||||
return
|
||||
}
|
||||
|
||||
if len(*rows) == 0 {
|
||||
return
|
||||
}
|
||||
log.Info(fmt.Sprintf("Found %d recovery rows", len(*rows)))
|
||||
microsecondsInDay := 1000 * 1000 * 24 * 60 * 60
|
||||
for _, row := range *rows {
|
||||
logger := log.WithFields(log.Fields{
|
||||
"userID": row.UserID,
|
||||
"contactID": row.EmergencyContactID,
|
||||
"status": row.Status,
|
||||
"waitTill": row.WaitTill,
|
||||
"nextReminderAt": row.NextReminderAt,
|
||||
"sessionID": row.ID,
|
||||
})
|
||||
|
||||
daysLeft := (row.WaitTill - row.NextReminderAt) / int64(microsecondsInDay)
|
||||
logger.Infof("Days left: %d", daysLeft)
|
||||
if row.WaitTill < time.Microseconds() && row.Status == ente.RecoveryStatusWaiting {
|
||||
_, updateErr := c.Repo.UpdateRecoveryStatusForID(context.Background(), row.ID, ente.RecoveryStatusReady)
|
||||
if updateErr != nil {
|
||||
logger.WithError(updateErr).Error("failed to update recovery status")
|
||||
continue
|
||||
}
|
||||
|
||||
go c.sendRecoveryNotification(context.Background(), row.UserID, row.EmergencyContactID, ente.RecoveryStatusReady, nil)
|
||||
} else if daysLeft >= 2 && row.Status == ente.RecoveryStatusWaiting {
|
||||
if daysLeft > 9 {
|
||||
// set another reminder after 7 days
|
||||
newNextReminderAt := row.NextReminderAt + int64(microsecondsInDay*7)
|
||||
if err := c.Repo.UpdateNextReminder(context.Background(), row.ID, newNextReminderAt); err != nil {
|
||||
logger.WithError(err).Error("failed to update next reminder")
|
||||
continue
|
||||
}
|
||||
} else if daysLeft > 2 {
|
||||
// send a reminder two days before the waitTill date
|
||||
newNextReminderAt := row.WaitTill - int64(microsecondsInDay*2)
|
||||
if err := c.Repo.UpdateNextReminder(context.Background(), row.ID, newNextReminderAt); err != nil {
|
||||
logger.WithError(err).Error("failed to update next reminder")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if row.Status == ente.RecoveryStatusWaiting {
|
||||
go c.sendRecoveryNotification(context.Background(), row.UserID, row.EmergencyContactID, ente.RecoveryStatusWaiting, &daysLeft)
|
||||
} else {
|
||||
logger.Warnf("No need to send email with status %v", row.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (c *Controller) StartRecovery(ctx *gin.Context,
|
||||
log.WithField("userID", actorUserID).WithField("req", req).
|
||||
Warn("No need to send email")
|
||||
} else {
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusInitiated)
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusInitiated, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
@@ -49,7 +49,7 @@ func (c *Controller) RejectRecovery(ctx *gin.Context,
|
||||
log.WithField("userID", userID).WithField("req", req).
|
||||
Warn("no row updated while rejecting recovery")
|
||||
} else {
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusRejected)
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusRejected, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
@@ -71,7 +71,7 @@ func (c *Controller) ApproveRecovery(ctx *gin.Context,
|
||||
log.WithField("userID", userID).WithField("req", req).
|
||||
Warn("no row updated while rejecting recovery")
|
||||
} else {
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusReady)
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusReady, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
@@ -96,7 +96,7 @@ func (c *Controller) StopRecovery(ctx *gin.Context,
|
||||
log.WithField("userID", userID).WithField("req", req).
|
||||
Warn("no row updated while stopping recovery")
|
||||
} else {
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusStopped)
|
||||
go c.sendRecoveryNotification(ctx, req.UserID, req.EmergencyContactID, ente.RecoveryStatusStopped, nil)
|
||||
}
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
@@ -91,6 +92,33 @@ FROM emergency_recovery WHERE user_id=$1 and emergency_contact_id=$2 AND status
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetActiveRecoveryForNotification() (*[]RecoverRow, error) {
|
||||
rows, err := r.DB.Query(`
|
||||
SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
|
||||
FROM emergency_recovery WHERE (status = $1) and next_reminder_at < now_utc_micro_seconds()`, ente.RecoveryStatusWaiting)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
defer rows.Close()
|
||||
var sessions []RecoverRow
|
||||
for rows.Next() {
|
||||
var row RecoverRow
|
||||
if err := rows.Scan(&row.ID, &row.UserID, &row.EmergencyContactID, &row.Status, &row.WaitTill, &row.NextReminderAt, &row.CreatedAt); err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
sessions = append(sessions, row)
|
||||
}
|
||||
return &sessions, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateNextReminder(ctx context.Context, sessionID uuid.UUID, nextReminder int64) error {
|
||||
_, err := r.DB.ExecContext(ctx, `UPDATE emergency_recovery SET next_reminder_at=$1 WHERE id=$2`, nextReminder, sessionID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) UpdateRecoveryStatusForID(ctx context.Context, sessionID uuid.UUID, status ente.RecoveryStatus) (bool, error) {
|
||||
validPrevStatus := validPreviousStatus(status)
|
||||
var result sql.Result
|
||||
@@ -106,6 +134,7 @@ func (repo *Repository) UpdateRecoveryStatusForID(ctx context.Context, sessionID
|
||||
rows, _ := result.RowsAffected()
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) GetRecoverRowByID(ctx context.Context, sessionID uuid.UUID) (*RecoverRow, error) {
|
||||
var row RecoverRow
|
||||
err := repo.DB.QueryRowContext(ctx, `SELECT id, user_id, emergency_contact_id, status, wait_till, next_reminder_at, created_at
|
||||
|
||||
Reference in New Issue
Block a user