Skip to content

Commit b3188fe

Browse files
authored
Merge pull request #17 from virtUOS/feat/template-auth-0.2
Template: auth 0.2.0 + ui 0.7.0 — basicbar_auth.urls, whoami_payload, Discovery-Fail-fast
2 parents 97d36ce + 7a63fb9 commit b3188fe

7 files changed

Lines changed: 42 additions & 88 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ Uhren zwischen IdP und Tool ggf. `OIDC_BACKCHANNEL_MAX_AGE` erhöhen.
142142

143143
### Template
144144

145+
- **Pins:** basicbar-auth `auth/v0.2.0`, @basicbar/ui `ui/v0.7.0`.
146+
- `config/urls.py` nutzt `include("basicbar_auth.urls")` (OIDC-Routen inkl.
147+
tolerantem Callback, `api/whoami/language/`); `accounts/views.py` enthält
148+
nur noch `whoami` als `{**whoami_payload(request), …Feature-Flags}` —
149+
`logout_view`/`set_language` kommen aus dem Paket. `accounts.User` hat kein
150+
eigenes `language` mehr (geerbt, Migration unverändert).
151+
- Settings brechen produktiv mit `ImproperlyConfigured` ab, wenn
152+
`OIDC_OP_ISSUER` gesetzt ist, die Discovery aber keine Endpunkte lieferte
153+
(sonst scheitert jeder Login später mit unklarem Fehler).
145154
- `REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES` nur noch
146155
`SessionAuthentication` — DRFs Default aktiviert `BasicAuthentication`, die
147156
mit dem Break-glass-Superuser (ModelBackend) jeden Endpunkt für

