package store import ( "context" "fmt" "time" ) // CreateEnrollmentToken persists a fresh one-time token. The caller // has already hashed the raw token; the raw form is returned to the // operator (printed in the install snippet) and never persisted. func (s *Store) CreateEnrollmentToken(ctx context.Context, tokenHash string, ttl time.Duration) error { now := time.Now().UTC() _, err := s.db.ExecContext(ctx, `INSERT INTO enrollment_tokens (token_hash, created_at, expires_at) VALUES (?, ?, ?)`, tokenHash, now.Format(time.RFC3339Nano), now.Add(ttl).Format(time.RFC3339Nano)) if err != nil { return fmt.Errorf("store: create enrollment token: %w", err) } return nil } // ConsumeEnrollmentToken atomically validates a token (must exist, // not be consumed, not be expired) and marks it consumed by hostID. // Returns ErrNotFound on any failure. func (s *Store) ConsumeEnrollmentToken(ctx context.Context, tokenHash, hostID string) error { now := time.Now().UTC().Format(time.RFC3339Nano) res, err := s.db.ExecContext(ctx, `UPDATE enrollment_tokens SET consumed_at = ?, consumed_host = ? WHERE token_hash = ? AND consumed_at IS NULL AND expires_at > ?`, now, hostID, tokenHash, now) if err != nil { return fmt.Errorf("store: consume enrollment token: %w", err) } n, _ := res.RowsAffected() if n == 0 { return ErrNotFound } return nil } // PurgeExpiredEnrollmentTokens deletes long-expired token rows. Tokens // retained for ~24h after expiry so audit traces still resolve them. func (s *Store) PurgeExpiredEnrollmentTokens(ctx context.Context) (int64, error) { cutoff := time.Now().Add(-24 * time.Hour).UTC().Format(time.RFC3339Nano) res, err := s.db.ExecContext(ctx, `DELETE FROM enrollment_tokens WHERE expires_at <= ?`, cutoff) if err != nil { return 0, fmt.Errorf("store: purge enrollment tokens: %w", err) } n, _ := res.RowsAffected() return n, nil }