fix(users): require otp verification before google signup
This commit is contained in:
@@ -173,6 +173,32 @@ def _build_claim_required_payload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _create_user_and_social_account_from_google_profile(*, mobile: str, profile: GoogleProfile) -> User:
|
||||||
|
user = User.objects.create_user(
|
||||||
|
mobile=mobile,
|
||||||
|
password=None,
|
||||||
|
first_name=profile.first_name,
|
||||||
|
last_name=profile.last_name,
|
||||||
|
email=profile.email,
|
||||||
|
is_verified=True,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
user.set_unusable_password()
|
||||||
|
user.save(update_fields=["password"])
|
||||||
|
sync_user_from_google_profile(user, profile)
|
||||||
|
|
||||||
|
UserSocialAccount.objects.create(
|
||||||
|
user=user,
|
||||||
|
provider=UserSocialAccount.ProviderType.GOOGLE,
|
||||||
|
provider_user_id=profile.provider_user_id,
|
||||||
|
email=profile.email,
|
||||||
|
email_verified=profile.email_verified,
|
||||||
|
avatar_url=profile.avatar_url,
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
def _build_public_google_flow_payload(flow_payload: dict[str, Any]) -> dict[str, Any]:
|
def _build_public_google_flow_payload(flow_payload: dict[str, Any]) -> dict[str, Any]:
|
||||||
status = flow_payload.get("status")
|
status = flow_payload.get("status")
|
||||||
if status == "authenticated":
|
if status == "authenticated":
|
||||||
@@ -410,32 +436,62 @@ def complete_google_signup(flow: str, mobile: str) -> dict[str, Any]:
|
|||||||
update_google_flow(flow, claim_payload)
|
update_google_flow(flow, claim_payload)
|
||||||
return _build_public_google_flow_payload(claim_payload)
|
return _build_public_google_flow_payload(claim_payload)
|
||||||
|
|
||||||
user = User.objects.create_user(
|
generate_and_send_otp(normalized_mobile, "register")
|
||||||
mobile=normalized_mobile,
|
signup_payload = {
|
||||||
password=None,
|
"status": "claim_required",
|
||||||
first_name=profile.first_name,
|
"google_profile": _profile_to_payload(profile),
|
||||||
last_name=profile.last_name,
|
"mobile": normalized_mobile,
|
||||||
email=profile.email,
|
"user_id": None,
|
||||||
is_verified=False,
|
"resolution": "new_account",
|
||||||
is_active=True,
|
"email": profile.email,
|
||||||
)
|
"mobile_hint": None,
|
||||||
user.set_unusable_password()
|
"detail": "Verify this mobile number to finish creating your account with Google.",
|
||||||
user.save(update_fields=["password"])
|
}
|
||||||
sync_user_from_google_profile(user, profile)
|
update_google_flow(flow, signup_payload)
|
||||||
|
return _build_public_google_flow_payload(signup_payload)
|
||||||
|
|
||||||
UserSocialAccount.objects.create(
|
|
||||||
user=user,
|
def _verify_otp_code(*, mobile: str, code: str) -> None:
|
||||||
provider=UserSocialAccount.ProviderType.GOOGLE,
|
from django_redis import get_redis_connection
|
||||||
provider_user_id=profile.provider_user_id,
|
|
||||||
email=profile.email,
|
redis_conn = get_redis_connection("default")
|
||||||
email_verified=profile.email_verified,
|
stored_code = redis_conn.get(f"verification_code:{mobile}")
|
||||||
avatar_url=profile.avatar_url,
|
if not stored_code or stored_code.decode("utf-8") != code:
|
||||||
is_active=True,
|
raise ValidationError({"code": "Invalid or expired verification code."})
|
||||||
|
redis_conn.delete(f"verification_code:{mobile}")
|
||||||
|
|
||||||
|
|
||||||
|
def _create_new_google_user_after_otp(*, mobile: str, profile: GoogleProfile) -> User:
|
||||||
|
existing_email_user = _find_user_by_email(profile.email)
|
||||||
|
if existing_email_user is not None:
|
||||||
|
raise GoogleOAuthFlowError(
|
||||||
|
"This Google email is already attached to an existing account.",
|
||||||
|
"google_email_already_claimed",
|
||||||
|
extra={"mobile_hint": mask_mobile(existing_email_user.mobile)},
|
||||||
)
|
)
|
||||||
|
|
||||||
authenticated_payload = build_authenticated_flow_payload(user)
|
existing_mobile_user = User.objects.filter(mobile=mobile).first()
|
||||||
update_google_flow(flow, authenticated_payload)
|
if existing_mobile_user is not None:
|
||||||
return authenticated_payload
|
existing_mobile_email = normalize_email_identity(existing_mobile_user.email)
|
||||||
|
if existing_mobile_email:
|
||||||
|
raise GoogleOAuthFlowError(
|
||||||
|
"This mobile number already belongs to another account with a different email address.",
|
||||||
|
"google_mobile_belongs_to_other_email",
|
||||||
|
)
|
||||||
|
raise GoogleOAuthFlowError(
|
||||||
|
"This mobile number is no longer available for creating a new Google account.",
|
||||||
|
"google_flow_invalid_state",
|
||||||
|
status_code=409,
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_link = find_social_account_for_profile(profile)
|
||||||
|
if existing_link is not None:
|
||||||
|
raise GoogleOAuthFlowError(
|
||||||
|
"This Google account is already attached to another user.",
|
||||||
|
"google_email_already_claimed",
|
||||||
|
)
|
||||||
|
|
||||||
|
return _create_user_and_social_account_from_google_profile(mobile=mobile, profile=profile)
|
||||||
|
|
||||||
|
|
||||||
def send_google_claim_otp(flow: str) -> dict[str, Any]:
|
def send_google_claim_otp(flow: str) -> dict[str, Any]:
|
||||||
@@ -446,13 +502,13 @@ def send_google_claim_otp(flow: str) -> dict[str, Any]:
|
|||||||
if not isinstance(mobile, str) or not mobile:
|
if not isinstance(mobile, str) or not mobile:
|
||||||
raise _invalid_flow_error("Claim mobile number is missing.")
|
raise _invalid_flow_error("Claim mobile number is missing.")
|
||||||
|
|
||||||
generate_and_send_otp(mobile, "login")
|
resolution = flow_payload.get("resolution")
|
||||||
|
otp_mode = "register" if resolution == "new_account" else "login"
|
||||||
|
generate_and_send_otp(mobile, otp_mode)
|
||||||
return {"detail": "Verification code sent successfully."}
|
return {"detail": "Verification code sent successfully."}
|
||||||
|
|
||||||
|
|
||||||
def verify_google_claim(flow: str, code: str) -> dict[str, Any]:
|
def verify_google_claim(flow: str, code: str) -> dict[str, Any]:
|
||||||
from django_redis import get_redis_connection
|
|
||||||
|
|
||||||
flow_payload = get_google_flow_payload(flow)
|
flow_payload = get_google_flow_payload(flow)
|
||||||
_ensure_flow_status(flow_payload, "claim_required")
|
_ensure_flow_status(flow_payload, "claim_required")
|
||||||
|
|
||||||
@@ -462,18 +518,19 @@ def verify_google_claim(flow: str, code: str) -> dict[str, Any]:
|
|||||||
|
|
||||||
profile = _profile_from_flow(flow_payload)
|
profile = _profile_from_flow(flow_payload)
|
||||||
user_id = flow_payload.get("user_id")
|
user_id = flow_payload.get("user_id")
|
||||||
|
resolution = flow_payload.get("resolution")
|
||||||
|
_verify_otp_code(mobile=mobile, code=code)
|
||||||
|
|
||||||
|
if resolution == "new_account":
|
||||||
|
user = _create_new_google_user_after_otp(mobile=mobile, profile=profile)
|
||||||
|
authenticated_payload = build_authenticated_flow_payload(user)
|
||||||
|
update_google_flow(flow, authenticated_payload)
|
||||||
|
return authenticated_payload
|
||||||
|
|
||||||
user = User.objects.filter(id=user_id, mobile=mobile).first()
|
user = User.objects.filter(id=user_id, mobile=mobile).first()
|
||||||
if not user:
|
if not user:
|
||||||
raise _invalid_flow_error("Target account could not be found.")
|
raise _invalid_flow_error("Target account could not be found.")
|
||||||
|
|
||||||
redis_conn = get_redis_connection("default")
|
|
||||||
stored_code = redis_conn.get(f"verification_code:{mobile}")
|
|
||||||
if not stored_code or stored_code.decode("utf-8") != code:
|
|
||||||
raise ValidationError({"code": "Invalid or expired verification code."})
|
|
||||||
|
|
||||||
redis_conn.delete(f"verification_code:{mobile}")
|
|
||||||
|
|
||||||
resolution = flow_payload.get("resolution")
|
|
||||||
user_email = normalize_email_identity(user.email)
|
user_email = normalize_email_identity(user.email)
|
||||||
|
|
||||||
if resolution == "existing_email_claim" and user_email != profile.email:
|
if resolution == "existing_email_claim" and user_email != profile.email:
|
||||||
|
|||||||
Reference in New Issue
Block a user