‎template/project/backend/accounts/models.py‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@
88
normal migrations.
99
"""
1010
from basicbar_auth.models import AbstractBasicUser
11-
from django.db import models
1211

1312

1413
class User(AbstractBasicUser):
15-
"""Application user (``subject``/``claims`` come from AbstractBasicUser)."""
16-
17-
# Preferred UI language ("en"/"de"), set from the SPA; blank = site default.
18-
language = models.CharField(max_length=10, blank=True)
14+
"""Application user (``subject``, ``claims`` and the UI ``language`` come
15+
from AbstractBasicUser)."""
1916

2017
def __str__(self):
2118
return self.get_username()
Lines changed: 15 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,29 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2026 Universität Osnabrück (virtUOS)
33

4-
"""Session/identity endpoints for the SPA."""
5-
import json
4+
"""Session/identity endpoints for the SPA.
65
7-
from basicbar_auth.oidc import provider_logout_url
6+
``logout_view`` and ``set_language`` come from basicbar-auth (wired by
7+
``basicbar_auth.urls``); only ``whoami`` is the tool's own, because it adds
8+
the tool's feature flags to the shared payload.
9+
"""
10+
from basicbar_auth.views import whoami_payload
811
from basicbar_integrations import ai, translation_service
912
from django.conf import settings
10-
from django.contrib.auth import logout as django_logout
1113
from django.http import JsonResponse
12-
from django.middleware.csrf import get_token
13-
from django.shortcuts import redirect
14-
from django.views.decorators.http import require_POST
1514

1615

1716
def whoami(request):
18-
"""Return the current session user (for the SPA to check login state).
19-
20-
Also returns the CSRF token in the body: ``get_token`` sets the cookie
21-
*and* hands the SPA an authoritative token, so unsafe requests work even
22-
cross-origin in dev, where reading the cookie from JavaScript can be
23-
unreliable (production is same-origin behind Caddy)."""
24-
csrf_token = get_token(request)
25-
user = request.user
26-
# Content-i18n config: the default/canonical authoring language and
27-
# whether machine-translation drafts are available, so the SPA can
28-
# decide which language to show/edit without a second round-trip.
29-
common = {
30-
"csrf_token": csrf_token,
31-
"ai_enabled": ai.is_enabled(),
32-
"content_default_language": settings.MODELTRANSLATION_DEFAULT_LANGUAGE,
33-
"content_translation_enabled": translation_service.is_enabled(),
34-
}
35-
if not user.is_authenticated:
36-
return JsonResponse({"authenticated": False, **common})
17+
"""Return the current session user (for the SPA to check login state),
18+
plus the feature flags the SPA needs before its first real request."""
3719
return JsonResponse(
3820
{
39-
"authenticated": True,
40-
"username": user.get_username(),
41-
"first_name": user.first_name,
42-
"last_name": user.last_name,
43-
"email": user.email,
44-
"subject": user.subject,
45-
"is_staff": user.is_staff,
46-
"language": user.language,
47-
**common,
21+
**whoami_payload(request),
22+
"ai_enabled": ai.is_enabled(),
23+
# Content-i18n config: the default/canonical authoring language and
24+
# whether machine-translation drafts are available, so the SPA can
25+
# decide which language to show/edit without a second round-trip.
26+
"content_default_language": settings.MODELTRANSLATION_DEFAULT_LANGUAGE,
27+
"content_translation_enabled": translation_service.is_enabled(),
4828
}
4929
)
50-
51-
52-
def logout_view(request):
53-
"""Log out of Django and (if logged in via OIDC) the identity provider.
54-
55-
GET-friendly so the SPA can trigger it with a plain redirect.
56-
"""
57-
was_authenticated = request.user.is_authenticated
58-
end_session_url = None
59-
if was_authenticated and settings.OIDC_OP_LOGOUT_ENDPOINT:
60-
end_session_url = provider_logout_url(request)
61-
django_logout(request)
62-
return redirect(end_session_url or settings.LOGOUT_REDIRECT_URL)
63-
64-
65-
@require_POST
66-
def set_language(request):
67-
"""POST /api/whoami/language/ {language} — remember the user's UI language.
68-
69-
Plain Django view (matches ``whoami``); the SPA sends its CSRF token from
70-
``whoami`` so the request passes CSRF as elsewhere.
71-
"""
72-
if not request.user.is_authenticated:
73-
return JsonResponse({"detail": "Not authenticated."}, status=403)
74-
try:
75-
data = json.loads(request.body or b"{}")
76-
except ValueError:
77-
data = {}
78-
raw = data.get("language") if isinstance(data, dict) else ""
79-
language = (str(raw) if raw else "").strip()
80-
if language not in dict(settings.LANGUAGES):
81-
return JsonResponse({"detail": "Unsupported language."}, status=400)
82-
request.user.language = language
83-
request.user.save(update_fields=["language"])
84-
return JsonResponse({"language": language})

‎template/project/backend/config/settings.py.jinja‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,17 @@ if OIDC_OP_ISSUER and not OIDC_OP_AUTHORIZATION_ENDPOINT:
262262
OIDC_OP_JWKS_ENDPOINT = OIDC_OP_JWKS_ENDPOINT or _discovered.get("jwks_uri", "")
263263
OIDC_OP_LOGOUT_ENDPOINT = OIDC_OP_LOGOUT_ENDPOINT or _discovered.get("end_session_endpoint", "")
264264

265+
# Fail fast in production: with an issuer configured but no endpoints (the
266+
# discovery document was unreachable or incomplete — see the WARNING from
267+
# basicbar_auth.discovery), every login would otherwise fail later with an
268+
# opaque error. In DEBUG the dev stack may simply not have Keycloak up yet.
269+
if OIDC_OP_ISSUER and not DEBUG and not (OIDC_OP_AUTHORIZATION_ENDPOINT and OIDC_OP_TOKEN_ENDPOINT):
270+
raise ImproperlyConfigured(
271+
f"OIDC_OP_ISSUER={OIDC_OP_ISSUER!r} is set, but the OIDC endpoints could not be "
272+
"resolved — the provider's discovery document was unreachable. Set the "
273+
"OIDC_OP_*_ENDPOINT variables explicitly or fix the issuer URL."
274+
)
275+
265276
OIDC_OP_LOGOUT_URL_METHOD = "basicbar_auth.oidc.provider_logout_url"
266277

267278
# Claim mapping (provider-agnostic; defaults are standard OIDC claim names).

‎template/project/backend/config/urls.py.jinja‎

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,18 @@
22
# Copyright 2026 Universität Osnabrück (virtUOS)
33

44
"""Root URL configuration."""
5-
from basicbar_auth.oidc import SilentLoginView, backchannel_logout
65
from django.conf import settings
76
from django.conf.urls.static import static
87
from django.contrib import admin
98
from django.urls import include, path
109

11-
from accounts.views import logout_view, set_language, whoami
10+
from accounts.views import whoami
1211

1312
urlpatterns = [
1413
path("admin/", admin.site.urls),
15-
path("oidc/logout-redirect/", logout_view, name="spa-logout"),
16-
path("oidc/silent/", SilentLoginView.as_view(), name="oidc-silent"),
17-
path(
18-
"oidc/backchannel-logout/",
19-
backchannel_logout,
20-
name="oidc-backchannel-logout",
21-
),
22-
path("oidc/", include("mozilla_django_oidc.urls")),
14+
# OIDC login/logout/silent/back-channel/callback and api/whoami/language/.
15+
path("", include("basicbar_auth.urls")),
2316
path("api/whoami/", whoami),
24-
path("api/whoami/language/", set_language),
2517
]
2618

2719
# Serve uploaded media and static files during local development. (Static

‎template/project/backend/requirements.txt.jinja‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ djangorestframework==3.15.2
33
# Gemeinsame Basis der -bar-Tools; Versionswechsel = Tag in der URL heben,
44
# Migrationsschritte im CHANGELOG von https://github.com/virtUOS/basicbar.
55
basicbar-integrations @ https://github.com/virtUOS/basicbar/archive/refs/tags/integrations/v0.2.2.tar.gz#subdirectory=packages/django/basicbar-integrations
6-
basicbar-auth @ https://github.com/virtUOS/basicbar/archive/refs/tags/auth/v0.1.1.tar.gz#subdirectory=packages/django/basicbar-auth
6+
basicbar-auth @ https://github.com/virtUOS/basicbar/archive/refs/tags/auth/v0.2.0.tar.gz#subdirectory=packages/django/basicbar-auth
77
{% if use_lti -%}
88
basicbar-lti @ https://github.com/virtUOS/basicbar/archive/refs/tags/lti/v0.1.4.tar.gz#subdirectory=packages/django/basicbar-lti
99
{% endif -%}

‎template/project/frontend/package.json.jinja‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12-
"@basicbar/ui": "https://github.com/virtUOS/basicbar/releases/download/ui/v0.6.0/basicbar-ui-0.6.0.tgz",
12+
"@basicbar/ui": "https://github.com/virtUOS/basicbar/releases/download/ui/v0.7.0/basicbar-ui-0.7.0.tgz",
1313
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
1414
"i18next": "^26.3.1",
1515
"i18next-browser-languagedetector": "^8.2.1",

0 commit comments

Comments
 (0)