Executive Summary
/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 |
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:
- Be registered in
API/index.html - Not appear in
MySqlAuthHandlerpublic allowlist - Delegate to existing PSR-4 services (thin controller pattern)
- Check
adminRole/superuserfor action-level authorization - 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()mapsadminRole/superusertosuper_admin,svc_rep, etc. - Credential storage:
adminAPISecretis 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.