OMIC INVESTMENT LIMITED — omicng.com
Property platform · built by Dash World Limited · DW-2026-001
================================================================

WHAT THIS IS
  A PHP 8 / MySQL property-management platform. No build step, no Node,
  no npm. Upload the files, run the installer, and complete the live checks.

  Public site      omicng.com
  Staff admin      omicng.com/admin/
  Tenant portal    omicng.com/portal/

INSTALL — about five minutes
  1. cPanel > MySQL Databases
       - create a database          (note the full name with prefix)
       - create a database user     (note the full name with prefix)
       - add the user to the database with ALL PRIVILEGES
  2. Upload everything in this folder into public_html. This release includes
     every referenced OMIC logo, mark, favicon and apple-touch-icon.
  3. Set folder permissions to 755 on:  app/   uploads/
  4. Visit https://clean-maroon-koala.51-79-17-60.cpanel.site/install.php. Read the one-time token from
     app/INSTALL-TOKEN.txt in File Manager, unlock the installer, and follow
     the remaining steps.
     It checks the server, builds all 18 tables, creates the first
     administrator and writes app/config.php for you.
  5. DELETE install.php   <-- do not skip this
  6. Sign in at https://clean-maroon-koala.51-79-17-60.cpanel.site/admin/

FOLDER MAP
  index.php              home — featured properties + search
  properties.php         listing with filters, sorting, pagination
  property.php           single property + enquiry form
  contact.php            contact + enquiry form
  admin/                 staff area (properties, enquiries, tenant/tenancy
                         setup, verified offline payments, private documents,
                         maintenance and staff accounts)
  portal/                tenant area (tenancy, receipts, documents,
                         maintenance and payment status)
  app/                   config, schema and all application logic.
                         BLOCKED from the web by app/.htaccess
  assets/                stylesheets plus all required OMIC brand images/icons
  uploads/               property photos plus web-denied private documents

ROLES
  admin    full access including staff accounts   (the CEO, and Dash World)
  manager  properties, enquiries, tenants/tenancies, verified payments,
           private documents and maintenance
  agent    draft properties and enquiries only; cannot alter live listings

PAYMENTS
  Ships with online payment DISABLED. Staff can record a real bank transfer,
  cash or POS payment only after verifying receipt; that flow creates a real
  tenant receipt and updates the outstanding balance transactionally.

  Paystack cannot be enabled by changing a setting. PAYSTACK_ENABLED is false
  and the required initialization/webhook/reconciliation files do not exist.
  Build and test that integration separately after OMIC verification.

SECURITY NOTES FOR WHOEVER REVIEWS THIS
  - PDO with prepared statements throughout. No user input is ever
    concatenated into SQL. Filters are whitelisted against fixed arrays.
  - LIMIT/OFFSET are the only interpolated values; both are (int) cast
    and clamped, so no string can reach the query.
  - Every application state change uses POST plus csrf_check(), compared with
    hash_equals(). The installer is separately protected by a one-time secret
    token and per-step CSRF, then self-disables when app/config.php exists.
  - Uploads: real MIME sniffing with finfo, not the filename or the
    browser's content-type. Filenames are replaced with random hex.
    PHP execution is turned off inside /uploads via .htaccess.
  - Passwords: password_hash / password_verify, with rehash on login.
    Login compares a dummy hash on unknown emails so response time
    does not reveal whether an account exists.
  - Sessions regenerate on login and periodically. Password reset or account
    disable increments a server-side version and revokes existing sessions.
    Cookies are HttpOnly, SameSite=Lax, and Secure on HTTPS.
  - Rate limiting on login, enquiries and maintenance requests.
  - Errors are logged to app/error.log, never shown to visitors.
  - Significant staff and tenant actions are written to the audit_log table.

MAINTENANCE CONTACT
  techdept@omicng.com

────────────────────────────────────────────────────────────────
CRON JOBS — set these up after installing
────────────────────────────────────────────────────────────────
cPanel > Cron Jobs. Replace USER with your cPanel username
(from the file paths in File Manager, e.g. ylsqqtii).

  Every 15 minutes — delivers queued email
    */15 * * * *  /usr/local/bin/php /home/USER/public_html/app/cron/send-queue.php

  Once daily at 07:00 — builds rent, expiry and maintenance reminders
    0 7 * * *     /usr/local/bin/php /home/USER/public_html/app/cron/reminders.php

  Weekly Sunday at 03:00 — rotates logs and purges old notifications
    0 3 * * 0     /usr/local/bin/php /home/USER/public_html/app/cron/rotate-logs.php

