Files
clients/libs/angular
Jared SniderandGitHub 8baa8902ab Auth/PM-38298 - Org Invite Acceptance Refactor (#21011)
* PM-38298 - Add AcceptFlowService to libs/angular

Extracts the shared param-check → auth-status → handler-dispatch →
error-handling shell from BaseAcceptComponent into a reusable, generic
Angular orchestration service. Pure addition with no consumers yet.
The component migration off BaseAcceptComponent lands in a later commit.

* PM-38298 - Relocate organization-invite to libs/common feature folder

Renames libs/common/src/auth/services/organization-invite/ to
libs/common/src/auth/organization-invite/, matching the feature-folder
convention used by password-prelogin, send-access, and two-factor.

Rewrites the ~14 repo-wide import sites in lockstep and adds an index.ts
barrel. Pure rename - no logic changes. Sets up a cleaner home for the
merged service that follows in the next commit.

* PM-38298 - Merge AcceptOrganizationInviteService into DefaultOrganizationInviteService

Eliminates the artificial state/orchestration split: the apps/web
AcceptOrganizationInviteService is absorbed into the libs/common
DefaultOrganizationInviteService, which now owns state persistence,
validate-and-accept orchestration, and policy fetching.

Key changes:
- Extends the OrganizationInviteService abstract class with
  validateAndAcceptInvite and getInvitePolicies.
- Folds the WebOrganizationInviteService state backing (GlobalStateProvider
  + ORGANIZATION_INVITE KeyDefinition) into the merged impl.
- Promotes the orchestration's private getPolicies to public
  getInvitePolicies, keyed by invite token, and adds full cache
  invalidation on setOrganizationInvitation / clearOrganizationInvitation
  so the now-app-scoped instance never serves stale entries.
- Updates the OrganizationInviteService DI registration in
  jslib-services.module.ts with the impl's expanded dependency list.
- Removes the apps/web WebOrganizationInviteService provider override and
  deletes the file - the libs/common impl now serves all clients.
- Adds eslint suppressions for the admin-console and key-management
  imports per the existing libs/common decomposition pattern.
- Adds a libs/common spec covering state, orchestration, and cache
  invariants.

AcceptOrganizationInviteService in apps/web still exists at this point;
component migration off it lands in the next commit.

* PM-38298 - Migrate AcceptOrganizationComponent off BaseAcceptComponent

Drops the BaseAcceptComponent inheritance pattern for AcceptOrganization:
the component now consumes AcceptFlowService (libs/angular) for the
orchestration shell and OrganizationInviteService (libs/common) for
invite validation and acceptance, with the same authedHandler /
unauthedHandler / "Expired token." error-mapping semantics it had as a
BaseAcceptComponent subclass.

Also:
- Converts AcceptOrganizationComponent to standalone with explicit
  IconModule and I18nPipe imports for the template's bit-icon and i18n
  pipe usage.
- Deletes AcceptOrganizationInviteService and its spec - the
  orchestration logic now lives in the merged libs/common service.
- Deletes accept-organization.module.ts (no longer needed once the
  component is standalone and its only provider, the legacy service,
  is removed) and drops it from auth.module.ts.

BaseAcceptComponent and its five other subclasses are out of scope; they
keep extending the base class.

* PM-38298 - Consolidate org-invite policy-fetch consumers

Routes the duplicated "fetch org policies via invite token" pattern
through OrganizationInviteService.getInvitePolicies(invite) so callers
share one cached implementation instead of each calling
policyApiService.getPoliciesByToken directly.

Migrates four call sites:
- web-login-component.service.ts (drops the now-unused
  PolicyApiServiceAbstraction injection; inline email-mismatch handling
  stays).
- web-registration-finish.service.ts (drops PolicyApiServiceAbstraction
  and LogService injections; error logging moves into the merged
  service).
- complete-trial-initiation.component.ts (drops policyApiService and
  logService injections).
- core.module.ts deps arrays updated to match the trimmed constructors;
  the now-orphaned PolicyApiServiceAbstraction import is removed.

Also fixes web-login-decryption-options.service.spec.ts, which had been
importing AcceptOrganizationInviteService directly as a mock target -
swapped to OrganizationInviteService to match the production wiring and
unblock the deletion in the previous commit.

Spec updates mirror the production changes: existing it() cases that
asserted against getPoliciesByToken now assert against getInvitePolicies.

* PM-38298 - Make OrganizationInvite enforce its own contract

Replaces the loose, all-optional OrganizationInvite shape with one that
matches the server contract emitted by OrganizationUserInvitedViewModel.Url:
seven fields are always present, only orgSsoIdentifier is conditional
(server emits it only when the org has SSO + the SSO-required policy on).

Key changes:
- All seven always-present fields become non-optional. orgSsoIdentifier
  stays optional.
- Adds a real constructor taking a typed data object, so partial
  construction is a compile error - the previous Object.assign + empty
  ctor pattern silently allowed test fixtures to omit fields.
- Adds an OrganizationInviteUrlParams interface documenting the URL
  query-string shape the server emits, so the contract is captured in
  code (no more spelunking the C# server file). Sets up future
  open-invite-link and browser-extension URL parsers to share the type.
- Adds OrganizationInvite.fromUrlParams static factory that validates +
  narrows the untyped Angular Params bag into the shape and returns
  null on any missing/invalid field. Replaces the private fromParams
  method that previously lived on AcceptOrganizationComponent - parsing
  now lives with the domain class, where the next two consumers
  (open-invite-link, browser-extension invite acceptance) can reuse it.
- fromJSON rebuilt to use the constructor.

Consumer cleanup made possible by the tightened type:
- complete-trial-initiation.component.ts drops the defensive
  invite.organizationId / token / email / organizationUserId truthiness
  checks. The type guarantees these are present whenever invite != null.

Test fixtures across web-registration-finish.service.spec.ts,
web-login-component.service.spec.ts, and the libs/common spec helper
all updated to construct via the new ctor. Adds a new
organization-invite.spec.ts covering the ctor, fromUrlParams (happy
path, every required-param-missing case, GUID validation, boolean
coercion, optional passthrough), and fromJSON.

* PM-38298 - Remove @ts-strict-ignore from invite-related files

Three files in the org-invite refactor footprint had carry-over
@ts-strict-ignore pragmas. tsc-strict now reports them clean - drops
the pragma + FIXME comment from each:

- libs/common/src/auth/organization-invite/default-organization-invite.service.spec.ts
- apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts
- apps/web/src/app/auth/organization-invite/accept-organization.component.ts

The merged DefaultOrganizationInviteService itself still carries the
pragma - removing it there requires in-file null guards for
activeAccount$ and userKey$, plus EncString.encryptedString handling,
which is a separate follow-up.

* PM-38298 - Drop dead null guards in OrganizationInviteService

After tightening OrganizationInvite to enforce required fields at its
constructor and at every typed callsite, the runtime
`if (invite == null) throw` guards in setOrganizationInvitation and
validateAndAcceptInvite became unreachable code - the abstract class
signature already rejects null at compile time, and every consumer
either constructs an invite via the typed constructor / factory, or
narrows with `if (invite != null)` before calling.

Removes:
- The two null-throw guards in DefaultOrganizationInviteService.
- The corresponding @throws JSDoc on the abstract class.
- The two "throws when invite is null" tests, which were verifying
  behavior the type system now prevents (and required passing `null`
  in places the signature no longer accepts it).

No behavior change for typed callsites: the only way to reach the
removed code paths was to defeat the type system with `as any`, which
is already undefined-behavior territory.

* PM-38298 - Make DefaultOrganizationInviteService type-safe

Switch getInvitePolicies to return undefined instead of null (per the
strict-mode ADR), plumb the user ID through the accept path so we no
longer have to read it from a nullable observable, and add defensive
guards around encryption outputs that the request constructors require.
Drops the now-unused AccountService dependency.

* PM-38298 - Address code review findings on org-invite refactor

Two small follow-ups from the post-refactor review:

- Document the trust-boundary asymmetry between OrganizationInvite's two
  static factories. fromUrlParams validates external URL input;
  fromJSON trusts persisted state because the only write path goes
  through the typed constructor, which already enforces required fields.
  A short JSDoc on fromJSON makes the choice explicit so readers don't
  flag the missing validation as an oversight.

- Restore the end-state assertions on two validateAndAcceptInvite cases
  that lost coverage when the apps/web spec was ported to libs/common:
  the initOrganization=true path and the no-MP-policy accept path both
  now verify the invite is cleared from state after acceptance. Brings
  them in line with the other "happy-path accept" cases that already
  check `expect(stored).toBeNull()`.

* PM-38298 - Fill org-invite test gaps

Cover the type-safety guards added in c475c541be and a few cache/edge
cases that were not exercised:

- DefaultOrganizationInviteService: throws when any encrypted output is
  null in acceptAndInitOrganization (3 cases); throws when org keys
  fetch returns null, user key is null, or the encapsulated user key is
  null in the reset-password enrollment path (3 cases); getInvitePolicies
  scopes its cache by invite token and does not cache null API results.
- AcceptFlowService: AuthenticationStatus.Locked routes to authedHandler;
  non-Error throws fall through to the full failed-message path; null
  queryParams is treated as a missing-param error.

* PM-38298 - Skip stale-invite clear when no invite is stored

* PM-38298 - Refactor AcceptFlowService to typed-parse shape

Move URL parsing into AcceptFlowService via a required parse callback
and dispatch the parsed result as a typed TInvite to both handlers.
Symmetric try/catch around authedHandler and unauthedHandler so error
handling no longer asymmetrically depends on which branch the user
lands in.

AcceptOrganizationComponent loses its requiredParameters field, its
handleInvalidInvite method, and both inline 'if (invite === null)'
guards; OrganizationInvite.fromUrlParams (validates presence + GUID
format) is now the single source of truth for invalid-link detection,
strictly stronger than the prior presence-only string-array check.

* PM-38298 - Add JSDoc to AcceptFlowService

* PM-38298 - Add JSDoc class headers to org-invite contracts

* PM-38298 - Remove dead org-invite policy lookup from trial initiation

* PM-38298 - Document policy cache in OrganizationInviteService

* PM-38298 - Rename OrganizationInvitation methods to OrganizationInvite

* PM-38298 - Document organization invite acceptance flows

* PM-38298 - Flag MP-policy logout branch for reachability investigation

* PM-38298 - Document MP-policy logout branch reachability

Replace the reachability TODO on validateAndAcceptInvite's MP-policy
logout branch with an inline explanation of the trigger (authed user
pastes the accept-invite URL into a signed-in session). Extend the
flow docs with the same scenario and align the SSO acceptance bullets
with the application's "member decryption option" terminology, plus a
scope note on the global single-slot stash.

* PM-38298 - Clarify init-organization invite scenario in flows doc

* PM-39073 - Surface ErrorResponse messages from accept flow

Recognize ErrorResponse alongside Error in AcceptFlowService's catch block
so server-side messages reach the toast instead of being dropped. ErrorResponse
extends BaseResponse, not Error, so the prior `instanceof Error` guard lost
the API message and fell back to the generic invite-failed string.
2026-06-16 11:35:10 -04:00
..