GitHub Social Login + Auth flow fixes. Password reset, email verification, etc.

This commit is contained in:
boris committed 2026-04-16 21:24:16 +01:00
1 parent f7cf22f970
commit 921b760f80
23 files changed
+1087 -228

No files matched your search

@@ -0,0 +1,8 @@
Hello {{ user.get_username }},
Verify your Keywarden email by opening this link:
{{ verification_link }}
This verification link expires in {{ expires_seconds }} seconds.
If you did not request this verification email, you can ignore it.
@@ -0,0 +1 @@
Verify your Keywarden email
@@ -82,6 +82,16 @@
>
Sign in
</button>
{% if password_reset_enabled %}
<div class="text-center">
<a
href="{% url 'accounts:password_reset' %}"
class="text-sm font-medium text-blue-700 hover:underline"
>
Forgot your password?
</a>
</div>
{% endif %}
</form>
{% if oidc_enabled or social_providers %}
@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Password Updated • Keywarden{% endblock %}
{% block content %}
<div class="mx-auto max-w-md rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Password updated</h1>
<p class="mt-3 text-sm text-gray-600">
Your Keywarden password has been changed successfully.
</p>
<a
href="{% url 'accounts:login' %}?native=1"
class="mt-6 inline-flex w-full items-center justify-center rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300"
>
Sign in
</a>
</div>
{% endblock %}
@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}Set New Password • Keywarden{% endblock %}
{% block content %}
<div class="mx-auto max-w-md rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Set a new password</h1>
{% if validlink %}
<form method="post" class="mt-6 space-y-4">
{% csrf_token %}
<div>
<label class="mb-2 block text-sm font-medium text-gray-900" for="{{ form.new_password1.id_for_label }}">New password</label>
{{ form.new_password1 }}
</div>
<div>
<label class="mb-2 block text-sm font-medium text-gray-900" for="{{ form.new_password2.id_for_label }}">Confirm password</label>
{{ form.new_password2 }}
</div>
{% if form.errors %}
<div class="rounded-lg bg-red-50 p-3 text-sm text-red-800">
Please fix the errors and try again.
</div>
{% endif %}
<button
type="submit"
class="inline-flex w-full items-center justify-center rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300"
>
Update password
</button>
</form>
{% else %}
<p class="mt-4 text-sm text-gray-600">
This password reset link is invalid or expired.
</p>
<a
href="{% url 'accounts:password_reset' %}"
class="mt-6 inline-flex w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-5 py-2.5 text-sm font-semibold text-gray-800 hover:bg-gray-100"
>
Request a new reset link
</a>
{% endif %}
</div>
{% endblock %}
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block title %}Reset Email Sent • Keywarden{% endblock %}
{% block content %}
<div class="mx-auto max-w-md rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Check your email</h1>
<p class="mt-3 text-sm text-gray-600">
If a native Keywarden account exists for the submitted email, a reset link has been sent.
</p>
<p class="mt-3 text-sm text-gray-600">
SSO accounts must reset credentials in the identity provider.
{% if idp_account_portal_url %}
<a href="{{ idp_account_portal_url }}" class="font-semibold text-blue-700 hover:underline">Open IdP portal</a>.
{% endif %}
</p>
<a
href="{% url 'accounts:login' %}?native=1"
class="mt-6 inline-flex w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-5 py-2.5 text-sm font-semibold text-gray-800 hover:bg-gray-100"
>
Return to login
</a>
</div>
{% endblock %}
@@ -0,0 +1,8 @@
Hello {{ user.get_username }},
You requested a password reset for your Keywarden account.
Reset link:
{{ protocol }}://{{ domain }}{% url 'accounts:password_reset_confirm' uidb64=uid token=token %}
If you did not request this, you can ignore this email.
@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block title %}Reset Password • Keywarden{% endblock %}
{% block content %}
<div class="mx-auto max-w-md rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Reset your password</h1>
<p class="mt-2 text-sm text-gray-500">
Enter your Keywarden account email to receive a password reset link.
</p>
<form method="post" class="mt-6 space-y-4">
{% csrf_token %}
<div>
<label class="mb-2 block text-sm font-medium text-gray-900" for="{{ form.email.id_for_label }}">Email</label>
{{ form.email }}
</div>
{% if form.errors %}
<div class="rounded-lg bg-red-50 p-3 text-sm text-red-800">
Please correct the highlighted issues and retry.
</div>
{% endif %}
<button
type="submit"
class="inline-flex w-full items-center justify-center rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300"
>
Send reset link
</button>
</form>
<p class="mt-6 text-xs text-gray-500">
Accounts managed by SSO do not use local password reset.
{% if idp_account_portal_url %}
Use your identity provider portal:
<a href="{{ idp_account_portal_url }}" class="font-semibold text-blue-700 hover:underline">{{ idp_account_portal_url }}</a>.
{% endif %}
</p>
</div>
{% endblock %}
@@ -0,0 +1 @@
Keywarden password reset
+136 -43
View File
@@ -3,6 +3,16 @@
{% block title %}Profile • Keywarden{% endblock %}
{% block content %}
{% if messages %}
<div class="mb-4 space-y-2">
{% for message in messages %}
<div class="rounded-lg border border-blue-100 bg-blue-50 px-4 py-3 text-sm text-blue-800" role="alert">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
<div class="grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(340px,1fr)] lg:items-start">
<div class="space-y-6">
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
@@ -231,6 +241,74 @@
</p>
</section>
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
<div class="space-y-2">
<h2 class="text-base font-semibold text-gray-900">Native account security</h2>
<p class="text-sm text-gray-500">Password reset and email verification for Keywarden-native credentials.</p>
</div>
{% if native_security_enabled %}
<div class="mt-4 space-y-4">
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<div class="flex items-center justify-between gap-3">
<span class="text-sm font-semibold text-gray-900">Email verification</span>
{% if native_account_security and native_account_security.email_verified %}
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-semibold text-emerald-700">Verified</span>
{% else %}
<span class="inline-flex items-center rounded-full bg-amber-100 px-2.5 py-1 text-xs font-semibold text-amber-700">Unverified</span>
{% endif %}
</div>
{% if email_verification_enabled %}
{% if native_account_security and native_account_security.email_verified %}
<p class="mt-2 text-xs text-gray-600">
Verified at {{ native_account_security.email_verified_at|date:"M j, Y H:i" }}.
</p>
{% else %}
<p class="mt-2 text-xs text-gray-600">
Send a verification link to {{ user.email }}.
</p>
<form method="post" class="mt-3">
{% csrf_token %}
<input type="hidden" name="form_type" value="send_email_verification">
<button
type="submit"
class="inline-flex items-center rounded-lg bg-blue-700 px-3 py-1.5 text-xs font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300"
>
Send verification email
</button>
</form>
{% endif %}
{% else %}
<p class="mt-2 text-xs text-gray-600">Email verification is disabled in this environment.</p>
{% endif %}
</div>
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<p class="text-sm font-semibold text-gray-900">Password reset</p>
<p class="mt-2 text-xs text-gray-600">
Reset your Keywarden-native password via secure email token flow.
</p>
<a
href="{% url 'accounts:password_reset' %}"
class="mt-3 inline-flex items-center rounded-lg border border-blue-200 bg-blue-50 px-3 py-1.5 text-xs font-semibold text-blue-700 hover:bg-blue-100"
>
Start password reset
</a>
</div>
</div>
{% else %}
<div class="mt-4 rounded-lg border border-gray-200 bg-gray-50 p-4">
<p class="text-sm text-gray-700">
Native password reset and local email verification are disabled because this account uses SSO.
</p>
<p class="mt-2 text-xs text-gray-600">
Manage password and email verification in your identity provider.
{% if idp_account_portal_url %}
<a href="{{ idp_account_portal_url }}" class="font-semibold text-blue-700 hover:underline">Open IdP account portal</a>.
{% endif %}
</p>
</div>
{% endif %}
</section>
<section class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
<div class="space-y-2">
<h2 class="text-base font-semibold text-gray-900">Single Sign-On</h2>
@@ -309,53 +387,68 @@
<article class="overflow-hidden rounded-xl border border-gray-200">
<div class="flex items-center justify-between bg-gray-50 px-4 py-3">
<h3 class="text-sm font-semibold text-gray-900">Social Providers</h3>
<span class="text-xs text-gray-500">GitHub, Google, Apple and more</span>
<h3 class="text-sm font-semibold text-gray-900">GitHub</h3>
{% if github_identity %}
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700">Linked</span>
{% elif github_social_configured %}
<span class="inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-700">Ready to link</span>
{% else %}
<span class="inline-flex items-center rounded-full bg-gray-200 px-2 py-0.5 text-[11px] font-semibold text-gray-700">Unavailable</span>
{% endif %}
</div>
<div class="divide-y divide-gray-100">
{% for provider in social_provider_cards %}
<div class="flex flex-wrap items-center justify-between gap-3 px-4 py-3">
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900">{{ provider.name }}</p>
{% if provider.linked and provider.identity %}
<p class="text-xs text-gray-600">
Linked as {{ provider.identity.email_at_link }}
{% if provider.identity.last_login_at %}• last login {{ provider.identity.last_login_at|date:"M j, Y H:i" }}{% endif %}
</p>
{% elif provider.configured %}
<p class="text-xs text-gray-600">Configured and ready to link.</p>
{% else %}
<p class="text-xs text-gray-500">Placeholder. Configure provider credentials to enable linking.</p>
{% endif %}
<div class="space-y-3 p-4">
{% if github_identity %}
<dl class="grid grid-cols-1 gap-2 text-xs text-gray-600">
<div class="grid grid-cols-[96px,1fr] items-start gap-2">
<dt class="font-semibold text-gray-700">Linked email</dt>
<dd>{{ github_identity.email_at_link }}</dd>
</div>
<div class="flex items-center gap-2">
{% if provider.linked %}
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700">Linked</span>
{% elif provider.configured %}
<span class="inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-700">Ready</span>
{% else %}
<span class="inline-flex items-center rounded-full bg-gray-200 px-2 py-0.5 text-[11px] font-semibold text-gray-700">Placeholder</span>
{% endif %}
{% if provider.configured %}
<a
href="{{ provider.login_url }}"
class="inline-flex items-center rounded-lg bg-blue-700 px-3 py-1.5 text-xs font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300"
>
{% if provider.linked %}Re-link{% else %}Link{% endif %}
</a>
{% else %}
<button
type="button"
disabled
class="inline-flex cursor-not-allowed items-center rounded-lg border border-gray-200 bg-gray-100 px-3 py-1.5 text-xs font-semibold text-gray-500"
>
Configure
</button>
{% endif %}
<div class="grid grid-cols-[96px,1fr] items-start gap-2">
<dt class="font-semibold text-gray-700">Subject</dt>
<dd class="break-all font-mono">{{ github_identity.subject }}</dd>
</div>
{% if github_identity.last_login_at %}
<div class="grid grid-cols-[96px,1fr] items-start gap-2">
<dt class="font-semibold text-gray-700">Last login</dt>
<dd>{{ github_identity.last_login_at|date:"M j, Y H:i" }}</dd>
</div>
{% endif %}
</dl>
<div class="flex flex-wrap items-center gap-2">
{% if github_social_connect_url %}
<a
href="{{ github_social_connect_url }}"
class="inline-flex items-center rounded-lg bg-blue-700 px-3 py-1.5 text-xs font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300"
>
Re-link GitHub
</a>
{% endif %}
<form method="post" class="inline-flex">
{% csrf_token %}
<input type="hidden" name="form_type" value="unlink_github">
<button
type="submit"
class="inline-flex items-center rounded-lg border border-rose-200 bg-rose-50 px-3 py-1.5 text-xs font-semibold text-rose-700 hover:bg-rose-100 focus:outline-none focus:ring-2 focus:ring-rose-300"
>
Unlink GitHub
</button>
</form>
</div>
{% endfor %}
{% elif github_social_configured %}
<p class="text-sm text-gray-600">
Link GitHub while signed in. GitHub login is only available after linking to an existing Keywarden account.
</p>
<a
href="{{ github_social_connect_url }}"
class="inline-flex items-center rounded-lg bg-blue-700 px-3 py-1.5 text-xs font-semibold text-white hover:bg-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300"
>
Link GitHub
</a>
{% else %}
<p class="text-sm text-gray-600">
GitHub authentication is not configured in this environment.
</p>
{% endif %}
</div>
</article>
</div>