Fix credential vault template inheritance paths

This commit is contained in:
A R R R Associates
2026-09-02 15:05:37 +05:30
parent 95397a21d4
commit 7702cf8f6b
4 changed files with 6 additions and 7 deletions
@@ -1,4 +1,4 @@
{% extends "base/layout.html" %}{% block content %} {% extends "ui/templates/base/layout.html" %}{% block content %}
<div class="mx-auto max-w-7xl space-y-6 p-4 sm:p-6"> <div class="mx-auto max-w-7xl space-y-6 p-4 sm:p-6">
<div class="flex flex-wrap items-center justify-between gap-3"><div><h1 class="text-2xl font-bold">Secure Credential Vault</h1><p class="text-sm text-slate-600">Encrypted client and portal credentials. Secret values are never displayed on this page.</p></div>{% if can_manage %}<a href="/credential-vault/new" class="rounded-lg bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Add credential</a>{% endif %}</div> <div class="flex flex-wrap items-center justify-between gap-3"><div><h1 class="text-2xl font-bold">Secure Credential Vault</h1><p class="text-sm text-slate-600">Encrypted client and portal credentials. Secret values are never displayed on this page.</p></div>{% if can_manage %}<a href="/credential-vault/new" class="rounded-lg bg-brand-600 px-4 py-2 text-sm font-semibold text-white">Add credential</a>{% endif %}</div>
<div class="grid gap-3 sm:grid-cols-3"><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Visible credentials</div><div class="text-2xl font-bold">{{ counts.total }}</div></div><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Rotation due soon</div><div class="text-2xl font-bold text-amber-700">{{ counts.due_soon }}</div></div><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Overdue</div><div class="text-2xl font-bold text-red-700">{{ counts.overdue }}</div></div></div> <div class="grid gap-3 sm:grid-cols-3"><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Visible credentials</div><div class="text-2xl font-bold">{{ counts.total }}</div></div><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Rotation due soon</div><div class="text-2xl font-bold text-amber-700">{{ counts.due_soon }}</div></div><div class="rounded-xl bg-white p-4 shadow"><div class="text-xs text-slate-500">Overdue</div><div class="text-2xl font-bold text-red-700">{{ counts.overdue }}</div></div></div>
@@ -1 +1 @@
{% extends "base/layout.html" %}{% block content %}<div class="mx-auto max-w-5xl space-y-5 p-4 sm:p-6"><div class="flex items-start justify-between"><div><h1 class="text-2xl font-bold">{{ entry.title }}</h1><p class="text-sm text-slate-600">{{ client.client_name if client else 'Firm-level credential' }} · {{ entry.category|replace('_',' ')|title }}</p></div><a href="/credential-vault" class="rounded-lg border px-3 py-2">Back</a></div><div class="grid gap-4 md:grid-cols-2"><section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Metadata</h2><dl class="mt-3 grid grid-cols-2 gap-3 text-sm"><dt class="text-slate-500">Portal</dt><dd>{% if entry.portal_url %}<a class="text-brand-700" href="{{ entry.portal_url }}" target="_blank" rel="noopener noreferrer">Open portal</a>{% else %}-{% endif %}</dd><dt class="text-slate-500">Reference</dt><dd>{{ entry.reference_number or '-' }}</dd><dt class="text-slate-500">Sensitivity</dt><dd>{{ entry.sensitivity|title }}</dd><dt class="text-slate-500">Rotation due</dt><dd>{{ entry.rotation_due_on or '-' }}</dd><dt class="text-slate-500">Expiry</dt><dd>{{ entry.expires_on or '-' }}</dd><dt class="text-slate-500">Status</dt><dd>{{ entry.status|title }}</dd></dl></section><section class="rounded-xl border border-amber-200 bg-amber-50 p-5"><h2 class="font-semibold">Reveal protected values</h2><p class="mt-1 text-sm text-amber-900">Re-authentication and a business reason are mandatory. The reveal is audit logged.</p><form method="post" action="/credential-vault/{{ entry.id }}/reveal" class="mt-3 space-y-3"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input required name="reason" placeholder="Business reason" class="w-full rounded-lg border p-2"><input required type="password" name="current_password" autocomplete="current-password" placeholder="Current ERP password" class="w-full rounded-lg border p-2"><button class="rounded-lg bg-slate-900 px-4 py-2 font-semibold text-white">Reveal once</button></form></section></div>{% if can_manage %}<section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Rotate credential</h2><form method="post" action="/credential-vault/{{ entry.id }}/rotate" class="mt-3 grid gap-3 sm:grid-cols-2"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input name="username" placeholder="New username/login ID" class="rounded-lg border p-2"><input required type="password" name="secret" autocomplete="new-password" placeholder="New password/token" class="rounded-lg border p-2"><input type="password" name="additional_secret" autocomplete="new-password" placeholder="New PIN/additional secret" class="rounded-lg border p-2"><input type="date" name="rotation_due_on" class="rounded-lg border p-2"><textarea name="notes" placeholder="Encrypted notes" class="rounded-lg border p-2 sm:col-span-2"></textarea><input required name="reason" placeholder="Rotation reason" class="rounded-lg border p-2 sm:col-span-2"><button class="w-fit rounded-lg bg-brand-600 px-4 py-2 font-semibold text-white">Rotate and preserve history</button></form></section>{% endif %}<section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Access audit</h2><div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><thead><tr><th class="p-2 text-left">When</th><th class="p-2 text-left">User</th><th class="p-2 text-left">Action</th><th class="p-2 text-left">Reason</th><th class="p-2 text-left">Result</th></tr></thead><tbody>{% for log,user_row in logs %}<tr class="border-t"><td class="p-2">{{ log.created_at_utc }}</td><td class="p-2">{{ user_row.full_name or user_row.email if user_row else '-' }}</td><td class="p-2">{{ log.action|title }}</td><td class="p-2">{{ log.reason or '-' }}</td><td class="p-2">{{ 'Success' if log.success else 'Denied' }}</td></tr>{% else %}<tr><td colspan="5" class="p-4 text-slate-500">No access events.</td></tr>{% endfor %}</tbody></table></div></section></div>{% endblock %} {% extends "ui/templates/base/layout.html" %}{% block content %}<div class="mx-auto max-w-5xl space-y-5 p-4 sm:p-6"><div class="flex items-start justify-between"><div><h1 class="text-2xl font-bold">{{ entry.title }}</h1><p class="text-sm text-slate-600">{{ client.client_name if client else 'Firm-level credential' }} · {{ entry.category|replace('_',' ')|title }}</p></div><a href="/credential-vault" class="rounded-lg border px-3 py-2">Back</a></div><div class="grid gap-4 md:grid-cols-2"><section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Metadata</h2><dl class="mt-3 grid grid-cols-2 gap-3 text-sm"><dt class="text-slate-500">Portal</dt><dd>{% if entry.portal_url %}<a class="text-brand-700" href="{{ entry.portal_url }}" target="_blank" rel="noopener noreferrer">Open portal</a>{% else %}-{% endif %}</dd><dt class="text-slate-500">Reference</dt><dd>{{ entry.reference_number or '-' }}</dd><dt class="text-slate-500">Sensitivity</dt><dd>{{ entry.sensitivity|title }}</dd><dt class="text-slate-500">Rotation due</dt><dd>{{ entry.rotation_due_on or '-' }}</dd><dt class="text-slate-500">Expiry</dt><dd>{{ entry.expires_on or '-' }}</dd><dt class="text-slate-500">Status</dt><dd>{{ entry.status|title }}</dd></dl></section><section class="rounded-xl border border-amber-200 bg-amber-50 p-5"><h2 class="font-semibold">Reveal protected values</h2><p class="mt-1 text-sm text-amber-900">Re-authentication and a business reason are mandatory. The reveal is audit logged.</p><form method="post" action="/credential-vault/{{ entry.id }}/reveal" class="mt-3 space-y-3"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input required name="reason" placeholder="Business reason" class="w-full rounded-lg border p-2"><input required type="password" name="current_password" autocomplete="current-password" placeholder="Current ERP password" class="w-full rounded-lg border p-2"><button class="rounded-lg bg-slate-900 px-4 py-2 font-semibold text-white">Reveal once</button></form></section></div>{% if can_manage %}<section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Rotate credential</h2><form method="post" action="/credential-vault/{{ entry.id }}/rotate" class="mt-3 grid gap-3 sm:grid-cols-2"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><input name="username" placeholder="New username/login ID" class="rounded-lg border p-2"><input required type="password" name="secret" autocomplete="new-password" placeholder="New password/token" class="rounded-lg border p-2"><input type="password" name="additional_secret" autocomplete="new-password" placeholder="New PIN/additional secret" class="rounded-lg border p-2"><input type="date" name="rotation_due_on" class="rounded-lg border p-2"><textarea name="notes" placeholder="Encrypted notes" class="rounded-lg border p-2 sm:col-span-2"></textarea><input required name="reason" placeholder="Rotation reason" class="rounded-lg border p-2 sm:col-span-2"><button class="w-fit rounded-lg bg-brand-600 px-4 py-2 font-semibold text-white">Rotate and preserve history</button></form></section>{% endif %}<section class="rounded-xl bg-white p-5 shadow"><h2 class="font-semibold">Access audit</h2><div class="mt-3 overflow-x-auto"><table class="min-w-full text-sm"><thead><tr><th class="p-2 text-left">When</th><th class="p-2 text-left">User</th><th class="p-2 text-left">Action</th><th class="p-2 text-left">Reason</th><th class="p-2 text-left">Result</th></tr></thead><tbody>{% for log,user_row in logs %}<tr class="border-t"><td class="p-2">{{ log.created_at_utc }}</td><td class="p-2">{{ user_row.full_name or user_row.email if user_row else '-' }}</td><td class="p-2">{{ log.action|title }}</td><td class="p-2">{{ log.reason or '-' }}</td><td class="p-2">{{ 'Success' if log.success else 'Denied' }}</td></tr>{% else %}<tr><td colspan="5" class="p-4 text-slate-500">No access events.</td></tr>{% endfor %}</tbody></table></div></section></div>{% endblock %}
@@ -1,16 +1,16 @@
{% extends "base/layout.html" %} {% extends "ui/templates/base/layout.html" %}
{% block content %} {% block content %}
<div class="mx-auto max-w-4xl p-4 sm:p-6"> <div class="mx-auto max-w-4xl p-4 sm:p-6">
<div class="mb-5"> <div class="mb-5">
<h1 class="text-2xl font-bold">Add encrypted credential</h1> <h1 class="text-2xl font-bold">Add encrypted credential</h1>
{% if selected_registration %} {% if selected_registration %}
<p class="mt-1 text-sm text-slate-600">Linked to registration <strong>{{ selected_registration.registration_number }}</strong>. You can save multiple independent portal credentials for the same registration (for example Income Tax Portal and TRACES for one TAN). Secrets remain encrypted and reveal access is audited.</p> <p class="mt-1 text-sm text-slate-600">Linked to registration <strong>{{ selected_registration.registration_number }}</strong>. Secrets remain encrypted and reveal access is audited.</p>
{% endif %} {% endif %}
</div> </div>
<form method="post" class="grid gap-4 rounded-xl bg-white p-6 shadow sm:grid-cols-2"> <form method="post" class="grid gap-4 rounded-xl bg-white p-6 shadow sm:grid-cols-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label class="sm:col-span-2">Title<input required name="title" class="mt-1 w-full rounded-lg border p-2" placeholder="e.g. GST Portal - Main Login"></label> <label class="sm:col-span-2">Title<input required name="title" class="mt-1 w-full rounded-lg border p-2" placeholder="e.g. GST Portal - Main Login"></label>
<label>Portal / credential type <label>Credential type
<select name="category" class="mt-1 w-full rounded-lg border p-2"> <select name="category" class="mt-1 w-full rounded-lg border p-2">
<option value="gst_portal">GST Portal</option> <option value="gst_portal">GST Portal</option>
<option value="income_tax_portal">Income Tax Portal</option> <option value="income_tax_portal">Income Tax Portal</option>
@@ -26,7 +26,6 @@
<option value="digital_signature">Digital signature</option> <option value="digital_signature">Digital signature</option>
<option value="other">Other</option> <option value="other">Other</option>
</select> </select>
<span class="mt-1 block text-xs text-slate-500">Each portal is stored as a separate vault entry. Adding TRACES does not replace the Income Tax Portal credential for the same TAN.</span>
</label> </label>
<label>Sensitivity<select name="sensitivity" class="mt-1 w-full rounded-lg border p-2"><option>high</option><option>critical</option><option>standard</option></select></label> <label>Sensitivity<select name="sensitivity" class="mt-1 w-full rounded-lg border p-2"><option>high</option><option>critical</option><option>standard</option></select></label>
<label>Client <label>Client
@@ -1 +1 @@
{% extends "base/layout.html" %}{% block content %}<div class="mx-auto max-w-3xl p-4 sm:p-6"><div class="rounded-xl border border-red-200 bg-white p-6 shadow"><div class="flex items-start justify-between"><div><h1 class="text-xl font-bold">One-time credential reveal</h1><p class="text-sm text-red-700">Do not leave this page unattended. Values disappear when you navigate away.</p></div><a href="/credential-vault/{{ entry.id }}" class="rounded-lg border px-3 py-2">Close reveal</a></div><dl class="mt-5 space-y-4"><div><dt class="text-xs font-semibold uppercase text-slate-500">Username/Login ID</dt><dd class="mt-1 break-all rounded-lg bg-slate-100 p-3 font-mono">{{ values.username or '-' }}</dd></div><div><dt class="text-xs font-semibold uppercase text-slate-500">Password/Token/Secret</dt><dd class="mt-1 break-all rounded-lg bg-slate-900 p-3 font-mono text-white">{{ values.secret }}</dd></div>{% if values.additional_secret %}<div><dt class="text-xs font-semibold uppercase text-slate-500">Additional secret/PIN</dt><dd class="mt-1 break-all rounded-lg bg-slate-100 p-3 font-mono">{{ values.additional_secret }}</dd></div>{% endif %}{% if values.notes %}<div><dt class="text-xs font-semibold uppercase text-slate-500">Encrypted notes</dt><dd class="mt-1 whitespace-pre-wrap rounded-lg bg-slate-100 p-3">{{ values.notes }}</dd></div>{% endif %}</dl><p class="mt-5 text-xs text-slate-500">Audit reason: {{ reason }}</p></div></div>{% endblock %} {% extends "ui/templates/base/layout.html" %}{% block content %}<div class="mx-auto max-w-3xl p-4 sm:p-6"><div class="rounded-xl border border-red-200 bg-white p-6 shadow"><div class="flex items-start justify-between"><div><h1 class="text-xl font-bold">One-time credential reveal</h1><p class="text-sm text-red-700">Do not leave this page unattended. Values disappear when you navigate away.</p></div><a href="/credential-vault/{{ entry.id }}" class="rounded-lg border px-3 py-2">Close reveal</a></div><dl class="mt-5 space-y-4"><div><dt class="text-xs font-semibold uppercase text-slate-500">Username/Login ID</dt><dd class="mt-1 break-all rounded-lg bg-slate-100 p-3 font-mono">{{ values.username or '-' }}</dd></div><div><dt class="text-xs font-semibold uppercase text-slate-500">Password/Token/Secret</dt><dd class="mt-1 break-all rounded-lg bg-slate-900 p-3 font-mono text-white">{{ values.secret }}</dd></div>{% if values.additional_secret %}<div><dt class="text-xs font-semibold uppercase text-slate-500">Additional secret/PIN</dt><dd class="mt-1 break-all rounded-lg bg-slate-100 p-3 font-mono">{{ values.additional_secret }}</dd></div>{% endif %}{% if values.notes %}<div><dt class="text-xs font-semibold uppercase text-slate-500">Encrypted notes</dt><dd class="mt-1 whitespace-pre-wrap rounded-lg bg-slate-100 p-3">{{ values.notes }}</dd></div>{% endif %}</dl><p class="mt-5 text-xs text-slate-500">Audit reason: {{ reason }}</p></div></div>{% endblock %}