Solavel Solavel Docs

Customer Portal — Overview

docs/customer-portal/overview.md

Audience: end users (the company using Solavel), customer support Difficulty: new user

What this covers

The two ways a customer can interact with documents you send from Solabooks: a one-tap secure link and a fully branded customer portal account. Both live under the Solabooks app and are distinct from the parent Solavel client portal (which is for tenant owners managing their own workspace).

If you are looking for the link-based flow only, jump to secure-document-links.


The two flows

Secure link Customer portal account
URL pattern /i/{token} (canonical) or /portal/{token} (legacy) /customer-portal/login
Identity None — the token is the credential Email + password
Lifetime 24 hours from creation Persistent until disabled
What customer sees One document (invoice / quote / etc.) Dashboard with all documents
Login required No Yes
Backed by customer_portal_links table customer_portal_accounts table
Auth guard None — token middleware Separate customer-portal guard

The two flows can co-exist for the same customer. A secure link can deep-link a one-off document to a customer who has never created an account; if the customer wants more, they can sign up using the Setup account prompt offered alongside the document.


Portal account architecture

The customer portal is built on a separate user model (App\Models\CustomerPortalAccount) and a separate auth guard (customer-portal). It does not share users with the staff side. A customer who is also an internal user must log in twice with different credentials.

The flow is bound to a tenant by URL: each request resolves the active tenant DB through customer-portal.tenant (ResolveCustomerPortalTenant middleware) before any auth happens. Sign-in happens at /customer-portal/login with a throttle bucket of customer-portal-login to deter credential stuffing.


Sign-up

Customer portal accounts are created in two ways:

  1. Triggered by the company. When you mark a customer as having portal access in Customers > [Customer] > Portal Access, the system issues a setup token valid for 60 minutes and emails the customer a one-time setup link. The link lands at /customer-portal/setup?token=<...>. The customer chooses a password and is signed in.
  2. Self-service from a secure link. When a customer opens a /i/{token} document, the page offers a "Create an account to see all your documents" prompt. Clicking it issues a setup token for that customer and emails it.

Setup tokens are SHA-256 hashed at rest, single-use, and consumed in a DB transaction with lockForUpdate() to prevent race conditions (CustomerPortalTokenService::consumeSetupToken).


Sign-in

/customer-portal/login accepts email and password.

  • Throttle: customer-portal-login. Repeated failures lock the bucket for the standard window.
  • On success, a session cookie is set scoped to the tenant subdomain/path.
  • "Forgot password" leads to /customer-portal/forgot-password, which issues a 60-minute reset token (also hashed).

Dashboard & document list

After login the customer lands on /customer-portal/dashboard with a side nav for:

Section URL Shows
Dashboard /customer-portal/dashboard Open balance summary, recent activity
Invoices /customer-portal/invoices All invoices issued to this customer
Payments /customer-portal/payments Recorded payments and refunds
Quotes /customer-portal/quotes Quotes (open, accepted, declined)
Orders /customer-portal/orders Sales orders if sales.use_sales_orders is on
Settings /customer-portal/settings Profile + change password

All views are scoped to the signed-in customer's customer_id. Data crosses tenant boundaries only via the tenant resolution middleware — two customers in two organizations of the same tenant DB cannot see each other.


Disabling and revoking access

  • Disable account. Set customer_portal_accounts.is_active = false. Existing sessions are invalidated on next request.
  • Revoke all secure links. From the customer record, Revoke active links. This sets revoked_at on every active row in customer_portal_links.
  • Revoke a specific document's links. PortalLinkService::revokeForDocument($doc) — fires from the document page's Revoke link action.

Logging

Every portal access is recorded in portal_access_logs with the customer id, document id, IP, and user agent. Admin support can search this log via Admin > Activity Logs.


Related

Source: docs/customer-portal/overview.md ← All documentation