mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-14 21:59:05 +02:00
114 lines
3.9 KiB
Go
114 lines
3.9 KiB
Go
package usersignup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/italypaleale/francis/builtin/cronjob"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
|
)
|
|
|
|
const (
|
|
// cleanupJobInterval is how often overdue signups are deleted, which bounds how long an account can outlive its deadline
|
|
cleanupJobInterval = time.Hour
|
|
// cleanupJobJitter spreads each occurrence around its scheduled time, so it doesn't coincide with the other cleanup jobs
|
|
cleanupJobJitter = 5 * time.Minute
|
|
)
|
|
|
|
// UserDeleter deletes a user together with everything that references it
|
|
type UserDeleter interface {
|
|
DeleteUser(ctx context.Context, dbConfig *appconfig.AppConfigModel, userID string, allowLdapDelete bool) error
|
|
}
|
|
|
|
type cleanupJob struct {
|
|
db *gorm.DB
|
|
appConfig appconfig.AppConfigResolver
|
|
users UserDeleter
|
|
}
|
|
|
|
func newCleanupJob(db *gorm.DB, appConfig appconfig.AppConfigResolver, users UserDeleter) (*cronjob.CronJob, error) {
|
|
job := &cleanupJob{
|
|
db: db,
|
|
appConfig: appConfig,
|
|
users: users,
|
|
}
|
|
|
|
cronActor, err := cronjob.New(
|
|
"DeleteUnverifiedSignups",
|
|
cronjob.WithJob(job.deleteUnverifiedSignups),
|
|
cronjob.WithInterval(cleanupJobInterval),
|
|
cronjob.WithJitter(cleanupJobJitter),
|
|
// Also run right after the job is first registered, so accounts that missed their deadline while Pocket ID wasn't running are removed at startup
|
|
cronjob.WithImmediate(),
|
|
cronjob.WithLogger(slog.Default()),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating unverified signup cleanup cron job: %w", err)
|
|
}
|
|
|
|
return cronActor, nil
|
|
}
|
|
|
|
// deleteUnverifiedSignups deletes self-signed-up accounts that did not verify their email before their deadline
|
|
func (j *cleanupJob) deleteUnverifiedSignups(ctx context.Context) error {
|
|
dbConfig, err := j.appConfig.GetConfig(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading app config: %w", err)
|
|
}
|
|
|
|
// Deadlines only apply while verification is required
|
|
// Clearing them otherwise keeps accounts that were used in the meantime from being deleted once verification is required again
|
|
if dbConfig.EmailVerification.String() != appconfig.EmailVerificationRequired {
|
|
st := j.db.
|
|
WithContext(ctx).
|
|
Model(&model.User{}).
|
|
Where("email_verification_deadline IS NOT NULL").
|
|
Update("email_verification_deadline", nil)
|
|
if st.Error != nil {
|
|
return fmt.Errorf("failed to clear email verification deadlines: %w", st.Error)
|
|
}
|
|
if st.RowsAffected > 0 {
|
|
slog.InfoContext(ctx, "Cleared email verification deadlines because verification is no longer required", slog.Int64("count", st.RowsAffected))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Load the overdue accounts
|
|
// Admins are exempt from email verification, so an account promoted to admin before its deadline is kept
|
|
var userIDs []string
|
|
err = j.db.
|
|
WithContext(ctx).
|
|
Model(&model.User{}).
|
|
Where("email_verification_deadline < ? AND email_verified = ? AND is_admin = ?", datatype.DateTime(time.Now()), false, false).
|
|
Pluck("id", &userIDs).
|
|
Error
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list accounts that missed their email verification deadline: %w", err)
|
|
}
|
|
if len(userIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Delete each account through the user service so its client sessions, SCIM peers and profile picture are cleaned up as well
|
|
// A failure is logged and the remaining accounts are still processed
|
|
deleted := 0
|
|
for _, userID := range userIDs {
|
|
err = j.users.DeleteUser(ctx, dbConfig, userID, false)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to delete an account that missed its email verification deadline", slog.String("userID", userID), slog.Any("error", err))
|
|
continue
|
|
}
|
|
deleted++
|
|
}
|
|
|
|
slog.InfoContext(ctx, "Deleted accounts that missed their email verification deadline", slog.Int("count", deleted))
|
|
|
|
return nil
|
|
}
|