# Admin API

Authentication model, current capabilities, and roadmap for admin-scoped REST endpoints

### Executive Summary

Admin API v5.9.3.3: Authentication plus admin-protected read, write, and DELETE endpoints for properties, invoices, tenants, reservations, expenses, housekeeping, and read-only reports. Login via `/API/auth/V1/login.json`, then call `admin*` controllers with the session cookie. Writes and DELETE require `super_admin`; `svc_rep` is staff read-only; `landlord` (adminRole=1, non-superuser) has read-only access scoped to owned properties.

The WiseLoop framework uses a two-tier model — public controllers on an allowlist, and `admin*` controllers requiring an authenticated admin session via `wlRestTriggerAuth`. Role checks (`super_admin`, `svc_rep`) are enforced per action via `RPM\Api\AdminAuthGuard`.

### Current State vs. Intended Design

Component | Status | Location
--- | --- | ---
Admin auth controller | Shipped | `wlRestControllerAuth` — login, logout, user
Admin credential storage | Shipped | `admins.adminAPIKey`, `admins.adminAPISecret`
Auth trigger on requests | Shipped | `wlRestTriggerAuth` in `API/index.html`
Public controller allowlist | Shipped | `API/MySqlAuthHandler.html` — marketing + guest flows only
Admin read endpoints (properties, invoices, tenants, reservations, expenses) | Shipped | `adminProperties`, `adminInvoices`, `adminTenants`, `adminReservations`, `adminExpenses`
Admin write endpoints (POST/PUT) | Shipped | Properties, invoices, tenants, reservations, expenses, housekeeping
Admin DELETE endpoints (soft-delete / cancel) | Shipped | `propertyDelete`, `invoiceDelete`, `tenantDelete`, `reservationDelete`, `expenseDelete`, `requestDelete`
Admin reports (read-only) | Shipped | `adminReports` — `incomeGet`, `occupancyGet`
Role-scoped API access | Shipped | `AdminAuthGuard` + `LandlordScope` — staff reads: `super_admin` + `svc_rep`; landlord read-only on owned data; writes/DELETE: `super_admin` only

What this means in practice: Website integrations continue using public endpoints (e.g. `/API/vrProperties/V1/vrProperties.json`). Server-to-server admin integrations call `/API/auth/V1/login.json` first, then access `admin*` endpoints with the session cookie. Landlords (`adminRole=1`, not superuser) may read their own properties, invoices, reservations, expenses, and tenants via API but cannot POST, PUT, or DELETE. Housekeepers have no admin API data access.

### Landlord read scope (adminRole=1)

Controller | Landlord access | Scope rule
--- | --- | ---
`adminProperties` | GET only | LTR: `assignedproperties.adminId`; VR: `Vrproperties.landlord`
`adminInvoices` | GET only | Invoice `propertyId` owned by landlord
`adminReservations` | GET only | VR booking on landlord-owned property
`adminExpenses` | GET only | Expense `propertyId` owned by landlord
`adminTenants` | GET only | Tenant `propertyId` on landlord LTR assignment
`adminHousekeeping`, `adminReports` | Denied | Staff roles only (`super_admin`, `svc_rep`)

### DELETE semantics (super_admin only)

Action | HTTP | Behavior | 409 when
--- | --- | --- | ---
`propertyDelete` | DELETE …/properties/{id}.json | Sets `isArchived=1` | Active future VR reservations
`invoiceDelete` | DELETE …/invoices/{id}.json | Cancel invoice (status=5), reverse journal | Settled payments exist
`tenantDelete` | DELETE …/tenants/{id}.json | Deactivate (`isActive=0`) | —
`reservationDelete` | DELETE …/reservations/{id}.json | Cancel booking (status=3); cancel unpaid invoices | Already cancelled
`expenseDelete` | DELETE …/expenses/{id}.json | Cancel expense (status=3) | Settled payments exist
`requestDelete` | DELETE …/requests/{id}.json | Archive housekeeping request (status=6) | Already archived

### Authentication Flow

When admin-protected endpoints are added, this is how authentication works:

    // 1. Login — establishes wlSession with admin data
    POST /API/auth/V1/login.json
    Content-Type: application/json

    {
      "adminAPIKey": "your-api-key",
      "adminAPISecret": "your-api-secret"
    }

    // Response (session cookie set):
    {
      "adminId": 1,
      "adminFirstName": "Jane",
      "adminLastName": "Admin",
      "adminRole": "0",
      "superuser": "1"
    }

    // 2. Verify session
    GET /API/auth/V1/user.json
    // or single field:
    GET /API/auth/V1/user/adminEmail.json

    // 3. Logout
    POST /API/auth/V1/logout.json

The session is managed by WiseLoop `wlSession` (key: `wlAuthData`). Requests to non-public `admin*` controllers require this session cookie. Per-action role checks return 403 Forbidden when the admin role is not allowed.

### Admin Credentials

Field | Table | Notes
--- | --- | ---
`adminAPIKey` | `admins` | API username. Fresh installs default to `0` until configured via myAdmin or network API.
`adminAPISecret` | `admins` | API password. Stored as plain text (not MD5 — `isPasswordMd5 = false` in `API/index.html`).
`adminRole` | `admins` | 0 = super_admin, 1 = landlord, 2 = svc_rep, 3 = housekeeper
`superuser` | `admins` | Additional superuser flag returned in auth response

