Skip to content

Self Service Portal

Ed Mozley edited this page Jul 18, 2026 · 10 revisions

Self-Service Portal

Folder: self-service/

End-user portal allowing ticket requesters to register, log in, and interact with the service desk directly. Separate authentication from the analyst login β€” analysts and end users use the same browser without conflict.

Authentication

  • Registration with email, name, and password
  • Account claiming β€” if a user already exists in the system (e.g. created automatically via email-to-ticket processing) but has no password, they can claim their account by registering with their email
  • Separate session key β€” $_SESSION['ss_user_id'] (analysts use $_SESSION['analyst_id']) so the two don't collide
  • MFA (TOTP) β€” same TOTP library as the analyst system, optional per-user

Single sign-on for requesters

The portal login used to be email + password only. It now also supports SSO using the same identity providers as the analyst login (configured in System β†’ Single Sign-On). The portal login page gains the same email-first router (type your email β†’ routed to your provider automatically) plus provider buttons and a local-account break-glass β€” turning Allow local login off (or ?local=1) behaves exactly as it does on the analyst login.

Under the hood the existing OIDC flow is made portal-aware: requesters resolve against the users table (a separate identity space from analysts β€” the same email resolves independently per portal), with a user_sso_identities link table and a users.auth_provider_id pin mirroring the analyst SSO tables one layer down. A requester matched by verified email who is unassigned is auto-claimed onto the provider on first sign-in (analysts, being privileged, still need explicit enrolment), and the provider's auto-create toggle can JIT-create brand-new requesters. Self-service logout now also ends the IdP session (single logout).

Multi-company (MSP)

A provider can be owned by a client company. On a multi-company install the portal login becomes email-first only (no provider buttons up front β€” they'd leak every client's IdP): your email domain β†’ company β†’ that company's enabled IdP(s). A company can have 0 providers (β†’ local password), 1 (β†’ straight through), or 2+ (β†’ a tenant-scoped picker, e.g. a client mid-migration between Entra and Okta). A returning requester is routed instantly by their per-user pin, so the picker is a once-only first-login step. Invisible at N=1 β€” single-company installs keep the buttons-up-front behaviour.

See the Single Sign-On page for full detail (provider config, the email-first router, the 0/1/2+ picker, and break-glass safety).

Dashboard (index.php)

Personalised overview showing:

  • Ticket summary cards: Open, In Progress, On Hold, Total
  • Recent tickets table β€” quick links into each ticket's detail page
  • Live system status panel β€” pulled from the Service Status module so users can see at a glance whether their issue might be a known outage

New Ticket (new-ticket.php)

Submit a new support ticket with:

  • Mailbox selection (which queue the ticket lands in)
  • Subject
  • Priority
  • Description (rich text)
  • Drag-and-drop file attachments
  • One-click screen recording β€” see below

Creates a ticket in the Tickets module with the portal user recorded as the requester.

Screen Recordings

End users can record their screen straight from the new-ticket form β€” no Loom, no Snagit, no plugin. The capability uses the browser's native getDisplayMedia() + MediaRecorder APIs, available in Chrome / Edge / Firefox / desktop Safari. iOS Safari has no support at all for getDisplayMedia, so the Record screen button is hidden entirely on those browsers β€” mobile users just don't see the option rather than getting a confusing error.

