PropOps Web maintains several distinct logs that together form a complete, tamper-resistant audit trail. Each log serves a specific compliance or operational purpose. This page documents every log that exists, what each entry contains, and why it is kept.
Activity log entries are retained for 12 months by default. Administrators can adjust the retention period in Admin → Settings. The GDPR retention cleanup cron job automatically purges entries older than the configured window.
Log tables at a glance
| Table | Primary purpose | Encrypted fields |
|---|
user_activity_log | Complete user activity audit trail | ip_address, activity_details |
login_attempts | Login success and failure tracking | None |
security_events | Security-specific guard and auth events | None |
api_endpoint_usage_logs | API request usage and performance | ip_address |
system_security_alerts | File integrity and system health alerts | None |
user_activity_log
This is the primary audit log. Every meaningful action performed by or on behalf of a user is written here, including page views, authentication events, admin operations, and guard page outcomes.
Record structure
| Column | Type | Description |
|---|
uuid | string | Unique identifier for this log entry |
account_id | integer | The account that performed or was subject to the action |
activity_type | string | Machine-readable event category (see Activity types below) |
ip_address | encrypted string | The IP address of the request — PII encrypted at rest |
user_agent | string | The browser or client user-agent string |
activity_details | encrypted JSON | Structured context for the event — PII encrypted at rest |
created_at | datetime | UTC timestamp of the event |
Encryption
Two fields in every user_activity_log row are encrypted using the platform’s authenticated PII encryption key:
ip_address — encrypted because an IP address is personal data under UK GDPR. Decrypted transparently for authorised staff in the activity log viewer.
activity_details — a JSON blob containing event-specific context (page URLs, targeted user identities, action outcomes, etc.). Encrypted because this blob may contain names, emails, and other personal data depending on the event type.
Encrypted values are stored with an enc:v1: prefix. The data is decrypted on-the-fly when rendered in the admin panel and is never written to application or server logs in plain text.
Activity types
Activity types follow a predictable naming pattern. The table below lists every activity type that PropOps writes, grouped by category.
Authentication
| Activity type | When it fires | Why it is logged |
|---|
login | Successful sign-in | Establishes the start of an authenticated session; supports ICO breach evidence requirements |
failed_login | Password incorrect at sign-in | Supports brute-force detection, targeted-account detection, and ICO breach reporting |
logout | User signs out | Closes the audit trail for the session |
Guard page flows
Guard pages are interstitial security checks shown between login and full platform access (email verification, IP location verification, document approval, and re-authentication). Every stage of every guard flow is logged.
Email verification guard (verify)
| Activity type | Meaning |
|---|
guard_verify_viewed | User landed on the email verification screen |
guard_verify_already_verified | Guard passed immediately — email was already verified |
guard_verify_code_sent | Verification code emailed successfully |
guard_verify_code_send_failed | Code could not be sent (email delivery failure) |
guard_verify_code_send_exception | Unexpected error while attempting to send the code |
guard_verify_code_invalid_format | Submitted code was not the correct length or format |
guard_verify_code_verification_failed | Submitted code was incorrect or expired |
guard_verify_code_verified | Code matched — email verified successfully |
guard_verify_code_verify_exception | Unexpected error during code verification |
IP location verification guard (ip)
| Activity type | Meaning |
|---|
guard_ip_viewed | User landed on the new-location verification screen |
guard_ip_pin_rate_limited | PIN resend blocked due to cooldown period |
guard_ip_pin_send_skipped | PIN not sent — already queued or recently sent |
guard_ip_pin_send_failed | PIN email delivery failed |
guard_ip_pin_sent | PIN emailed to user successfully |
guard_ip_pin_send_exception | Unexpected error while sending the PIN |
guard_ip_pin_invalid_or_expired | Submitted PIN was not valid or had expired |
guard_ip_pin_attempts_exceeded | Too many failed PIN attempts — session blocked |
guard_ip_pin_failed | PIN incorrect |
guard_ip_pin_verified | PIN correct — IP location added to trusted list |
guard_ip_pin_verified_trust_failed | PIN correct but saving trusted IP failed |
guard_ip_pin_verify_exception | Unexpected error during PIN verification |
Document approval guard (documents)
| Activity type | Meaning |
|---|
guard_documents_viewed | User landed on the document approval screen |
guard_documents_approve_incomplete | Approval attempted but not all documents were checked |
guard_documents_approved | User approved all required documents |
guard_documents_approve_exception | Unexpected error during document approval |
guard_documents_approve_failed | Server returned an error response to the approval request |
guard_documents_rejected_and_terminated | User rejected the documents — account terminated |
guard_documents_reject_exception | Unexpected error during account termination after rejection |
guard_documents_reject_failed | Server returned an error response to the rejection request |
Re-authentication guard (reauth)
| Activity type | Meaning |
|---|
guard_reauth_viewed | User was redirected to the re-authentication screen |
guard_reauth_attempt_blocked | Account locked out or blocked — authentication refused |
guard_reauth_attempt_empty_password | Submitted password was blank |
guard_reauth_attempt_failed | Password was incorrect |
guard_reauth_verified | Password confirmed — re-authentication passed |
Page views
| Activity type | When it fires | Why it is logged |
|---|
page_view | Any authenticated page load | Supports data access audit trail under UK GDPR Article 30 |
User management (staff actions)
These events are written when a staff member performs an action on another user’s account. The activity_details blob includes both the staff member’s identity and the target user’s identity.
| Activity type | Meaning |
|---|
staff_user_status_change | User’s account status was changed (e.g. suspended, activated) |
staff_user_type_change | User’s account type was changed (e.g. promoted to admin) |
staff_user_create | New user account created by staff |
staff_user_delete | User account deleted by staff |
staff_session_terminate | All sessions for a user forcibly terminated by staff |
staff_email_verification_toggle | Email verification requirement toggled by staff |
staff_password_scan | Password breach scan triggered manually for a user |
staff_bulk_<operation> | A bulk action performed on multiple users simultaneously |
button_click | Staff clicked a specific action button in the user management panel |
Permissions management
| Activity type | Meaning |
|---|
permission_granted | An API permission was granted to an account type |
permission_revoked | An API permission was revoked from an account type |
Branch management
| Activity type | Meaning |
|---|
branch_created | A new branch was created |
branch_updated | Branch settings were updated |
branch_disabled | A branch was disabled |
branch_enabled | A branch was re-enabled |
Tag management
| Activity type | Meaning |
|---|
tag_created | A job tag was created |
tag_updated | A job tag was modified |
tag_deleted | A job tag was removed |
Security administration
| Activity type | Meaning |
|---|
file_integrity_check_run | A manual file integrity check was triggered |
security_alert_resolved | A security alert was marked as resolved |
security_alert_deleted | A security alert was dismissed and removed |
bulk_alerts_resolved | Multiple security alerts resolved in one operation |
bulk_alerts_deleted | Multiple security alerts deleted in one operation |
Error and system events
| Activity type | Meaning |
|---|
403_access_denied | User attempted to access a page they do not have permission for |
404_not_found | User navigated to a page that does not exist |
license_inactive | User attempted to access the platform while the licence was inactive |
login_attempts
A dedicated table for raw login attempt records. This is separate from user_activity_log to allow efficient brute-force detection queries without decryption overhead.
| Column | Description |
|---|
user_id | Account ID of the user whose credentials were submitted (NULL for unknown usernames) |
email | The email address that was attempted |
ip_address | Source IP of the attempt |
success | Boolean — whether the login succeeded |
attempted_at | UTC timestamp |
Why it is logged: The security dashboard and brute-force detection logic query this table to identify suspicious IP addresses (5+ failures in 1 hour), targeted accounts (5+ failures in 24 hours), and multi-account spray attacks (3+ accounts from one IP in 24 hours). This data directly supports UK GDPR Article 33 breach notification obligations.
security_events
A secondary security-specific event log used in parallel with user_activity_log. Currently populated by the IP guard PIN verification flow and other low-level auth events.
| Column | Description |
|---|
user_id | Account ID associated with the event |
event_type | Category string (e.g. ip_pin_verified, ip_pin_failed) |
description | Human-readable event description |
metadata | JSON context object |
created_at | UTC timestamp |
Why it exists alongside user_activity_log: The security_events table is indexed for fast security-focused queries that span many users, whereas user_activity_log is the per-user audit trail. Both are written for IP guard PIN events so that security dashboards and per-user audit queries can each use the most efficient data source.
api_endpoint_usage_logs
Every API request is logged here for usage analytics, performance monitoring, and anomaly detection.
| Column | Description |
|---|
endpoint | The API path called (e.g. /api/jobs/manage) |
method | HTTP method (GET, POST, etc.) |
account_id | The user who made the request |
ip_address | Encrypted source IP |
response_code | HTTP status code returned |
response_time_ms | Server processing time in milliseconds |
requested_at | UTC timestamp |
Why it is logged: Enables week-over-week API usage comparison, identification of unusual request patterns, and performance regression tracking. The security dashboard shows this week vs last week volume and highlights spikes. ip_address is PII encrypted for the same reason as in user_activity_log.
system_security_alerts
Populated by the file integrity monitoring cron job and related automated checks.
| Column | Description |
|---|
alert_type | Category — currently file_integrity_violation |
severity | critical, warning, or info |
message | Human-readable description of the alert |
details | JSON metadata (file path, expected vs actual checksums) |
resolved | Boolean — whether a staff member has acknowledged the alert |
created_at | UTC timestamp |
Why it is logged: Any modification to a production application file that was not part of a deployment is a potential security incident. The cron job checksums every PHP file every hour and raises an alert on any unexpected change. All file_integrity_violation alerts are treated as critical by the security dashboard regardless of their severity field.
What is encrypted in the logs
The following fields are encrypted with the platform’s authenticated PII encryption key (enc:v1: prefix):
| Table | Column | Reason |
|---|
user_activity_log | ip_address | IP addresses are personal data under UK GDPR |
user_activity_log | activity_details | May contain names, emails, and other personal identifiers in the JSON payload |
api_endpoint_usage_logs | ip_address | Same reason as above |
All other log columns (user agent strings, activity types, timestamps, response codes) are stored unencrypted as they do not constitute personal data in isolation.
Encrypted values are decrypted transparently by the admin panel API before being returned to the browser. The raw encrypted values are never exposed to end users.
Retention and data minimisation
| Log | Default retention | Configurable |
|---|
user_activity_log | 12 months | Yes — Admin → Settings |
login_attempts | Cleared after successful login (per-account) | No |
security_events | Retained indefinitely (no cron cleanup) | No |
api_endpoint_usage_logs | Retained indefinitely (no cron cleanup) | No |
system_security_alerts | Retained until manually resolved or deleted | No |
The GDPR retention cleanup cron job (cleanup_gdpr_data.php) runs on a schedule and removes user_activity_log entries older than the configured retention window. This supports the UK GDPR data minimisation principle.
If your organisation requires longer retention for regulatory or contractual reasons, increase the retention period in Admin → Settings before the default 12-month window elapses. Records that have already been purged cannot be recovered.