# Technical documentation — logintuto

Evidence date: 2026-10-10.

Source: `git@bitbucket.org:overdrive/logintuto.git`, `master` at `f679256` (2020-04-02, Uriel Balela, "all forms and dashboard"). Only branch: `master`. `origin/HEAD` points at `master`.

The SMTP password committed in `php/email.php` is omitted. The database password in `php/db.php` is an empty string; that fact is stated because it is the configured value, not a secret to reproduce.

## Architecture

Server-rendered PHP pages in the repository root, with jQuery Ajax into scripts under `php/`. Persistence is MySQL through `mysqli`. Mail is PHPMailer over SMTP. There is no framework, front controller, autoloaded application namespace, or build step for the PHP.

`php/composer.json` exists only to require PHPMailer. The committed `php/vendor` tree is what the mail script loads. Application code does not use Composer's autoload except inside `php/email.php`.

Page composition is `include`, not a template engine:

- `dashboard.php` includes `php/login.php` (and therefore `php/db.php` and `php/functions.php`) and then prints the sidebar.
- `profileform.php` and `usergroup.php` include `dashboard.php` before their own `<!DOCTYPE html>`. A response from those URLs is the dashboard document followed by a second document.
- `usergroup.php` also includes `php/db.php` before `dashboard.php`, so the connection file runs twice on that request.

## Repository layout

```text
index.php, signUpForm.php, recover.php, newPassword.php
dashboard.php, dashboard2.php, profileform.php, usergroup.php, welcome.php
css/          Bootstrap 4.4.1, Font Awesome 5.13.0, project stylesheets, font files
js/           jQuery, Bootstrap, Popper, Font Awesome JS, validation.js
images/       avatar.png, planet.jpg (no references from PHP, JS, or project CSS)
php/          endpoints, db.php, functions.php, composer.json, composer.lock
php/vendor/   PHPMailer 6.1.4 and Composer autoload files
```

Not in git at `f679256`: README, `.gitignore`, `.env`, SQL, tests, Dockerfile, `bitbucket-pipelines.yml`, `sonar-project.properties`, `sonarqube_scan.bat`.

The OVER-188 task text listed `sonar-project.properties` and `sonarqube_scan.bat` in the scan-host working tree. They are not tracked. **Unknown / Requires Confirmation** whether they were local-only files.

## Languages and libraries

| Piece | Version evidence | Used by |
|---|---|---|
| PHP | `random_bytes` and `mysqli`. No `composer.json` `php` constraint | All endpoints |
| MySQL client | `mysqli_connect` in `php/db.php` | Every script that includes `db.php` |
| PHPMailer | `VERSION` file `6.1.4`. Constraint `^6.1` | `php/email.php` |
| Bootstrap CSS | File header `Bootstrap v4.4.1` in `css/bootstrap.css` | Login, sign-up, recover, reset, `dashboard2.php` |
| Bootstrap JS | Local `js/bootstrap.js`. CDN 4.4.1 on `usergroup.php` and `php/resetPassword.php` | `dashboard2.php` menu toggle uses Bootstrap's collapse attributes. `usergroup.php` loads the CDN script |
| Font Awesome | Header `Font Awesome Free 5.13.0` in `css/all.css`. `js/all.js` is also loaded on the form pages | Icons via CSS classes. Dashboard icons are inline SVG, not the font |
| jQuery | Header of `js/jquery.js`: `jQuery v3.4.1` with ajax and effects excluded. CDN `jquery-3.4.1.min.js` on profile, groups, and `php/resetPassword.php` | See the jQuery section |
| Popper | `js/popper.js` | Loaded with Bootstrap on the form pages and `dashboard2.php` |

PHP version on the intended host is **Unknown / Requires Confirmation**. `random_bytes` requires PHP 7 or later. `if ($row > 0)` where `$row` is a `mysqli_fetch_array` result is valid as a comparison in older PHP and throws `TypeError` when an array is compared to an integer under PHP 8. That test is in `php/login.php` and `SaveLog`.

## Runtime entry points

Assumed document root: the repository root, so that `index.php` is `/index.php` and the mail link's path `/logintuto/` matches a directory name. The link is hard-coded. The actual virtual host is **Unknown / Requires Confirmation**.

