Skip to content

Commit 60a8b38

Browse files
committed
feat(auth): Add web authentication with django-allauth
Add social login (GitHub, GitLab), passwordless login-by-code, styled login/dashboard pages, and API key management UI. - django-allauth with conditional provider registration - Tailwind CSS v4 via standalone CLI (ARM-compatible) - WhiteNoise for static file serving - Dark-themed login, code entry, dashboard, and API keys pages - setup_default_site management command (reads DAIV_EXTERNAL_URL) - Custom social account adapter for GitLab server URL separation
1 parent 89da40e commit 60a8b38

37 files changed

Lines changed: 1015 additions & 15 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Added
1515

16+
- Added web-based authentication via django-allauth with GitHub and GitLab social login, passwordless login-by-code for existing users, and a styled dark-themed login page. Includes a post-login dashboard with API key management (create, list, revoke).
17+
1618
- Added `setup_langsmith_dashboard` management command to create a pre-configured LangSmith custom dashboard with 27 monitoring charts covering trace volume, latency, cost, tool usage, model comparison, and pipeline health. Supports `--project` and `--recreate` options.
1719
- Added `model` and `thinking_level` metadata to all LangSmith traces, enabling per-model dashboards and A/B comparison when switching between models.
1820
- Added async Jobs API (`POST /api/jobs`, `GET /api/jobs/{id}`) for programmatic agent execution with configurable per-user rate limiting (`JOBS_THROTTLE_RATE`). Enables scheduled CI pipelines, Slack bots, and scripted workflows to trigger DAIV agents without blocking.

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Makefile
22

3-
.PHONY: help setup test test-ci lint lint-check lint-format lint-fix lint-typing evals
3+
.PHONY: help setup test test-ci lint lint-check lint-format lint-fix lint-typing evals tailwind-build tailwind-watch
44

55
help:
66
@echo "Available commands:"
@@ -103,5 +103,11 @@ swerebench-clean:
103103
docs-serve:
104104
uv run --only-group=docs mkdocs serve -o -a localhost:4000 -w docs/
105105

106+
tailwind-build:
107+
docker compose exec app tailwindcss -i daiv/accounts/static_src/css/input.css -o daiv/accounts/static/accounts/css/styles.css --minify
108+
109+
tailwind-watch:
110+
docker compose exec app tailwindcss -i daiv/accounts/static_src/css/input.css -o daiv/accounts/static/accounts/css/styles.css --watch
111+
106112
langsmith-fetch:
107113
uv run langsmith-fetch traces --project-uuid 00d1a04e-0087-4813-9a18-5995cd5bee5c --limit 1 ./daiv-traces

daiv/accounts/adapter.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from allauth.account.adapter import DefaultAccountAdapter
2+
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
3+
4+
5+
class AccountAdapter(DefaultAccountAdapter):
6+
def is_open_for_signup(self, request):
7+
"""Disable standard email/password signup. Users are created via social providers only."""
8+
return False
9+
10+
11+
class SocialAccountAdapter(DefaultSocialAccountAdapter):
12+
def is_open_for_signup(self, request, sociallogin):
13+
"""Allow new user creation via social providers (GitHub, GitLab)."""
14+
return True