All three refuse to run over the web — CLI only.
reminders.php only QUEUES messages; send-queue.php delivers them.
Every reminder carries a unique dedupe key, so no tenant is ever
reminded twice for the same thing.

Test them by hand first from cPanel > Terminal, or set the cron to
run once and check app/error.log.

────────────────────────────────────────────────────────────────
EMAIL
────────────────────────────────────────────────────────────────
app/config.php ships with MAIL_TRANSPORT = 'mail', which uses the
server's own mailer and works immediately.

For better deliverability switch it to 'smtp' and fill in:
  SMTP_HOST  mail.omicng.com
  SMTP_PORT  587
  SMTP_USER  info@omicng.com
  SMTP_PASS  (that mailbox's password)

Also add SPF, DKIM and DMARC records in cPanel > Email Deliverability.
Without them, mail claiming to be from @omicng.com can be spoofed,
and yours is more likely to land in spam.

────────────────────────────────────────────────────────────────
GOING LIVE WITH PAYSTACK — LATER PHASE
────────────────────────────────────────────────────────────────
1. OMIC completes Paystack business verification and legal review.
2. Build initialization, server-side verification, signed webhook handling,
   amount/tenant checks, idempotency and reconciliation.
3. Test all success, failure, replay and timeout cases in Paystack test mode.
4. Only after sign-off: add live keys, set PAYSTACK_ENABLED=true, and change
   payments_mode to live.

Until then the tenant portal shows online payment as unavailable. Verified
bank transfer, cash and POS payments can still be recorded by a manager/admin.

────────────────────────────────────────────────────────────────
WHAT IS NOT DONE YET — state this honestly to the client
────────────────────────────────────────────────────────────────
  - Paystack live integration (waiting on business verification)
  - Real property photography and the 20+ property records
  - The client's vector logo (current file is a 500px PNG)
  - Landlord login accounts and landlord-management screen. The database is
    ready for landlord records, but no landlord portal was in this phase.
  - Full end-to-end QA on the live server

════════════════════════════════════════════════════════════════
v4 — SECURITY AUDIT REMEDIATION (Claude)
════════════════════════════════════════════════════════════════
FIXED — deployment blockers
  .htaccess    Removed <Directory> (invalid in .htaccess) and every
               php_flag (breaks under PHP-FPM/CGI). Both would have
               returned HTTP 500 for the whole site on upload.
               Upload protection now uses RemoveHandler + FilesMatch,
               which work on every PHP SAPI.

FIXED — tenant isolation
  documents    Table rewritten with tenant_id, tenancy_id, foreign keys
               and visibility public|tenants|private.
  portal       Document query is scoped to the signed-in tenant.
  NEW          portal/document.php serves private files after an
               ownership check. Returns 404 (not 403) for another
               tenant's document, so ids cannot be enumerated.
  NEW          uploads/private/ is denied at the web-server level.
               Private documents belong there, never in a public path.

FIXED — authorisation
  Permission matrix in app/lib/auth.php, enforced by
  require_permission() inside every admin controller BEFORE any query.
  Navigation is driven by the same matrix, but hiding a link is
  presentation only — the controller is the gate.
    admin    everything
    manager  properties (incl. publish), enquiries, tenants,
             payments, maintenance
    agent    properties (draft/edit only) and enquiries
  Agents can no longer open tenants, payments or maintenance, nor
  publish or feature a property.

FIXED — financial integrity
  Demo payments NO LONGER touch balance_outstanding. A tenant could
  previously zero their real balance without paying. Test totals are
  now shown as a separate, clearly-labelled figure. Demo amounts are
  capped at the rent plus service charge for that tenancy.

FIXED — CSRF and HTTP semantics
  Photo deletion and cover-photo changes were GET. Both are now POST
  with a CSRF token, a permission check, and an ownership check that
  the image belongs to that property. Deleting a cover promotes the
  next photo instead of leaving the listing blank.

FIXED — mail
  SMTP AUTH commands are never logged. Previously a failed login wrote
  the base64 username and password into app/error.log; base64 is
  reversible, so that was a credential leak. Only a stage label and a
  numeric response code are logged now.
  Reply-To validated as an email address. The 'log' transport refuses
  to run when DEBUG_MODE is false, and never writes message bodies.

FIXED — queue correctness
  send-queue.php claims rows atomically with a token and a 'processing'
  status before sending, and recovers claims stranded for 15 minutes by
  a crashed run. Two overlapping crons can no longer send the same
  email. Receipts, maintenance updates and new-request alerts all carry
  dedupe keys.

FIXED — sessions and login
  Strict, cookie-only sessions. One-hour idle limit, twelve-hour
  absolute limit and periodic ID rotation. Tenant passwords rehash on login
  (staff already did). Password resets and account toggles revoke existing
  sessions. Both logout actions are CSRF-protected POST and expire the cookie.
  X-Forwarded-Proto is only trusted when TRUST_PROXY is enabled in config.

FIXED — installer
  A one-time token is written to app/INSTALL-TOKEN.txt and must be
  pasted in before the installer will run, so nobody can claim the site
  in the window between upload and setup. CSRF on every step. Raw PDO
  errors are logged, not displayed. Database credentials are wiped from
  the session and the token file deleted once config.php is written.
  An installed site no longer recreates INSTALL-TOKEN.txt on later visits.

FIXED — uploads
  Images are decoded with getimagesize(), rejected above 50 megapixels
  (decompression bombs), downscaled past 2400px, and RE-ENCODED to
  JPEG so only pixel data reaches disk. Maximum 15 photos per request.

FIXED — logs and caching
  app/cron/rotate-logs.php rotates error.log weekly, keeps four
  archives and purges notifications older than twelve months.
  Add:  0 3 * * 0  /usr/local/bin/php /home/USER/public_html/app/cron/rotate-logs.php
  admin/ and portal/ pages send Cache-Control: no-store, private.

STILL OPEN — deliberately
  - Live Paystack: portal/paystack-init.php does not exist and live
    mode is blocked while PAYSTACK_SECRET is empty. Initialisation,
    webhook signature verification, idempotency and amount checks must
    be built and tested before payments_mode is switched to 'live'.
  - Content-Security-Policy: not added yet. Introduce in report-only
    mode first, since the pages carry inline styles.
  - HSTS: add only after HTTPS is confirmed working on the domain.
  - Database user: grant ALL PRIVILEGES for installation, then reduce
    the runtime account to SELECT/INSERT/UPDATE/DELETE.
  - No live server test has been run. Verify on the real cPanel
    account: schema import, .htaccess acceptance, upload execution
    blocking, role boundaries, tenant document isolation, and
    concurrent cron runs.

════════════════════════════════════════════════════════════════
v5 — INDEPENDENT DEPLOYMENT AUDIT AND OPERATIONAL COMPLETION
════════════════════════════════════════════════════════════════
FIXED — root deployment blocker
  Removed an unmatched </If> left in the v4 root .htaccess. That malformed
  directive could itself have caused an HTTP 500. Authenticated no-cache
  headers now come from PHP, avoiding request-expression compatibility issues.

FIXED — complete operational admin flows
  Staff can now create/update tenant portal accounts, attach and update a
  tenancy, reset a tenant password, record verified offline payments, and
  securely upload/archive/download tenant documents.

FIXED — authorization gaps
  Agent dashboards no longer query or expose tenant/maintenance data. Agents
  cannot edit an already-published property. New tenant, payment and document
  controllers enforce manager/admin permissions before querying or changing
  data.

FIXED — payment safety
  Online payment defaults to disabled and remains hard-blocked unless a later
  complete Paystack integration explicitly enables it. Verified offline
  payments use a transaction, create a live receipt and update balances.

FIXED — session, audit and queue correctness
  Staff/tenant identities cannot coexist in one session, timeout creates a new
  session id, tenant actions identify the tenant in audit logs, and the queue
  now honors all four configured delivery attempts.

MANDATORY LIVE-SERVER ACCEPTANCE TEST
  Before public launch verify: installer + schema, admin/manager/agent gates,
  tenant A cannot access tenant B data, private document direct URLs return
  403/404, uploads cannot execute PHP, verified payment rollback/balance logic,
  mail delivery, cron overlap behavior, HTTPS redirect, and backup/restore.
