Phase 2: session auth for the web UI
Require login for /app/, with a long sliding session so you log in once per device and effectively stay in. - AppLoginRequiredMiddleware gates only /app/; /api/ keeps DRF token auth and /admin/ keeps its own login (a blanket LoginRequired would break token requests, whose user isn't resolved until the view runs). - Login page (styled to the dark palette) via django.contrib.auth.urls; logout control in the nav. - Session: ~1 year cookie, sliding (saved every request), survives browser close. - Dropped every @csrf_exempt now that a real session + CSRF token are in place (HTMX already sends X-CSRFToken). - SECRET_KEY and DEBUG now read from the environment (prod-safe defaults); systemd loads an optional /var/lib/food/.env. - Tests authenticate, plus new coverage: /app/ redirects when logged out, login grants access, /api/ is not caught by the app gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
073ab85f30
commit
6bad2a2ad1
@@ -409,6 +409,10 @@
|
||||
<a href="{% url 'app-shopping' %}" {% if active_tab == 'shopping' %}class="active"{% endif %}>Shopping</a>
|
||||
<a href="{% url 'app-log' %}" {% if active_tab == 'log' %}class="active"{% endif %}>Cook Log</a>
|
||||
</div>
|
||||
<form method="post" action="{% url 'logout' %}" style="margin-left: auto;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" style="background: none; border: 1px solid var(--teal-dark); color: var(--grey-light); padding: 0.4rem 0.75rem; border-radius: 4px; font-size: 0.85rem; cursor: pointer;">Log out</button>
|
||||
</form>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sign in — Kitchen</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-dark: #15131c;
|
||||
--navy: #0e0e5b;
|
||||
--teal-dark: #325664;
|
||||
--sage: #658d89;
|
||||
--red-bright: #f01111;
|
||||
--yellow: #f9df11;
|
||||
--teal-light: #87d1d1;
|
||||
--grey-light: #babcc4;
|
||||
--cream: #f7fdc7;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
background: var(--bg-dark);
|
||||
color: var(--grey-light);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
background: rgba(50, 86, 100, 0.15);
|
||||
border: 1px solid var(--teal-dark);
|
||||
border-radius: 10px;
|
||||
padding: 1.75rem 1.5rem;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
color: var(--yellow);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
color: var(--teal-light);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0 0 0.3rem;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
background: rgba(14, 14, 91, 0.3);
|
||||
border: 1px solid var(--teal-dark);
|
||||
color: var(--cream);
|
||||
padding: 0.7rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
min-height: 48px;
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: var(--yellow);
|
||||
box-shadow: 0 0 0 2px rgba(249, 223, 17, 0.15);
|
||||
}
|
||||
.field { margin-bottom: 1rem; }
|
||||
.btn {
|
||||
width: 100%;
|
||||
background: var(--yellow);
|
||||
color: var(--bg-dark);
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
min-height: 48px;
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.errors {
|
||||
background: rgba(240, 17, 17, 0.14);
|
||||
color: var(--red-bright);
|
||||
border-radius: 8px;
|
||||
padding: 0.6rem 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form class="login-card" method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
<div class="logo">🍳 Kitchen</div>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="errors">That username and password didn't match. Try again.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="field">
|
||||
<label for="id_username">Username</label>
|
||||
<input type="text" name="username" id="id_username" autocapitalize="none"
|
||||
autocomplete="username" autofocus required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="id_password">Password</label>
|
||||
<input type="password" name="password" id="id_password"
|
||||
autocomplete="current-password" required>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button type="submit" class="btn">Sign in</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user