onboarding: reveal temp password on demand
This commit is contained in:
parent
3ff868a3ed
commit
7893c787b8
@ -694,6 +694,9 @@ def register(app) -> None:
|
||||
code = (payload.get("request_code") or payload.get("code") or "").strip()
|
||||
if not code:
|
||||
return jsonify({"error": "request_code is required"}), 400
|
||||
reveal_initial_password = bool(
|
||||
payload.get("reveal_initial_password") or payload.get("reveal_password")
|
||||
)
|
||||
|
||||
if not rate_limit_allow(
|
||||
f"{ip}:{code}",
|
||||
@ -852,11 +855,12 @@ def register(app) -> None:
|
||||
response["tasks"] = tasks
|
||||
response["automation_complete"] = provision_tasks_complete(conn, code)
|
||||
response["blocked"] = blocked
|
||||
if status in {"awaiting_onboarding", "ready"}:
|
||||
if status in {"awaiting_onboarding", "ready"} and reveal_initial_password:
|
||||
password = row.get("initial_password")
|
||||
revealed_at = row.get("initial_password_revealed_at")
|
||||
if isinstance(password, str) and password and revealed_at is None:
|
||||
if isinstance(password, str) and password:
|
||||
response["initial_password"] = password
|
||||
if revealed_at is None:
|
||||
conn.execute(
|
||||
"UPDATE access_requests SET initial_password_revealed_at = NOW() WHERE request_code = %s AND initial_password_revealed_at IS NULL",
|
||||
(code,),
|
||||
|
||||
@ -226,3 +226,49 @@ class AccessRequestTests(TestCase):
|
||||
data = resp.get_json()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTrue(data.get("email_verified"))
|
||||
|
||||
def test_status_hides_initial_password_without_reveal_flag(self):
|
||||
rows = {
|
||||
"SELECT status": {
|
||||
"status": "awaiting_onboarding",
|
||||
"username": "alice",
|
||||
"initial_password": "temp-pass",
|
||||
"initial_password_revealed_at": None,
|
||||
"email_verified_at": None,
|
||||
}
|
||||
}
|
||||
with (
|
||||
mock.patch.object(ar, "connect", lambda: dummy_connect(rows)),
|
||||
mock.patch.object(ar, "_advance_status", lambda *args, **kwargs: "awaiting_onboarding"),
|
||||
):
|
||||
resp = self.client.post(
|
||||
"/api/access/request/status",
|
||||
data=json.dumps({"request_code": "alice~CODE123"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
data = resp.get_json()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertIsNone(data.get("initial_password"))
|
||||
|
||||
def test_status_reveals_initial_password_with_flag(self):
|
||||
rows = {
|
||||
"SELECT status": {
|
||||
"status": "awaiting_onboarding",
|
||||
"username": "alice",
|
||||
"initial_password": "temp-pass",
|
||||
"initial_password_revealed_at": None,
|
||||
"email_verified_at": None,
|
||||
}
|
||||
}
|
||||
with (
|
||||
mock.patch.object(ar, "connect", lambda: dummy_connect(rows)),
|
||||
mock.patch.object(ar, "_advance_status", lambda *args, **kwargs: "awaiting_onboarding"),
|
||||
):
|
||||
resp = self.client.post(
|
||||
"/api/access/request/status",
|
||||
data=json.dumps({"request_code": "alice~CODE123", "reveal_initial_password": True}),
|
||||
content_type="application/json",
|
||||
)
|
||||
data = resp.get_json()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(data.get("initial_password"), "temp-pass")
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
<div class="stepper-title">{{ index + 1 }}. {{ section.title }}</div>
|
||||
<div class="stepper-meta">
|
||||
<span class="pill mono" :class="sectionPillClass(section)">{{ sectionStatusLabel(section) }}</span>
|
||||
<span class="mono">{{ sectionProgress(section) }}</span>
|
||||
<span class="pill mono pill-compact">{{ sectionProgress(section) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@ -149,11 +149,6 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="!auth.authenticated" class="login-callout">
|
||||
<p class="muted">Log in to check off onboarding steps and verify your Element recovery key.</p>
|
||||
<button class="primary" type="button" @click="loginToContinue" :disabled="loading">Log in</button>
|
||||
</div>
|
||||
|
||||
<div class="section-shell" v-if="activeSection">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
@ -294,7 +289,7 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { auth, authFetch, login } from "../auth";
|
||||
import { auth, authFetch } from "../auth";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@ -764,7 +759,10 @@ async function check() {
|
||||
const resp = await fetch("/api/access/request/status", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request_code: requestCode.value.trim() }),
|
||||
body: JSON.stringify({
|
||||
request_code: requestCode.value.trim(),
|
||||
reveal_initial_password: true,
|
||||
}),
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (!resp.ok) throw new Error(data.error || resp.statusText || `status ${resp.status}`);
|
||||
@ -825,11 +823,6 @@ function copyUsername() {
|
||||
copyText(requestUsername.value, (value) => (usernameCopied.value = value));
|
||||
}
|
||||
|
||||
async function loginToContinue() {
|
||||
const hint = requestUsername.value.trim() || requestCode.value.split("~", 1)[0] || "";
|
||||
await login(`/onboarding?code=${encodeURIComponent(requestCode.value.trim())}`, hint);
|
||||
}
|
||||
|
||||
async function toggleStep(stepId, event) {
|
||||
const checked = Boolean(event?.target?.checked);
|
||||
if (!auth.authenticated) {
|
||||
@ -1118,15 +1111,14 @@ button.secondary {
|
||||
.section-stepper {
|
||||
margin: 16px 0 18px;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stepper-item {
|
||||
flex: 1 1 180px;
|
||||
min-width: 170px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stepper-card {
|
||||
@ -1143,17 +1135,6 @@ button.secondary {
|
||||
}
|
||||
|
||||
.stepper-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
right: -12px;
|
||||
top: 18px;
|
||||
height: 2px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.stepper-item:last-child .stepper-card::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -1183,10 +1164,20 @@ button.secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 6px 10px;
|
||||
flex-wrap: wrap;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.pill-compact {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.stepper-card.active {
|
||||
border-color: rgba(125, 208, 255, 0.5);
|
||||
box-shadow: 0 0 0 1px rgba(79, 139, 255, 0.3);
|
||||
@ -1211,9 +1202,21 @@ button.secondary {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.section-stepper {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.stepper-card::after {
|
||||
display: none;
|
||||
.section-stepper {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.section-stepper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1247,18 +1250,6 @@ button.secondary {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-callout {
|
||||
margin-top: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.section-shell {
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user