# Technical documentation — unity-api

Evidence date: 2026-09-30.

Source: `git@bitbucket.org:overdrive/unity-api.git`, `master` at `eeafec9` (2019-08-30, "Including new db").

The scan-host checkout listed `sonar-project.properties` and `sonarqube_scan.bat`. Those files are not in this git revision. They are not documented as part of the application.

Secrets, password hashes, OAuth token ids, and personal rows from `db/unity.sql` are omitted on purpose.

## Stack

| Layer | Evidence |
|---|---|
| Language | PHP `^7.1.3` (`composer.json`). Dump header says the dump was taken with PHP 7.3.1 |
| Framework | Laravel `5.8.*`, locked `v5.8.28` |
| API auth | `laravel/passport` `^7.3`, locked `v7.3.2` |
| CORS package | `spatie/laravel-cors` `^1.5`, locked `1.5.1` |
| HTTP entry | `public/index.php`. Apache rewrite in `public/.htaccess`. IIS rules in `public/web.config`. `server.php` is the framework's PHP built-in server router |
| Database intended by env example | MySQL, `127.0.0.1:3306`, database `homestead` (`.env.example`) |
| Database in the committed dump | MySQL 5.7.24, database name `unity`, phpMyAdmin 4.8.4, generated 2019-08-30 09:53 |
| Front end | Laravel Mix 4, Vue 2.5, Bootstrap 4.1, Axios 0.18 (`package.json`). No `package-lock.json` or `yarn.lock` |
| Tests | PHPUnit `^7.5` (`phpunit.xml`) |
| Style | `.styleci.yml`, `.editorconfig` present. No CI config that runs them |

`composer.json` name is still `laravel/laravel`, description "The Laravel Framework.", license MIT. That is the skeleton metadata, not a renamed package.

## Application structure

```text
app/Http/Controllers/API/     AuthController, RegisterController, ProductController, BaseController
app/Http/Controllers/Auth/    Laravel scaffold; not referenced by routes
app/Http/Middleware/Cors.php  second CORS layer, used only on v1 login and register
app/User.php                  Passport user
app/Product.php               products table
app/Providers/AuthServiceProvider.php   Passport::routes()
routes/api.php                /api routes
routes/web.php                GET /
database/migrations/          users, password_resets, products
db/unity.sql                  full dump, not loaded by the app
config/                       Laravel 5.8 config set plus config/cors.php
public/                       front controller, compiled Mix assets
resources/                    welcome view, Vue scaffold, lang lines
tests/                        two example tests
```