daiv/accounts/forms.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django import forms
2+
3+
from accounts.models import APIKey
4+
5+
6+
class APIKeyCreateForm(forms.ModelForm):
7+
name = forms.CharField(max_length=128)
8+
9+
class Meta:
10+
model = APIKey
11+
fields = ["name"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from django.conf import settings
2+
from django.contrib.sites.models import Site
3+
from django.core.management.base import BaseCommand, CommandError
4+
5+
from core.conf import settings as core_settings
6+
7+
8+
class Command(BaseCommand):
9+
help = "Update the default Site domain from DAIV_EXTERNAL_URL."
10+
11+
def handle(self, *args, **options):
12+
domain = core_settings.EXTERNAL_URL.host
13+
if not domain:
14+
raise CommandError("DAIV_EXTERNAL_URL has no valid host. Check your DAIV_EXTERNAL_URL setting.")
15+
16+
rows = Site.objects.filter(pk=settings.SITE_ID).update(domain=domain, name="DAIV")
17+
if rows == 0:
18+
raise CommandError(f"Site with pk={settings.SITE_ID} does not exist. Run 'migrate' first.")
19+
20+
self.stdout.write(self.style.SUCCESS(f"Default site updated: domain={domain}, name=DAIV"))

daiv/accounts/socialaccount.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from allauth.core import context
2+
from allauth.socialaccount.adapter import get_adapter
3+
from allauth.socialaccount.providers.gitlab.views import GitLabOAuth2Adapter
4+
from allauth.socialaccount.providers.oauth2.views import OAuth2CallbackView, OAuth2LoginView
5+
6+
7+
class GitLabServerAwareAdapter(GitLabOAuth2Adapter):
8+
"""
9+
GitLab adapter that supports a separate ``gitlab_server_url`` app setting
10+
for server-side HTTP calls (token exchange, profile fetch). This is needed
11+
in Docker/compose environments where the browser-facing URL differs from
12+
the URL reachable inside the container network.
13+
14+
When ``gitlab_server_url`` is empty or absent the adapter falls back to the
15+
standard ``gitlab_url``.
16+
"""
17+
18+
def _build_server_url(self, path):
19+
app = get_adapter().get_app(context.request, provider=self.provider_id)
20+
server_url = app.settings.get("gitlab_server_url")
21+
if server_url:
22+
return f"{server_url}{path}"
23+
return self._build_url(path)
24+
25+
@property
26+
def access_token_url(self):
27+
return self._build_server_url("/oauth/token")
28+
29+
@property
30+
def profile_url(self):
31+
return self._build_server_url(f"/api/{self.provider_api_version}/user")
32+
33+
34+
oauth2_login = OAuth2LoginView.adapter_view(GitLabServerAwareAdapter)
35+
oauth2_callback = OAuth2CallbackView.adapter_view(GitLabServerAwareAdapter)

daiv/accounts/static/accounts/css/styles.css

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
238 KB
Loading
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@import "tailwindcss";
2+
3+
@theme {
4+
--font-sans: "Outfit", ui-sans-serif, system-ui, sans-serif;
5+
}
6+
7+
@keyframes fade-up {
8+
from {
9+
opacity: 0;
10+
transform: translateY(10px);
11+
}
12+
}
13+
14+
a, button, [role="button"] {
15+
cursor: pointer;
16+
}
17+
18+
.animate-fade-up {
19+
animation: fade-up 0.5s ease-out both;
20+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{% extends "base.html" %}
2+
{% load static %}
3+
4+
{% block title %}Enter code — DAIV{% endblock %}
5+
6+
{% block content %}
7+
<div class="relative flex min-h-dvh items-center justify-center overflow-hidden px-4">
8+
<div class="pointer-events-none absolute inset-0"
9+
style="background: radial-gradient(ellipse 60% 50% at 50% 40%, rgba(255,255,255,0.025) 0%, transparent 100%)"></div>
10+
<div class="pointer-events-none absolute inset-0 opacity-30"
11+
style="background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.07) 1px, transparent 0); background-size: 32px 32px"></div>
12+
13+
<div class="relative w-full max-w-[380px]">
14+
<!-- Logo -->
15+
<div class="animate-fade-up mb-10 flex flex-col items-center">
16+
<img src="{% static 'accounts/img/logo.png' %}" alt="DAIV" class="h-10">
17+
</div>
18+
19+
<!-- Icon + message -->
20+
<div class="animate-fade-up mb-8 text-center" style="animation-delay: 80ms">
21+
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.03]">
22+
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
23+
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/>
24+
</svg>
25+
</div>
26+
<h2 class="text-[16px] font-semibold text-white">Check your email</h2>
27+
<p class="mt-2 text-[14px] font-light text-gray-400">
28+
We sent a login code to your inbox.<br>Enter it below to continue.
29+
</p>
30+
</div>
31+
32+
<form method="post" class="animate-fade-up" style="animation-delay: 160ms">
33+
{% csrf_token %}
34+
<div class="space-y-3">
35+
{% for field in form %}
36+
<div>
37+
{% if field.errors %}
38+
<p class="mb-2 text-[14px] text-red-400">{{ field.errors.0 }}</p>
39+
{% endif %}
40+
<input type="text"
41+
name="{{ field.html_name }}"
42+
id="{{ field.id_for_label }}"
43+
value="{{ field.value|default:'' }}"
44+
{% if field.field.required %}required{% endif %}
45+
autocomplete="one-time-code"
46+
inputmode="numeric"
47+
placeholder="Enter code"
48+
class="block w-full rounded-xl border border-white/[0.06] bg-white/[0.03] px-4 py-3.5 text-center text-lg font-semibold tracking-[0.3em] text-white placeholder-gray-500 outline-none transition-all duration-200 focus:border-white/[0.15] focus:bg-white/[0.05] focus:ring-1 focus:ring-white/[0.08]">
49+
</div>
50+
{% endfor %}
51+
<button type="submit"
52+
class="w-full rounded-xl bg-white px-4 py-3 text-[14px] font-semibold text-[#030712] transition-all duration-200 hover:bg-gray-200 active:scale-[0.98]">
53+
Verify &amp; sign in
54+
</button>
55+
</div>
56+
</form>
57+
58+
<p class="animate-fade-up mt-6 text-center text-[14px] text-gray-500" style="animation-delay: 220ms">
59+
<a href="{% url 'account_login' %}" class="text-gray-400 transition-colors hover:text-white">Back to sign in</a>
60+
</p>
61+
</div>
62+
</div>
63+
{% endblock %}

0 commit comments

Comments
 (0)