// Copyright (c) 2024 Mattermost Community Enterprise // Push Proxy Authentication Token Implementation package push_proxy import ( "crypto/rand" "encoding/hex" "net/http" "sync" "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" ejobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs" ) // PushProxyConfig holds configuration for the push proxy interface type PushProxyConfig struct { Config func() *model.Config Logger mlog.LoggerIFace } // PushProxyImpl implements the PushProxyInterface type PushProxyImpl struct { config func() *model.Config logger mlog.LoggerIFace authToken string mutex sync.RWMutex // For the worker stopChan chan struct{} jobChan chan model.Job } // NewPushProxyInterface creates a new push proxy interface func NewPushProxyInterface(cfg *PushProxyConfig) *PushProxyImpl { return &PushProxyImpl{ config: cfg.Config, logger: cfg.Logger, stopChan: make(chan struct{}), jobChan: make(chan model.Job, 1), } } // GetAuthToken returns the current auth token func (pp *PushProxyImpl) GetAuthToken() string { pp.mutex.RLock() defer pp.mutex.RUnlock() return pp.authToken } // GenerateAuthToken generates and stores a new authentication token func (pp *PushProxyImpl) GenerateAuthToken() *model.AppError { pp.mutex.Lock() defer pp.mutex.Unlock() // Generate a cryptographically secure random token tokenBytes := make([]byte, 32) if _, err := rand.Read(tokenBytes); err != nil { return model.NewAppError("GenerateAuthToken", "push_proxy.token_generation_failed", nil, err.Error(), http.StatusInternalServerError) } pp.authToken = hex.EncodeToString(tokenBytes) pp.logger.Info("Generated new push proxy authentication token") return nil } // DeleteAuthToken deletes the stored authentication token func (pp *PushProxyImpl) DeleteAuthToken() *model.AppError { pp.mutex.Lock() defer pp.mutex.Unlock() pp.authToken = "" pp.logger.Info("Deleted push proxy authentication token") return nil } // MakeWorker creates a worker for the auth token generation job func (pp *PushProxyImpl) MakeWorker() model.Worker { return &PushProxyWorker{ pushProxy: pp, stopChan: pp.stopChan, jobChan: pp.jobChan, } } // MakeScheduler creates a scheduler for the auth token generation job func (pp *PushProxyImpl) MakeScheduler() ejobs.Scheduler { return &PushProxyScheduler{ pushProxy: pp, } } // PushProxyWorker implements model.Worker for push proxy token management type PushProxyWorker struct { pushProxy *PushProxyImpl stopChan chan struct{} jobChan chan model.Job } // Run starts the worker func (w *PushProxyWorker) Run() { w.pushProxy.logger.Debug("Push proxy worker started") for { select { case <-w.stopChan: w.pushProxy.logger.Debug("Push proxy worker stopped") return case job := <-w.jobChan: w.pushProxy.logger.Info("Processing push proxy job", mlog.String("job_id", job.Id), ) // Generate a new auth token if err := w.pushProxy.GenerateAuthToken(); err != nil { w.pushProxy.logger.Error("Failed to generate push proxy auth token", mlog.String("job_id", job.Id), mlog.Err(err), ) } } } } // Stop stops the worker func (w *PushProxyWorker) Stop() { w.pushProxy.logger.Debug("Stopping push proxy worker") close(w.stopChan) } // JobChannel returns the job channel func (w *PushProxyWorker) JobChannel() chan<- model.Job { return w.jobChan } // IsEnabled checks if the worker is enabled func (w *PushProxyWorker) IsEnabled(cfg *model.Config) bool { // Push proxy is enabled if push notification server is configured if cfg.EmailSettings.PushNotificationServer == nil { return false } return *cfg.EmailSettings.PushNotificationServer != "" } // PushProxyScheduler implements ejobs.Scheduler for push proxy token management type PushProxyScheduler struct { pushProxy *PushProxyImpl } // Enabled checks if the scheduler is enabled func (s *PushProxyScheduler) Enabled(cfg *model.Config) bool { // Push proxy scheduler is enabled if push notification server is configured if cfg.EmailSettings.PushNotificationServer == nil { return false } return *cfg.EmailSettings.PushNotificationServer != "" } // NextScheduleTime returns the next time the job should be scheduled func (s *PushProxyScheduler) NextScheduleTime(cfg *model.Config, now time.Time, pendingJobs bool, lastSuccessfulJob *model.Job) *time.Time { // If there's a pending job, don't schedule another one if pendingJobs { return nil } // Schedule token refresh every 24 hours if lastSuccessfulJob == nil { // No previous job, schedule immediately nextTime := now.Add(time.Minute) return &nextTime } // Calculate next schedule based on last successful job lastJobTime := time.Unix(lastSuccessfulJob.LastActivityAt/1000, 0) nextTime := lastJobTime.Add(24 * time.Hour) if nextTime.Before(now) { nextTime = now.Add(time.Minute) } return &nextTime } // ScheduleJob schedules a push proxy job func (s *PushProxyScheduler) ScheduleJob(rctx request.CTX, cfg *model.Config, pendingJobs bool, lastSuccessfulJob *model.Job) (*model.Job, *model.AppError) { // Create a new job job := &model.Job{ Id: model.NewId(), Type: "push_proxy_auth_token", Status: model.JobStatusPending, CreateAt: model.GetMillis(), } s.pushProxy.logger.Info("Scheduled push proxy auth token job", mlog.String("job_id", job.Id), ) return job, nil }