FossilRepo

Global OAuth callback URLs for GitHub/GitLab Callback URLs are now top-level (not per-project): - https://fossilrepo.io/oauth/callback/github/ - https://fossilrepo.io/oauth/callback/gitlab/ The state parameter carries the project slug so the callback redirects back to the correct project's Git mirror config page. This means one OAuth App configuration works for all projects.

lmata 2026-04-07 03:25 trunk
Commit 9460009c81996484d6dbe1d49e46752ad5f30ac35c10f34e2cbe5d9d408f7cc9
2 files changed +33 +3 -3
--- config/urls.py
+++ config/urls.py
@@ -2,12 +2,43 @@
22
from datetime import UTC, datetime
33
44
from django.conf import settings
55
from django.contrib import admin
66
from django.http import HttpResponse, JsonResponse
7
+from django.shortcuts import redirect as _redirect
78
from django.urls import include, path
89
from django.views.generic import RedirectView
10
+
11
+
12
+def _oauth_github_callback(request):
13
+ """Global GitHub OAuth callback. Extracts slug from state param and delegates."""
14
+ state = request.GET.get("state", "")
15
+ slug = state.split(":")[0] if ":" in state else ""
16
+ if not slug:
17
+ return _redirect("/dashboard/")
18
+ from fossil.oauth import github_exchange_token
19
+
20
+ result = github_exchange_token(request, slug)
21
+ if result.get("token"):
22
+ request.session["github_oauth_token"] = result["token"]
23
+ request.session["github_oauth_user"] = result.get("username", "")
24
+ return _redirect(f"/projects/{slug}/fossil/sync/git/")
25
+
26
+
27
+def _oauth_gitlab_callback(request):
28
+ """Global GitLab OAuth callback. Extracts slug from state param and delegates."""
29
+ state = request.GET.get("state", "")
30
+ slug = state.split(":")[0] if ":" in state else ""
31
+ if not slug:
32
+ return _redirect("/dashboard/")
33
+ from fossil.oauth import gitlab_exchange_token
34
+
35
+ result = gitlab_exchange_token(request, slug)
36
+ if result.get("token"):
37
+ request.session["gitlab_oauth_token"] = result["token"]
38
+ return _redirect(f"/projects/{slug}/fossil/sync/git/")
39
+
940
1041
admin.site.site_header = settings.ADMIN_SITE_HEADER
1142
admin.site.site_title = settings.ADMIN_SITE_TITLE
1243
admin.site.index_title = "Welcome to Fossilrepo"
1344
@@ -193,8 +224,10 @@
193224
path("settings/", include("organization.urls")),
194225
path("projects/", include("projects.urls")),
195226
path("projects/<slug:slug>/fossil/", include("fossil.urls")),
196227
path("kb/", include("pages.urls")),
197228
path("items/", include("items.urls")),
229
+ path("oauth/callback/github/", _oauth_github_callback, name="oauth_github_callback_global"),
230
+ path("oauth/callback/gitlab/", _oauth_gitlab_callback, name="oauth_gitlab_callback_global"),
198231
path("admin/", admin.site.urls),
199232
path("health/", health_check, name="health"),
200233
]
201234
--- config/urls.py
+++ config/urls.py
@@ -2,12 +2,43 @@
2 from datetime import UTC, datetime
3
4 from django.conf import settings
5 from django.contrib import admin
6 from django.http import HttpResponse, JsonResponse
 
