package internal import ( "crypto/rand" "database/sql" "encoding/hex" "fmt" "time" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) var authDB *sql.DB // InitAuthDB initializes the authentication database func InitAuthDB(filepath string) error { var err error authDB, err = sql.Open("sqlite3", filepath) if err != nil { return err } if err = authDB.Ping(); err != nil { return err } createTableSQL := ` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id); CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); ` _, err = authDB.Exec(createTableSQL) return err } // CreateUser creates a new user with hashed password func CreateUser(username, password string) (*User, error) { // Generate UUID for user userID := uuid.New().String() // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } createdAt := time.Now().Unix() _, err = authDB.Exec( "INSERT INTO users (id, username, password_hash, created_at) VALUES (?, ?, ?, ?)", userID, username, string(hashedPassword), createdAt, ) if err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } return &User{ ID: userID, Username: username, PasswordHash: string(hashedPassword), CreatedAt: time.Unix(createdAt, 0), }, nil } // GetUserByUsername retrieves a user by username func GetUserByUsername(username string) (*User, error) { var user User var createdAt int64 err := authDB.QueryRow( "SELECT id, username, password_hash, created_at FROM users WHERE username = ?", username, ).Scan(&user.ID, &user.Username, &user.PasswordHash, &createdAt) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("user not found") } return nil, err } user.CreatedAt = time.Unix(createdAt, 0) return &user, nil } // VerifyPassword checks if the provided password matches the user's password hash func VerifyPassword(user *User, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) return err == nil } // GenerateSessionID generates a random session ID func GenerateSessionID() (string, error) { bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { return "", err } return hex.EncodeToString(bytes), nil } // CreateSession creates a new session for a user func CreateSession(userID string, username string) (*Session, error) { sessionID, err := GenerateSessionID() if err != nil { return nil, fmt.Errorf("failed to generate session ID: %w", err) } createdAt := time.Now() expiresAt := createdAt.Add(30 * 24 * time.Hour) // 30 days _, err = authDB.Exec( "INSERT INTO sessions (id, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)", sessionID, userID, createdAt.Unix(), expiresAt.Unix(), ) if err != nil { return nil, fmt.Errorf("failed to create session: %w", err) } return &Session{ ID: sessionID, UserID: userID, Username: username, CreatedAt: createdAt, ExpiresAt: expiresAt, }, nil } // GetSession retrieves a session by ID func GetSession(sessionID string) (*Session, error) { var session Session var createdAt, expiresAt int64 err := authDB.QueryRow( `SELECT s.id, s.user_id, u.username, s.created_at, s.expires_at FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.id = ?`, sessionID, ).Scan(&session.ID, &session.UserID, &session.Username, &createdAt, &expiresAt) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("session not found") } return nil, err } session.CreatedAt = time.Unix(createdAt, 0) session.ExpiresAt = time.Unix(expiresAt, 0) // Check if session is expired if time.Now().After(session.ExpiresAt) { DeleteSession(sessionID) return nil, fmt.Errorf("session expired") } return &session, nil } // DeleteSession deletes a session by ID func DeleteSession(sessionID string) error { _, err := authDB.Exec("DELETE FROM sessions WHERE id = ?", sessionID) return err } // CleanExpiredSessions removes all expired sessions func CleanExpiredSessions() error { _, err := authDB.Exec("DELETE FROM sessions WHERE expires_at < ?", time.Now().Unix()) return err } // UpdatePassword updates a user's password func UpdatePassword(userID string, newPassword string) error { // Hash the new password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } _, err = authDB.Exec( "UPDATE users SET password_hash = ? WHERE id = ?", string(hashedPassword), userID, ) if err != nil { return fmt.Errorf("failed to update password: %w", err) } return nil }