Skip to content

Commit 315553c

Browse files
committed
add sso auth to help
1 parent c9114c4 commit 315553c

File tree

17 files changed

+691
-142
lines changed

17 files changed

+691
-142
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generated by Django 5.0.3 on 2025-04-10 19:32
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('core', '0008_pawuser_receive_email_notifications'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='Oauth2User',
17+
fields=[
18+
('paw_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
19+
('oauth2_id', models.CharField(max_length=255)),
20+
],
21+
options={
22+
'verbose_name': 'OAuth2 User',
23+
'db_table': 'oauth2_user',
24+
},
25+
),
26+
migrations.AlterField(
27+
model_name='mailtemplate',
28+
name='language',
29+
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('de', 'German'), ('nl', 'Dutch')], default='en', max_length=2),
30+
),
31+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.3 on 2025-04-10 19:52
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('core', '0009_oauth2user_alter_mailtemplate_language'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='pawuser',
15+
name='display_name',
16+
field=models.CharField(blank=True, max_length=100, null=True),
17+
),
18+
]

core/models.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ class PawUser(AbstractUser):
99
profile_picture = models.ImageField(
1010
upload_to='profile_pics/', null=True, blank=True)
1111
language = models.CharField(max_length=2, default='en')
12+
display_name = models.CharField(max_length=100, null=True, blank=True)
1213
telegram_username = models.CharField(max_length=50, null=True, blank=True)
1314
use_darkmode = models.BooleanField(default=False)
1415
receive_email_notifications = models.BooleanField(default=True)
15-
1616
def __str__(self):
1717
return self.username
1818

@@ -29,6 +29,18 @@ class Meta:
2929
db_table = "google_sso_user"
3030
verbose_name = _("Google SSO User")
3131

32+
class Oauth2User(models.Model):
33+
paw_user = models.OneToOneField(
34+
PawUser, on_delete=models.CASCADE, primary_key=True)
35+
oauth2_id = models.CharField(max_length=255)
36+
37+
def __str__(self):
38+
return self.paw_user.username
39+
40+
class Meta:
41+
db_table = "oauth2_user"
42+
verbose_name = _("OAuth2 User")
43+
3244
class MailTemplate(models.Model):
3345
event = models.CharField(max_length=100)
3446
language = models.CharField(max_length=2, default='en', choices=settings.LANGUAGES)

core/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.urls import path
22

3-
from .views import home_view, logout_view, settings_view, register_view, login_view, google_callback_view
3+
from .views import home_view, logout_view, settings_view, register_view, login_view, google_callback_view, oauth2_callback_view
44

55
urlpatterns = [
66
path("", home_view, name="home"),
@@ -9,4 +9,5 @@
99
path("login", login_view, name="login"),
1010
path("logout", logout_view, name="logout"),
1111
path("callback/google", google_callback_view, name="google_callback"),
12+
path("callback/oauth2", oauth2_callback_view, name="oauth2_callback"),
1213
]

core/utils/oauth2.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
4+
from django.utils.translation import gettext_lazy as _
5+
from requests_oauthlib import OAuth2Session
6+
from django.conf import settings
7+
8+
@dataclass
9+
class OAuth2:
10+
_oauth2_session: OAuth2Session = None
11+
12+
@property
13+
def oauth2_session(self) -> OAuth2Session:
14+
if not self._oauth2_session:
15+
self._oauth2_session = OAuth2Session(
16+
client_id=settings.OAUTH2_CLIENT_ID,
17+
redirect_uri=settings.OAUTH2_REDIRECT_URI,
18+
scope=settings.OAUTH2_SCOPE,
19+
)
20+
return self._oauth2_session
21+
22+
def get_authorization_url(self) -> tuple[str, str]:
23+
return self.oauth2_session.authorization_url(
24+
settings.OAUTH2_AUTH_URL,
25+
)
26+
27+
def fetch_token(self, code: str) -> str:
28+
token = self.oauth2_session.fetch_token(
29+
token_url=settings.OAUTH2_TOKEN_URL,
30+
client_id=settings.OAUTH2_CLIENT_ID,
31+
client_secret=settings.OAUTH2_CLIENT_SECRET,
32+
code=code,
33+
)
34+
return token
35+
36+
def get_user_info(self) -> dict:
37+
return self.oauth2_session.get(
38+
settings.OAUTH2_USERINFO_URL).json()

