tTransfer ranking request as database logic - scoreboard - Interactive scoreboard for CTF-like games
 (HTM) git clone git://git.z3bra.org/scoreboard.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
 (DIR) commit e64e15888e10934f5d3ee1c75facb402562a0549
 (DIR) parent 61269ee4372b67a9d212fc9227c2c46acbb58f39
 (HTM) Author: Willy Goiffon <contact@z3bra.org>
       Date:   Tue,  1 Oct 2024 00:40:02 +0200
       
       Transfer ranking request as database logic
       
       Diffstat:
         M db.go                               |      56 ++++++++++++++++++++++---------
         M main.go                             |      15 ++++++++-------
         M player.go                           |      71 +++++++++++++++++--------------
         M playerbox.go                        |       2 +-
         M ui.go                               |       2 +-
       
       5 files changed, 88 insertions(+), 58 deletions(-)
       ---
 (DIR) diff --git a/db.go b/db.go
       t@@ -42,7 +42,6 @@ const (
                `
        )
        
       -
        func db_init(file string) (*sql.DB, error) {
                var err error
                var db *sql.DB
       t@@ -72,21 +71,6 @@ func db_count_players(db *sql.DB) (int, error) {
                return count, err
        }
        
       -func db_get_user_score(db *sql.DB, name string) (int, error) {
       -        var count int
       -
       -        query := `SELECT
       -          IFNULL(SUM(flag.score),0)
       -          FROM flag
       -          INNER JOIN score ON score.flag = flag.value
       -          WHERE score.name = ?;`
       -
       -        row := db.QueryRow(query, name)
       -        err := row.Scan(&count)
       -
       -        return count, err
       -}
       -
        func db_get_user_flags(db *sql.DB, name string) ([]Flag, error) {
                var flags []Flag
        
       t@@ -113,6 +97,46 @@ func db_get_user_flags(db *sql.DB, name string) ([]Flag, error) {
                return flags, err
        }
        
       +func db_get_user_score(db *sql.DB, name string) (int, error) {
       +        var count int
       +
       +        query := `SELECT
       +          IFNULL(SUM(flag.score),0)
       +          FROM flag
       +          INNER JOIN score ON score.flag = flag.value
       +          WHERE score.name = ?;`
       +
       +        row := db.QueryRow(query, name)
       +        err := row.Scan(&count)
       +
       +        return count, err
       +}
       +
       +func db_get_user_rank(db *sql.DB, name string) (int, error) {
       +        var rank int
       +
       +        score, err := db_get_user_score(db, name)
       +        if err != nil {
       +                return -1, err
       +        }
       +
       +        query := `SELECT COUNT(*) FROM (
       +          SELECT
       +            user.name, user.ts as ts, SUM(flag.score) as score
       +            FROM score
       +            INNER JOIN user ON user.name = score.name
       +            INNER JOIN flag ON flag.value = score.flag
       +            WHERE user.name != ?
       +            GROUP BY user.name
       +          ) WHERE score > ? OR (score == ? AND ts < (SELECT ts FROM user WHERE name = ?))
       +          ;`
       +
       +        row := db.QueryRow(query, name, score, score)
       +        row.Scan(&rank)
       +
       +        return rank, err
       +}
       +
        func db_get_flag(db *sql.DB, flag string) (Flag, error) {
                var res Flag
        
 (DIR) diff --git a/main.go b/main.go
       t@@ -15,6 +15,7 @@
        package main
        
        import (
       +        "errors"
                "flag"
                "fmt"
                "os"
       t@@ -68,13 +69,13 @@ func usage() {
        }
        
        
       -func flagid(hash string) int {
       -        for i := 0; i<len(scoreboard.flag_ref); i++ {
       -                if strings.ToUpper(hash) == scoreboard.flag_ref[i].value {
       -                        return scoreboard.flag_ref[i].id
       +func checkflag(ref []Flag, flag string) (Flag, error) {
       +        for _, f := range ref {
       +                if strings.ToUpper(flag) == f.value {
       +                        return f, nil
                        }
                }
       -        return -1
       +        return Flag{}, errors.New("Unknown flag")
        }
        
        func pageBoard() tview.Primitive {
       t@@ -162,7 +163,7 @@ func main() {
        
                        /* anything not a command is treated as a flag */
                        default:
       -                        scoreboard.flag, err = db_get_flag(scoreboard.db, args[0])
       +                        scoreboard.flag, err = checkflag(scoreboard.flag_ref, args[0])
                                if err != nil {
                                        fmt.Println("Incorrect flag")
                                        return
       t@@ -174,7 +175,7 @@ func main() {
                                                scoreboard.Fatal(err)
                                                return
                                        }
       -                                scoreboard.HighlightBoard(scoreboard.player.ScoreRank() + 1)
       +                                scoreboard.HighlightBoard(scoreboard.player.Rank() + 1)
                                        scoreboard.pages.RemovePage("token")
                                        scoreboard.GenerateHTML()
                                })
 (DIR) diff --git a/player.go b/player.go
       t@@ -49,14 +49,13 @@ func randbuf(length int64) []byte {
        }
        
        /* Generate a random base32 token using the provided input as a salt */
       -func mktoken(input string) (string, string, error) {
       -        key := randbuf(12)
       -        salt := base32.StdEncoding.EncodeToString([]byte(input))
       +func mktoken(key []byte, salt string) (string, string, error) {
       +        salt32 := base32.StdEncoding.EncodeToString([]byte(salt))
        
                token := key
       -        token = append(token, []byte(input)...)
       +        token = append(token, []byte(salt)...)
        
       -        dk, err := scrypt.Key(key, []byte(salt), 1<<15, 8, 1, 32)
       +        dk, err := scrypt.Key(key, []byte(salt32), 1<<15, 8, 1, 32)
                if err != nil {
                        return "", "", err
                }
       t@@ -66,18 +65,40 @@ func mktoken(input string) (string, string, error) {
                return token32, hash32, nil
        }
        
       +func hashtoken(token string) (string, error) {
       +        var err error
       +
       +        blob, err := base32.StdEncoding.DecodeString(token)
       +        if err != nil {
       +                return "", err
       +        }
       +
       +        key := blob[:12]
       +        name := string(blob[12:])
       +
       +        salt := base32.StdEncoding.EncodeToString([]byte(name))
       +
       +        dk, err := scrypt.Key(key, []byte(salt), 1<<15, 8, 1, 32)
       +        if err != nil {
       +                return "", err
       +        }
       +        hash := base32.StdEncoding.EncodeToString(dk)
       +
       +        return hash, nil
       +}
       +
        /* Register a user in the database */
        func (p *Player) Register() error {
                var hash string
                var err error
        
                p.ts = time.Now().Unix()
       -        p.token, hash, err = mktoken(p.name)
       +        p.token, hash, err = mktoken(randbuf(12), p.name)
                if err != nil {
                        return err
                }
        
       -        query := `INSERT INTO user(name,hash,score,flag,ts) VALUES(?,?,0,0,?);`
       +        query := `INSERT INTO user(name,hash,ts) VALUES(?,?,?);`
                _, err = p.db.Exec(query, p.name, hash, p.ts)
                if err != nil {
                        return err
       t@@ -121,7 +142,7 @@ func (p *Player) Refresh(ts int64) error {
                        return err
                }
        
       -        query := `UPDATE user ts = ? WHERE name = ?;`
       +        query := `UPDATE user SET ts = ? WHERE name = ?;`
                _, err = p.db.Exec(query, ts, p.name)
                if err != nil {
                        return err
       t@@ -132,18 +153,13 @@ func (p *Player) Refresh(ts int64) error {
                return nil
        }
        
       -func (p *Player) ScoreRank() int {
       -        var count int
       -        query := `SELECT
       -          count(*)
       -          FROM user
       -          WHERE
       -            name != ? AND (score > ? OR (score == ? AND ts <= ?))
       -        ;`
       +func (p *Player) Rank() int {
       +        rank, err := db_get_user_rank(p.db, p.name)
       +        if err != nil {
       +                return -1
       +        }
        
       -        row := p.db.QueryRow(query, p.name, p.score, p.score, p.ts)
       -        row.Scan(&count)
       -        return count
       +        return rank
        }
        
        func (p *Player) FlagStr() string {
       t@@ -221,7 +237,7 @@ func (p *Player) BadgeStr() string {
        }
        
        func (p *Player) RankStr() string {
       -        return humanize.Ordinal(p.ScoreRank() + 1)
       +        return humanize.Ordinal(p.Rank() + 1)
        }
        
        func (p *Player) Exists() bool {
       t@@ -279,23 +295,12 @@ func (p *Player) Submit(flag Flag) error {
        /* Retrieve username from given token */
        func (p *Player) FromToken(token string) error {
                var err error
       -        blob, err := base32.StdEncoding.DecodeString(token)
       +        hash, err := hashtoken(token)
                if err != nil {
                        return err
                }
        
       -        key := blob[:12]
       -        p.name = string(blob[12:])
       -
       -        salt := base32.StdEncoding.EncodeToString([]byte(p.name))
       -
       -        // use player id as salt
       -        dk, err := scrypt.Key(key, []byte(salt), 1<<15, 8, 1, 32)
       -        if err != nil {
       -                return err
       -        }
       -        hash := base32.StdEncoding.EncodeToString(dk)
       -        query := `SELECT name,flag,score,ts FROM user WHERE hash = ?`
       +        query := `SELECT name,ts FROM user WHERE hash = ?`
        
                row := p.db.QueryRow(query, hash)
                err = row.Scan(&p.name, &p.ts)
 (DIR) diff --git a/playerbox.go b/playerbox.go
       t@@ -90,7 +90,7 @@ func PlayerBoxName(p *Player) *tview.TextView {
                                                if err != nil {
                                                        scoreboard.Fatal(err)
                                                }
       -                                        scoreboard.HighlightBoard(p.ScoreRank())
       +                                        scoreboard.HighlightBoard(p.Rank())
                                                scoreboard.GenerateHTML()
                                                scoreboard.Popup("CONGRATULATIONS", fmt.Sprintf(TOKEN_WELCOME, p.name, p.token));
                                        } else {
 (DIR) diff --git a/ui.go b/ui.go
       t@@ -135,7 +135,7 @@ func (a *Application) DrawBoard() {
                        if event.Rune() == 'l' && a.player.token == "" {
                                page := a.Token(func () {
                                        a.pages.RemovePage("token")
       -                                a.HighlightBoard(a.player.ScoreRank() + 1)
       +                                a.HighlightBoard(a.player.Rank() + 1)
                                })
                                a.pages.AddAndSwitchToPage("token", page, true)
                        }