dev: update oauth endpoint to support connected accounts

This commit is contained in:
pablohashescobar 2023-12-18 16:40:57 +05:30
parent 5756555507
commit ee90a22a0c
2 changed files with 241 additions and 294 deletions

View File

@ -6,6 +6,7 @@ import os
# Django imports
from django.utils import timezone
from django.conf import settings
from django.contrib.auth.hashers import make_password
# Third Party modules
from rest_framework.response import Response
@ -27,6 +28,7 @@ from plane.db.models import (
WorkspaceMember,
ProjectMemberInvite,
ProjectMember,
ConnectedAccount,
)
from plane.bgtasks.event_tracking_task import auth_events
from .base import BaseAPIView
@ -95,7 +97,8 @@ def get_access_token(request_token: str, client_id: str) -> str:
]
)
url = f"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret=${CLIENT_SECRET}&code={request_token}"
url = f"https://github.com/login/oauth/access_token?client_id={str(client_id)}&client_secret={str(CLIENT_SECRET)}&code={str(request_token)}"
headers = {"accept": "application/json"}
res = requests.post(url, headers=headers)
@ -140,314 +143,258 @@ class OauthEndpoint(BaseAPIView):
permission_classes = [AllowAny]
def post(self, request):
try:
# Check if instance is registered or not
instance = Instance.objects.first()
if instance is None and not instance.is_setup_done:
return Response(
{"error": "Instance is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
medium = request.data.get("medium", False)
id_token = request.data.get("credential", False)
client_id = request.data.get("clientId", False)
print(request.data)
GOOGLE_CLIENT_ID, GITHUB_CLIENT_ID = get_configuration_value(
[
{
"key": "GOOGLE_CLIENT_ID",
"default": os.environ.get("GOOGLE_CLIENT_ID"),
},
{
"key": "GITHUB_CLIENT_ID",
"default": os.environ.get("GITHUB_CLIENT_ID"),
},
]
# Check if instance is registered or not
instance = Instance.objects.first()
if instance is None and not instance.is_setup_done:
return Response(
{"error": "Instance is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
if not medium or not id_token:
# Get the medium and temporary code
medium = request.data.get("medium", False)
id_token = request.data.get("credential", False)
GOOGLE_CLIENT_ID, GITHUB_CLIENT_ID = get_configuration_value(
[
{
"key": "GOOGLE_CLIENT_ID",
"default": os.environ.get("GOOGLE_CLIENT_ID"),
},
{
"key": "GITHUB_CLIENT_ID",
"default": os.environ.get("GITHUB_CLIENT_ID"),
},
]
)
# Return error if medium and id_token are not preset
if not medium or not id_token:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
if medium == "google":
if not GOOGLE_CLIENT_ID:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
{"error": "Google login is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
data = validate_google_token(id_token, GOOGLE_CLIENT_ID)
if medium == "github":
if not GITHUB_CLIENT_ID:
return Response(
{"error": "Github login is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
account_data = get_access_token(id_token, GITHUB_CLIENT_ID)
# access token authentication
access_token = account_data.get("access_token", False)
if not access_token:
return Response(
{"error": "Invalid credentials used"},
status=status.HTTP_400_BAD_REQUEST,
)
if medium == "google":
if not GOOGLE_CLIENT_ID:
return Response(
{"error": "Google login is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
data = validate_google_token(id_token, client_id)
data = get_user_data(access_token=access_token)
if medium == "github":
if not GITHUB_CLIENT_ID:
return Response(
{"error": "Github login is not configured"},
status=status.HTTP_400_BAD_REQUEST,
)
access_token = get_access_token(id_token, client_id)
data = get_user_data(access_token)
email = data.get("email", None)
if email is None:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
email = data.get("email", None)
if email is None:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
if "@" in email:
if "@" in email:
try:
user = User.objects.get(email=email)
email = data["email"]
mobile_number = uuid.uuid4().hex
email_verified = True
else:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
user.is_active = True
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_medium = "oauth"
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.is_email_verified = email_verified
user.save()
# Check if user has any accepted invites for workspace and add them to workspace
workspace_member_invites = WorkspaceMemberInvite.objects.filter(
email=user.email, accepted=True
)
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=workspace_member_invite.workspace_id,
member=user,
role=workspace_member_invite.role,
)
for workspace_member_invite in workspace_member_invites
],
ignore_conflicts=True,
)
# Check if user has any project invites
project_member_invites = ProjectMemberInvite.objects.filter(
email=user.email, accepted=True
)
# Add user to workspace
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Now add the users to project
ProjectMember.objects.bulk_create(
[
ProjectMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Delete all the invites
workspace_member_invites.delete()
project_member_invites.delete()
SocialLoginConnection.objects.update_or_create(
medium=medium,
extra_data={},
user=user,
defaults={
"token_data": {"id_token": id_token},
"last_login_at": timezone.now(),
},
)
# Send event
auth_events.delay(
user=user.id,
email=email,
user_agent=request.META.get("HTTP_USER_AGENT"),
ip=request.META.get("REMOTE_ADDR"),
event_name="SIGN_IN",
medium=medium.upper(),
first_time=False,
)
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
return Response(data, status=status.HTTP_200_OK)
except User.DoesNotExist:
(ENABLE_SIGNUP,) = get_configuration_value(
[
{
"key": "ENABLE_SIGNUP",
"default": os.environ.get("ENABLE_SIGNUP", "0"),
}
]
)
if (
ENABLE_SIGNUP == "0"
and not WorkspaceMemberInvite.objects.filter(
# Send event
auth_events.delay(
user=user.id,
email=email,
).exists()
):
return Response(
{
"error": "New account creation is disabled. Please contact your site administrator"
},
status=status.HTTP_400_BAD_REQUEST,
user_agent=request.META.get("HTTP_USER_AGENT"),
ip=request.META.get("REMOTE_ADDR"),
event_name="SIGN_IN",
medium=medium.upper(),
first_time=False,
)
except User.DoesNotExist:
(ENABLE_SIGNUP,) = get_configuration_value(
[
{
"key": "ENABLE_SIGNUP",
"default": os.environ.get("ENABLE_SIGNUP", "0"),
}
]
)
if (
ENABLE_SIGNUP == "0"
and not WorkspaceMemberInvite.objects.filter(
email=email,
).exists()
):
return Response(
{
"error": "New account creation is disabled. Please contact your site administrator"
},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(
username=uuid.uuid4().hex,
email=email,
mobile_number=mobile_number,
first_name=data.get("first_name", ""),
last_name=data.get("last_name", ""),
is_email_verified=email_verified,
is_password_autoset=True,
password=make_password(uuid.uuid4().hex),
)
# Send event
auth_events.delay(
user=user.id,
email=email,
user_agent=request.META.get("HTTP_USER_AGENT"),
ip=request.META.get("REMOTE_ADDR"),
event_name="SIGN_IN",
medium=medium.upper(),
first_time=True,
)
username = uuid.uuid4().hex
if "@" in email:
email = data["email"]
mobile_number = uuid.uuid4().hex
email_verified = True
else:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(
username=username,
email=email,
mobile_number=mobile_number,
first_name=data.get("first_name", ""),
last_name=data.get("last_name", ""),
is_email_verified=email_verified,
is_password_autoset=True,
)
user.set_password(uuid.uuid4().hex)
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_medium = "oauth"
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
# Check if user has any accepted invites for workspace and add them to workspace
workspace_member_invites = WorkspaceMemberInvite.objects.filter(
email=user.email, accepted=True
)
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=workspace_member_invite.workspace_id,
member=user,
role=workspace_member_invite.role,
)
for workspace_member_invite in workspace_member_invites
],
ignore_conflicts=True,
)
# Check if user has any project invites
project_member_invites = ProjectMemberInvite.objects.filter(
email=user.email, accepted=True
)
# Add user to workspace
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Now add the users to project
ProjectMember.objects.bulk_create(
[
ProjectMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Delete all the invites
workspace_member_invites.delete()
project_member_invites.delete()
# Send event
auth_events.delay(
user=user.id,
email=email,
user_agent=request.META.get("HTTP_USER_AGENT"),
ip=request.META.get("REMOTE_ADDR"),
event_name="SIGN_IN",
medium=medium.upper(),
first_time=True,
)
SocialLoginConnection.objects.update_or_create(
medium=medium,
extra_data={},
user=user,
defaults={
"token_data": {"id_token": id_token},
"last_login_at": timezone.now(),
else:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
if medium == "github":
# Get the values from the tokens
(
github_access_token,
github_refresh_token,
access_token_expired_at,
refresh_token_expired_at,
) = (
account_data.get("access_token"),
account_data.get("refresh_token", None),
account_data.get("expires_in", None),
account_data.get("refresh_token_expires_in", None),
)
return Response(data, status=status.HTTP_201_CREATED)
# Get the connected account
connected_account = ConnectedAccount.objects.filter(
user=user, medium=medium
).first()
# If the connected account exists
if connected_account:
connected_account.access_token = github_access_token
connected_account.refresh_token = github_refresh_token
connected_account.access_token_expired_at = access_token_expired_at
connected_account.refresh_token_expired_at = refresh_token_expired_at
connected_account.last_connected_at = timezone.now()
connected_account.save()
else:
# Create the connected account
ConnectedAccount.objects.create(
user=user,
access_token=github_access_token,
refresh_token=github_refresh_token,
access_token_expired_at=access_token_expired_at,
refresh_token_expired_at=refresh_token_expired_at,
last_connected_at=timezone.now(),
)
user.is_active = True
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_medium = "oauth"
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.is_email_verified = email_verified
user.save()
# Check if user has any accepted invites for workspace and add them to workspace
workspace_member_invites = WorkspaceMemberInvite.objects.filter(
email=user.email, accepted=True
)
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=workspace_member_invite.workspace_id,
member=user,
role=workspace_member_invite.role,
)
for workspace_member_invite in workspace_member_invites
],
ignore_conflicts=True,
)
# Check if user has any project invites
project_member_invites = ProjectMemberInvite.objects.filter(
email=user.email, accepted=True
)
# Add user to workspace
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Now add the users to project
ProjectMember.objects.bulk_create(
[
ProjectMember(
workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15,
member=user,
created_by_id=project_member_invite.created_by_id,
)
for project_member_invite in project_member_invites
],
ignore_conflicts=True,
)
# Delete all the invites
workspace_member_invites.delete()
project_member_invites.delete()
SocialLoginConnection.objects.update_or_create(
medium=medium,
extra_data={},
user=user,
defaults={
"token_data": {"id_token": id_token},
"last_login_at": timezone.now(),
},
)
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
return Response(data, status=status.HTTP_200_OK)

View File

@ -145,7 +145,7 @@ class ConnectedAccount(BaseModel):
)
access_token = models.CharField(max_length=255)
access_token_expired_at = models.DateTimeField(null=True)
refresh_token = models.CharField(max_length=255)
refresh_token = models.CharField(max_length=255, null=True)
refresh_token_expired_at = models.DateTimeField(null=True)
metadata = models.JSONField(default=dict)
last_connected_at = models.DateTimeField(default=timezone.now)