Acts on the site comments from 8/3 plus the follow-ups. Foundation work first — four of the comments all needed the project team to resolve to real user accounts. Permissions vs project role (new) - User.role is now the PERMISSIONS role: admin | project_admin | project_user. project_admin may delete work packages, change a SOP after it is complete, and delete a project; project_user may not (archiving a WP is still open to them). Enforced by require_project_admin() server-side; the UI only hides dead ends. - New User.project_role holds the person's JOB FUNCTION on the project. It grants nothing — it feeds the SOP team pickers and notification routing. - Admin console shows both columns and explains the difference. Migration rewrites the legacy role 'user' to 'project_user'. - Deleting a project was previously open to any member and unaudited; it now needs project_admin and writes an audit event. ProjectData.remove no longer drops the project from the local cache when the server refuses. SOP project team from user accounts - PM/APM/CM/QM and additional team members are pickers over the project's members, storing the account id next to the display name. A name from an older SOP with no matching account is kept and flagged rather than dropped. - The WP Creator lists the SOP team first in the Owner picker, and a new package defaults to whoever is creating it. Critical constraints - SOP constraints carry a Critical flag; buildConstraints() now copies the whole definition through to the package (it previously reduced them to names, losing description too), and critical rows are marked in the WP form. The email on reopen-after-release is wave 3. Password reset by email - login.html gains Forgot password and a set-a-new-password view, offered only when the server reports email is actually configured. - Single-use signed token (AUTH_RESET_MINUTES, default 60) bound to token_version, sent immediately rather than through the notifications outbox so a reset link is never persisted. Identical response for unknown accounts; per-account send cooldown; a completed reset clears any login lockout. - Session and reset tokens are no longer interchangeable. BIM kill-switch - New admin Features card with bim_enabled, OFF by default. The SOP creator hides the BIM section and the Creator treats every package as install-only while it is off; a SOP that already has BIM keeps its data untouched. Verified with two throwaway-database test scripts: 44 checks on the permissions matrix and token handling, 22 on the reset flow end-to-end against a local SMTP sink (real message captured, link extracted and used). Front-end files parse-checked in headless Chrome. Not yet exercised in a browser against a real login. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
164 lines
6.2 KiB
HTML
164 lines
6.2 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Sign in — Work Package Suite</title>
|
|
<link rel="icon" href="favicon.ico" sizes="any">
|
|
<link rel="stylesheet" href="theme-light.css">
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--cds-background);
|
|
padding: 1.5rem;
|
|
}
|
|
.card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
background: var(--cds-layer);
|
|
border: 1px solid var(--cds-border-subtle);
|
|
border-top: 3px solid var(--cds-interactive-01);
|
|
padding: 2.5rem 2rem;
|
|
}
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.brand img { height: 36px; width: auto; }
|
|
.brand .name { font-weight: 700; font-size: 0.95rem; color: var(--cds-text-primary); }
|
|
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
|
|
.sub { color: var(--cds-text-secondary); font-size: 0.875rem; margin-bottom: 1.75rem; }
|
|
label { display: block; font-size: 0.75rem; color: var(--cds-text-secondary); margin-bottom: 0.375rem; }
|
|
.field { margin-bottom: 1.25rem; }
|
|
input[type=text], input[type=password] {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
font-size: 1rem;
|
|
background: var(--cds-field);
|
|
border: none;
|
|
border-bottom: 1px solid var(--cds-border-strong);
|
|
outline: 2px solid transparent;
|
|
outline-offset: -2px;
|
|
}
|
|
input:focus { outline: 2px solid var(--cds-focus); background: var(--cds-field-hover); }
|
|
button {
|
|
width: 100%;
|
|
padding: 0.875rem 1rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--cds-text-on-color);
|
|
background: var(--cds-button-primary);
|
|
border: none;
|
|
transition: background 0.15s;
|
|
}
|
|
button:hover:not(:disabled) { background: var(--cds-hover-primary); }
|
|
button:disabled { background: var(--cds-disabled-02); cursor: not-allowed; }
|
|
.error {
|
|
display: none;
|
|
background: #fff1f1;
|
|
border-left: 3px solid var(--cds-support-error);
|
|
color: var(--cds-text-error);
|
|
padding: 0.75rem;
|
|
font-size: 0.8125rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.error.show { display: block; }
|
|
.foot { margin-top: 1.5rem; font-size: 0.75rem; color: var(--cds-text-helper); text-align: center; }
|
|
.ok {
|
|
display: none;
|
|
background: #defbe6;
|
|
border-left: 3px solid var(--cds-support-success);
|
|
color: #0e6027;
|
|
padding: 0.75rem;
|
|
font-size: 0.8125rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.ok.show { display: block; }
|
|
.note {
|
|
font-size: 0.8125rem; color: var(--cds-text-secondary);
|
|
background: var(--cds-layer-accent); border-left: 3px solid var(--cds-link-primary);
|
|
padding: 0.75rem; margin-bottom: 1.25rem;
|
|
}
|
|
.hint { font-size: 0.75rem; color: var(--cds-text-helper); margin-top: -0.75rem; margin-bottom: 1.25rem; }
|
|
a.link { color: var(--cds-link-primary); text-decoration: none; font-size: 0.8125rem; }
|
|
a.link:hover { text-decoration: underline; }
|
|
.center { text-align: center; margin-top: 1.25rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="card">
|
|
<div class="brand">
|
|
<img src="prime-controls-logo.jpg" alt="Prime Controls" onerror="this.style.display='none'">
|
|
</div>
|
|
<div id="error" class="error" role="alert"></div>
|
|
<div id="ok" class="ok" role="status"></div>
|
|
|
|
<!-- SIGN IN -->
|
|
<section id="view-login">
|
|
<h1>Sign in</h1>
|
|
<p class="sub">Work Package Suite</p>
|
|
<form id="login-form" autocomplete="on">
|
|
<div class="field">
|
|
<label for="username">Username</label>
|
|
<input id="username" name="username" type="text" autocomplete="username" autofocus required>
|
|
</div>
|
|
<div class="field">
|
|
<label for="password">Password</label>
|
|
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
|
</div>
|
|
<button id="submit" type="submit">Sign in</button>
|
|
</form>
|
|
<p class="center"><a href="#" id="forgot-link" class="link">Forgot password?</a></p>
|
|
</section>
|
|
|
|
<!-- FORGOT PASSWORD (email reset) -->
|
|
<section id="view-forgot" style="display:none">
|
|
<h1>Reset password</h1>
|
|
<p class="sub">We'll email you a link to set a new one.</p>
|
|
<div id="forgot-unavailable" class="note" style="display:none">
|
|
Password reset by email isn't switched on yet. Contact your project admin and
|
|
they'll set a new password for you. Once you're signed in you can change it
|
|
yourself from the menu in the top-right corner.
|
|
</div>
|
|
<form id="forgot-form" autocomplete="on">
|
|
<div class="field">
|
|
<label for="forgot-username">Username or email</label>
|
|
<input id="forgot-username" type="text" autocomplete="username" required>
|
|
</div>
|
|
<button id="forgot-submit" type="submit">Email me a reset link</button>
|
|
</form>
|
|
<p class="center"><a href="#" id="back-to-login" class="link">← Back to sign in</a></p>
|
|
</section>
|
|
|
|
<!-- SET A NEW PASSWORD (arrived from the emailed link) -->
|
|
<section id="view-reset" style="display:none">
|
|
<h1>Set a new password</h1>
|
|
<p class="sub">Choose a password you don't use anywhere else.</p>
|
|
<form id="reset-form" autocomplete="on">
|
|
<div class="field">
|
|
<label for="new-password">New password</label>
|
|
<input id="new-password" type="password" autocomplete="new-password" autofocus required>
|
|
</div>
|
|
<div class="hint">At least 12 characters.</div>
|
|
<div class="field">
|
|
<label for="new-password2">Confirm new password</label>
|
|
<input id="new-password2" type="password" autocomplete="new-password" required>
|
|
</div>
|
|
<button id="reset-submit" type="submit">Set password & sign in</button>
|
|
</form>
|
|
<p class="center"><a href="#" id="reset-to-login" class="link">← Back to sign in</a></p>
|
|
</section>
|
|
|
|
<p class="foot">Authorized use only · BTG / Pilot</p>
|
|
</main>
|
|
|
|
<script src="login.js"></script>
|
|
</body>
|
|
</html>
|