source: docs/P4-Prototype/wiki/UseCase0002Implementation.md

main
Last change on this file was ef1c1c7, checked in by Stefan <trsunovstefan@…>, 6 days ago

Wiki docs, phase 6 and phase 7 added

  • Property mode set to 100644
File size: 4.9 KB
Line 
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}}}
Note: See TracBrowser for help on using the repository browser.