4 Commits

Author SHA1 Message Date
thatguygriff f3f5c7801f Security fixes: CSV injection, policy body output, invite hashing, slot datetimes
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 1m11s
CI / PHPStan (pull_request) Successful in 1m20s
CI / Build Plugin Zip (pull_request) Has been skipped
Four fixes from a security review pass:

- Neutralise CSV formula injection in the payments export: fields with a
  leading =, +, -, @, tab, or CR (e.g. a hostile student display name) are
  apostrophe-prefixed in PaymentReport::csvLine() so they open as text in
  Excel/Google Sheets. Fixes #39.
- Sanitise policy bodies with wp_kses_post at output in
  PolicyEndpoint::index() (the booking JS renders that HTML raw), so a
  future write path that forgets kses can never become stored XSS.
  Fixes #40.
- Store invite tokens hashed (SHA-256) at rest: a database leak can no
  longer redeem pending invites. The registration link is shown once, at
  creation; the pending list shows email/invited date; lookups hash the
  submitted token. Existing plaintext pending invites must be re-issued.
  Fixes #41.
- Validate availability slot datetimes on both creation paths (REST and
  admin form) via AvailabilitySlot::normalizeDateTime(): canonical and
  datetime-local forms normalise to Y-m-d H:i:s, garbage and end <= start
  are rejected (REST 400) instead of reaching the DATETIME column or
  throwing inside the weekly-series date arithmetic. Fixes #42.

composer test (204 tests, 594 assertions), PHPStan L6, and PHPCS all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:36:26 -03:00
thatguygriff 330900a246 Auto-redirect invite tokens to the registration page; rename invite button
CI / Coding Standards (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 59s
CI / Tests (PHP 8.1) (pull_request) Successful in 51s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.3) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / Build Plugin Zip (pull_request) Has been skipped
- RegistrationPage::maybeRedirectToRegistrationPage() (hooked on
  template_redirect): any front-end request carrying a us_invite token is
  redirected to the configured registration page (token preserved), unless
  already there. Covers links shared before a page was selected; no-op when
  no page is set.
- Invites button text: "Send Invite" -> "Generate Invitation Link".
- Doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:31:11 -03:00
thatguygriff 372f981a08 Add registration-page selector to the Invites page
CI / Coding Standards (pull_request) Successful in 1m4s
CI / PHPStan (pull_request) Successful in 1m3s
CI / Tests (PHP 8.1) (pull_request) Successful in 50s
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / Tests (PHP 8.3) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 3s
CI / Build Plugin Zip (pull_request) Has been skipped
Invitation links previously pointed at the site home page, which usually
does not host the [us_student_register] shortcode. Let the studio admin
choose the registration page (stored in the us_registration_page_id
option); invitation links now point there, falling back to the home page
when unset (with a warning notice).

- RegistrationController: OPTION_PAGE constant; set_page action; pass the
  page id/url to the template.
- templates/admin/invites.php: wp_dropdown_pages selector + save; build the
  invite link from the selected page.
- Doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:24:33 -03:00
thatguygriff 9c900d6553 Add account registration with signup policy acceptance
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 2s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Coding Standards (pull_request) Successful in 52s
CI / PHPStan (pull_request) Successful in 1m1s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.3) (pull_request) Successful in 45s
Implements #16: invite-only student self-registration through a front-end
page, accepting signup-scoped policies at account creation.

Policy domain:
- us_policies.acceptance_scope (signup/booking/both); Policy::appliesTo();
  PolicyRepository::findForScope(); scope threaded through PolicyService,
  the REST create, the admin controller, and the Policies form.
- PolicyAcceptance::REG_ACCOUNT (registration_id = the new user's ID).

Auth:
- Invite value object + InviteRepository; us_invites table.
- RegistrationController + Invites admin page (manage_students): invite an
  email, share the registration link, revoke.
- RegistrationPage ([us_student_register] shortcode): validates the invite
  token, collects name/password, renders signup-scoped published policies
  with required acceptance, creates the us_student user, records account-type
  acceptances, marks the invite accepted, and logs the user in.
- RoleManager: manage_students cap added to STUDIO_ADMIN_CAPS.

Invite-only is implemented; the us_registration_mode self_approval path is a
documented future seam.

Docs: docs/features/account-registration.md; policies.md updated.
Tests: tests/Unit/Auth/ (Invite, InviteRepository) plus Policy scope
updates. composer test (104), cs, and PHPStan level 6 all pass.

Refs #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:39:39 -03:00