diff --git a/api/custom_auth/oauth/serializers.py b/api/custom_auth/oauth/serializers.py index afd1a4232689..7d504f9f9676 100644 --- a/api/custom_auth/oauth/serializers.py +++ b/api/custom_auth/oauth/serializers.py @@ -22,6 +22,11 @@ UserModel = get_user_model() +class OAuthTokenSerializer(serializers.Serializer): # type: ignore[type-arg] + key = serializers.CharField(read_only=True) + is_new_user = serializers.BooleanField(read_only=True) + + class OAuthLoginSerializer(InviteLinkValidationMixin, serializers.Serializer): # type: ignore[type-arg] access_token = serializers.CharField( required=True, @@ -42,6 +47,7 @@ class OAuthLoginSerializer(InviteLinkValidationMixin, serializers.Serializer): utm_data = UTMDataSerializer(required=False, allow_null=True) auth_type: AuthType | None = None user_model_id_attribute: str = "id" + is_new_user: bool = False class Meta: abstract = True @@ -98,6 +104,7 @@ def _get_user(self, user_data: dict): # type: ignore[type-arg,no-untyped-def] user = FFAdminUser.objects.create( **user_data, email=email.lower(), sign_up_type=sign_up_type ) + self.is_new_user = True # On first OAuth signup, we register the hubspot cookies and utms before creating the hubspot contact if request := self.context.get("request"): diff --git a/api/custom_auth/oauth/views.py b/api/custom_auth/oauth/views.py index 0ee93a30c770..6b152a77c819 100644 --- a/api/custom_auth/oauth/views.py +++ b/api/custom_auth/oauth/views.py @@ -14,8 +14,8 @@ from custom_auth.oauth.serializers import ( GithubLoginSerializer, GoogleLoginSerializer, + OAuthTokenSerializer, ) -from custom_auth.serializers import CustomTokenSerializer logger = logging.getLogger(__name__) @@ -26,7 +26,7 @@ @extend_schema( request=GoogleLoginSerializer, - responses={200: CustomTokenSerializer, 502: ErrorSerializer}, + responses={200: OAuthTokenSerializer, 502: ErrorSerializer}, ) @api_view(["POST"]) @permission_classes([AllowAny]) @@ -39,7 +39,11 @@ def login_with_google(request): # type: ignore[no-untyped-def] token = serializer.save() if settings.COOKIE_AUTH_ENABLED: return authorise_response(token.user, Response(status=HTTP_204_NO_CONTENT)) - return Response(data=CustomTokenSerializer(instance=token).data) + return Response( + data=OAuthTokenSerializer( + {"key": token.key, "is_new_user": serializer.is_new_user} + ).data + ) except GoogleError as e: logger.warning("%s: %s" % (GOOGLE_AUTH_ERROR_MESSAGE, str(e))) return Response( @@ -50,7 +54,7 @@ def login_with_google(request): # type: ignore[no-untyped-def] @extend_schema( request=GithubLoginSerializer, - responses={200: CustomTokenSerializer, 502: ErrorSerializer}, + responses={200: OAuthTokenSerializer, 502: ErrorSerializer}, ) @api_view(["POST"]) @permission_classes([AllowAny]) @@ -63,7 +67,11 @@ def login_with_github(request): # type: ignore[no-untyped-def] token = serializer.save() if settings.COOKIE_AUTH_ENABLED: return authorise_response(token.user, Response(status=HTTP_204_NO_CONTENT)) - return Response(data=CustomTokenSerializer(instance=token).data) + return Response( + data=OAuthTokenSerializer( + {"key": token.key, "is_new_user": serializer.is_new_user} + ).data + ) except GithubError as e: logger.warning("%s: %s" % (GITHUB_AUTH_ERROR_MESSAGE, str(e))) return Response( diff --git a/api/tests/unit/custom_auth/oauth/test_unit_oauth_views.py b/api/tests/unit/custom_auth/oauth/test_unit_oauth_views.py index 9c36407c14e6..8053ff1bcd4b 100644 --- a/api/tests/unit/custom_auth/oauth/test_unit_oauth_views.py +++ b/api/tests/unit/custom_auth/oauth/test_unit_oauth_views.py @@ -78,6 +78,7 @@ def test_google_oauth_login__invite_exists_and_registration_disabled__returns_20 # Then assert response.status_code == status.HTTP_200_OK + assert response.json()["is_new_user"] is True @mock.patch("custom_auth.oauth.serializers.GithubUser") @@ -107,6 +108,7 @@ def test_github_oauth_login__invite_exists_and_registration_disabled__returns_20 # Then assert response.status_code == status.HTTP_200_OK + assert response.json()["is_new_user"] is True @mock.patch("custom_auth.oauth.serializers.get_user_info") @@ -133,6 +135,7 @@ def test_google_oauth_login__existing_user_and_registration_disabled__returns_20 # Then assert response.status_code == status.HTTP_200_OK assert "key" in response.json() + assert response.json()["is_new_user"] is False @mock.patch("custom_auth.oauth.serializers.GithubUser") @@ -161,6 +164,7 @@ def test_github_oauth_login__existing_user_and_registration_disabled__returns_20 # Then assert response.status_code == status.HTTP_200_OK assert "key" in response.json() + assert response.json()["is_new_user"] is False def test_google_oauth_login__case_insensitive_email__updates_existing_user( diff --git a/openapi.yaml b/openapi.yaml index 10083805de9d..d89d7bbe0392 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -497,7 +497,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CustomToken' + $ref: '#/components/schemas/OAuthToken' '502': description: '' content: @@ -531,7 +531,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CustomToken' + $ref: '#/components/schemas/OAuthToken' '502': description: '' content: @@ -19140,14 +19140,6 @@ components: $ref: '#/components/schemas/MultivariateFeatureStateValue' required: - feature_state_value - CustomToken: - type: object - properties: - key: - type: string - maxLength: 40 - required: - - key CustomUserCreate: type: object properties: @@ -22049,6 +22041,15 @@ components: - app_id NullEnum: type: 'null' + OAuthToken: + type: object + properties: + key: + type: string + readOnly: true + is_new_user: + type: boolean + readOnly: true OrganisationAPIUsageNotification: type: object properties: