@@ -144,6 +144,105 @@ def post(self, request):
144144 )
145145
146146
147+ class GoogleIdTokenView (APIView ):
148+ """
149+ Handle Google Sign-In from mobile apps using ID token.
150+ Mobile app sends Google ID token, backend verifies and returns JWT.
151+ """
152+
153+ permission_classes = []
154+ authentication_classes = []
155+
156+ @extend_schema (
157+ tags = ["auth" ],
158+ request = inline_serializer (
159+ name = "GoogleIdTokenRequest" ,
160+ fields = {"idToken" : serializers .CharField ()},
161+ ),
162+ responses = {
163+ 200 : inline_serializer (
164+ name = "GoogleIdTokenResponse" ,
165+ fields = {
166+ "JWTtoken" : serializers .CharField (),
167+ "user" : serializers .DictField (),
168+ "organizations" : serializers .ListField (),
169+ },
170+ )
171+ },
172+ )
173+ def post (self , request ):
174+ from django .utils import timezone
175+
176+ from google .oauth2 import id_token
177+ from google .auth .transport import requests as google_requests
178+
179+ id_token_str = request .data .get ("idToken" )
180+ if not id_token_str :
181+ return Response (
182+ {"error" : "Missing idToken" },
183+ status = status .HTTP_400_BAD_REQUEST ,
184+ )
185+
186+ # Verify the ID token with Google
187+ try :
188+ idinfo = id_token .verify_oauth2_token (
189+ id_token_str ,
190+ google_requests .Request (),
191+ settings .GOOGLE_CLIENT_ID ,
192+ )
193+ email = idinfo .get ("email" )
194+ picture = idinfo .get ("picture" , "" )
195+ except ValueError as e :
196+ return Response (
197+ {"error" : f"Invalid token: { str (e )} " },
198+ status = status .HTTP_400_BAD_REQUEST ,
199+ )
200+
201+ if not email :
202+ return Response (
203+ {"error" : "No email in token" },
204+ status = status .HTTP_400_BAD_REQUEST ,
205+ )
206+
207+ # Get or create user
208+ user , created = User .objects .get_or_create (
209+ email = email ,
210+ defaults = {
211+ "profile_pic" : picture ,
212+ "password" : make_password (secrets .token_urlsafe (32 )),
213+ },
214+ )
215+ user .last_login = timezone .now ()
216+ user .save (update_fields = ["last_login" ])
217+
218+ # Get user's organizations
219+ profiles = Profile .objects .filter (user = user ).select_related ("org" )
220+ organizations = [
221+ {
222+ "id" : str (p .org .id ),
223+ "name" : p .org .name ,
224+ "role" : p .role ,
225+ }
226+ for p in profiles
227+ ]
228+
229+ # Generate JWT token
230+ token = OrgAwareRefreshToken .for_user_and_org (user , None )
231+
232+ return Response (
233+ {
234+ "JWTtoken" : str (token .access_token ),
235+ "user" : {
236+ "id" : str (user .id ),
237+ "email" : user .email ,
238+ "name" : email .split ("@" )[0 ],
239+ "profileImage" : user .profile_pic ,
240+ },
241+ "organizations" : organizations ,
242+ }
243+ )
244+
245+
147246class LoginView (APIView ):
148247 """
149248 Login with email and password, returns JWT tokens
0 commit comments