# SCORM Player

An independent application inside the authoring repository. It has its own dependencies, environment, database, and session cookie.

## Applications

```text
scorm_player/
├── backend/                     Laravel 13 API + Blade administration
│   ├── app/Console/Commands/    Initial account setup
│   ├── app/Enums/               UserRole (superadmin, user)
│   ├── app/Http/Controllers/    API and session controllers
│   ├── app/Http/Middleware/     Admin authorization
│   ├── app/Http/Requests/       Login validation and authentication
│   ├── resources/views/         Blade pages and shared components
│   ├── public/css/              Backend styles; no Node build required
│   ├── routes/api.php           Versioned JSON endpoints
│   └── routes/web.php           Public docs, login, admin, logout
└── frontend/                    React + TypeScript + Vite
    └── src/
        ├── components/          Reusable UI components
        ├── layouts/             Shared page structure
        ├── pages/               Page components
        └── styles/              Shared responsive styles and tokens
```

## Run the installed local application

Open two terminals from `scorm_player`:

```powershell
cd backend
./artisan.cmd serve --host=127.0.0.1 --port=8001
```

```powershell
cd frontend
npm run dev
```

| Address | Purpose |
| --- | --- |
| http://127.0.0.1:5174 | React coming-soon landing page |
| http://127.0.0.1:8001/ | Public API documentation (Blade) |
| http://127.0.0.1:8001/login | Superadmin sign in (Blade) |
| http://127.0.0.1:8001/admin | Protected admin dashboard (Blade) |
| http://127.0.0.1:8001/api/v1/health | Public JSON health endpoint |

Use `127.0.0.1` consistently for session cookies. Frontend port 5174 uses `strictPort` so an occupied port produces an explicit error.

Local account credentials, when provisioned, are in `backend/.local-accounts.txt`. That file and environment credentials are ignored by Git. Application data is stored in the dedicated MySQL database `scorm_player`. Normal users cannot use local password login. Member sign-in is available through the configured DocuDeck connection. There is no public registration or normal-user password authentication endpoint.

## Fresh checkout setup

Requires PHP 8.3+ with PDO MySQL, MySQL, Composer, Node compatible with the installed Vite version, and npm. Start MySQL in Laragon before starting the backend. The Windows launcher defaults to `C:/laragon/bin/php/php8.3/php.exe`; set `SCORM_PLAYER_PHP` to use a different executable. It also loads project-local SQLite extensions from `backend/tools/php/sqlite.ini` for isolated tests without modifying the global `php.ini`. On Linux/macOS use `php artisan` instead of `./artisan.cmd`, with PDO SQLite also enabled when running tests.

Create an empty, dedicated database in Laragon's database manager or MySQL console before running migrations:

