# PRD — unity-framework

Evidence date: 2026-09-30. Source: `https://bitbucket.org/overdrive/unity-framework.git`, `master` `c72613b` (2021-02-13).

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/unity-framework/` before this file. Requirements below are recovered from routes, Amplify calls, and the mock API. They are not a new product decision.

Identifier values in `src/config.js` are omitted. The prefilled username and password in `src/app/main/login/tabs/JWTLoginTab.js` are omitted; the file that holds them is named.

## Product purpose

A person opens a web app titled **Unity - Custom , Secure framework**. The login card says **Welcome to Unity Framework**. The shell brands itself **UNITY**. They can open login and register without a session. Most other routes do not declare an auth role, so the route guard allows them as well. A completed Cognito sign-in loads one row from the `unity` API and always stores the role `superuser`, then the auth slice points the next redirect at `/apps/dashboards/analytics`.

**Implemented** in `public/index.html`, `src/app/main/login/Login.js`, `src/app/fuse-layouts/shared-components/Logo.js`, `src/app/fuse-configs/routesConfig.js`, `src/app/services/jwtService/jwtService.js`, and `src/app/auth/store/userSlice.js`.

The README does not state a product purpose. It is the Create React App boilerplate. Provenance of the README: **Documented**. Provenance of the product behaviour: **Implemented**.

Four names are in the tree and they are not the same string: page title `Unity - Custom , Secure framework`, login heading `Unity Framework`, logo `UNITY`, package name `unity-react-app`. Which name is the product name is **Unknown / Requires Confirmation**.

## Users and roles

| Role | How it is assigned | What the code distinguishes | Provenance |
|---|---|---|---|
| Guest | Initial Redux user `role: []` (`src/app/auth/store/userSlice.js`) | May open routes whose `auth` array is empty: `/login`, `/register`. Display name defaults to `John Doe` | **Implemented** |
| `superuser` | Hard-coded in `buildUser` inside `jwtService.js` after Cognito sign-in and a uuid lookup | Not listed in `src/app/auth/authRoles.js`. Default route auth is `null`, so this string is not checked on most routes | **Implemented** |
| `admin`, `staff`, `user` | `authRoles.js` defines these arrays. Mock register sets `role: 'admin'` (`src/@fake-db/db/auth-db.js`). Auth0 and Firebase helpers would set `role: ['admin']` if those paths ran | Navigation examples under Authentication Pages use these roles. Cognito sign-in does not assign them | **Implemented** in the mock and the unused helpers |

`FuseUtils.hasPermission` (`src/@fuse/utils/FuseUtils.js`):

- `auth` null or undefined → allow.
- `auth` empty array → allow only when `user.role` is missing or empty (guest).
- Otherwise the user's role string or role array must intersect `auth`.

`routesConfig.js` passes `null` as the default auth for every generated route. Login and register set `auth: authRoles.onlyGuest` (`[]`). Logout sets `auth: authRoles.user` (`['admin','staff','user']`). A session whose role is the string `superuser` does not intersect that logout list.

Signed-in state for the route gate is the Redux `auth.user.role`, not a live Cognito check on each navigation.

What the `unity` API returns beyond the fields the client reads is **Unknown / Requires Confirmation**.

## Major workflows

### 1. Open the app

Trigger: browser loads the CRA bundle (`src/index.js`). `serviceWorker.unregister()` is called. The service worker is not registered.

Behaviour:

- Amplify is configured for Auth (Cognito, `mandatorySignIn: true`), Storage (S3 bucket `unity-app-files`), and an API endpoint named `unity`. Region in config is `eu-west-1`. Ids live in `src/config.js`.
- `App` imports `@fake-db`, which installs an axios mock adapter and passes unmatched requests through (`src/@fake-db/index.js`).
- Redux store is created in `src/app/store` with no redux-persist. Auth therefore survives only in `localStorage.jwt_access_token`, not in a persisted reducer.
- `Auth` runs `jwtService.init()` and shows `FuseSplashScreen` while `waitAuthCheck` is true.
- `FuseAuthorization` wraps the router. A guest hitting a route whose `auth` is a non-empty array is sent to `/login` with `state.redirectUrl`. A signed-in person hitting a guest-only route is sent to `redirectUrl` or `/`.
- `/` redirects to `/login`. Unmatched paths redirect to `/pages/errors/error-404`.

Output: splash, then the matched route. For a guest opening `/`, that is the login page.

### 2. Sign in

Trigger: Login button on `/login`. Tabs for JWT / Firebase / Auth0 are commented out. `selectedTab` stays `0`, so only `JWTLoginTab` renders. It calls `submitLogin` → `jwtService.signInWithEmailAndPassword`.

Inputs: `email`, `password`. The field label is "Username/Email".

Validation (**Implemented**, `JWTLoginTab.js`):

- Both fields required.
- Each must be at least 4 characters ("Min character length is 4").
- No email-format check.
- The form's `value` props contain a username and a password literal. Those literals are omitted from this document. A fresh visit shows them already filled in.

Behaviour when validation passes:

- `Auth.signIn(email, password)` (Amplify / Cognito).
- On a session, `response.signInUserSession.accessToken.jwtToken` is stored as `localStorage.jwt_access_token` and set as `Authorization: Bearer` on axios. The Cognito username (`response.username`) is sent to `API.get("unity", "/unity/uuid/{username}")`.
- `buildUser` reads `JSON.parse(body)[0]` and takes `strUser`, `strEmail`, `strCognitoUuid`, `strSettings`. Photo is the static asset `assets/images/avatars/Abbott.jpg`. Role is the string `superuser`. Shortcuts are the fixed ids `calendar`, `mail`, `contacts`.
- The function resolves only when `userUuid` is truthy. It does not check that the API row exists before `JSON.parse(body)[0]`. A missing row throws inside the promise. The `else` branch that rejects `"Api, error"` is unreachable when `userUuid` was used to build the URL.
- `LOGIN_SUCCESS` is dispatched and `setUserData` sets `history.location.state.redirectUrl` to `/apps/dashboards/analytics`. Where the app navigates is then determined by `FuseAuthorization` (a signed-in role on the guest-only `/login` route is sent to `redirectUrl` or `/`).

Error conditions:

- Cognito rejection is dispatched as `loginError` with the error object. The login tab only copies `login.error.email` and `login.error.password` onto the form. A Cognito error object does not have those fields, so the message may not appear on the inputs.
- Axios 401 responses emit `onAutoLogout` with "Invalid access_token" and clear the stored token (`jwtService.setInterceptors`).

### 3. Restore a session

Trigger: `jwtService.init()` on startup when `localStorage.jwt_access_token` is present and `jwt-decode` says `exp` is still in the future.

Behaviour:

- `signInWithToken` calls axios `GET /api/auth/access-token` with that token.
- The handler in `src/@fake-db/db/auth-db.js` verifies the token with the mock JWT secret and looks up `authDB.users` by uuid. A Cognito access token is not signed with that secret, so verification throws and the mock returns 401 "Invalid access token detected".
- `Auth.js` shows `error.message` and resolves. `signInWithToken` also calls `logout()`, which clears the stored token.

Output: a Cognito sign-in does not survive a full page reload through this path. **Implemented**. Whether a deployed environment replaces the mock is **Unknown / Requires Confirmation**.

### 4. Register

Trigger: "Register" link on the login card → `/register` → `JWTRegisterTab` → `submitRegister` → `jwtService.createUser`.

Behaviour: `axios.post('/api/auth/register', data)`. The mock (`auth-db.js`) rejects a duplicate email with "The email is already in use", rejects an empty display name with "Enter display name", and otherwise inserts a user with `role: 'admin'` and `from: 'custom-db'` into the in-memory `authDB`. It does not call Cognito `Auth.signUp`.

Password rules on the register form beyond what Formsy marks required were not fully traced field by field. The mock sets `password: null` in its error object, so it does not enforce a password policy. **Implemented** mock. A Cognito registration requirement is **not implemented**.

### 5. Recover account

The login card links "Recover Account" to `/`. `/` redirects to `/login`. There is no forgot-password handler on that link. Separate Fuse pages exist at `/pages/auth/forgot-password` and `/pages/auth/reset-password` and are still in the navigation. Whether those pages call Cognito is the stock Fuse implementation; they are not wired from the Unity login card. **Implemented** link target. A recovery requirement is **Unknown / Requires Confirmation**.

### 6. Log out

Trigger: route `/logout`, which requires role `admin`, `staff`, or `user`.

Behaviour (`logoutUser` in `userSlice.js`): if the role is empty, return. Otherwise `history.push('/')`, then `jwtService.logout()` for the default provider. That removes `jwt_access_token` and the axios Authorization header. It does not call `Auth.signOut()`. Redux user returns to the guest initial state.

A session with role `superuser` does not pass the logout route's auth array, so `FuseAuthorization` will not render that route for them. **Implemented**.

### 7. Open User Management

Trigger: navigation item "User Management" → `/apps/users`, which redirects to `/apps/users/all`. Route `/apps/users/:id` loads `UsersApp`.

Behaviour (`UsersApp.js`):

- Dispatch `getUsers(routeParams)` and `getUserData()`.
- `getUsers` ignores the route id and always calls `unityRequests('/unity/getusers/1')`.
- `unityRequests` builds an Authorization header from `Auth.currentSession().getIdToken().getJwtToken()` and a `myInit` object, then calls `API.post(apiName, path)` without passing `myInit`. The header it built is not sent by that call. It expects the Amplify result to have a `body` string, `JSON.parse`s it, and returns the value.
- `getUsers.fulfilled` runs `usersAdapter.setAll` on that value and clears search text.
- `getUserData` calls `API.get("unity", "/unity/uuid/{loggedInUser.username}")` and stores `JSON.parse(body)[0]` on `usersApp.user`.

The numeric suffix `1` is a literal. A commented line mentions `/unity/getusers/2`. What `1` and `2` mean is **Unknown / Requires Confirmation**.

List columns (**Implemented**, `UsersList.js`): avatar (`strPicturePath`), First Name (`strUser`), Job Title (`strJobTitle`), Email (`strEmail`), User Created (`strLastUser`). Search filters the in-memory list with `FuseUtils.filterArrayByString`. Empty result copy is "There are no Users!".

Row actions: an edit icon links to `/apps/users/profile/{UserID}` and passes `location.data = UserID`. A delete icon dispatches `removeUser(row.original.id)` — the field is `id`, not `UserID`. Remove posts to the mock `/api/users-app/remove-user`.

Sidebar shows `usersApp.user.strUser` and a "New User" button. The All / Frequent / Starred links are commented out.

### 8. Create or edit a user in the dialog

Trigger: "New User" opens `userDialog`. Edit-dialog open from the list row click is commented out.

Validation (**Implemented**, `UserDialog.js`): `canBeSubmitted` requires `form.name.length > 0`. Other fields (last name, nickname, phone, email, company, job title, birthday, address, notes) have no extra checks. New records get `id: FuseUtils.generateGUID()` and avatar `assets/images/avatars/profile.jpg`.

Submit: `addUser` or `updateUser` → axios POST `/api/users-app/add-user` or `/api/users-app/update-user` → mock `users-db.js` → then `getUsers()` which reloads from the `unity` API. The mock write and the API list are different stores. A user added in the dialog is not sent to `/unity/...`. **Implemented**.

Remove from the dialog posts `/api/users-app/remove-user` the same way.

### 9. Open a user profile

Trigger: edit icon. `UserProfilePage` reads `props.location.data`, not the `:id` route param. If `location.data` is undefined (refresh, or a pasted URL), it calls `history.push('/apps/users/all')` during render.

Otherwise it dispatches `getUserById`, which `API.get`s `/unity/getuser/{userId}` and stores `JSON.parse(body)[0]`.

Header shows `editUser.strUsr` (note the spelling `strUsr`, not `strUser`) and `editUser.avatar`. Buttons "Follow" and "Send Message" have no click handlers.

Tabs:

- **Details** — form fields Name, Last name, Nickname, Phone, Email, Company, Job title, Birthday, Address, Notes. The Name input's `name` is `strUser` while `value` is `form.name`, and the effect that would `setForm(editUser)` is commented out, so the form stays on `defaultFormState`. Save and Deactivate both call `handleSubmit`, which dispatches `updateUser(form)` to the mock. Deactivate has no separate action.
- **Access Control** — walks `appConfig.js` and renders `tabs/SecurityListItem.js`. Checkboxes View, Add, Edit, Delete initialise to `true`. `parentPermission` is an empty function. Checkbox changes stay in component state. Commented code references `user.strPermissions[item.id + 'View'|'Add'|'Edit'|'Delete']`.
- **Custom** — renders Fuse settings and theme schemes (`FuseSettings`, `FuseThemeSchemes`). `handleSubmit` exists and is not attached to those controls.

A second checkbox component, `src/app/main/apps/users/SecurityListItem.js`, reads `user.permissions[item.id + action]` and is used by `SecurityDialog.js`. The list no longer opens that dialog (the click is commented out).

### 10. Use a demo application

Trigger: any other item in `navigationConfig.js` (analytics, project, calendar, e-commerce, academy, mail, to-do, file manager, contacts, chat, scrumboard, notes, and the Pages group).

Behaviour: those routes are still generated. Their data comes from `@fake-db` handlers. They are the Fuse template, not a separate product decision recorded in this repository. Provenance: **Implemented** as shipped template code.

## Integrations

| System | Direction | Trigger | Data the client sends or reads | Auth | Failure the client shows |
|---|---|---|---|---|---|
| Amazon Cognito | Outbound | Login submit | email, password via Amplify `Auth.signIn` | User pool in `src/config.js` | `loginError`; form only paints `error.email` / `error.password` |
| API named `unity` | Outbound | After sign-in; user list; profile | GET `/unity/uuid/{username}`, POST `/unity/getusers/1`, GET `/unity/getuser/{id}` | List helper builds a Bearer id token and does not pass it. Sign-in and profile `API.get` pass no explicit header | Unhandled rejection if `body` is missing. No dedicated error screen |
| Amazon S3 `unity-app-files` | Configured | None in `src` | `MAX_ATTACHMENT_SIZE` 5000000 is unused | Identity pool id in config | No client path |
| Axios mock `@fake-db` | In browser | Register, token refresh, user mutations, demo apps | JSON bodies described above | Mock JWT secret in `auth-db.js` (value omitted) | 401 on token refresh logs the user out |

The API's implementation, database, and authorizer are not in this repository. **Unknown / Requires Confirmation**.

## Constraints

- **Implemented:** login fields minimum length 4; new-user dialog requires a non-empty `name`; guest-only routes reject a non-empty role; logout route requires `admin`, `staff`, or `user`.
- **Implemented:** Cognito `mandatorySignIn: true` is set in Amplify config. The route table does not enforce a session on demo routes.
- **Implemented:** production build sets `GENERATE_SOURCEMAP=false` (`package.json` `build` script).
- ThemeForest regular license URL is the `LICENSE` file. What that allows for this fork is **Unknown / Requires Confirmation**.
- Credentials and cloud identifiers are committed in source. Values are omitted here. Human review should decide whether they must be rotated. This document does not treat them as a product requirement.

## Requirements recovered from the implementation

### FR-001 — Branded shell

The app shows the Unity Framework login heading and the UNITY logo, and uses Fuse layout 1 by default (`settingsConfig.js`: `layout1`, themes `default` / `mainThemeDark` / `mainThemeLight`, `customScrollbars: false`, direction `ltr`).

Evidence: `Login.js`, `Logo.js`, `settingsConfig.js`. Implementation: **Implemented**. Confidence: High.

### FR-002 — Cognito sign-in

The login form signs in with Amplify Auth and then loads `/unity/uuid/{cognitoUsername}`.

Evidence: `JWTLoginTab.js`, `loginSlice.js`, `jwtService.js`, `src/index.js`. Implementation: **Implemented**. Confidence: High.

### FR-003 — Session role

A successful uuid lookup stores `role: "superuser"` regardless of any role field in the API payload. `buildUser` does not read a role property.

Evidence: `jwtService.js` `buildUser`. Implementation: **Implemented**. Confidence: High. Whether the API has a role that should be used instead is **Unknown / Requires Confirmation**.

### FR-004 — User list from the unity API

Opening `/apps/users` loads users with POST `/unity/getusers/1` and shows the columns named in workflow 7.

Evidence: `UsersApp.js`, `usersSlice.js`, `unityRequests.js`, `UsersList.js`. Implementation: **Implemented**, with the header and id-field gaps in that workflow. Confidence: High for the call site; the response schema is **Unknown**.

### FR-005 — Profile from the unity API

The edit icon loads GET `/unity/getuser/{UserID}` when `location.data` is set.

Evidence: `UsersList.js`, `UserProfilePage.js`, `userSlice.js`. Implementation: **Implemented**. Confidence: High.

### FR-006 — User mutations stay on the mock

Add, update, remove, star, and update-security post to `/api/users-app/*`, which `users-db.js` handles in memory.

Evidence: `usersSlice.js`, `src/@fake-db/db/users-db.js`. Implementation: **Implemented**. Confidence: High. Whether this is unfinished work or intentional is **Unknown / Requires Confirmation**.

### FR-007 — Access-control checkboxes

The profile Access Control tab shows View, Add, Edit, and Delete for each child of `appConfig.js`. Changes are not saved.

Evidence: `AccessControlTab.js`, `tabs/SecurityListItem.js`. Implementation: **Partial**. Confidence: High.

### FR-008 — Demo modules remain available

Dashboards, calendar, mail, e-commerce, and the other Fuse apps in `navigationConfig.js` and `appsConfigs.js` are routable.

Evidence: `navigationConfig.js`, `appsConfigs.js`, `routesConfig.js`. Implementation: **Implemented** as template. Confidence: High. Whether they are in product scope is **Unknown / Requires Confirmation**.

### FR-009 — Register against the mock

`/register` creates a mock user and does not call Cognito.

Evidence: `registerSlice.js`, `jwtService.createUser`, `auth-db.js`. Implementation: **Implemented**. Confidence: High.

### FR-010 — Page reload clears a Cognito session

Token refresh verifies a mock JWT, not the Cognito token.

Evidence: `jwtService.signInWithToken`, `auth-db.js`. Implementation: **Implemented**. Confidence: High.

## Planned behaviour

No roadmap file, TODO issue list, or milestone document is in the repository. Commented blocks (starred users, security dialog open, `setForm(editUser)`, permission reads from `strPermissions`) show work that was started and left inactive. Treating those comments as an approved plan would be an inference. Status: **Unknown / Requires Confirmation**.