core/views.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from django.shortcuts import render, redirect
22
from django.contrib.auth import logout, login
3+
4+
from core.utils.oauth2 import OAuth2
35
from .forms import UserChangeForm, RegisterForm, AccountFinishForm
46
from django.contrib.auth.forms import AuthenticationForm
5-
from .models import PawUser, GoogleSSOUser
7+
from .models import Oauth2User, PawUser, GoogleSSOUser
68
from django.utils import translation
79
from django.conf import settings
810
from django.contrib.auth.decorators import login_required
@@ -51,6 +53,12 @@ def login_view(request):
5153
request.session["sso_state"] = state
5254
# request.session["sso_next_url"] = next_path
5355
request.session.save()
56+
57+
if settings.OAUTH2_ENABLED:
58+
oauth_sso = OAuth2()
59+
oauth2_auth_url, state = oauth_sso.get_authorization_url()
60+
request.session["oauth_state"] = state
61+
request.session.save()
5462

5563
if request.method == "POST":
5664
form = AuthenticationForm(request=request, data=request.POST)
@@ -61,8 +69,47 @@ def login_view(request):
6169
else:
6270
form = AuthenticationForm()
6371

64-
return render(request, "core/login.html", {"form": form, "google_sso_enabled": settings.GOOGLE_OAUTH_ENABLED, "google_sso_auth_url": auth_url})
72+
return render(request, "core/login.html", {
73+
"form": form,
74+
"google_sso_enabled": settings.GOOGLE_OAUTH_ENABLED,
75+
"google_sso_auth_url": auth_url,
76+
"oauth2_enabled": settings.OAUTH2_ENABLED,
77+
"oauth2_auth_url": oauth2_auth_url,
78+
})
79+
80+
def oauth2_callback_view(request):
81+
82+
if not settings.OAUTH2_ENABLED:
83+
return redirect("login")
84+
state = request.GET.get("state")
6585

86+
if state != request.session.get("oauth_state"):
87+
return redirect("login")
88+
try:
89+
oauth_sso = OAuth2()
90+
_ = oauth_sso.fetch_token(request.GET.get("code"))
91+
user_info = oauth_sso.get_user_info()
92+
except Exception:
93+
return redirect("login")
94+
print(user_info)
95+
# Check if user already exists
96+
oauth2_user = Oauth2User.objects.filter(oauth2_id=user_info["sub"]).first()
97+
if oauth2_user:
98+
login(request, oauth2_user.paw_user)
99+
return redirect("home")
100+
101+
# Create user if not exists
102+
unique_username = PawUser.objects.filter(username=user_info["nickname"]).exists()
103+
# TODO: Set up account finish form
104+
user, created = PawUser.objects.get_or_create(email=user_info["email"], defaults={
105+
"username": user_info["nickname"] if not unique_username else user_info["email"],
106+
"display_name": user_info["name"],
107+
})
108+
if created:
109+
Oauth2User.objects.create(paw_user=user, oauth2_id=user_info["sub"])
110+
111+
login(request, user)
112+
return redirect("home")
66113

67114
def google_callback_view(request):
68115

@@ -121,7 +168,7 @@ def settings_view(request):
121168
translation.activate(form.cleaned_data["language"])
122169
changed_user_language = True
123170

124-
if not hasattr(request.user, 'googlessouser'):
171+
if not hasattr(request.user, 'googlessouser') and not hasattr(request.user, 'oauth2user'):
125172
request.user.email = form.cleaned_data["email"]
126173

127174
request.user.language = form.cleaned_data["language"]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% block attendee_login %}
2+
{% load i18n %}
3+
4+
{% if oauth2_enabled %}
5+
<a href="{{ oauth2_auth_url }}" class="btn w-full bg-slate-900 hover:bg-slate-800 border-slate-800 text-white">
6+
<img src="https://i.imgur.com/kxouY5R.png" class="w-6 h-6" alt="FBL Logo">
7+
{% trans 'Log in with SSO' %}
8+
</a>
9+
{% endif %}
10+
11+
{% if general_login %}<div class="divider">{% trans "I don\'t have a seat for this years FBL" %}</div>{% endif %}
12+
{% endblock %}

0 commit comments

Comments
 (0)