binarybeachio: trusted view — mirror OAuth adapter create-shape (Profile, username, is_email_verified)
Plane's OAuth adapter (apps/api/plane/authentication/adapter/base.py:289-342) creates User AND Profile when a new identity arrives. My trusted view was calling User.objects.get_or_create() without the Profile, so the SPA's /api/users/me/profile/ 404'd and the SPA bounced the user back to /login in an onboarding loop. Mirror the adapter's full create-shape: random username (uuid hex), first/last names from JWT claims, is_password_autoset=True, is_email_verified=True, random password (so Django's auth hash is non-empty for break-glass), then Profile.objects.create(user=user). Wrapped in a transaction so partial creation can't leave the DB inconsistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
13b4de6d82
commit
c0cfbb2bdc
1 changed files with 31 additions and 13 deletions
|
|
@ -38,12 +38,14 @@
|
|||
import logging
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import jwt as pyjwt
|
||||
import redis
|
||||
import requests
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseRedirect, HttpResponseNotFound
|
||||
from django.views import View
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ from plane.authentication.utils.host import base_host
|
|||
from plane.authentication.utils.login import user_login
|
||||
from plane.authentication.utils.redirection_path import get_redirection_path
|
||||
from plane.authentication.utils.user_auth_workflow import post_user_auth_workflow
|
||||
from plane.db.models import User
|
||||
from plane.db.models import Profile, User
|
||||
from plane.settings.redis import redis_instance
|
||||
from plane.utils.path_validator import get_safe_redirect_url
|
||||
|
||||
|
|
@ -231,18 +233,34 @@ class TrustedSignInEndpoint(View):
|
|||
if not email:
|
||||
return _redirect_with_error(request, "TRUSTED_JWT_TOKEN_INVALID", "TRUSTED_JWT_TOKEN_NO_EMAIL", next_path)
|
||||
|
||||
# Find-or-create. Plane's User model uses email as a unique natural key;
|
||||
# other OAuth providers do the same lookup via the OauthAdapter base.
|
||||
# We mirror that behavior here without going through OauthAdapter — this
|
||||
# endpoint is a NEW entry-point, not a fifth OAuth provider.
|
||||
user, created = User.objects.get_or_create(
|
||||
email=email,
|
||||
defaults={
|
||||
"first_name": claims.get("first_name") or claims.get("given_name") or "",
|
||||
"last_name": claims.get("last_name") or claims.get("family_name") or "",
|
||||
"is_password_autoset": True,
|
||||
},
|
||||
)
|
||||
# Find-or-create. We mirror the User-creation shape that
|
||||
# OauthAdapter.complete_login_or_signup() produces (apps/api/plane/
|
||||
# authentication/adapter/base.py:289-342) — same field set, same
|
||||
# required side-effect of creating a Profile row. Skipping the Profile
|
||||
# would cause Plane's SPA /api/users/me/profile/ to 404 and bounce
|
||||
# the user back to /login in an onboarding loop.
|
||||
user = User.objects.filter(email=email).first()
|
||||
created = user is None
|
||||
if created:
|
||||
with transaction.atomic():
|
||||
user = User(
|
||||
email=email,
|
||||
username=uuid.uuid4().hex,
|
||||
first_name=claims.get("first_name") or claims.get("given_name") or "",
|
||||
last_name=claims.get("last_name") or claims.get("family_name") or "",
|
||||
is_password_autoset=True,
|
||||
is_email_verified=True,
|
||||
)
|
||||
# Random password — user signs in via SSO; the password exists
|
||||
# only so Django's auth machinery has a non-empty hash and the
|
||||
# break-glass admin pattern can be applied later by ALTER-ing
|
||||
# this user out of band if needed.
|
||||
user.set_password(uuid.uuid4().hex)
|
||||
user.save()
|
||||
# Profile row is mandatory: every Plane API endpoint that
|
||||
# touches user state (workspace listings, onboarding) reads
|
||||
# Profile, and the SPA's /api/users/me/profile/ 404s without it.
|
||||
Profile.objects.create(user=user)
|
||||
|
||||
# Plane's existing post-auth workflow (default workspace, invitations, etc.)
|
||||
post_user_auth_workflow(user=user, is_signup=created, request=request)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue