|
@@ -1,383 +0,0 @@
|
|
|
-package state
|
|
|
|
|
-
|
|
|
|
|
-import (
|
|
|
|
|
- "context"
|
|
|
|
|
- "database/sql"
|
|
|
|
|
- "encoding/hex"
|
|
|
|
|
- "errors"
|
|
|
|
|
- "fmt"
|
|
|
|
|
- "time"
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-// OSCARBridgeStore manages the persistence of OSCAR bridge sessions in the database.
|
|
|
|
|
-// It provides methods to store, retrieve, and manage the mapping between WebAPI
|
|
|
|
|
-// sessions and OSCAR authentication cookies.
|
|
|
|
|
-type OSCARBridgeStore struct {
|
|
|
|
|
- store *SQLiteUserStore
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// NewOSCARBridgeStore creates a new OSCAR bridge store instance.
|
|
|
|
|
-func (s *SQLiteUserStore) NewOSCARBridgeStore() *OSCARBridgeStore {
|
|
|
|
|
- return &OSCARBridgeStore{store: s}
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// OSCARBridgeSession represents a bridge between WebAPI and OSCAR sessions.
|
|
|
|
|
-type OSCARBridgeSession struct {
|
|
|
|
|
- WebSessionID string // WebAPI session identifier
|
|
|
|
|
- OSCARCookie []byte // OSCAR authentication cookie
|
|
|
|
|
- BOSHost string // BOS server hostname
|
|
|
|
|
- BOSPort int // BOS server port
|
|
|
|
|
- UseSSL bool // Whether to use SSL connection
|
|
|
|
|
- ScreenName string // Screen name associated with the session
|
|
|
|
|
- ClientName string // Client application name
|
|
|
|
|
- ClientVersion string // Client application version
|
|
|
|
|
- CreatedAt time.Time // Bridge creation timestamp
|
|
|
|
|
- LastAccessed time.Time // Last access timestamp
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// SaveBridgeSession stores the mapping between WebAPI and OSCAR sessions.
|
|
|
|
|
-func (s *OSCARBridgeStore) SaveBridgeSession(ctx context.Context, webSessionID string,
|
|
|
|
|
- oscarCookie []byte, bosHost string, bosPort int) error {
|
|
|
|
|
-
|
|
|
|
|
- query := `
|
|
|
|
|
- INSERT INTO oscar_bridge_sessions
|
|
|
|
|
- (web_session_id, oscar_cookie, bos_host, bos_port, screen_name, created_at, last_accessed)
|
|
|
|
|
- VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
- ON CONFLICT(web_session_id) DO UPDATE SET
|
|
|
|
|
- oscar_cookie = excluded.oscar_cookie,
|
|
|
|
|
- bos_host = excluded.bos_host,
|
|
|
|
|
- bos_port = excluded.bos_port,
|
|
|
|
|
- last_accessed = excluded.last_accessed
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- now := time.Now()
|
|
|
|
|
- // Note: We'll need to get the screen name from the session manager
|
|
|
|
|
- // For now, using a placeholder
|
|
|
|
|
- screenName := "" // This should be passed from the handler
|
|
|
|
|
-
|
|
|
|
|
- _, err := s.store.db.ExecContext(ctx, query,
|
|
|
|
|
- webSessionID, oscarCookie, bosHost, bosPort, screenName, now, now)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Errorf("failed to save bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// SaveBridgeSessionWithDetails stores a complete bridge session with all details.
|
|
|
|
|
-func (s *OSCARBridgeStore) SaveBridgeSessionWithDetails(ctx context.Context, session *OSCARBridgeSession) error {
|
|
|
|
|
- query := `
|
|
|
|
|
- INSERT INTO oscar_bridge_sessions
|
|
|
|
|
- (web_session_id, oscar_cookie, bos_host, bos_port, use_ssl, screen_name,
|
|
|
|
|
- client_name, client_version, created_at, last_accessed)
|
|
|
|
|
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
- ON CONFLICT(web_session_id) DO UPDATE SET
|
|
|
|
|
- oscar_cookie = excluded.oscar_cookie,
|
|
|
|
|
- bos_host = excluded.bos_host,
|
|
|
|
|
- bos_port = excluded.bos_port,
|
|
|
|
|
- use_ssl = excluded.use_ssl,
|
|
|
|
|
- last_accessed = excluded.last_accessed
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- _, err := s.store.db.ExecContext(ctx, query,
|
|
|
|
|
- session.WebSessionID, session.OSCARCookie, session.BOSHost, session.BOSPort,
|
|
|
|
|
- session.UseSSL, session.ScreenName, session.ClientName, session.ClientVersion,
|
|
|
|
|
- session.CreatedAt, session.LastAccessed)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Errorf("failed to save bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetBridgeSession retrieves bridge session details by WebAPI session ID.
|
|
|
|
|
-func (s *OSCARBridgeStore) GetBridgeSession(ctx context.Context, webSessionID string) (*OSCARBridgeSession, error) {
|
|
|
|
|
- query := `
|
|
|
|
|
- SELECT web_session_id, oscar_cookie, bos_host, bos_port, use_ssl, screen_name,
|
|
|
|
|
- client_name, client_version, created_at, last_accessed
|
|
|
|
|
- FROM oscar_bridge_sessions
|
|
|
|
|
- WHERE web_session_id = ?
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- var session OSCARBridgeSession
|
|
|
|
|
- var clientName, clientVersion sql.NullString
|
|
|
|
|
-
|
|
|
|
|
- err := s.store.db.QueryRowContext(ctx, query, webSessionID).Scan(
|
|
|
|
|
- &session.WebSessionID,
|
|
|
|
|
- &session.OSCARCookie,
|
|
|
|
|
- &session.BOSHost,
|
|
|
|
|
- &session.BOSPort,
|
|
|
|
|
- &session.UseSSL,
|
|
|
|
|
- &session.ScreenName,
|
|
|
|
|
- &clientName,
|
|
|
|
|
- &clientVersion,
|
|
|
|
|
- &session.CreatedAt,
|
|
|
|
|
- &session.LastAccessed,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
|
- return nil, fmt.Errorf("bridge session not found")
|
|
|
|
|
- }
|
|
|
|
|
- return nil, fmt.Errorf("failed to get bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Handle nullable fields
|
|
|
|
|
- if clientName.Valid {
|
|
|
|
|
- session.ClientName = clientName.String
|
|
|
|
|
- }
|
|
|
|
|
- if clientVersion.Valid {
|
|
|
|
|
- session.ClientVersion = clientVersion.String
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Update last accessed time
|
|
|
|
|
- go s.touchSession(context.Background(), webSessionID)
|
|
|
|
|
-
|
|
|
|
|
- return &session, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetBridgeSessionByScreenName retrieves bridge sessions by screen name.
|
|
|
|
|
-func (s *OSCARBridgeStore) GetBridgeSessionByScreenName(ctx context.Context, screenName string) ([]*OSCARBridgeSession, error) {
|
|
|
|
|
- query := `
|
|
|
|
|
- SELECT web_session_id, oscar_cookie, bos_host, bos_port, use_ssl, screen_name,
|
|
|
|
|
- client_name, client_version, created_at, last_accessed
|
|
|
|
|
- FROM oscar_bridge_sessions
|
|
|
|
|
- WHERE screen_name = ?
|
|
|
|
|
- ORDER BY last_accessed DESC
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- rows, err := s.store.db.QueryContext(ctx, query, screenName)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to query bridge sessions: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
- defer rows.Close()
|
|
|
|
|
-
|
|
|
|
|
- var sessions []*OSCARBridgeSession
|
|
|
|
|
-
|
|
|
|
|
- for rows.Next() {
|
|
|
|
|
- var session OSCARBridgeSession
|
|
|
|
|
- var clientName, clientVersion sql.NullString
|
|
|
|
|
-
|
|
|
|
|
- err := rows.Scan(
|
|
|
|
|
- &session.WebSessionID,
|
|
|
|
|
- &session.OSCARCookie,
|
|
|
|
|
- &session.BOSHost,
|
|
|
|
|
- &session.BOSPort,
|
|
|
|
|
- &session.UseSSL,
|
|
|
|
|
- &session.ScreenName,
|
|
|
|
|
- &clientName,
|
|
|
|
|
- &clientVersion,
|
|
|
|
|
- &session.CreatedAt,
|
|
|
|
|
- &session.LastAccessed,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to scan bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Handle nullable fields
|
|
|
|
|
- if clientName.Valid {
|
|
|
|
|
- session.ClientName = clientName.String
|
|
|
|
|
- }
|
|
|
|
|
- if clientVersion.Valid {
|
|
|
|
|
- session.ClientVersion = clientVersion.String
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- sessions = append(sessions, &session)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if err := rows.Err(); err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("error iterating bridge sessions: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return sessions, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// DeleteBridgeSession removes a bridge session.
|
|
|
|
|
-func (s *OSCARBridgeStore) DeleteBridgeSession(ctx context.Context, webSessionID string) error {
|
|
|
|
|
- query := `DELETE FROM oscar_bridge_sessions WHERE web_session_id = ?`
|
|
|
|
|
-
|
|
|
|
|
- result, err := s.store.db.ExecContext(ctx, query, webSessionID)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Errorf("failed to delete bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- rowsAffected, err := result.RowsAffected()
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Errorf("failed to get rows affected: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if rowsAffected == 0 {
|
|
|
|
|
- return fmt.Errorf("bridge session not found")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// CleanupExpiredSessions removes bridge sessions that haven't been accessed recently.
|
|
|
|
|
-func (s *OSCARBridgeStore) CleanupExpiredSessions(ctx context.Context, maxAge time.Duration) (int, error) {
|
|
|
|
|
- cutoff := time.Now().Add(-maxAge)
|
|
|
|
|
-
|
|
|
|
|
- query := `DELETE FROM oscar_bridge_sessions WHERE last_accessed < ?`
|
|
|
|
|
-
|
|
|
|
|
- result, err := s.store.db.ExecContext(ctx, query, cutoff)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return 0, fmt.Errorf("failed to cleanup expired sessions: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- rowsAffected, err := result.RowsAffected()
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return 0, fmt.Errorf("failed to get rows affected: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return int(rowsAffected), nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// touchSession updates the last accessed time for a session (internal helper).
|
|
|
|
|
-func (s *OSCARBridgeStore) touchSession(ctx context.Context, webSessionID string) {
|
|
|
|
|
- query := `UPDATE oscar_bridge_sessions SET last_accessed = ? WHERE web_session_id = ?`
|
|
|
|
|
- _, _ = s.store.db.ExecContext(ctx, query, time.Now(), webSessionID)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetAllBridgeSessions returns all active bridge sessions (for monitoring/admin).
|
|
|
|
|
-func (s *OSCARBridgeStore) GetAllBridgeSessions(ctx context.Context) ([]*OSCARBridgeSession, error) {
|
|
|
|
|
- query := `
|
|
|
|
|
- SELECT web_session_id, oscar_cookie, bos_host, bos_port, use_ssl, screen_name,
|
|
|
|
|
- client_name, client_version, created_at, last_accessed
|
|
|
|
|
- FROM oscar_bridge_sessions
|
|
|
|
|
- ORDER BY last_accessed DESC
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- rows, err := s.store.db.QueryContext(ctx, query)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to query all bridge sessions: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
- defer rows.Close()
|
|
|
|
|
-
|
|
|
|
|
- var sessions []*OSCARBridgeSession
|
|
|
|
|
-
|
|
|
|
|
- for rows.Next() {
|
|
|
|
|
- var session OSCARBridgeSession
|
|
|
|
|
- var clientName, clientVersion sql.NullString
|
|
|
|
|
-
|
|
|
|
|
- err := rows.Scan(
|
|
|
|
|
- &session.WebSessionID,
|
|
|
|
|
- &session.OSCARCookie,
|
|
|
|
|
- &session.BOSHost,
|
|
|
|
|
- &session.BOSPort,
|
|
|
|
|
- &session.UseSSL,
|
|
|
|
|
- &session.ScreenName,
|
|
|
|
|
- &clientName,
|
|
|
|
|
- &clientVersion,
|
|
|
|
|
- &session.CreatedAt,
|
|
|
|
|
- &session.LastAccessed,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to scan bridge session: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Handle nullable fields
|
|
|
|
|
- if clientName.Valid {
|
|
|
|
|
- session.ClientName = clientName.String
|
|
|
|
|
- }
|
|
|
|
|
- if clientVersion.Valid {
|
|
|
|
|
- session.ClientVersion = clientVersion.String
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- sessions = append(sessions, &session)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if err := rows.Err(); err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("error iterating bridge sessions: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return sessions, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// GetStatistics returns statistics about bridge sessions.
|
|
|
|
|
-func (s *OSCARBridgeStore) GetStatistics(ctx context.Context) (map[string]interface{}, error) {
|
|
|
|
|
- stats := make(map[string]interface{})
|
|
|
|
|
-
|
|
|
|
|
- // Total sessions
|
|
|
|
|
- var totalCount int
|
|
|
|
|
- err := s.store.db.QueryRowContext(ctx,
|
|
|
|
|
- `SELECT COUNT(*) FROM oscar_bridge_sessions`).Scan(&totalCount)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to get total count: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
- stats["total_sessions"] = totalCount
|
|
|
|
|
-
|
|
|
|
|
- // Active sessions (accessed in last hour)
|
|
|
|
|
- var activeCount int
|
|
|
|
|
- oneHourAgo := time.Now().Add(-time.Hour)
|
|
|
|
|
- err = s.store.db.QueryRowContext(ctx,
|
|
|
|
|
- `SELECT COUNT(*) FROM oscar_bridge_sessions WHERE last_accessed > ?`,
|
|
|
|
|
- oneHourAgo).Scan(&activeCount)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to get active count: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
- stats["active_sessions"] = activeCount
|
|
|
|
|
-
|
|
|
|
|
- // SSL vs non-SSL
|
|
|
|
|
- var sslCount int
|
|
|
|
|
- err = s.store.db.QueryRowContext(ctx,
|
|
|
|
|
- `SELECT COUNT(*) FROM oscar_bridge_sessions WHERE use_ssl = true`).Scan(&sslCount)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("failed to get SSL count: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
- stats["ssl_sessions"] = sslCount
|
|
|
|
|
- stats["non_ssl_sessions"] = totalCount - sslCount
|
|
|
|
|
-
|
|
|
|
|
- return stats, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// ValidateOSCARCookie checks if an OSCAR cookie exists in the bridge store.
|
|
|
|
|
-// This can be used to validate incoming OSCAR connections.
|
|
|
|
|
-func (s *OSCARBridgeStore) ValidateOSCARCookie(ctx context.Context, cookie []byte) (*OSCARBridgeSession, error) {
|
|
|
|
|
- // Convert cookie to hex for comparison
|
|
|
|
|
- cookieHex := hex.EncodeToString(cookie)
|
|
|
|
|
-
|
|
|
|
|
- query := `
|
|
|
|
|
- SELECT web_session_id, oscar_cookie, bos_host, bos_port, use_ssl, screen_name,
|
|
|
|
|
- client_name, client_version, created_at, last_accessed
|
|
|
|
|
- FROM oscar_bridge_sessions
|
|
|
|
|
- WHERE hex(oscar_cookie) = ?
|
|
|
|
|
- `
|
|
|
|
|
-
|
|
|
|
|
- var session OSCARBridgeSession
|
|
|
|
|
- var clientName, clientVersion sql.NullString
|
|
|
|
|
-
|
|
|
|
|
- err := s.store.db.QueryRowContext(ctx, query, cookieHex).Scan(
|
|
|
|
|
- &session.WebSessionID,
|
|
|
|
|
- &session.OSCARCookie,
|
|
|
|
|
- &session.BOSHost,
|
|
|
|
|
- &session.BOSPort,
|
|
|
|
|
- &session.UseSSL,
|
|
|
|
|
- &session.ScreenName,
|
|
|
|
|
- &clientName,
|
|
|
|
|
- &clientVersion,
|
|
|
|
|
- &session.CreatedAt,
|
|
|
|
|
- &session.LastAccessed,
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
|
- return nil, fmt.Errorf("cookie not found")
|
|
|
|
|
- }
|
|
|
|
|
- return nil, fmt.Errorf("failed to validate cookie: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Handle nullable fields
|
|
|
|
|
- if clientName.Valid {
|
|
|
|
|
- session.ClientName = clientName.String
|
|
|
|
|
- }
|
|
|
|
|
- if clientVersion.Valid {
|
|
|
|
|
- session.ClientVersion = clientVersion.String
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Update last accessed time
|
|
|
|
|
- go s.touchSession(context.Background(), session.WebSessionID)
|
|
|
|
|
-
|
|
|
|
|
- return &session, nil
|
|
|
|
|
-}
|
|
|