Changes between Initial Version and Version 1 of UseCase0001Implementation


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

--

Legend:

Unmodified
Added
Removed
Modified
  • UseCase0001Implementation

    v1 v1  
     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}}}