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>
117 lines
3.0 KiB
HTML
117 lines
3.0 KiB
HTML
<!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>
|