mirror of
https://github.com/ente-io/ente.git
synced 2025-05-05 12:57:34 +00:00
177 lines
5.9 KiB
Go
177 lines
5.9 KiB
Go
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,
|
|
) (*string, *ente.KeyAttributes, error) {
|
|
contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
recoveryTarget, err := c.UserRepo.Get(contact.UserID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
keyAttr, err := c.UserRepo.GetKeyAttributes(recoveryTarget.ID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return contact.EncryptedKey, &keyAttr, nil
|
|
}
|
|
|
|
func (c *Controller) InitChangePassword(ctx *gin.Context, userID int64, request ente.RecoverySrpSetupRequest) (*ente.SetupSRPResponse, error) {
|
|
sessionID := request.RecoveryID
|
|
contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := c.UserCtrl.SetupSRP(ctx, contact.UserID, request.SetUpSRPReq)
|
|
if err != nil {
|
|
return nil, stacktrace.Propagate(err, "")
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *Controller) ChangePassword(ctx *gin.Context, userID int64, request ente.RecoveryUpdateSRPAndKeysRequest) (*ente.UpdateSRPSetupResponse, error) {
|
|
sessionID := request.RecoveryID
|
|
contact, err := c.checkRecoveryAndGetContact(ctx, userID, sessionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// disable 2fa
|
|
if disableErr := c.UserCtrl.DisableTwoFactor(contact.UserID); disableErr != nil {
|
|
return nil, stacktrace.Propagate(disableErr, "failed to disable 2fa")
|
|
}
|
|
if disableErr := c.PasskeyController.RemovePasskey2FA(contact.UserID); disableErr != nil {
|
|
return nil, stacktrace.Propagate(disableErr, "failed to disable passkey")
|
|
}
|
|
resp, err := c.UserCtrl.UpdateSrpAndKeyAttributes(ctx, contact.UserID, request.UpdateSrp, false)
|
|
if err != nil {
|
|
return nil, stacktrace.Propagate(err, "")
|
|
}
|
|
|
|
hasUpdate, err := c.Repo.UpdateRecoveryStatusForID(ctx, sessionID, ente.RecoveryStatusRecovered)
|
|
if err != nil {
|
|
return nil, stacktrace.Propagate(err, "failed to update recovery status")
|
|
}
|
|
if !hasUpdate {
|
|
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, nil)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *Controller) checkRecoveryAndGetContact(ctx *gin.Context,
|
|
userID int64,
|
|
sessionID uuid.UUID) (*emergency.ContactRow, error) {
|
|
recoverRow, err := c.Repo.GetRecoverRowByID(ctx, sessionID)
|
|
if err != nil {
|
|
return nil, stacktrace.Propagate(err, "")
|
|
}
|
|
if recoverRow.EmergencyContactID != userID {
|
|
return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "only the emergency contact can get recovery info")
|
|
}
|
|
if err = recoverRow.CanRecover(); err != nil {
|
|
return nil, stacktrace.Propagate(ente.NewBadRequestWithMessage(err.Error()), "")
|
|
}
|
|
contact, err := c.Repo.GetActiveEmergencyContact(ctx, recoverRow.UserID, recoverRow.EmergencyContactID)
|
|
if err != nil {
|
|
return nil, stacktrace.Propagate(err, "")
|
|
}
|
|
if contact.EncryptedKey == nil {
|
|
return nil, stacktrace.Propagate(ente.ErrNotFound, "no encrypted key found")
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|