package db import ( "database/sql" "errors" "fmt" ) // Token is a row in the tokens table. The plaintext is never stored. type Token struct { ID int64 UserID int64 Name string Hint string CreatedAt int64 LastUsedAt sql.NullInt64 } const tokenColumns = `SELECT id, user_id, name, hint, created_at, last_used_at FROM tokens WHERE ` // CreateToken stores a git token (tokenHash is a digest; hint a display // prefix) and returns the stored row. func CreateToken(database *sql.DB, userID int64, name, hint, tokenHash string) (Token, error) { id, err := execLastID(database, `INSERT INTO tokens (user_id, name, hint, token_hash) VALUES (?, ?, ?, ?)`, userID, name, hint, tokenHash) if err != nil { return Token{}, fmt.Errorf("create token: %w", err) } return getToken(database, tokenColumns+`id = ?`, id) } // GetTokenByHash looks up a token by its digest; ErrNotFound when absent. func GetTokenByHash(database *sql.DB, tokenHash string) (Token, error) { return getToken(database, tokenColumns+`token_hash = ?`, tokenHash) } // scanToken reads one tokens row (see tokenColumns) into tok. func scanToken(s rowScanner, tok *Token) error { return s.Scan(&tok.ID, &tok.UserID, &tok.Name, &tok.Hint, &tok.CreatedAt, &tok.LastUsedAt) } func getToken(database *sql.DB, query string, arg any) (Token, error) { var tok Token err := scanToken(database.QueryRow(query, arg), &tok) if errors.Is(err, sql.ErrNoRows) { return Token{}, fmt.Errorf("token: %w", ErrNotFound) } if err != nil { return Token{}, fmt.Errorf("get token: %w", err) } return tok, nil } // ListTokens returns a user's tokens, newest first. func ListTokens(database *sql.DB, userID int64) ([]Token, error) { tokens, err := listQuery(database, tokenColumns+`user_id = ? ORDER BY created_at DESC, id DESC`, []any{userID}, scanToken) if err != nil { return nil, fmt.Errorf("list tokens: %w", err) } return tokens, nil } // TouchToken records that a token was just used. func TouchToken(database *sql.DB, id int64) error { if _, err := database.Exec(`UPDATE tokens SET last_used_at = unixepoch() WHERE id = ?`, id); err != nil { return fmt.Errorf("touch token %d: %w", id, err) } return nil } // DeleteToken revokes a token, scoped to its owner. func DeleteToken(database *sql.DB, userID, id int64) error { return execScoped(database, fmt.Sprintf("delete token %d", id), `DELETE FROM tokens WHERE id = ? AND user_id = ?`, id, userID) }