source: docs/P4-Prototype/wiki/UseCase0001Implementation.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: 5.7 KB
Line 
1= Use-case 0001 Implementation - Register new account =
2
3'''Initiating actor:''' Visitor
4
5'''Other actors:''' —
6
7A new person creates an account on !EduBerza so that they can later log in as a
8Trader ([wiki:UseCase0002Implementation UseCase0002]). The Visitor enters a
9username, an e-mail address, a full name and a password. The system validates the
10input (required fields, an `@` in the e-mail, a password of at least 6 characters),
11refuses a username or e-mail that is already registered, and stores only a SHA-256
12hash of the password, never the password itself. A new account starts with a cash
13balance of 0 USD; money is added later with a deposit
14([wiki:UseCase0003Implementation UseCase0003]).
15
16Original use-case description (P3): [wiki:UseCase0001].
17Implementation: `server/auth.go`, function `Register` (the password hash is computed
18by `hashPassword` in the same file; the code is shown at the end of this page).
19
20== Scenario ==
21
22 1. '''Visitor''' chooses `[1] Register` in the anonymous menu (types `1`).
23 2. '''System''' prints `-- Register --` and asks, one prompt after another, for
24 `Username:`, `Email:`, `Full name:` and `Password (min 6 chars):`.
25
26The screenshot shows steps 1–2: option `1` is chosen and the first prompt
27(`Username:`) is waiting for input.
28
29[[Image(uc0001_1_register.png)]]
30
31 3. '''Visitor''' enters the values: `marko`, `marko@example.com`, `Marko Markovski`,
32 `secret1`.
33 4. '''System''' validates the input in Go, without accessing the database:
34 * username, e-mail and password must be non-empty, otherwise it prints
35 `Username, email and password are required.` and the scenario ends;
36 * the e-mail must contain `@` (see alternate flow 3a);
37 * the password must be at least 6 characters long, otherwise it prints
38 `Password must be at least 6 characters.` and the scenario ends.
39 5. '''System''' checks whether the username or the e-mail already exists
40 (`$1` = username, `$2` = e-mail):
41
42{{{
43SELECT EXISTS(SELECT 1 FROM users WHERE username = $1 OR email = $2)
44}}}
45
46If the result is `true`, alternate flow 5a applies.
47
48 6. '''System''' creates the account (`$1` = username, `$2` = e-mail, `$3` = full name,
49 `$4` = password hash). The hash is computed in Go by `hashPassword` as the
50 hex-encoded SHA-256 of the password — the same value that P3's
51 `encode(digest($4, 'sha256'), 'hex')` would produce in SQL. Hashing on the Go side
52 keeps it identical to the check done at login (SQL as in the code, only the Go
53 source indentation removed):
54
55{{{
56INSERT INTO users (username, email, full_name, password_hash, available_balance)
57VALUES ($1, $2, $3, $4, 0)
58}}}
59
60 7. '''System''' prints `Account created. You can now log in.` and returns to the
61 anonymous menu; the Visitor can continue with
62 [wiki:UseCase0002Implementation UseCase0002].
63
64The screenshot shows steps 3–7 of the successful attempt (bottom half): the
65entered values, the confirmation and the anonymous menu again. The top half is the
66earlier rejected attempt from alternate flow 3a.
67
68[[Image(uc0001_3_7_created.png)]]
69
70All statements are run on the `project` schema: the connection sets
71`search_path=project,public` (`server/db/db.go`), so `users` means `project.users`.
72
73=== Alternate flow 3a — invalid e-mail ===
74
75In step 3 the Visitor entered `marko.example.com` (no `@`). The check in step 4
76fails, the system prints `Invalid email.` and no SQL statement is executed. In the
77prototype the system then shows the anonymous menu again and the Visitor chooses
78`[1] Register` once more, which returns the scenario to step 2.
79
80[[Image(uc0001_3a_invalid_email.png)]]
81
82=== Alternate flow 5a — duplicate username or e-mail ===
83
84After `marko` has been created, the Visitor tries to register again with username
85`marko`, e-mail `other@example.com`, full name `Marko Two`, password `secret2`. The
86query from step 5 (`$1` = `marko`, `$2` = `other@example.com`) returns `true`
87because the username is taken, so the system prints
88`Username or email already taken.`, does not run the `INSERT`, and the scenario
89ends in the anonymous menu.
90
91[[Image(uc0001_5a_duplicate.png)]]
92
93== How to reproduce ==
94
95{{{
96./eduberza -init # optional: reset to a known state
97./eduberza
98# [1] Register: marko / marko.example.com / Marko Markovski / secret1 -> Invalid email.
99# [1] Register: marko / marko@example.com / Marko Markovski / secret1 -> Account created.
100# [1] Register: marko / other@example.com / Marko Two / secret2 -> Username or email already taken.
101}}}
102
103All three screenshots come from one real run of exactly these inputs.
104
105== Source code ==
106
107`server/auth.go` — `hashPassword` and `Register`:
108
109{{{
110func hashPassword(pw string) string {
111 sum := sha256.Sum256([]byte(pw))
112 return hex.EncodeToString(sum[:])
113}
114
115// Register - UC0001
116func Register() {
117 fmt.Println("\n-- Register --")
118 username := prompt("Username: ")
119 email := prompt("Email: ")
120 fullName := prompt("Full name: ")
121 pw := prompt("Password (min 6 chars): ")
122
123 if username == "" || email == "" || pw == "" {
124 fmt.Println("Username, email and password are required.")
125 return
126 }
127 if !strings.Contains(email, "@") {
128 fmt.Println("Invalid email.")
129 return
130 }
131 if len(pw) < 6 {
132 fmt.Println("Password must be at least 6 characters.")
133 return
134 }
135
136 var exists bool
137 err := db.DB.QueryRow(
138 `SELECT EXISTS(SELECT 1 FROM users WHERE username = $1 OR email = $2)`,
139 username, email,
140 ).Scan(&exists)
141 if err != nil {
142 fmt.Println("Database error:", err)
143 return
144 }
145 if exists {
146 fmt.Println("Username or email already taken.")
147 return
148 }
149
150 _, err = db.DB.Exec(
151 `INSERT INTO users (username, email, full_name, password_hash, available_balance)
152 VALUES ($1, $2, $3, $4, 0)`,
153 username, email, fullName, hashPassword(pw),
154 )
155 if err != nil {
156 fmt.Println("Failed to register:", err)
157 return
158 }
159 fmt.Println("Account created. You can now log in.")
160}
161}}}
Note: See TracBrowser for help on using the repository browser.