Namespace note. `RegisterController`, `ProductController`, and `BaseController` declare `namespace App\Http\Controllers\API`. `AuthController` declares `namespace App\Http\Controllers\Api`. The directory on disk is `app/Http/Controllers/API`. `routes/api.php` asks for `API\RegisterController` and `Api\AuthController`. PSR-4 maps `App\` to `app/`. On a case-sensitive volume, `Api` and `API` are different paths. Windows checkouts hide that. Whether any deployed host was case-sensitive is **Unknown / Requires Confirmation**.

`RouteServiceProvider` sets namespace `App\Http\Controllers`, prefix `api`, middleware group `api`.

## HTTP surface

Prefix `/api` is applied in the provider, not repeated inside `routes/api.php`.

| Method | Path | Middleware beyond the `api` group | Controller |
|---|---|---|---|
| POST | `/api/register` | none | `API\RegisterController@register` |
| POST | `/api/v1/login` | `cors` | `Api\AuthController@login` |
| POST | `/api/v1/register` | `cors` | `Api\AuthController@register` |
| POST | `/api/v1/getUser` | `auth:api` | `Api\AuthController@getUser` |
| GET | `/api/user` | `auth:api` | closure |
| * | `/api/products` resource | `auth:api` | `API\ProductController` |
| GET | `/` | `web` group | welcome view |

`api` group: `throttle:60,1`, `bindings`.

`web` group: cookies, session, CSRF, bindings. CSRF exceptions list is empty (`app/Http/Middleware/VerifyCsrfToken.php`). API routes are not in the `web` group, so they are not CSRF-protected. That is Laravel's default split.

Global middleware on every request: maintenance mode, post size, trim strings, empty strings to null, trust proxies, `Spatie\Cors\Cors`.

`Passport::routes()` adds the package default OAuth routes. They are not written in `routes/api.php`. Exact paths are whatever `laravel/passport` v7.3.2 registers when called with no options. This document does not restate a path list that is not in the repository.

## Authentication

`config/auth.php`:

- Default guard `web` (session).
- Guard `api`: driver `passport`, provider `users`, eloquent model `App\User`. The `hash` key is commented out.
- Passwords: table `password_resets`, expire 60 minutes. Unused by routes.

`app/User.php`:

- Traits `HasApiTokens`, `Notifiable`.
- Implements `MustVerifyEmail`.
- Fillable: `name`, `email`, `password`.
- Hidden: `password`, `remember_token`.

Token creation:

- `RegisterController`: `createToken('MyApp')`.
- `AuthController` register and login: `createToken('AppName')`.

`Auth::attempt` uses the default guard, which is `web` (session), against the same `users` provider. Login then mints a Passport token from `Auth::user()`. Session state for that attempt depends on the middleware of the route. The login route is in the `api` group, which does not start a session. How `Auth::attempt` behaves without `StartSession` on this stack was not executed here. **Unknown / Requires Confirmation** if login was verified against a running server.

Passport encryption keys are not in git. `.gitignore` ignores `/storage/*.key`. A deploy needs keys generated outside this repository (`passport:keys` or equivalent). That step is not scripted here.

The policy map in `AuthServiceProvider` points at `App\Policies\ModelPolicy`. The class file is absent. `registerPolicies()` is still called.

`BroadcastServiceProvider` is commented out in `config/app.php`. `routes/channels.php` defines `App.User.{id}` and is not loaded.

## Response shapes

`BaseController::sendResponse` always uses HTTP 200:

```text
{ "success": true, "data": <payload>, "message": <string> }
```

`BaseController::sendError` defaults to HTTP 404:

```text
{ "success": false, "message": <string>, "data": <errors if non-empty> }
```

`AuthController` does not use `BaseController`. Its success body is `{ "success": { "token": "<access token>" } }` with HTTP 200. Login failure is `{ "error": "Unauthorised" }` HTTP 401. Register validation failure is `{ "error": <validator errors> }` HTTP 401.

`app/Exceptions/Handler.php` does not special-case JSON. Unauthenticated, not-found, and query exceptions use Laravel 5.8's parent renderer.

## Database

### Tables this application migrates

| Migration file | Table | Columns that matter |
|---|---|---|
| `2014_10_12_000000_create_users_table.php` | `users` | `id` bigIncrements, `name`, `email` unique, `email_verified_at` nullable, `password`, `remember_token`, timestamps |
| `2014_10_12_100000_create_password_resets_table.php` | `password_resets` | `email` index, `token`, `created_at` nullable |
| `2019_07_13_213211_create_products_table.php` | `products` | `id` increments (integer), `name` string, `detail` text, timestamps |

No foreign key is declared. `products` is not owned by `users`.

`database/seeds/DatabaseSeeder.php` has the user seeder call commented out. `database/factories/UserFactory.php` defines a user whose `email_verified_at` is `now()` and whose password hash is the Laravel sample hash (comment in the factory: `password`). No test uses the factory.

### What the dump says was already migrated

`db/unity.sql` `migrations` rows:

| id | migration | batch |
|---|---|---|
| 1 | `2014_10_12_000000_create_users_table` | 1 |
| 2 | `2014_10_12_100000_create_password_resets_table` | 1 |
| 3 | `2016_06_01_000001_create_oauth_auth_codes_table` | 2 |
| 4 | `2016_06_01_000002_create_oauth_access_tokens_table` | 2 |
| 5 | `2016_06_01_000003_create_oauth_refresh_tokens_table` | 2 |
| 6 | `2016_06_01_000004_create_oauth_clients_table` | 2 |
| 7 | `2016_06_01_000005_create_oauth_personal_access_clients_table` | 2 |
| 8 | `2019_07_13_213211_create_products_table` | 3 |

Batch 2 filenames are Passport's. They are not files in this repo. The dump's `users` and `products` column lists match the migrations. Dump engine for those Laravel tables is MyISAM. The migration files do not set an engine, so the engine is the server default at migrate time. CRM tables in the same dump are InnoDB.

`users` in the dump has four rows (ids 1–4), names `developer` or `developer2`, addresses `dev@gmail.com`, `dev2@gmail.com`, `dev3@gmail.com`, `dev4@gmail.com`, `email_verified_at` null. Password hashes are in the file and are not copied here. `products` has no `INSERT`.

`oauth_access_tokens` and related oauth tables contain dumped rows. Token material is not copied here.

### The other schema in the same file

The rest of `db/unity.sql` is a CRM, HR, SLA, audit, and settings schema (table names are listed in `features.md`). Triggers on `crmcontact`, `crmproduct`, `crmtask`, `crmticket`, `crmproposal`, `hrleaveapplication`, and `hrreview` insert into `syscronjob` or rewrite columns. Those rules are written up in `business-rules.md`. No PHP connects to them.

`sysuser` is the dump's staff table. It is not `App\User`.

The dump header and many `INSERT` statements contain staff and client data, password material (`users`, `syspassword`), and tokens. This documentation does not repeat them. Keeping that file in git is a review item, not an application feature.

## CORS and the web server

`config/cors.php` (`Spatie` `DefaultProfile`):

- `allow_credentials`: true
- `allow_origins`: `*`
- `allow_methods`: POST, GET, OPTIONS, PUT, PATCH, DELETE
- `allow_headers`: `*`
- `max_age`: 86400
- forbidden response message `Forbidden (cors).`, status 403

`App\Http\Middleware\Cors` (v1 login and register only) sets:

- `Access-Control-Allow-Origin: *`
- `Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS`
- Allow-Headers line is commented out, so this middleware does not send that header

Repository root `.htaccess` sets `Access-Control-Allow-Origin: *` inside `mod_headers`. It does not rewrite to `public/index.php`. The front-controller rewrite is `public/.htaccess`, which also copies the `Authorization` header into `HTTP_AUTHORIZATION` so Passport can see it.

`public/web.config` is the Laravel IIS rewrite file. No hosting document says Apache versus IIS was used.

## Configuration

`.env.example` (stock Laravel 5.8 plus nothing project-specific):

| Key | Example value |
|---|---|
| `APP_NAME` | `Laravel` |
| `APP_ENV` | `local` |
| `APP_DEBUG` | `true` |
| `APP_URL` | `http://localhost` |
| `DB_*` | MySQL, database `homestead`, user `homestead`, password `secret` |
| `CACHE_DRIVER` | `file` |
| `QUEUE_CONNECTION` | `sync` |
| `SESSION_DRIVER` | `file`, lifetime 120 |
| `BROADCAST_DRIVER` | `log` |
| `MAIL_*` | `smtp.mailtrap.io:2525` |
| `REDIS_*` | `127.0.0.1:6379` |
| `AWS_*` | empty, region `us-east-1` |
| `PUSHER_*` | empty |

`config/app.php` timezone `UTC`. Debug defaults to false if `APP_DEBUG` is unset, which disagrees with the example file's `true`.

`config/services.php` holds Mailgun, Postmark, SES, SparkPost, and Stripe env keys. No controller uses them.

No `.env` is committed (`.gitignore`).

## Jobs, mail, storage, monitoring

- Queues: default connection `sync`. No job classes.
- Scheduler: empty. Artisan command `inspire` exists (`routes/console.php`) and is not scheduled.
- Mail: config only. Verification notification is wired to an event the API does not fire.
- Storage: `config/filesystems.php` is the Laravel default. No application upload path. `.gitignore` ignores `/public/storage` and `/storage/*.key`.
- Logging: `config/logging.php` is the Laravel default. `LOG_CHANNEL=stack` in the example.
- Monitoring: none in git. Sonar files named by the onboarding inventory are not in this revision.
- Cron inside the dump (`syscronjob`) is a MySQL queue written by triggers. This PHP app has no worker for it.

## Front end

`webpack.mix.js` compiles `resources/js/app.js` to `public/js` and `resources/sass/app.scss` to `public/css`.

`resources/js/app.js` loads `bootstrap.js`, registers Vue, and mounts `#app` with component `example-component`. The recursive `require.context` scan is commented out.

`public/js/app.js` is a committed compiled bundle (one very long line). `public/css/app.css` is the committed CSS build. They are build output, not a second application.

`resources/views/welcome.blade.php` is the Laravel welcome page. It is not a product screen for register or products.

## Security observations (from the tree, not a pentest)

- Register and login are unauthenticated and sit on the 60/minute API throttle.
- Any authenticated token can read and write all products.
- Validation failure uses 401 or 404 rather than 422.
- Duplicate email is an uncaught database error.
- CORS allows origin `*` in three places, and the Spatie profile also sets `allow_credentials` true.
- `APP_DEBUG=true` in `.env.example`. If a deployment copied that, exception pages include stack traces.
- `db/unity.sql` contains password hashes, oauth token rows, a `syspassword` table, and personal CRM data, and it is committed. Values are not repeated in Silicon Ark.
- Passport key files are gitignored. They are not a substitute for the hashes already inside the dump.
- `MustVerifyEmail` is not enforced.
- No captcha, no per-route authorisation, no row-level scope.

## Development and test process

Documented steps in-repo: none beyond the stock Laravel README (install Composer dependencies, copy `.env`, `key:generate`, the usual framework tour). `composer.json` scripts copy `.env.example` to `.env` on create-project and generate a key. They do not migrate or install Passport.

Tests:

```text
phpunit
```

`phpunit.xml` forces `APP_ENV=testing`, `BCRYPT_ROUNDS=4`, cache `array`, mail `array`, queue `sync`, session `array`. It does not set a testing database.

`tests/Feature/ExampleTest.php` requests `GET /` and asserts status 200. `tests/Unit/ExampleTest.php` asserts true. Both files import `RefreshDatabase` and neither class applies that trait. Neither test hits `/api`.

No factory run, no seed command, and no CI definition were found.

## Deployment

**Unknown / Requires Confirmation.**

Evidence that exists:

- `public/.htaccess` and `public/web.config` can front the app on Apache or IIS.
- Root `.htaccess` only adds a CORS header.
- No Docker, no pipeline, no process manager, no health check, no environment name other than the example `local` and the config default `production`.
- Dump was produced against `127.0.0.1:3306`, which is a local MySQL, not a described host.

## Source history

| Commit | Date | Author | Effect |
|---|---|---|---|
| `bed847e` | 2019-08-07 | Jaco Brits | Bitbucket starter README |
| `23fc561` | 2019-08-14 | siliconoverdrive | Laravel 5.8 API |
| `eeafec9` | 2019-08-30 | siliconoverdrive | `db/unity.sql` added |

Only branch: `master`. No tags were present.