Admin API credentials are separate from myAdmin portal login (`adminEmail` / `password`). Portal sessions use PHP `$_SESSION['adminId']`; REST admin auth uses `wlSession`.

### Auth Endpoints (Shipped)

##### POST /API/auth/V1/login.json

Authenticates `adminAPIKey` + `adminAPISecret` against the `admins` table via PDO prepared statement.

Errors: 401 Invalid credentials

##### POST /API/auth/V1/logout.json

Clears `wlAuthData` from session.

##### GET /API/auth/V1/user.json

Returns full admin session object. Optional path segment returns a single field.

Errors: 401 Unauthorized if not logged in

### Admin Data Endpoints (Shipped — Phase 3 + 4 writes)

#### Registered in `API/index.html`, not on the public allowlist. Requires login + role.

Controller | Actions | Roles | Service
--- | --- | --- | ---
`adminProperties` | `propertiesGet`, `propertyGet`, `propertyPost`, `propertyPut` | read: super_admin, svc_rep · write: super_admin | `rpm_property_catalog_service()`
`adminInvoices` | `invoicesGet`, `invoiceGet`, `invoicePost`, `invoicePut` | read: super_admin, svc_rep · write: super_admin | `rpm_finance_invoice_service()`
`adminTenants` | `tenantsGet`, `tenantGet`, `tenantPost`, `tenantPut` | read: super_admin, svc_rep · write: super_admin | `rpm_admin_tenant_service()`
`adminReservations` | `reservationsGet`, `reservationGet`, `reservationPost`, `reservationPut` | read: super_admin, svc_rep · write: super_admin | `rpm_booking_reservation_service()`

Write actions log to `activityLog` via `rpm_admin_activity_log_service()` on every successful POST/PUT.

Full request/response field reference: API Reference — Admin sections.

### Public Allowlist

#### Controllers in `MySqlAuthHandler::isAuthorized()` bypass admin auth entirely.

Controller | Purpose
--- | ---
`siteSettings` | Public site configuration
`siteContent` | CMS content blocks
`sitePages` | Static pages
`siteReviews` | Property reviews
`siteSliders` | Homepage sliders
`siteNavigation` | Nav menus
`siteFaq` | FAQs
`siteEvents` | Events
`siteAttractions` | Attractions + categories
`siteContact` | Contact form, newsletter
`vrProperties` | VR property catalog
`ltrProperties` | LTR property catalog
`networkBlog` | Blog posts and categories
`publicAuth` | Tenant login/register/activate
`publicBooking` | Availability, quotes, booking
`leaseApplications` | Lease applications (session required in action)
`auth` | Admin login/logout/user (always public per WiseLoop)

### Future Work

Phase 4 shipped write endpoints. Phase 5 (v5.9.3.3) completed optional admin surface:

Controller | Status | Service
--- | --- | ---
`adminExpenses` | Shipped | `rpm_finance_expense_service()`
`adminHousekeeping` | Shipped | `rpm_housekeeping_service()`
`adminReports` | Shipped (read-only) | `rpm_finance_report_service()`
Landlord-scoped reads | Shipped | `RPM\Api\LandlordScope`
DELETE endpoints | Shipped | Soft-delete / cancel per finance invariants

Each new controller should:

1. Be registered in `API/index.html`
2. Not appear in `MySqlAuthHandler` public allowlist
3. Delegate to existing PSR-4 services (thin controller pattern)
4. Check `adminRole` / `superuser` for action-level authorization
5. Use prepared statements and match `systemDocuments/myAdmin/coding-standards.md`

### Recommended Implementation Roadmap

Phase | Work | Outcome
--- | --- | ---
Phase 1 | ApiSecurityPolicy, site settings panel, auth off public allowlist, session bootstrap | Complete
Phase 2 | CSRF on financial pages, bcrypt, CMS sanitization, login lockout | Complete
Phase 3 | `admin*` read endpoints with role checks; APIDocs + OpenAPI + Postman | Complete
Phase 4 | Write endpoints (POST/PUT) with audit logging, per-key rate limits, landlord read scoping | Planned
Phase 5 | OAuth2/JWT alternative to session cookies for mobile clients | Planned

### Legacy Unregistered Controllers

These files exist in `API/` but are not registered in `API/index.html`. They contain SQL injection patterns and should not be enabled without a full security rewrite:

File | Issue | Action
--- | --- | ---
`networkBlogRestControlerV1.html` (typo) | SQL concat | Delete — superseded by `networkBlogRestControllerV1.html`
`networkVideoRestControlerV1.html` | SQLi, unregistered | Remove or rebuild with services
`networkTestimonialRestControlerV1.html` | SQLi | Remove or rebuild with services

### Security Notes

- API-001 (fixed): Public allowlist default-denies unknown controllers; `admin*` controllers require session.
- Role enforcement: `AdminAuthGuard::requireRole()` maps `adminRole`/`superuser` to `super_admin`, `svc_rep`, etc.
- Credential storage: `adminAPISecret` is plain text in DB — consider hashing before Phase 3 write endpoints.
- Separate auth domains: Tenant session (`publicAuth`), admin portal session (`myAdmin`), and REST admin session (`wlSession`) are three independent mechanisms.
- Integration scripts (Stripe, QuickBooks, BookingPal) use their own auth — not the wlRest admin auth. See Integration Scripts.

Mobile / cookieless clients: The WiseLoop session model works for server-to-server integrations. Mobile clients without cookie support may need Phase 5 token auth — use server-side proxy until then.