Skip to content

Admin Audit Logging

Flow ID: AD-32 Module(s): audit, auth, job Complexity: Low Last Updated: 2026-04-27

Business Context

Every admin panel request and every authenticated REST write is automatically logged to the user_audit_logs table. This provides a full activity trail of which user accessed what URLs, with what HTTP method, from which IP address, and (for POST requests / JSON bodies) what parameters were submitted. The audit log is accessible to Advisable and Admin roles in the admin menu under "Administrator Activities". A nightly cleanup job purges entries older than 30 days.

Entry Points

TypePath / TriggerControllerMethod
AdminAudit log listingecommercen/audit/controllers/Adv_audit.phpindex()
AdminUser detail logs (AJAX)sameajaxGetAuditLogs($username)
AutoEvery admin requestecommercen/core/Adv_admin_controller.phpstoreUserAuditLogs()
AutoEvery authenticated REST write (POST/PUT/DELETE/PATCH)src/Rest/Middleware/AuditMiddleware.phphandle()
JobNightly cleanupecommercen/job/libraries/AdvClearUsersAudit.phpexecuteCommand()

How Logging Works

Audit logging is triggered automatically from two paths: the legacy admin controller constructor and the REST middleware pipeline.

Legacy admin (every request)

  1. Adv_admin_controller::__construct() calls $this->storeUserAuditLogs() (line 71)
  2. storeUserAuditLogs() loads audit/audit_model and calls storeUserLog()
  3. AdvAuditModel::storeUserLog() checks ignoreUrl(), then inserts a row composed of:
    • getGeneralInfo() -- timestamp (created_at)
    • getUserData() -- user_id, username, role names (comma-separated), provider (DATABASE or CONFIG), ip_address
    • getPageInfo() -- HTTP method (GET/POST/PUT/DELETE/PATCH), full url with query string
    • getStates() -- POST params as JSON (if any)

REST writes (authenticated POST/PUT/DELETE/PATCH only)

The REST pipeline runs AuditMiddleware last (after auth + relation filtering). It logs only authenticated writes — read operations and unauthenticated requests are skipped.

  1. RouterDispatcher::dispatch() pipes AuditMiddleware after RelationFilterMiddleware
  2. AuditMiddleware::handle() short-circuits when !$context->isAuthenticated or the HTTP method is not in [POST, PUT, DELETE, PATCH]
  3. User data is extracted from the JWT (RequestContext->tokenData):
    • Numeric userId → stored in user_id column (DB-backed users)
    • String userId → stored in username column (config_auth users)
    • Role IDs → resolved to comma-separated role names via auth/roles_model
    • providerREST_BACKEND (admin JWT) or REST_CUSTOMER (customer JWT)
  4. AdvAuditModel::storeRestLog($userData) inserts a row using the same getPageInfo() (URL + method) plus a JSON-aware getRestStates() that captures both $_POST and JSON request bodies via $ci->input->inputStream()
  5. Logging failures are swallowed and routed to log_message('error', ...) — audit issues never break a production write

Ignored URLs

Certain high-frequency polling endpoints are excluded from logging to avoid table bloat:

URLReason
auth/getUserTasksTask badge polling
reporting/dashboardLiveLive dashboard refresh
adminrun/pendingOrdersForAsapASAP order polling
audit/indexViewing the audit page itself

Defined in AdvAuditModel::IGNORED_URLS.

Database Schema

Table: user_audit_logs

ColumnTypeDescription
idINT UNSIGNED, PK, AUTO_INCREMENTRow identifier
user_idINT UNSIGNED, nullableAdmin user ID (null for SSO users)
usernameVARCHAR(255)Admin username
rolesVARCHAR(255)Comma-separated role names at time of request
providerVARCHAR(255)Auth provider: DATABASE, CONFIG, REST_BACKEND, or REST_CUSTOMER
methodENUM('GET','POST','PUT','DELETE','PATCH')HTTP method
urlTEXTFull URL including query string
ip_addressVARCHAR(45)Client IP (supports IPv6)
paramsJSON, nullablePOST body parameters as JSON
created_atDATETIMETimestamp of the request

Indexes: user_method (user_id, method), created_at, user_method_date (user_id, method, created_at).

Migration: database/migrations/20250509080101_audit_log.php

Admin UI

Route: /audit (mapped in application/config/routes.php)

Menu entry: Listed under the SETTINGS group with label "Administrator Activities", visible to Advisable and Admin roles.

