Changes between Initial Version and Version 1 of UseCase0002Implementation


Ignore:
Timestamp:
09/24/26 14:01:43 (4 days ago)
Author:
231285
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • UseCase0002Implementation

    v1 v1  
     1= Use-case 0002 Implementation - Log in =
     2
     3'''Initiating actor:''' Visitor
     4
     5'''Other actors:''' —
     6
     7A registered user authenticates with a username and password so that the system
     8treats all following actions as actions of that Trader. The system looks the user up
     9by username and compares the stored password hash with the SHA-256 hash of the
     10entered password. An unknown username and a wrong password give the same answer,
     11`Invalid credentials.`, so the system does not reveal which usernames exist. After a
     12successful login the user's id and username are kept in the in-process session and
     13the authenticated (Trader) menu is shown, from which all other Trader use-cases
     14start.
     15
     16Original use-case description (P3): [wiki:UseCase0002].
     17Implementation: `server/auth.go`, functions `Login` and `authenticate` (password hash
     18by `hashPassword`; the code is shown at the end of this page).
     19
     20== Scenario ==
     21
     22 1. '''Visitor''' chooses `[2] Login` in the anonymous menu (types `2`).
     23 2. '''System''' prints `-- Login --` and asks for `Username:` and then `Password:`.
     24
     25The screenshot shows steps 1–2: option `2` is chosen and the `Username:` prompt
     26is waiting for input.
     27
     28[[Image(uc0002_1_login.png)]]
     29
     30 3. '''Visitor''' enters the username and the password. (If either is empty, the
     31    system prints `Username and password are required.` without accessing the
     32    database.)
     33 4. '''System''' looks up the user (`$1` = entered username):
     34
     35{{{
     36SELECT id, password_hash FROM users WHERE username = $1
     37}}}
     38
     39 5. If no row is returned (`sql.ErrNoRows` in Go), the '''System''' responds
     40    `Invalid credentials.` and the scenario ends.
     41 6. If a row is returned, the '''System''' compares the returned `password_hash` with
     42    `hashPassword(entered password)` (hex-encoded SHA-256, computed in Go). On a
     43    mismatch it responds `Invalid credentials.` and the scenario ends.
     44
     45The screenshot shows this failure path with an existing user and a wrong
     46password: `alice` / `wrongpass`. The query from step 4 finds alice's row, the
     47hash comparison of step 6 fails, and the system prints `Invalid credentials.` and
     48returns to the anonymous menu. (An unknown username — step 5 — prints exactly the
     49same message.)
     50
     51[[Image(uc0002_5_6_invalid.png)]]
     52
     53 7. On a match, the '''System''' stores the returned `id` and the username in the
     54    session (`s.UserID`, `s.Username`), prints `Login successful.` and displays the
     55    authenticated menu headed `--- Logged in as alice ---`.
     56
     57The screenshot shows steps 3–7 of the second, successful attempt with the seed
     58credentials `alice` / `test123` (the first, rejected attempt is still visible at
     59the top of the window).
     60
     61[[Image(uc0002_7_success.png)]]
     62
     63The query runs on the `project` schema (the connection sets
     64`search_path=project,public`), so `users` means `project.users`.
     65
     66=== Alternate flow 4a (P3) — lookup combined with the live balance ===
     67
     68P3 describes an optional variant that checks the password in SQL and returns the
     69balances in the same query. The P4 prototype does '''not''' use it: login always uses
     70the query from step 4 with the hash comparison in Go, and the balances are read
     71separately when the Trader asks for them (`[1] View balance`, see
     72[wiki:UseCase0003Implementation UseCase0003]).
     73
     74== Seed credentials ==
     75
     76State after `./eduberza -init` (`server/db/data_load.sql`):
     77
     78||= Username =||= Password =||= Available balance =||= Invested balance =||= Holdings =||
     79|| `alice` || `test123` || 8250.00 USD || 1750.00 USD || 0.5 ETH ||
     80|| `bob` || `test123` || 5000.00 USD || 0.00 USD || — ||
     81|| `charlie` || `test123` || 2500.00 USD || 0.00 USD || — ||
     82
     83== How to reproduce ==
     84
     85{{{
     86./eduberza -init
     87./eduberza
     88# [2] Login: alice / wrongpass  -> Invalid credentials.
     89# [2] Login: alice / test123    -> Login successful.  (authenticated menu)
     90}}}
     91
     92The screenshots come from one real run of exactly these inputs.
     93
     94== Source code ==
     95
     96`server/auth.go` — `hashPassword`, `Login` and `authenticate`:
     97
     98{{{
     99func hashPassword(pw string) string {
     100        sum := sha256.Sum256([]byte(pw))
     101        return hex.EncodeToString(sum[:])
     102}
     103}}}
     104
     105{{{
     106// Login - UC0002
     107func Login(s *Session) {
     108        fmt.Println("\n-- Login --")
     109        username := prompt("Username: ")
     110        pw := prompt("Password: ")
     111        if username == "" || pw == "" {
     112                fmt.Println("Username and password are required.")
     113                return
     114        }
     115
     116        id, err := authenticate(username, pw)
     117        if err != nil {
     118                if errors.Is(err, errInvalidCreds) {
     119                        fmt.Println("Invalid credentials.")
     120                        return
     121                }
     122                fmt.Println("Login error:", err)
     123                return
     124        }
     125        s.UserID = id
     126        s.Username = username
     127        fmt.Println("Login successful.")
     128}
     129
     130var errInvalidCreds = errors.New("invalid credentials")
     131
     132func authenticate(username, pw string) (string, error) {
     133        var id, stored string
     134        err := db.DB.QueryRow(
     135                `SELECT id, password_hash FROM users WHERE username = $1`,
     136                username,
     137        ).Scan(&id, &stored)
     138        if err == sql.ErrNoRows {
     139                return "", errInvalidCreds
     140        }
     141        if err != nil {
     142                return "", err
     143        }
     144        if stored != hashPassword(pw) {
     145                return "", errInvalidCreds
     146        }
     147        return id, nil
     148}
     149}}}