Commit Graph

4 Commits

Author SHA1 Message Date
ab7bce9f5b T10.6 D13 - strip the password UI; "Forgot password?" goes to Okta
login.html/login.js: the two reset views are gone along with the reset-token
handling, and the sign-in form now says which password to type - "your Windows
password, the same one you use to sign in to your computer" - using the .hint
class the page already had, so no new CSS and no new literal.

"Forgot password?" is KEPT and points at https://primecontrols.okta.com/. An
earlier draft of this task deleted the link and I proposed a plain "contact IT"
sentence instead; Okta is the better answer, and with no app password and no
break-glass it is the only recovery path that exists.

Three details that would each have broken it:

- The old click handler on #forgot-link called preventDefault() to swap views.
  Left in place it would have silently swallowed the navigation, so the link
  would look right and do nothing. There is now deliberately no handler, and
  login.js says why so nobody adds one back.
- target="_blank" without rel="noopener noreferrer" hands the opened page a
  window.opener handle back to the login page.
- Worth recording since it was checked rather than assumed: the CSP allows
  this. form-action 'self' governs form submission, not link navigation, and no
  navigate-to directive is set - so a plain <a href> off-origin is fine and the
  nginx config needs no change.

login.js also handles 503 distinctly now. T10.2 made that mean "the directory
is unreachable or misconfigured", which is our fault - showing "invalid
password" would send people hunting for a password they no longer have while a
deploy is broken.

Also removed, because T10.3 deleted the endpoints behind them and leaving them
would have produced visible 404s rather than dead-but-harmless markup:

  auth-guard.js   the whole change-password dialog (POST /api/auth/password)
  wp-sidenav.js   the "Password / Change your password" menu entry that opened it
  users.js        the per-row "Reset password" action
  users.js        the password field in the create-account form - NewUserIn no
  users.html      longer accepts one, so the form was posting a rejected field

The self-row placeholder button pointed at a top-bar Password link that no
longer exists; it is now a plain "you" marker.

Verified: node --check passes on all four touched JS files; the only password
references left in html/ are the sign-in form and the SMTP config in admin.js,
which is unrelated and stays.

