# Business rules — unityNodejsApi

Evidence date: 2026-09-30. Revision: `master` `8d9588c`.

No README or policy document states these rules. Unless a rule is marked **Documented**, it is **Inferred** from the JavaScript at HEAD or from a commit subject. Inferred rules describe what the code does. They are not a confirmed business policy.

The database password, the JWT secret, and the username and password in `test.js` are not copied here.

## Who may call the function

**BR-001 — The handler does not check a caller identity.**
`index.js` reads `event.module`, `event.action`, and the fields for that action. It does not read a header, signature, or role. **Inferred.**

**BR-002 — An API Gateway authorizer used to be the only allow/deny policy in this repo.**
At `e367364`, `login.js` returned IAM `Allow` on `execute-api:Invoke` for `process.env.RESOURCE` after RS256 verification, or the string `"Unauthorized"`. That file was replaced in `d4602f7`. **Removed.** Whether API Gateway still has an authorizer configured outside git is **Unknown / Requires Confirmation.**

**BR-003 — `strRole` is returned as data and is not a permission.**
The query aliases `strRole` as `role`. No branch compares it. **Inferred.**

## Login

**BR-004 — The login name is `strUsername`, and the handler calls that field `email`.**
`index.js` passes `event.email`. The SQL compares it to `strUsername` and aliases that column as `email`. **Inferred** from the names. No document says the username must be an email address.

**BR-005 — The stored password is an MD5 hex digest.**
Commit `21214d8` is titled "Update the hash md5". The callback runs `crypto.createHash('md5').update(...).digest("hex")` and compares the result to `strPassword`. **Documented** by that commit subject. **Implemented** in the callback. The handler path throws on `passwordin` before this runs. See BR-006.

**BR-006 — The Lambda login path and `test.js` do not hash the same variable.**
`loginUser` receives `password` and then shadows it with `var password = ""`. The callback hashes `passwordin`, which `login.js` does not define. `test.js` does define `passwordin`. **Inferred** from the two files at `8d9588c`. **Requires Confirmation** whether this split was accidental.

**BR-007 — A missing user and a wrong password are different messages, only inside the callback.**
If `results[0]` is missing, `email` is `Check your username/email`. If a row exists and the digest does not match `passwordq`, `password` is `Check your password`. **Inferred** from the inner error object.

The value `loginUser` actually returns to `index.js` always carries both messages, because that object is built before the query. **Implemented.**

**BR-008 — Username and digest are escaped in the SQL string at HEAD.**
`mysql.escape` wraps both values. **Implemented** since `8d9588c`. At `21214d8` they were concatenated inside double quotes. **Requires Confirmation** that no other client queries this table with the old statement.

**BR-009 — A matching row would produce a token whose only claim is the user's `strUuid`.**
`jwt.sign({ id: user.uuid }, secret, { expiresIn: "2 days" })`. **Implemented** in the callback. The secret literal is omitted. No audience, issuer, or role claim is set.

**BR-010 — Token lifetime is two days when the string form is used.**
The config value is `"2 days"`. The comment in `login.js` says a number would be seconds and a string must include units. **Documented** by that comment. **Implemented** as the `expiresIn` option. `jsonwebtoken` 8.5.1 is the library in the lockfile. This repository does not contain a test that a token expires.

**BR-011 — The success object deletes `password`, not `passwordq`.**
**Inferred** from `delete user['password']` next to alias `passwordq`. The digest can remain on `passwordq` in the object logged and returned inside the callback. **Requires Confirmation** of intent.

## Verify and refresh

**BR-012 — `verify` accepts a token argument from the handler and does not read it.**
**Implemented.** There is no success or failure outcome.

**BR-013 — Any throw inside `getAccessToken` is reported as an invalid access token.**
The `catch` always returns `[401, { error: "Invalid access token detected" }]`. A missing `jwtConfig` takes that path, as would `jwt.verify` rejecting a bad token. **Inferred.** The caller of the Lambda still receives HTTP 200 from `index.js`, with that tuple in the body. **Implemented.**

**BR-014 — Refresh does not load the user from MySQL.**
The only user lookup in `getAccessToken` is a commented call to `authDB.users`. **Implemented** as a comment. `authDB` is not defined in this repository.

## Data

**BR-015 — One row is the login result.**
The callback uses `results[0]` and does not loop. Two rows with the same username and digest would ignore the second. **Inferred.** No unique key is declared in git.

**BR-016 — `txtPreference` is selected and not parsed.**
A commented line would strip the first and last character of `user.data`. It does not run. **Inferred** that someone expected a wrapped string. What the column contains is **Unknown / Requires Confirmation.**

**BR-017 — Column names changed during the five commits and are not enforced here.**
`21214d8` selected `userID`. `8d9588c` selects `strUuid`. **Implemented** as SQL text only. The live table is **Unknown / Requires Confirmation.**

## Delivery

**BR-018 — A default pipeline run replaces the code of Lambda `unityAuth`.**
**Implemented** in `bitbucket-pipelines.yml` (`COMMAND: 'update'`). It does not run `test.js`. It does not rotate the database password or the JWT secret.

**BR-019 — The zip is the git tree, including `node_modules`, without a fresh install.**
**Inferred** from `zip -r codeNodejs.zip *` and the absence of `npm install`. A dependency change that is not committed under `node_modules` would not be in the artifact.

## Removed rule

**BR-020 — A Bearer token verified against a JWKS endpoint was allowed to invoke `process.env.RESOURCE`.**
**Removed** with `login.js` at `d4602f7`. Environment variable names `JWKS_URI` and `RESOURCE` are not read by the current code.