```sql
CREATE DATABASE IF NOT EXISTS scorm_player
    CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

The example environment uses `DB_CONNECTION=mysql`, host `127.0.0.1`, port `3306`, database `scorm_player`, and local Laragon user `root` with an empty password. Set `DB_USERNAME` and `DB_PASSWORD` to match your installation. For deployment, use a dedicated application database account. Keep this database separate from the parent authoring application.

Backend (PowerShell):

```powershell
cd backend
$env:Path = 'C:/laragon/bin/php/php8.3;' + $env:Path
composer install
Copy-Item .env.example .env # Fresh checkout only; preserve an existing .env
./artisan.cmd key:generate # New installation only
./artisan.cmd migrate
./artisan.cmd app:setup-users
./artisan.cmd serve --host=127.0.0.1 --port=8001
```

`app:setup-users` prompts for a superadmin email/password and a normal-user email/password. Passwords require at least 12 characters and confirmation. It creates both accounts in one transaction and refuses existing emails; it never resets existing credentials. The default database seeder creates no accounts or known passwords.

Frontend:

```powershell
cd frontend
npm ci
npm run dev
```

## Authentication and page conventions

Login uses Laravel's web session guard, password hashing, CSRF protection, session ID regeneration, and login throttling. Local password credentials must belong to a `superadmin`; DocuDeck SSO members use `/workspace` instead. Every `/admin` route is also protected with `auth` and `superadmin` middleware, so an existing normal-user session receives HTTP 403. Logout is POST-only and invalidates the session.

Backend views use standard Blade layouts/components (`x-layout`, `x-card`) and regular HTML forms. Styles are served directly from `public/css/app.css`; no backend Vite server is needed. Add future admin pages to the existing protected route group.

Frontend pages belong in `src/pages`; shared shells in `src/layouts`; reusable UI in `src/components`. `App.tsx` composes the page. The current app has one page, so no routing library or frontend authentication has been introduced. Shared styles and CSS variables live in `src/styles/global.css`.

## API

`GET /api/v1/health` is public and returns:

```json
{"status":"ok","service":"scorm-player","version":"1.0"}
```

The root documentation reflects the implemented health endpoint. SCORM uploads, playback, tracking, and password-based user API login are future work. Members can now sign in through DocuDeck SSO as described below.

## Connected DocuDeck sign-in

The authoring application confirms identity. The player keeps its own session, database, local admin login, and authorization. No passwords or application encryption keys are shared. New DocuDeck identities become regular player members; authoring roles never grant player administration. The identity key is the DocuDeck user ID, not the email address. An email collision with an existing local account requires administrator review and does not silently link accounts.

Flow: **Continue with DocuDeck → confirm the current DocuDeck account → player workspace**. If the authoring session has expired, its existing login page returns to the confirmation page after sign-in. This connects accounts only; it does not publish courses or grant access to authoring projects.

Apply only these additive migrations in the respective backends after backing up existing configuration/database data:

```powershell
# Authoring backend
php artisan migrate --path=database/migrations/2026_11_08_190000_create_scorm_player_login_codes_table.php
# Player backend
./artisan.cmd migrate --path=database/migrations/2026_11_08_190000_create_external_identities_table.php
```

Set the following in the authoring backend `.env`:

```dotenv
SCORM_PLAYER_SSO_ENABLED=true
SCORM_PLAYER_URL=http://127.0.0.1:8001
SCORM_PLAYER_CALLBACK_URL=http://127.0.0.1:8001/sso/docudeck/callback
SCORM_PLAYER_CLIENT_SECRET=<a-new-random-secret-at-least-32-characters>
```

And in the player backend `.env`:

```dotenv
DOCUDECK_SSO_ENABLED=true
DOCUDECK_ISSUER_URL=http://localhost:8000
DOCUDECK_FRONTEND_URL=http://localhost:5173
DOCUDECK_CALLBACK_URL=http://127.0.0.1:8001/sso/docudeck/callback
DOCUDECK_CLIENT_SECRET=<the-same-integration-secret>
```

`DOCUDECK_ISSUER_URL` must exactly match the authoring backend's `APP_URL` (without a trailing slash). Both callback settings must match exactly. Keep each app's existing session-cookie name and `APP_KEY`. Clear each backend's config cache after configuration changes. For the player frontend, set `VITE_PLAYER_BACKEND_URL` to the player backend URL before building; the local default is `http://127.0.0.1:8001`.

Production requires HTTPS for all four origins/URLs, a server-only integration secret, persistent server-side sessions, and HTTPS access from the player backend to the issuer. The browser never receives the integration secret. Deploy both backends and both frontend builds together when enabling this feature.

Sign-in uses a browser-session state value, S256 PKCE, exact registered callback matching, a hashed single-use code valid for 60 seconds, and a server-to-server exchange. These redirect protections follow [RFC 9700](https://www.rfc-editor.org/rfc/rfc9700.html); this is a dedicated two-application handoff, not a general-purpose OAuth/OIDC provider. Expired login-code records may be pruned from `scorm_player_login_codes`; they contain no plaintext codes. Player member sessions last up to eight hours and recheck the DocuDeck account on the next workspace request after five minutes. A suspended/deleted account, disabled integration, or failed recheck ends the member session. Player logout signs out of the player only; the DocuDeck session remains independent.

After enabling the integration, refreshed DocuDeck sessions expose an **SCORM Player** link in the workspace header. The player's landing page and backend login also provide **Continue with DocuDeck**. Support impersonation sessions cannot issue player sign-in codes. Existing local superadmin login and permissions remain unchanged.

## Checks

```powershell
# backend
./artisan.cmd test
./artisan.cmd route:list

# frontend
npm run build
npm run lint
```

Backend tests use isolated SQLite `:memory:` through PHPUnit, including the superadmin/normal-user access boundary and account setup. Do not use destructive migration commands against a configured database to run tests.

For an existing SQLite installation, back up `.env` and the SQLite database, apply migrations to an empty MySQL database, then transfer existing records while preserving IDs, password hashes, and roles before changing `.env`. A connection change alone does not transfer data. Keep the SQLite source until the MySQL installation has been verified. After changing connection settings, run `./artisan.cmd config:clear` and restart any running backend or queue processes.

For deployment, point the web server document root at `backend/public`, configure a fresh application-specific environment and database, set `APP_DEBUG=false`, use HTTPS with `SESSION_SECURE_COOKIE=true`, run reviewed migrations, and provision your own admin credentials. Build the frontend with `npm ci` and `npm run build`, then serve `frontend/dist`. Local account credentials are for development only.
