[][src]Module sudoku_backend::doc::user

User list

Users contain identifying data, status (admin/not), and points.

Authentication

Client-side

When the user presses "Log in" on the login page, the plaintext password is key-derived with scrypt.

The entered password is not normalised in any way. The arguments passed to the client-side scrypt function are

parameter value comment
N 214 Recommended for passwords
r 8 "Optimum value" specified in paper (page 12)
p 1 "Optimum value" specified in paper (page 12)
dkLen 64
salt "Sudoku" That's what the project is called, and, honestly, it doesn't need to be that secure, just preferably not plaintext

Nota bene: these values must be consistent once and for all, as other values will create different hashes, which will in turn create different passwords.

That value is then hex-string-encoded (case irrelevant), a JSON-stringified form is constructed, then base64-encoded as "data" and that is sent in a form.

In other words, with data being the key and value, and doubling as User Login Data (all keys strings):

let data = base64(JSON.stringify({
    username: raw_username,
    email: raw_email, // See note below
    password: scrypt(raw_password, /* With ^ params */)
}));

The email key shall only be present for user registration – the form will be rejected if it contains an email and is used to log in; vice versa – the registration request will be rejected if it doesn't contain the email key.

Server-side verification

If a user with the specified username exists in the database, the server lower-cases the key-derived password, then compares it to the stored value.

Otherwise, the server shall return the same value as with above with unmatched password.

The returned status shall be 202 Accepted on correct verification and 401 Unauthorized otherwise.

The returned value shall be Sanitised User data on correct verification, and Login Error otherwise.

The returned bundle shall not distinguish between any two cases of incorrect verification.

Server-side entry creation

If a user with the specified username or e-mail doesn't exist in the database, the server lower-cases the key-derived password, then derives it again with parameters as guessed via util::SCRYPT_IDEAL_PARAMS. Finally, the server stores that username, e-mail, and doubly-derived password in the rscrypt format in the users table.

The returned status shall be 201 Created on correct creation, 409 Conflict if a user with that name/e-mail already exists, and an otherwise implementation-defined relevant status on other errors.

The returned value shall be Sanitised User data on correct creation, an appropriately filled out Generic Error if a user with that name/e-mail already exists, and an otherwise implementation-defined relevant Error on other errors.

Logging out

Independently of whether the user is logged in, the server shall return 204 No Content.

SQL table def

CREATE TABLE IF NOT EXISTS users (
    id                 INTEGER PRIMARY KEY ASC,     -- Unique user ID
    username           TEXT NOT NULL UNIQUE,        -- User's name or "login" or whatever you want to call it
    password           TEXT NOT NULL,               -- Doubly scrypted password text, see above.
    email              TEXT NOT NULL UNIQUE,        -- User's contact e-mail
    created_at         DATETIME NOT NULL,           -- Time user was created
    is_admin           BOOLEAN NOT NULL DEFAULT 0,  -- Whether the user has administrative privileges
    points_total       INTEGER NOT NULL DEFAULT 0,  -- Sum total of the user's points, calculated according to sudoku.md#scoring-formula, non-negative
    games_total        INTEGER NOT NULL DEFAULT 0,  -- Total amount of games played, non-negative
    games_total_easy   INTEGER NOT NULL DEFAULT 0,  -- Amount of easy games played, non-negative
    games_total_medium INTEGER NOT NULL DEFAULT 0,  -- Amount of medium games played, non-negative
    games_total_hard   INTEGER NOT NULL DEFAULT 0,  -- Amount of hard games played, non-negative
 
    CHECK ((points_total >= 0) AND (games_total >= 0) AND (games_total_easy >= 0) AND (games_total_medium >= 0) AND (games_total_hard >= 0))
);

Sanitised User data

{
    "username":           "string",
    "created_at":         "RFC3339 (string)",
    "is_admin":           "boolean",
    "points_total":       "number",
    "games_total":        "number",
    "games_total_easy":   "number",
    "games_total_medium": "number",
    "games_total_hard":   "number"
}

Admins

Admins are assigned manually, where $1 is the username whose adminness to set:

UPDATE users
    SET is_admin = 1
    WHERE username = "$1";

Other keys will of course, per analogiam, work.