This commit is contained in:
2025-11-11 15:51:54 +00:00
parent 99ae905cb0
commit 3e0c5d2ecc
114 changed files with 9278 additions and 929 deletions

View File

@@ -1,37 +1,3 @@
from django.contrib import admin
from unfold.admin import ModelAdmin
from unfold.sections import TableSection, TemplateSection
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from .models import Account
def __str__(self):
return f"{self.firstname} {self.lastname}"
# # Register your models here.
# admin.site.register(Account)
# Table for related records
class CustomTableSection(TableSection):
verbose_name = _("Keywarden Users") # Displays custom table title
height = 300 # Force the table height. Ideal for large amount of records
# related_name = "related_name_set" # Related model field name
fields = ["id", "firstname", "lastname", "joined_date"] # Fields from related model
# # Custom field
# def custom_field(self, instance):
# return instance.pk
# # Simple template with custom content
# class CardSection(TemplateSection):
# template_name = "keywarden/some_template.html"
@admin.register(Account)
class SomeAdmin(ModelAdmin):
list_sections = [
#CardSection,
CustomTableSection,
]
#
# No custom models registered in accounts app. The legacy Account model has been removed.

View File

@@ -0,0 +1,18 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0002_rename_accounts_account"),
]
operations = [
migrations.AlterField(
model_name="account",
name="email",
field=models.EmailField(max_length=254, unique=True),
),
]

View File

@@ -0,0 +1,16 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounts", "0003_alter_account_email"),
]
operations = [
migrations.DeleteModel(
name="Account",
),
]

View File

@@ -1,7 +1,3 @@
from django.db import models
class Account(models.Model):
firstname = models.CharField(max_length=255)
lastname = models.CharField(max_length=255)
email = models.CharField(max_length=255)
joined_date = models.DateField(null=True)
#
# Legacy Account model has been removed. This app now contains URLs/views only.

View File

@@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block title %}Sign in • Keywarden{% endblock %}
{% block content %}
<div class="mx-auto max-w-md">
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="mb-6 text-xl font-semibold tracking-tight text-gray-900">Sign in</h1>
<form method="post" class="space-y-4">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'accounts:profile' %}">
<div class="space-y-1.5">
<label class="block text-sm font-medium text-gray-700">Username</label>
<input type="text" name="username" autocomplete="username" required class="block w-full rounded-md border-gray-300 shadow-sm focus:border-purple-600 focus:ring-purple-600">
</div>
<div class="space-y-1.5">
<label class="block text-sm font-medium text-gray-700">Password</label>
<input type="password" name="password" autocomplete="current-password" required class="block w-full rounded-md border-gray-300 shadow-sm focus:border-purple-600 focus:ring-purple-600">
</div>
{% if form.errors %}
<p class="text-sm text-red-600">Please check your username and password.</p>
{% endif %}
<div class="pt-2">
<button type="submit" class="inline-flex w-full items-center justify-center rounded-md bg-purple-600 px-4 py-2.5 text-sm font-semibold text-white shadow hover:bg-purple-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-purple-600">
Sign in
</button>
</div>
</form>
<div class="mt-6 border-t border-gray-200 pt-6">
<p class="text-sm text-gray-600">
Or, if configured, use
<a href="/oidc/authenticate/" class="font-medium text-purple-700 hover:text-purple-800">OIDC login</a>.
</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}Profile • Keywarden{% endblock %}
{% block content %}
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div>
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h1 class="mb-6 text-xl font-semibold tracking-tight text-gray-900">Your Profile</h1>
<dl class="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
<div>
<dt class="text-sm font-medium text-gray-500">Username</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.username }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Email</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.email }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">First name</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.first_name|default:"—" }}</dd>
</div>
<div>
<dt class="text-sm font-medium text-gray-500">Last name</dt>
<dd class="mt-1 text-sm text-gray-900">{{ user.last_name|default:"—" }}</dd>
</div>
</dl>
</div>
</div>
<div>
<div class="rounded-xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8">
<h2 class="mb-4 text-base font-semibold tracking-tight text-gray-900">Single Sign-On</h2>
{% if auth_mode == "hybrid" %}
<div class="mt-6 border-t border-gray-200 pt-6">
<p class="text-sm text-gray-600">
Optional: Link your account with your identity provider for single sign-on.
<a href="/oidc/authenticate/" class="font-medium text-purple-700 hover:text-purple-800">Link with SSO</a>
</p>
</div>
{% elif auth_mode == "oidc" %}
<p class="text-sm text-gray-600">OIDC is required. Sign-in is managed by your identity provider.</p>
{% else %}
<p class="text-sm text-gray-600">OIDC is disabled. You are using native authentication.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
from django.urls import path
from . import views
app_name = "accounts"
urlpatterns = [
path("login/", views.login_view, name="login"),
path("logout/", views.logout_view, name="logout"),
path("profile/", views.profile, name="profile"),
]

View File

@@ -0,0 +1,29 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth import views as auth_views
from django.contrib.auth import logout
@login_required(login_url="/accounts/login/")
def profile(request):
context = {
"user": request.user,
"auth_mode": getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid"),
}
return render(request, "accounts/profile.html", context)
def login_view(request):
auth_mode = getattr(settings, "KEYWARDEN_AUTH_MODE", "hybrid")
if auth_mode == "oidc":
return redirect("/oidc/authenticate/")
# native or hybrid -> render Django's built-in login view
return auth_views.LoginView.as_view(template_name="accounts/login.html")(request)
def logout_view(request):
logout(request)
return redirect(getattr(settings, "LOGOUT_REDIRECT_URL", "/"))