7 from django.urls import include, path
8 from django.views.generic import RedirectView
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
10 admin.site.site_header = settings.ADMIN_SITE_HEADER
11 admin.site.site_title = settings.ADMIN_SITE_TITLE
12 admin.site.index_title = "Welcome to Fossilrepo"
13
@@ -193,8 +224,10 @@
193 path("settings/", include("organization.urls")),
194 path("projects/", include("projects.urls")),
195 path("projects/<slug:slug>/fossil/", include("fossil.urls")),
196 path("kb/", include("pages.urls")),
197 path("items/", include("items.urls")),
 
 
198 path("admin/", admin.site.urls),
199 path("health/", health_check, name="health"),
200 ]
201
--- config/urls.py
+++ config/urls.py
@@ -2,12 +2,43 @@
2 from datetime import UTC, datetime
3
4 from django.conf import settings
5 from django.contrib import admin
6 from django.http import HttpResponse, JsonResponse
7 from django.shortcuts import redirect as _redirect
8 from django.urls import include, path
9 from django.views.generic import RedirectView
10
11
12 def _oauth_github_callback(request):
13 """Global GitHub OAuth callback. Extracts slug from state param and delegates."""
14 state = request.GET.get("state", "")
15 slug = state.split(":")[0] if ":" in state else ""
16 if not slug:
17 return _redirect("/dashboard/")
18 from fossil.oauth import github_exchange_token
19
20 result = github_exchange_token(request, slug)
21 if result.get("token"):
22 request.session["github_oauth_token"] = result["token"]
23 request.session["github_oauth_user"] = result.get("username", "")
24 return _redirect(f"/projects/{slug}/fossil/sync/git/")
25
26
27 def _oauth_gitlab_callback(request):
28 """Global GitLab OAuth callback. Extracts slug from state param and delegates."""
29 state = request.GET.get("state", "")
30 slug = state.split(":")[0] if ":" in state else ""
31 if not slug:
32 return _redirect("/dashboard/")
33 from fossil.oauth import gitlab_exchange_token
34
35 result = gitlab_exchange_token(request, slug)
36 if result.get("token"):
37 request.session["gitlab_oauth_token"] = result["token"]
38 return _redirect(f"/projects/{slug}/fossil/sync/git/")
39
40
41 admin.site.site_header = settings.ADMIN_SITE_HEADER
42 admin.site.site_title = settings.ADMIN_SITE_TITLE
43 admin.site.index_title = "Welcome to Fossilrepo"
44
@@ -193,8 +224,10 @@
224 path("settings/", include("organization.urls")),
225 path("projects/", include("projects.urls")),
226 path("projects/<slug:slug>/fossil/", include("fossil.urls")),
227 path("kb/", include("pages.urls")),
228 path("items/", include("items.urls")),
229 path("oauth/callback/github/", _oauth_github_callback, name="oauth_github_callback_global"),
230 path("oauth/callback/gitlab/", _oauth_gitlab_callback, name="oauth_gitlab_callback_global"),
231 path("admin/", admin.site.urls),
232 path("health/", health_check, name="health"),
233 ]
234
+3 -3
--- fossil/oauth.py
+++ fossil/oauth.py
@@ -24,11 +24,11 @@
2424
2525
client_id = config.GITHUB_OAUTH_CLIENT_ID
2626
if not client_id:
2727
return None
2828
29
- callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/github/")
29
+ callback = request.build_absolute_uri("/oauth/callback/github/")
3030
state = f"{slug}:{mirror_id or 'new'}"
3131
3232
return f"{GITHUB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&scope=repo&state={state}"
3333
3434
@@ -71,11 +71,11 @@
7171
7272
client_id = config.GITLAB_OAUTH_CLIENT_ID
7373
if not client_id:
7474
return None
7575
76
- callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/gitlab/")
76
+ callback = request.build_absolute_uri("/oauth/callback/gitlab/")
7777
state = f"{slug}:{mirror_id or 'new'}"
7878
7979
return f"{GITLAB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&response_type=code&scope=api&state={state}"
8080
8181
@@ -87,11 +87,11 @@
8787
if not code:
8888
return {"token": "", "error": "No code received"}
8989
9090
client_id = config.GITLAB_OAUTH_CLIENT_ID
9191
client_secret = config.GITLAB_OAUTH_CLIENT_SECRET
92
- callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/gitlab/")
92
+ callback = request.build_absolute_uri("/oauth/callback/gitlab/")
9393
9494
try:
9595
resp = requests.post(
9696
GITLAB_TOKEN_URL,
9797
data={
9898
--- fossil/oauth.py
+++ fossil/oauth.py
@@ -24,11 +24,11 @@
24
25 client_id = config.GITHUB_OAUTH_CLIENT_ID
26 if not client_id:
27 return None
28
29 callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/github/")
30 state = f"{slug}:{mirror_id or 'new'}"
31
32 return f"{GITHUB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&scope=repo&state={state}"
33
34
@@ -71,11 +71,11 @@
71
72 client_id = config.GITLAB_OAUTH_CLIENT_ID
73 if not client_id:
74 return None
75
76 callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/gitlab/")
77 state = f"{slug}:{mirror_id or 'new'}"
78
79 return f"{GITLAB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&response_type=code&scope=api&state={state}"
80
81
@@ -87,11 +87,11 @@
87 if not code:
88 return {"token": "", "error": "No code received"}
89
90 client_id = config.GITLAB_OAUTH_CLIENT_ID
91 client_secret = config.GITLAB_OAUTH_CLIENT_SECRET
92 callback = request.build_absolute_uri(f"/projects/{slug}/fossil/sync/git/callback/gitlab/")
93
94 try:
95 resp = requests.post(
96 GITLAB_TOKEN_URL,
97 data={
98
--- fossil/oauth.py
+++ fossil/oauth.py
@@ -24,11 +24,11 @@
24
25 client_id = config.GITHUB_OAUTH_CLIENT_ID
26 if not client_id:
27 return None
28
29 callback = request.build_absolute_uri("/oauth/callback/github/")
30 state = f"{slug}:{mirror_id or 'new'}"
31
32 return f"{GITHUB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&scope=repo&state={state}"
33
34
@@ -71,11 +71,11 @@
71
72 client_id = config.GITLAB_OAUTH_CLIENT_ID
73 if not client_id:
74 return None
75
76 callback = request.build_absolute_uri("/oauth/callback/gitlab/")
77 state = f"{slug}:{mirror_id or 'new'}"
78
79 return f"{GITLAB_AUTHORIZE_URL}?client_id={client_id}&redirect_uri={callback}&response_type=code&scope=api&state={state}"
80
81
@@ -87,11 +87,11 @@
87 if not code:
88 return {"token": "", "error": "No code received"}
89
90 client_id = config.GITLAB_OAUTH_CLIENT_ID
91 client_secret = config.GITLAB_OAUTH_CLIENT_SECRET
92 callback = request.build_absolute_uri("/oauth/callback/gitlab/")
93
94 try:
95 resp = requests.post(
96 GITLAB_TOKEN_URL,
97 data={
98

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button