| URL (relative) | Role | Method that does the work |
|---|---|---|
| `index.php` | Login screen | Browser POST is not used. Ajax POST to `php/login.php` |
| `php/login.php` | Authenticate | POST `userName`, `passWord`. Body `1` or `0` |
| `signUpForm.php` | Registration screen | Ajax POST to `php/signUp.php` |
| `php/signUp.php` | Insert account | POST `userName`, `passWord`, `email`. Body `1` or `0` plus any MySQL error text |
| `recover.php` | Ask for email | Ajax POST to `php/email.php` |
| `php/email.php` | Token and mail | POST `email`. Redirects to `../index.php` when `email` is absent |
| `newPassword.php` | New password form | GET `email`, `token`. Ajax POST to `php/reset_pass.php` |
| `php/reset_pass.php` | Store password | POST `passWord`, `email`, `token`. Body `1` or `0` |
| `dashboard.php` | Shell | GET. Also includes `php/login.php`, so a POST to this URL with `userName` and `passWord` runs the login script |
| `profileform.php` | Profile UI | Ajax POST to `php/profile.php` |
| `php/profile.php` | Profile writes | POST either `oldPassWord` and `NewPassWord`, or `newUserName` and `email` |
| `usergroup.php` | Group UI | GET renders the table. Ajax POST to `php/usergroups.php` |
| `php/usergroups.php` | Group writes | POST `newGroup`, or `blnUpdate` with `GroupID` and `strName` |
| `php/logout.php` | End session | GET from the dashboard anchor |
| `welcome.php` | Greeting | GET. Unlinked |
| `dashboard2.php` | Alternate shell | GET. Unlinked |
| `php/resetPassword.php` | Older recover UI | GET. Unlinked. Ajax URL `email.php` |
| `php/test.php` | Calls `SelectData` | GET. Unlinked |
| `php/updateuser.php` | Dumps `$_POST['checkRead']` | POST. Unlinked |

There is no router and no rewrite config in the repository.

## Authentication

Session name and cookie parameters are PHP defaults. The code never calls `session_set_cookie_params`, `session_regenerate_id`, or `ini_set` for the session.

`php/login.php` calls `session_start()` only after a matching row. `index.php`, `dashboard.php`, `welcome.php`, `php/logout.php`, `php/profile.php`, and `php/email.php` call `session_start()` themselves. `php/email.php` does not read the session.

Logout unsets `username` and then `session_destroy()`. It does not unset `id` or `userGroup` before destroy. Destroy clears the session data.

No CSRF token is generated or checked. **Implemented** absence.

## Database

`php/db.php`:

- Server `localhost`
- Username `root`
- Password empty string
- Database name `login`
- On failure: `die` with `Connection failed:` and `mysqli_connect_error()`

There is no other environment. No `$_SERVER['SERVER_NAME']` switch.

Queries concatenate request values into SQL strings. No prepared statements are used. `Insertdata` joins field names with commas and values with `','` inside a single quoted value list.

Helpers in `php/functions.php`:

| Function | Behaviour |
|---|---|
| `Insertdata($table, $field, $data)` | INSERT. Returns true or false. Echoes `mysqli_error` on failure. Used by sign-up, login log, and group insert |
| `updateDate($field, $name)` | `UPDATE user SET <field> = NOW() WHERE username = '<name>'` |
| `updatetUser($username, $email, $oldusername)` | Updates `username` and `email`. Echoes `1` on a true query result. Returns bool |
| `SaveLog($username, $email, $password, $key)` | Reads `user` by `$_SESSION['id']`, inserts `loguser` |
| `insertUser` | Alternate insert. No caller outside this file |
| `selectUser` | Uses `$conn` without `global`. No return. No caller |
| `UpdateTable` | Builds SQL, does not query. Comment: not implemented yet |
| `SelectData` | Echoes the SQL, returns an array of rows. Caller: `php/test.php` |
| `redirectToLoginPage` | `Location: ../index.php` and `exit`. Caller: `php/logout.php`, and `php/email.php` when `email` is not posted |

Schema DDL is not in the repository. Column names the DML uses are listed in `business-rules.md`.

## Mail

`php/email.php`:

- `isSMTP()`, host `smtp.gmail.com`, port `587`, `SMTPSecure` `tls`, `SMTPAuth` true.
- Username, password, and From address are string literals in that file. From display name is `Consolate`.
- `SMTPDebug = SMTP::DEBUG_SERVER` (protocol transcript is written to the response).
- HTML body, subject `Reset Password`.
- Requires `vendor/autoload.php`, and also requires `PHPMailer.php` and `SMTP.php` by path. `Exception.php` is not required by path; the class reference is `PHPMailer\PHPMailer\Exception`.
- The script echoes `success` before `send()`.

OAuth is not configured. PHPMailer's `OAuth.php` and `get_oauth_token.php` are vendor files only.

## Front-end behaviour that changes the contract

### jQuery builds

`js/jquery.js` is jQuery 3.4.1 built with ajax and effects removed (the file banner lists those exclusions). `$.ajax` is not in that build.

