fix(help): readonly users can't complete first-login onboarding (Fixes #3683)#3684
fix(help): readonly users can't complete first-login onboarding (Fixes #3683)#3684marcelfolaron wants to merge 1 commit into
Conversation
…ckets (Fixes #3683) Readonly users (role level 5) lack TicketsPermissions::CREATE, so FirstTaskStep::handle() threw on quickAddTicket() before the firstLoginCompleted flag was ever written — leaving the onboarding modal in an infinite loop. Guard the optional ticket creation so the completion flag is always persisted; only attempt the ticket when the current user is permitted to create one.
|
Leantime OSS Maintainer Bot seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
There was a problem hiding this comment.
Pull request overview
Fixes an onboarding dead-end where readonly users (lacking TicketsPermissions::CREATE) could never complete first-login onboarding because the completion flag write was blocked by a permission error thrown during optional first-task creation.
Changes:
- Wrap first-task creation (
Tickets::quickAddTicket()) in atry/catchso onboarding completion proceeds even when ticket creation fails (e.g., 403 for readonly users). - Skip ticket creation when the headline is empty.
- Add an in-code explanation of the invariant: the
firstLoginCompletedflag must be persisted regardless of ticket creation success.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| use Leantime\Core\Events\DispatchesEvents; | ||
| use Leantime\Domain\Auth\Services\Auth; | ||
| use Leantime\Domain\Help\Contracts\OnboardingSteps; |
| if (isset($params['headline']) && $params['headline'] !== '') { | ||
| try { | ||
| $this->ticketService->quickAddTicket(['headline' => $params['headline']]); | ||
| } catch (Throwable $e) { | ||
| // The user may not have permission to create tickets (e.g. readonly | ||
| // role). That must not block onboarding from completing, so swallow | ||
| // the error and continue to persist the completion flag below. | ||
| report($e); | ||
| } | ||
| } |
| public function handle($params): bool | ||
| { | ||
|
|
||
| if (isset($params['headline'])) { | ||
| $this->ticketService->quickAddTicket(['headline' => $params['headline']]); | ||
| if (isset($params['headline']) && $params['headline'] !== '') { | ||
| try { | ||
| $this->ticketService->quickAddTicket(['headline' => $params['headline']]); |
|
Status: triaged / fix-in-draft · Priority: P2 (functional lockout for readonly users; no data loss or security exposure) · Next action: drop the unused Automated maintainer first-response (advisory only — not an approval; not marking this draft ready; merge authority stays with @marcelfolaron / @broskees). Reviewed at head 1. Intent: Decouples the load-bearing 2. Change requests:
3. Acceptance checklist (must pass before merge):
4. CI status: Advisory only — not an approval, not marking ready, not merging. Merge authority stays with @marcelfolaron / @broskees. |
Summary
Fixes #3683 — readonly users (role level 5) are trapped in an infinite first-login onboarding modal loop.
FirstTaskStep::handle()called the permission-guardedTickets::quickAddTicket()before writing thefirstLoginCompletedflag. Readonly users lackTicketsPermissions::CREATE, so thePOST /help/firstLogin?step=endthrew a 403 before the flag was ever persisted — andHelpermodalkeptisFirstLogintrue on every render, re-showing the modal indefinitely with no escape.Reported and root-caused by @christiansteier (thank you — the trace was spot-on, incl. the observation that readonly is the de-facto default in the Add-User dialog).
Root cause
app/Domain/Help/Services/FirstTaskStep.php— the completion flag write was coupled to the optional first-task creation:quickAddTicket()is guarded by#[RequiresPermission(TicketsPermissions::CREATE, ...)].['scope' => 'project', 'verbs' => ['view']]→ 403 on the POST.saveSetting('user.<id>.firstLoginCompleted', true)on the next line never runs.Change
Wrap the optional ticket creation in a
try/catch(Throwable)so a permission failure (or any other error) isreport()-ed but does not block the load-bearing completion-flag write. Also skip creation on an empty headline. The first task is a convenience; onboarding completion is the invariant.Single file:
app/Domain/Help/Services/FirstTaskStep.php.Test plan
./vendor/bin/pint --test./vendor/bin/phpstan analyse --level=5php -l app/Domain/Help/Services/FirstTaskStep.php./vendor/bin/phpunit)handle()persistsfirstLoginCompletedand does not throw; editor/manager still creates the task (happy path unregressed).Acceptance checklist
firstLoginCompletedwritten; modal doesn't loop.Notes / not in scope
cc @marcelfolaron @broskees