Logged BL-027 rather than acted on: the Okta URL is the first sign of an Okta
tenant on this estate, which means an OIDC flow is available in principle and
would remove the domain-lockout hazard that forced AUTH_MAX_ATTEMPTS to 2. D13
was decided and reaffirmed and T10.1-T10.4 are built, so swapping the mechanism
mid-wave is the reordering CLAUDE.md forbids. Recording is not reopening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 15:30:16 -05:00
560f0cb3cc BL-024 - the last 21 native dialogs, onto the shared kit
S1 counted 79 native dialogs app-wide and its tasks removed 58; the audit
found the rest on surfaces no S1 task named: admin.js (6), users.js (10), the
launcher's inline script (5). All 21 now go through wp-dialog.js - the T7.9
kit extracted as a self-injecting shared component: markup and styles land on
first use, styles are theme tokens only with its own wp-dlg-* class names (the
consoles' existing .modal styles are untouched), 44px targets on coarse
pointers, and the whole file is guarded so the creator's inline copy - which
owns the same-id markup in its HTML - still wins on its own page. The kit's
toast comes along (S10 role rules), since none of the three pages had one.

Conversion follows the T7.9 precedent: confirms -> wpConfirmDialog with named
ok-labels, the password prompt -> wpPromptDialog whose validate() finally
enforces min-12 AT the input (it was label-text-only before, server-enforced),
API failures with detail -> wpAlertDialog, small info/validation messages ->
the announced toast.

New probe console_dialogs_check (17): counts pinned at 0, kit guarded and
loaded by all three pages, and the users console driven live with natives
poisoned - reset a password end to end (short refused inline, good one accepted
by the server and announced), cancel a delete and prove nothing died.

Items: BL-024 (closed), S1 completed to zero app-wide, C1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 18:24:09 -07:00
b83f2fd8d5 T9.3 - S6: one icon system - monochrome text glyphs, one meaning each
The set mixed colour emoji with dingbats, and the same glyph read as two
things partly BECAUSE emoji render as per-platform artwork. The system chosen:
monochrome text-presentation glyphs - the suite is classic-script vanilla HTML
with no bundler, so an SVG sprite or icon font is a new asset pipeline, while
text glyphs render through the same font stack as the words beside them. The
enforceable form of "renders identically on Windows, macOS and a tablet":
no emoji-range codepoint and no U+FE0F selector anywhere in UI source,
swept by the probe on every run.

Converted: green-check/red-cross emoji in the admin and users consoles to
checkmark/cross, no-entry to circled-slash (blocked/on hold), the lock to the
pencil already meaning "edit with a logged reason" on sign-offs, the star to
the diamond, the folder to the reference marker, the side nav's lightning to
the gear, and the WATCH glyph (U+231A - emoji-presentation BY DEFAULT per
Unicode) to a text-presentation clock face. Dropped where the label already
carried the meaning: lightning on Save & view, the camera on Add photo, the
page/frame pictograms on file rows (the filename is the label). Stale help
copy fixed while its emoji left: it still described the pre-T9.4 "Load
sample" and the pre-T7.10 "Usage Logs" locations.

The meaning-to-icon mapping is in docs/reference/tokens.md - one meaning per
glyph, one glyph per meaning, both directions asserted from the document
itself; the probe also sweeps every page for glyphs not in the approved set,
so an unmapped icon cannot creep in.

Verification (each probe run alone): NEW tests/icon_check.py 5/5.
Regressions: frame_check 38/38, files_check 36/36, a11y_check 22/22,
cards_check 44/44.

Items: S6

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:15:59 -07:00
4ace2afb1c Move user administration to its own page; add Project Super User
User accounts lived in the Admin Console, which is admins-only. Project admins
need to create the accounts on their own jobs without an app admin on the phone,
so accounts move to a new User Directory page and a new role carries the right.

server/auth.py, server/app.py
  New permissions role `project_super_user`, between admin and project_admin:
  everything a project admin may do, plus user administration SCOPED to the
  projects they hold the role on. Four limits make it safe to hand out, all
  enforced server-side:

    * Scope comes from projects, not the job title. It resolves per membership
      (managed_project_ids), so an ordinary account can hold it on one job via
      ProjectMember.role, and a super user demoted on one job administers
      nobody there. No projects, no authority.
    * Account-level changes (password, disable, rename, permissions, delete)
      require EXCLUSIVE scope: refused when the target is also on a project the
      caller does not administer, because those changes are global. The
      directory renders such rows read-only with the reason.
    * No admin or super-user targets, and neither role can be granted by a
      super user -- that is the line that stops it becoming app-wide control.
    * PUT .../projects rebuilds only the caller's own slice; memberships on
      projects they do not administer are left untouched. A payload that simply
      omits them must not cut someone off a job the caller cannot see.

  Creating requires naming at least one of your own projects: an account with
  none would be one the creator instantly cannot manage.

  /api/auth/users is now scoped rather than admin-only, and carries a per-row
  `manageable` verdict plus the reason. Non-managers get a contact card only --
  a project user has no business reading colleagues' login history. New
  /api/auth/user-scope tells the page what it may offer. Administrative
  password resets are now audited; they were the one account change that left
  no trace. Settings, feature flags and the auto-add rule stay admin-only.

  While here: one definition of "is a user manager", derived from the managed
  set. An account-role-only version disagreed with the scoped one and locked
  per-project super users out of routes they were entitled to.

html/users.html, html/users.js
  The directory: three renderings from one page -- admin (everything), super
  user (controls per row, read-only where scope is shared), everyone else (a
  read-only directory of the people on their own projects).

html/console.css, html/console-util.js
  Extracted from admin.html/admin.js so both console pages share them. A
  divergent jsq() is an XSS and a divergent role list offers permissions the
  server refuses, so neither may exist twice.

html/wp-sidenav.{js,css}
  Global nav drawer, role-gated, carrying ?project= across links. Mounted on
  the field view (which had no way to anywhere) plus both console pages.

No migration: users.role is already String(20) and the new value fits.

Verified: 93 scope/gate tests, 29 live HTTP tests through the real dependency
stack, 33 static JS checks. Not verified in a browser -- no JS engine on this
machine -- so users.html and field.html want one manual load.

server/smoketest.py still fails with 401s. Pre-existing: it has no login code,
so auth_gate refuses it. Confirmed unchanged by stashing this work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 17:36:14 -07:00