Root cause of the 'Link invalid' onboarding link: Frappe's TemplatePage
resolves a www page's Python controller by converting hyphens to underscores
(see frappe/website/page_renderers/template_page.py set_pymodule: it looks for
'set_password.py' next to 'set-password.html'). Our controller was named
'set-password.py' (hyphen), so os.path.exists() missed it, pymodule_name stayed
None, get_context never ran over HTTP, and the template rendered with no
context -> raw {{ email }}, title 'Link invalid', token never verified. (It
worked under bench/in-process only because we called get_context directly.)
Fix: rename www/set-password.py -> www/set_password.py (route stays
/set-password, driven by the .html filename) and update the whitelisted submit
endpoint path in set-password.html to ...www.set_password.submit.
NOTE: the sibling legacy CRTC/CDR admin pages (admin-filings.py,
admin-resellers.py, cdr-*.py) have the same latent hyphen bug; left as-is since
they're outside the compliance portal, but they are silently controller-less.
124 lines
4.3 KiB
HTML
124 lines
4.3 KiB
HTML
{% extends "templates/web.html" %}
|
|
|
|
{% block page_content %}
|
|
<style>
|
|
:root { --pw-navy: #1e3a5f; --pw-blue: #2d4e78; --pw-green: #059669; }
|
|
.pw-sp-card {
|
|
max-width: 440px;
|
|
margin: 3rem auto;
|
|
background: #fff;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 12px;
|
|
padding: 2rem 2rem 2.25rem;
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
|
|
}
|
|
.pw-sp-card h1 { margin: 0 0 .5rem; color: var(--pw-navy); font-size: 1.4rem; }
|
|
.pw-sp-card p.pw-intro { margin: 0 0 1.25rem; color: #475569; font-size: .95rem; }
|
|
.pw-sp-card label { display: block; font-weight: 600; margin: 1rem 0 .4rem; color: #1f2937; }
|
|
.pw-sp-card input[type="password"] {
|
|
width: 100%; padding: .6rem .75rem; border: 1px solid #cbd5e1;
|
|
border-radius: 6px; font-size: 1rem;
|
|
}
|
|
.pw-sp-card button {
|
|
margin-top: 1.5rem; width: 100%;
|
|
background: var(--pw-green); color: #fff; border: 0;
|
|
padding: .75rem 1rem; border-radius: 6px;
|
|
font-size: 1rem; font-weight: 600; cursor: pointer;
|
|
}
|
|
.pw-sp-card button:disabled { background: #9ca3af; cursor: not-allowed; }
|
|
.pw-sp-err { color: #b91c1c; margin-top: 1rem; font-size: .9rem; }
|
|
.pw-sp-ok { color: #065f46; margin-top: 1rem; font-size: .9rem; }
|
|
.pw-sp-help { color: #64748b; font-size: .8rem; margin-top: .4rem; }
|
|
</style>
|
|
|
|
<div class="pw-sp-card">
|
|
{% if error %}
|
|
<h1>Link invalid</h1>
|
|
<p class="pw-intro">{{ error }}</p>
|
|
{% else %}
|
|
<h1>Set your password</h1>
|
|
<p class="pw-intro">
|
|
Hi — set a password for <strong>{{ email }}</strong> to view your compliance
|
|
deliverables and place follow-up orders.
|
|
{% if order_number %}
|
|
<br><span class="pw-order-id">Order: {{ order_number }}</span>
|
|
{% endif %}
|
|
</p>
|
|
|
|
<form id="pw-set-password-form" autocomplete="off">
|
|
<input type="hidden" name="token" value="{{ token }}">
|
|
<label for="pw-pass">New password</label>
|
|
<input id="pw-pass" name="password" type="password" minlength="8" required>
|
|
<div class="pw-sp-help">At least 8 characters.</div>
|
|
|
|
<label for="pw-pass2">Confirm password</label>
|
|
<input id="pw-pass2" name="password2" type="password" minlength="8" required>
|
|
|
|
<button type="submit" id="pw-sp-submit">Set password & continue</button>
|
|
<div id="pw-sp-msg"></div>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const form = document.getElementById("pw-set-password-form");
|
|
if (!form) return;
|
|
|
|
const msg = document.getElementById("pw-sp-msg");
|
|
const btn = document.getElementById("pw-sp-submit");
|
|
|
|
form.addEventListener("submit", async function (e) {
|
|
e.preventDefault();
|
|
msg.textContent = "";
|
|
msg.className = "";
|
|
|
|
const pass = form.password.value;
|
|
const pass2 = form.password2.value;
|
|
if (pass !== pass2) {
|
|
msg.className = "pw-sp-err";
|
|
msg.textContent = "Passwords do not match.";
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = "Setting password…";
|
|
|
|
try {
|
|
const resp = await fetch(
|
|
"/api/method/performancewest_erpnext.www.set_password.submit",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: new URLSearchParams({
|
|
token: form.token.value,
|
|
password: pass,
|
|
}).toString(),
|
|
credentials: "same-origin",
|
|
}
|
|
);
|
|
const data = await resp.json();
|
|
|
|
if (resp.ok && data.message && data.message.success) {
|
|
msg.className = "pw-sp-ok";
|
|
msg.textContent = "Password set — redirecting…";
|
|
window.location.href = data.message.redirect || "/orders";
|
|
} else {
|
|
const err =
|
|
(data && (data._server_messages || data.exc || data.message && data.message.error)) ||
|
|
"Could not set password.";
|
|
msg.className = "pw-sp-err";
|
|
msg.textContent = typeof err === "string" ? err : "Could not set password.";
|
|
btn.disabled = false;
|
|
btn.textContent = "Set password & continue";
|
|
}
|
|
} catch (err) {
|
|
msg.className = "pw-sp-err";
|
|
msg.textContent = "Network error. Please try again.";
|
|
btn.disabled = false;
|
|
btn.textContent = "Set password & continue";
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|