Real deletion (D15's 'full replacement'), not a toggle. Okta is now the only credential this app accepts anywhere. Backend: - server/models.py: drop User.password_hash. - server/alembic/versions/1d60a608bb51_...: matching migration (op.drop_column, same plain-drop precedent as project_role/locked_until/etc.; downgrade re-adds it with server_default=''). - server/auth.py: remove hash_password/verify_password/password_problem/ MIN_PASSWORD_LEN/_COMMON_PASSWORDS, create_reset_token/decode_reset_token/ RESET_MINUTES, the bcrypt import. Roles/tokens/cookies/get_current_user untouched. - server/app.py: remove login(), the whole self-service reset-password block (forgot-password/reset-available/reset-password), and change_password() (POST /api/auth/password). Rework create_user() to drop the password field (with a docstring note: the username must exactly match the eventual Okta identity claim, or a later sign-in provisions a second account instead of matching this one). Remove admin_reset_password() outright - nothing left to reset. Fixes a bug this task's own predecessor left behind: okta_callback()'s JIT provisioning (T10.3) was still setting password_hash="", which would have raised TypeError the moment the column was actually dropped. Admin bootstrap (D16): server/manage_users.py moves from creating accounts (create/create-admin/reset-password, all password-based) to a single 'promote <username> --role <role>' command that changes the role on a row Okta's JIT provisioning already created - the documented path for naming the first admin. list/disable/enable unchanged. Frontend: html/users.js drops the password field and validation from createUser(), removes resetPw() and its button (nothing left to reset). html/users.html drops the #nu-password input, adds a tooltip on username explaining the exact-match-to-Okta requirement. html/auth-guard.js removes the wpChangePassword dialog; html/wp-sidenav.js removes the 'Password' menu item that opened it. Tests: tests/browser_check.py and tests/launcher_check.py stop hashing a password to seed fixture rows (and the --keep-server hint now prints a ready-to-use cookie-setting snippet instead of a dead username/password). tests/pipeline_check.py and tests/token_check.py drop an unused PW import. tests/console_dialogs_check.py: the admin password-reset dialog it drove no longer exists, so that scenario is removed - the prompt-with-validate() UI pattern it exercised is still covered via creator_dialogs_check.py's wp-creation-app.js call sites, noted in this file's docstring so the coverage move isn't silent. tests/url_state_check.py: the "next= survives a real sign-in via login" scenario is explicitly marked SKIPPED (not deleted, not faked) - that promise is specific to the login FORM this task removed and can't be honestly re-proven until T10.5 rebuilds it as an Okta redirect; a minted-token cookie now stands in as setup only, so scenarios 3-6 in that file still get a signed-in page to run against. server/smoketest.py and server/seed_demo.py: switched from POST /api/auth/login to minting a session the same way okta_callback() does (auth.create_token(), seeded into the cookie jar) rather than waiting on T10.7. This is a real operational change, documented in both files' own AUTHENTICATION sections: they now need to run where AUTH_SECRET_KEY and the database match the target server's (inside the api container, or local dev) - they can no longer sign in to an arbitrary remote URL from an unrelated workstation, because Okta requires a real browser and these are stdlib scripts. The account must already exist; neither script creates or promotes one. server/requirements.txt: bcrypt dropped, nothing imports it anymore. Verified: full Alembic chain (baseline through this migration) upgrades and downgrades cleanly against a throwaway SQLite DB. okta_callback() JIT provisioning re-tested against the post-migration schema (would have thrown before the password_hash="" fix above). create_user() verified via a live HTTP call with no password field. manage_users.py promote verified end to end (seed a JIT-shaped row at project_user, promote to admin, list). smoketest.py and seed_demo.py both run to completion against a live uvicorn instance using the new minted-session path - 25/25 checks, including logout actually invalidating the session (proving the cookie-jar seeding didn't just fake the sign-in, it preserved the real expiry mechanics). wave-10.md T10.4 / D15 / D16
112 lines
4.9 KiB
HTML
112 lines
4.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>User Directory — Work Package Suite</title>
|
|
<script src="auth-guard.js"></script>
|
|
<!-- Date/number formatting. Must parse BEFORE the app scripts: they format
|
|
timestamps during their own boot. -->
|
|
<script src="wp-format.js"></script>
|
|
<!-- Addressable state (S3). Parses before the app scripts, which read the URL
|
|
during their own boot. -->
|
|
<script src="wp-url.js"></script>
|
|
<link rel="icon" href="favicon.ico" sizes="any">
|
|
<link rel="manifest" href="manifest.webmanifest">
|
|
<meta name="theme-color" content="#161616">
|
|
<link rel="stylesheet" href="theme-light.css">
|
|
<link rel="stylesheet" href="wp-chrome.css">
|
|
<link rel="stylesheet" href="console.css">
|
|
<link rel="stylesheet" href="wp-sidenav.css">
|
|
<style>
|
|
/* Page-specific only — everything structural is in console.css.
|
|
|
|
The directory is one wide table, so the column exceptions live here: email is
|
|
the one cell long enough to stretch a row, and the two role dropdowns need
|
|
room for "Assistant Project Manager" without pushing Actions off screen. */
|
|
#users-table table td:nth-child(3){ max-width:230px; overflow:hidden; text-overflow:ellipsis; }
|
|
#users-banner:not(:empty), #scope-banner:not(:empty){ margin-bottom:var(--s3); }
|
|
#nu-projects{ margin-top:var(--s2); }
|
|
#nu-projects .pickrow{ padding:var(--s1) var(--s1); }
|
|
/* A manager with one project doesn't need a scrolling picker; a manager with
|
|
thirty does, and it must not push the Create button below the fold. */
|
|
#nu-project-list{ max-height:200px; overflow:auto; border:1px solid var(--border); }
|
|
.whoami-chip{ font-size:12px; color:var(--muted); }
|
|
.whoami-chip strong{ color:var(--text); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- SHARED DARK APP BAR -->
|
|
<header class="wp-appbar">
|
|
<a href="index.html" class="wp-appbar-brand" title="Back to site">
|
|
<span class="wp-logo-chip"><img src="prime-controls-logo.jpg" alt="Prime Controls"></span>
|
|
<span class="wp-appbar-title">Work Package Suite <span class="wp-appbar-sub">| User Directory</span></span>
|
|
</a>
|
|
</header>
|
|
|
|
<div class="wrap" id="users-main" style="display:none">
|
|
<div class="row" style="justify-content:space-between; margin-bottom:var(--s5)">
|
|
<div>
|
|
<h1>User Directory</h1>
|
|
<div class="sub" style="margin:0" id="dir-sub">The people on your projects — who they are, and how to reach them.</div>
|
|
</div>
|
|
<div class="row"><a class="home" href="index.html">← Site</a></div>
|
|
</div>
|
|
|
|
<!-- WHAT YOU MAY DO HERE (rendered from GET /api/auth/user-scope) -->
|
|
<div id="scope-banner"></div>
|
|
|
|
<!-- THE DIRECTORY -->
|
|
<div class="card">
|
|
<h2>People</h2>
|
|
<div class="sub" id="people-sub"></div>
|
|
<div class="toolbar">
|
|
<button onclick="loadUsers()">Refresh</button>
|
|
<input id="user-search" placeholder="Search name / username / email / job function…" oninput="renderUsers()">
|
|
<select id="user-filter" onchange="renderUsers()">
|
|
<option value="">Everyone</option>
|
|
<option value="active">Active only</option>
|
|
<option value="disabled">Disabled only</option>
|
|
<option value="mine">Accounts I manage</option>
|
|
</select>
|
|
</div>
|
|
<div id="users-banner"></div>
|
|
<div id="users-table"><div class="note">Loading…</div></div>
|
|
</div>
|
|
|
|
<!-- ADD A USER (managers only; hidden otherwise) -->
|
|
<div class="card" id="create-card" style="display:none">
|
|
<h2>Add a user</h2>
|
|
<div class="sub" id="create-sub"></div>
|
|
<div class="urow">
|
|
<input id="nu-username" placeholder="Username *" autocomplete="off"
|
|
title="Must exactly match this person's Okta sign-in identity — that's how their first Okta sign-in finds this account instead of creating a second one.">
|
|
<input id="nu-fullname" placeholder="Full name" autocomplete="off">
|
|
<input id="nu-email" placeholder="Email" autocomplete="off">
|
|
<select id="nu-role" title="Permissions — what this account may do"></select>
|
|
<select id="nu-project-role" title="Job function on the project"></select>
|
|
</div>
|
|
<div id="nu-projects">
|
|
<div class="note" id="nu-projects-label" style="margin-bottom:var(--s1)"></div>
|
|
<div id="nu-project-list"></div>
|
|
</div>
|
|
<div class="row" style="margin-top:var(--s3)">
|
|
<button class="primary" onclick="createUser()">Create user</button>
|
|
<span id="users-create-msg" class="note" style="margin:0"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="console-util.js"></script>
|
|
<script src="wp-dialog.js"></script>
|
|
<script src="users.js"></script>
|
|
<!-- The app bar's project switcher reads ProjectData; without this the bar on this
|
|
page could never show a project and always read "Select a project" (F1). Must
|
|
parse before wp-chrome.js, which reads it as it mounts. -->
|
|
<script src="project-data.js"></script>
|
|
<script src="wp-chrome.js"></script>
|
|
<script src="wp-sidenav.js"></script>
|
|
</body>
|
|
</html>
|