User flow

  1. Click Record screen β€” an inline capture panel expands
  2. Tick Include microphone audio if the user wants to narrate the issue verbally (off by default for privacy; the browser will show a separate mic permission prompt on top of the screen-share prompt)
  3. Click Start β†’ pick a tab / window / whole screen from the browser's native picker
  4. Demonstrate the issue (max 5 minutes; a live 0:00 countdown appears next to the Stop button)
  5. Click Stop (or hit the browser's own "Stop sharing" bar β€” both work)
  6. A preview <video> appears inline so the user can re-record if they messed up
  7. Click Use this to upload the recording; it appears as a πŸŽ₯ row in the attachments list
  8. Submit the ticket

Submit guard

Forgetting to click Use this after recording used to silently drop the recording on submit (it lived in JS memory as recordedBlob but never made it to the server). The submit handler now blocks if there's an unclaimed recording in the preview, expands the panel back open, scrolls it into view, and shows a red instruction line β€” no silent loss.

Format negotiation

MediaRecorder.isTypeSupported() is probed in this order, picking the first match:

  1. video/mp4; codecs=avc1.42E01E,mp4a.40.2
  2. video/mp4; codecs=avc1
  3. video/mp4
  4. video/webm; codecs=vp9,opus
  5. video/webm; codecs=vp9
  6. video/webm; codecs=vp8,opus
  7. video/webm

Modern Chrome / Edge 134+ produces real MP4 / H.264 in-browser. Firefox + older Chrome falls back to WebM / VP9. The analyst's <video> element plays either natively, so cross-browser is invisible. No server-side transcode required β€” the file lands on disk in whatever format the browser produced.

Storage

Files live on disk under recordings/{ticket_id}/{uuid}.{mp4|webm} β€” never as DB blobs. The ticket_recordings table stores metadata only (filename, content_type, file_path, file_size, duration_seconds, has_audio). 50MB cap per recording, MIME whitelist with a magic-byte sniff fallback (so a browser sending video/x-matroska doesn't crash the upload).

Pending β†’ claimed lifecycle

Recordings have to be uploaded before the ticket exists, because the user can re-record several times before submitting:

  1. Upload β€” POST api/self-service/upload_recording.php (multipart) writes to recordings/pending/{uuid}.{ext} and inserts a ticket_recordings row with ticket_id = NULL and recorded_by_user_id = current session user. Returns a recording_id.
  2. Hold in memory β€” the new-ticket form keeps the array of recording_ids alongside the file attachments.
  3. Claim β€” on ticket submit, create_ticket.php reads the recording_ids array, then for each row: validates ticket_id IS NULL AND recorded_by_user_id = current user (so a malicious user can't claim someone else's pending recording), rename()s the file from recordings/pending/ to recordings/{ticket_id}/, and sets ticket_id on the row. Rename failures are logged but the row is still claimed so analysts can see what's broken.

Orphan pending rows (user abandons the form) sit in recordings/pending/ indefinitely β€” a garbage-collect cron is a follow-up.

Streaming + auth

api/self-service/get_recording.php does dual auth (an analyst session OR the ticket owner) and supports HTTP Range requests, so the <video> element can seek without downloading the entire file first. Direct web access to recordings/ is blocked by an .htaccess Require all denied (same pattern contracts/rfp-builder/uploads/ uses); everything goes through the PHP endpoint with the auth check.

Display

  • Self-service ticket detail page β€” recordings render as full-width <video controls> cards in a Screen recordings section above the conversation, with filename / size / duration / with audio metadata
  • Analyst inbox reading pane β€” same data renders as a horizontal-scrolling strip between the email header and the action toolbar (320px-wide cards, 200px max height)

Ticket Detail (ticket.php)

  • Conversation thread (incoming + outgoing emails)
  • Non-internal notes from analysts
  • Internal analyst notes are hidden from the portal β€” only notes flagged as visible to the requester show through
  • Replying β€” requesters can answer their own ticket, with attachments. The reply is filed on the ticket and appears in the analyst's inbox; no email is sent back to the requester for their own words, so this works even on an install with no mailbox configured
  • Attachments travel both ways β€” files an analyst sent are now retrievable from the thread; images and PDFs preview in the browser, everything else downloads
  • πŸ”’ A ticket can hold correspondence that isn't the requester's β€” forwards to suppliers, copied-in colleagues and their replies all land on the same ticket. Portal privacy decides how much of that they see, and defaults to hiding it

User Avatar Menu

Initials circle in the header with a dropdown for:

  • Account management
  • MFA setup / disable
  • Password change
  • Logout

My Account

Users can:

  • Set a preferred name (e.g. "Ed" instead of "Ed Mozley") used in greetings
  • Change their password (validates current, min 8 chars)
  • Enable / disable MFA (TOTP β€” same flow as analyst MFA)

Multi-Factor Authentication

TOTP-based MFA using the same core libraries as the analyst system (includes/totp.php, includes/encryption.php). Users can enable / disable MFA from their account menu. Secrets are encrypted at rest with AES-256-GCM.

Help (help.php)

The portal has its own help guide aimed at end users (deliberately separate from the analyst-focused Tickets help page β€” same audience separation as the rest of the portal). Reachable from a Help link in the portal nav (visible from dashboard, new-ticket, ticket detail, and the help page itself). 7 sections:

  1. Welcome β€” what the portal is for
  2. Signing in β€” the three ways an account exists (self-register, claim an inbound-email-created account, IT pre-creates), forgotten password flow
  3. Raising a ticket β€” mailbox / subject / priority / description / attachments
  4. Recording your screen β€” full step-by-step walkthrough including the Use this / Discard submit guard and the iOS Safari gap
  5. Viewing & tracking your tickets β€” dashboard cards, recent tickets, system status, ticket detail (conversation thread + recordings + non-internal notes), how email replies thread back in
  6. Account & security β€” preferred name, password change, MFA, plus a heads-up that a CSAT survey may arrive on close
  7. Tips for a fast resolution β€” one issue per ticket, reproducibility info, mention what you've tried, check system status, reply by email

End-user tone throughout (no jargon, no analyst-side concepts). Same visual chrome as the rest of the portal β€” blue sticky header, numbered section bubbles, tip callouts.

Key Tables

Table Purpose
users End users (shared with the Tickets module); password_hash is NULL for accounts auto-created by inbound email β€” those users can later claim the row by registering via the portal
ticket_recordings Screen recordings attached to tickets β€” metadata only (file_path, file_size, duration_seconds, has_audio); files live on disk under recordings/{ticket_id}/

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally