-
Notifications
You must be signed in to change notification settings - Fork 15
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.
- 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
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).
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).
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
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.
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.
- Click Record screen β an inline capture panel expands
- 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)
- Click Start β pick a tab / window / whole screen from the browser's native picker
- Demonstrate the issue (max 5 minutes; a live
0:00countdown appears next to the Stop button) - Click Stop (or hit the browser's own "Stop sharing" bar β both work)
- A preview
<video>appears inline so the user can re-record if they messed up - Click Use this to upload the recording; it appears as a π₯ row in the attachments list
- Submit the ticket
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.
MediaRecorder.isTypeSupported() is probed in this order, picking the first match:
video/mp4; codecs=avc1.42E01E,mp4a.40.2video/mp4; codecs=avc1video/mp4video/webm; codecs=vp9,opusvideo/webm; codecs=vp9video/webm; codecs=vp8,opusvideo/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.
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).
Recordings have to be uploaded before the ticket exists, because the user can re-record several times before submitting:
-
Upload β
POST api/self-service/upload_recording.php(multipart) writes torecordings/pending/{uuid}.{ext}and inserts aticket_recordingsrow withticket_id = NULLandrecorded_by_user_id= current session user. Returns arecording_id. -
Hold in memory β the new-ticket form keeps the array of
recording_ids alongside the file attachments. -
Claim β on ticket submit,
create_ticket.phpreads therecording_idsarray, then for each row: validatesticket_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 fromrecordings/pending/torecordings/{ticket_id}/, and setsticket_idon 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.
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.
-
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)
- 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
Initials circle in the header with a dropdown for:
- Account management
- MFA setup / disable
- Password change
- Logout
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)
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.
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:
- Welcome β what the portal is for
- Signing in β the three ways an account exists (self-register, claim an inbound-email-created account, IT pre-creates), forgotten password flow
- Raising a ticket β mailbox / subject / priority / description / attachments
- Recording your screen β full step-by-step walkthrough including the Use this / Discard submit guard and the iOS Safari gap
- Viewing & tracking your tickets β dashboard cards, recent tickets, system status, ticket detail (conversation thread + recordings + non-internal notes), how email replies thread back in
- Account & security β preferred name, password change, MFA, plus a heads-up that a CSAT survey may arrive on close
- 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.
| 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 β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
- MobileβFriendly
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)