| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- package state
- import (
- "context"
- "database/sql"
- "errors"
- "fmt"
- "time"
- )
- // WebAPITokenStore manages authentication tokens for Web API sessions.
- type WebAPITokenStore struct {
- store *SQLiteUserStore
- }
- // NewWebAPITokenStore creates a new token store.
- func (s *SQLiteUserStore) NewWebAPITokenStore() *WebAPITokenStore {
- return &WebAPITokenStore{store: s}
- }
- // StoreToken saves an authentication token for a user.
- func (s *WebAPITokenStore) StoreToken(ctx context.Context, token string, screenName IdentScreenName, expiresAt time.Time) error {
- query := `
- INSERT INTO webapi_tokens (token, screen_name, expires_at, created_at)
- VALUES (?, ?, ?, ?)
- ON CONFLICT(token) DO UPDATE SET
- screen_name = excluded.screen_name,
- expires_at = excluded.expires_at
- `
- _, err := s.store.db.ExecContext(ctx, query, token, screenName.String(), expiresAt, time.Now())
- if err != nil {
- return fmt.Errorf("failed to store token: %w", err)
- }
- return nil
- }
- // ValidateToken checks if a token is valid and returns the associated screen name.
- func (s *WebAPITokenStore) ValidateToken(ctx context.Context, token string) (IdentScreenName, error) {
- var screenNameStr string
- var expiresAt time.Time
- query := `
- SELECT screen_name, expires_at
- FROM webapi_tokens
- WHERE token = ?
- `
- err := s.store.db.QueryRowContext(ctx, query, token).Scan(&screenNameStr, &expiresAt)
- if err != nil {
- if errors.Is(err, sql.ErrNoRows) {
- return NewIdentScreenName(""), errors.New("invalid token")
- }
- return NewIdentScreenName(""), fmt.Errorf("failed to validate token: %w", err)
- }
- // Check if token has expired
- if time.Now().After(expiresAt) {
- // Clean up expired token
- s.DeleteToken(ctx, token)
- return NewIdentScreenName(""), errors.New("token expired")
- }
- return NewIdentScreenName(screenNameStr), nil
- }
- // DeleteToken removes a token.
- func (s *WebAPITokenStore) DeleteToken(ctx context.Context, token string) error {
- query := `DELETE FROM webapi_tokens WHERE token = ?`
- _, err := s.store.db.ExecContext(ctx, query, token)
- if err != nil {
- return fmt.Errorf("failed to delete token: %w", err)
- }
- return nil
- }
- // CleanupExpiredTokens removes all expired tokens from the database.
- func (s *WebAPITokenStore) CleanupExpiredTokens(ctx context.Context) error {
- query := `DELETE FROM webapi_tokens WHERE expires_at < ?`
- _, err := s.store.db.ExecContext(ctx, query, time.Now())
- if err != nil {
- return fmt.Errorf("failed to cleanup expired tokens: %w", err)
- }
- return nil
- }
- // AuthenticateUser verifies username and password.
- // This implementation uses the existing user store for authentication.
- func (u *SQLiteUserStore) AuthenticateUser(ctx context.Context, username, password string) (*User, error) {
- // Convert username to IdentScreenName for lookup
- identSN := NewIdentScreenName(username)
- // Try to find the user
- user, err := u.User(ctx, identSN)
- if err != nil {
- return nil, fmt.Errorf("user not found: %w", err)
- }
- // In development mode with DISABLE_AUTH=true, accept any password
- // In production, this would verify the password hash
- // For now, we'll accept any non-empty password if the user exists
- if password == "" {
- return nil, errors.New("password required")
- }
- // TODO: In production, verify password hash here
- // For development with DISABLE_AUTH, we just check if user exists
- return user, nil
- }
- // FindUserByScreenName finds a user by their screen name.
- // This is just an alias for the User method to satisfy the UserManager interface.
- func (u *SQLiteUserStore) FindUserByScreenName(ctx context.Context, screenName IdentScreenName) (*User, error) {
- return u.User(ctx, screenName)
- }
|