`index.php`, `signUpForm.php`, `recover.php`, and `newPassword.php` load `js/jquery.js` and then `js/jqueryDev.js`. The second file replaces `window.jQuery`. Those pages' Ajax calls depend on `jqueryDev.js`.

`profileform.php` and `usergroup.php` load jQuery 3.4.1 from the jQuery CDN, which includes ajax. They do not load `jqueryDev.js`.

`dashboard2.php` loads only `js/jquery.js`. Its script uses `.click` and `.toggleClass`, which do not need ajax.

### `indexOf` checks

`recover.php` uses `if (response.indexOf('success'))`. Index `0` is falsy.

`newPassword.php` uses `if (response.indexOf(1))`. `indexOf` converts the argument to the string `"1"`. A body of `1` yields `0`. A body of `0` yields `-1`, which is truthy.

`signUpForm.php`, `profileform.php` (details), and the group add button compare with `== 1`, which is a different test.

### Group rename response

`php/usergroups.php` echoes the UPDATE SQL and then `1`. The client compares the whole body to `1`.

### Font path

`css/all.css` loads Font Awesome files from `../webfonts/`. The font files are stored under `css/` (`fa-solid-900.woff2` and the other weights). There is no `webfonts` directory. Pages that depend on `all.css` for icons (login, sign-up, recover, reset, `dashboard2.php`) point at the wrong relative path. The dashboard SVG icons do not use those files.

### Missing stylesheet

`dashboard2.php` links `css/dashbaord.css`. The tracked names are `css/dash.css` and `css/dashboard.css`.

## Security observations

These are properties of the code, not a remediation plan.

- Database user `root` with an empty password, host `localhost`, credentials in `php/db.php`.
- SMTP username and password in `php/email.php`, and therefore in git history. Human review should treat that password as exposed.
- Passwords stored and matched in SQL as clear text. `log_oldData` and `log_newData` also store the password field inside JSON.
- Request values are concatenated into SQL on the login, sign-up, mail, reset, profile, group, and helper paths.
- Profile, group, and several POST scripts do not check that a session exists.
- Reset and login responses are not bound to a CSRF token.
- `SMTP::DEBUG_SERVER` returns server dialogue to the browser.
- `php/test.php` echoes a SELECT of `user` if requested.
- `SelectData` echoes the SQL it runs.

## Background work, queues, storage, monitoring

None are implemented. There is no cron script, queue worker, or log framework. Audit data is two MySQL inserts (`tblloginuser`, `loguser`). Static images under `images/` are unused. Uploaded file storage is absent.

## Environment and deployment

No web-server config, no environment file, no container, no pipeline. The mail link assumes the app is served at `http://localhost/logintuto/`.

`php/vendor` is committed, so `composer install` is not required to load PHPMailer from this tree. `composer.lock` is present beside `composer.json`.

## Development and testing process

No PHPUnit, no JavaScript test, no fixture, no CI definition. `php/test.php` is a manual probe: it includes the database and `SelectData`, and the comment says it is for testing functions before implementing them.

Git history is 16 commits on `master`, 2020-02-25 through 2020-04-02, authors Uriel Balela (`uriel@overdrive.co.za`, 15 commits) and Kagiso Malepe (`kagiso@overdrive.co.za`, one commit).

## Configuration inventory

| Setting | Location | Value |
|---|---|---|
| DB host | `php/db.php` | `localhost` |
| DB user | `php/db.php` | `root` |
| DB password | `php/db.php` | empty |
| DB name | `php/db.php` | `login` |
| SMTP host | `php/email.php` | `smtp.gmail.com` |
| SMTP port | `php/email.php` | `587` |
| SMTP security | `php/email.php` | `tls` |
| SMTP identity | `php/email.php` | literals; password omitted from this document |
| Reset URL | `php/email.php` | `http://localhost/logintuto/newPassword.php` |
| Token length | `php/email.php` | 20 random bytes, hex encoded |
| Token lifetime | `php/email.php` | 20 minutes |
| PHPMailer | `php/composer.json`, `VERSION` | `^6.1`, installed 6.1.4 |

## Open technical questions

- Which PHP version and which MySQL or MariaDB version this was run on.
- The real `CREATE TABLE` statements, indexes, and foreign keys.
- Whether `sonar-project.properties` and `sonarqube_scan.bat` from the scan host should be recovered.
- Whether `jqueryDev.js` was kept as a second jQuery on purpose because `jquery.js` has no ajax.
- Whether the Font Awesome files were meant to live in `webfonts/`.
- Whether the Gmail account is still valid. The password is in history either way.