List view (application/views/admin/audit/list.php):

  • Paginated table showing username, roles, method, URL, IP, and date
  • Filter form: text search (username or IP) and multi-select HTTP method filter (GET, POST, PUT, DELETE)
  • Each username is clickable, opening a modal with the user's full activity log

User detail modal:

  • Loads via AJAX: GET /audit/ajaxGetAuditLogs/{username}
  • Shows method, IP, date, and a toggleable textarea for POST params
  • Second row per entry shows the full URL
  • CSV export button downloads {username}_audit_logs.csv with columns: username, user_id, method, url, ip_address, created_at

Role-Based Access

The audit controller restricts role visibility: non-Advisable users cannot see the Advisable (ID 1) and Developer (ID 2) roles in the role dropdown filter. This prevents lower-privileged admins from filtering for superuser activity patterns.

Cleanup Job

Class: AdvClearUsersAudit (implements JobCommand)

Schedule: Daily at 00:30 (30 0 * * *), configured in application/config/jobs.php

Behavior: Calls AdvAuditModel::cleanUpAudit(30) which deletes all rows where created_at is older than 30 days.

Application override: application/modules/job/libraries/ClearUsersAudit.php extends AdvClearUsersAudit (empty override, allows client customization of retention period).

Key Files

FilePurpose
ecommercen/audit/controllers/Adv_audit.phpAdmin list + AJAX detail endpoint
ecommercen/audit/models/AdvAuditModel.phpLog storage, ignored URLs, cleanup, storeRestLog() for REST writes
ecommercen/core/Adv_admin_controller.phpAuto-logging in constructor (line 71, 346-350)
src/Rest/Middleware/AuditMiddleware.phpREST audit middleware — logs authenticated writes
application/controllers/RouterDispatcher.phpWires AuditMiddleware into the REST pipeline
ecommercen/auth/models/Adv_users_model.phpQuery methods: getAdminList(), getAuditLogsByUsername()
ecommercen/job/libraries/AdvClearUsersAudit.phpCleanup job (30-day retention)
application/modules/audit/controllers/Audit.phpApplication-level override (empty)
application/modules/audit/models/Audit_model.phpApplication-level override (empty)
application/modules/job/libraries/ClearUsersAudit.phpApplication-level override (empty)
application/views/admin/audit/list.phpList view with modal
application/views/admin/footer_js.phpAJAX log loading + CSV export JS (lines 3851-3932)
database/migrations/20250509080101_audit_log.phpTable creation migration

Data Flow Diagram

Legacy admin

Admin Request
    |
    v
Adv_admin_controller::__construct()
    |
    +-- storeUserAuditLogs()
    |       |
    |       v
    |   AdvAuditModel::storeUserLog()
    |       |
    |       +-- ignoreUrl()? --> skip
    |       |
    |       +-- Collect: timestamp, user data, page info, POST params
    |       |
    |       v
    |   INSERT into user_audit_logs
    |
    v
Normal controller execution continues

REST writes

REST Request
    |
    v
RouterDispatcher::dispatch()
    |
    +-- MiddlewarePipeline
    |     Auth -> Authz -> ResourceContext -> RelationFilter -> AuditMiddleware
    |
    +-- AuditMiddleware::handle()
    |       |
    |       +-- !isAuthenticated? --> skip
    |       +-- method is GET/HEAD? --> skip
    |       |
    |       +-- buildUserData() from JWT
    |       |     - user_id (numeric) or username (config_auth)
    |       |     - roles -> CSV of names via auth/roles_model
    |       |     - provider = REST_BACKEND | REST_CUSTOMER
    |       |
    |       v
    |   AdvAuditModel::storeRestLog($userData)
    |       |
    |       +-- ignoreUrl()? --> skip
    |       +-- INSERT row (general info + provided user data + page info + JSON-aware params)
    |
    v
Controller method runs

Business Rules

  • Logging is automatic and cannot be disabled per-user or per-controller
  • SSO users have user_id = null but username is populated from the session
  • POST parameters are captured as raw JSON with JSON_UNESCAPED_UNICODE
  • REST writes capture both $_POST (multipart uploads) and JSON request bodies (read via $ci->input->inputStream()); REST reads (GET/HEAD) and unauthenticated requests are NOT logged
  • The provider field reflects auth type at time of action: DATABASE (legacy admin DB user), CONFIG (legacy admin config_auth user), REST_BACKEND (admin via JWT), or REST_CUSTOMER (customer via JWT)
  • 30-day retention is hardcoded in the cleanup job; client repos can override by extending ClearUsersAudit
  • The AdvAuditModel uses the GenericWriteModel trait for basic CRUD operations (add(), deleteWhere())