Coverage for polar/integrations/apple/endpoints.py: 32%

73 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-05 16:17 +0000

1import uuid 1a

2from typing import Any 1a

3 

4from fastapi import Depends, Form, Request 1a

5from fastapi.responses import RedirectResponse 1a

6from httpx_oauth.integrations.fastapi import ( 1a

7 OAuth2AuthorizeCallbackError, 

8) 

9from httpx_oauth.oauth2 import GetAccessTokenError 1a

10 

11from polar.auth.dependencies import WebUserOrAnonymous 1a

12from polar.auth.models import is_user 1a

13from polar.auth.service import auth as auth_service 1a

14from polar.config import settings 1a

15from polar.exceptions import PolarRedirectionError 1a

16from polar.integrations.loops.service import loops as loops_service 1a

17from polar.kit import jwt 1a

18from polar.kit.http import ReturnTo 1a

19from polar.openapi import APITag 1a

20from polar.postgres import AsyncSession, get_db_session 1a

21from polar.posthog import posthog 1a

22from polar.routing import APIRouter 1a

23from polar.user.schemas import UserSignupAttribution, UserSignupAttributionQuery 1a

24 

25from .service import AppleServiceError, get_apple_oauth_client 1a

26from .service import apple as apple_service 1a

27 

28 

29class OAuthCallbackError(PolarRedirectionError): ... 1a

30 

31 

32router = APIRouter( 1a

33 prefix="/integrations/apple", 

34 tags=["integrations_apple", APITag.private], 

35) 

36 

37 

38@router.get("/authorize", name="integrations.apple.authorize") 1a

39async def apple_authorize( 1a

40 request: Request, 

41 auth_subject: WebUserOrAnonymous, 

42 return_to: ReturnTo, 

43 signup_attribution: UserSignupAttributionQuery, 

44) -> RedirectResponse: 

45 state: dict[str, Any] = {} 

46 

47 state["return_to"] = return_to 

48 

49 if signup_attribution: 

50 state["signup_attribution"] = signup_attribution.model_dump(exclude_unset=True) 

51 

52 if is_user(auth_subject): 

53 state["user_id"] = str(auth_subject.subject.id) 

54 

55 encoded_state = jwt.encode(data=state, secret=settings.SECRET, type="apple_oauth") 

56 redirect_uri = str(request.url_for("integrations.apple.callback")) 

57 apple_oauth_client = get_apple_oauth_client() 

58 authorization_url = await apple_oauth_client.get_authorization_url( 

59 redirect_uri=redirect_uri, 

60 state=encoded_state, 

61 extras_params={"response_mode": "form_post"}, 

62 ) 

63 return RedirectResponse(authorization_url, 303) 

64 

65 

66@router.post("/callback", name="integrations.apple.callback") 1a

67async def apple_callback( 1a

68 request: Request, 

69 auth_subject: WebUserOrAnonymous, 

70 code: str | None = Form(None), 

71 code_verifier: str | None = Form(None), 

72 state: str | None = Form(None), 

73 error: str | None = Form(None), 

74 session: AsyncSession = Depends(get_db_session), 

75) -> RedirectResponse: 

76 if code is None or error is not None: 

77 raise OAuth2AuthorizeCallbackError( 

78 status_code=400, 

79 detail=error if error is not None else None, 

80 ) 

81 

82 redirect_uri = str(request.url_for("integrations.apple.callback")) 

83 try: 

84 apple_oauth_client = get_apple_oauth_client(secret=True) 

85 token_data = await apple_oauth_client.get_access_token( 

86 code, redirect_uri, code_verifier 

87 ) 

88 except GetAccessTokenError as e: 

89 raise OAuth2AuthorizeCallbackError( 

90 status_code=500, 

91 detail=e.message, 

92 response=e.response, 

93 ) from e 

94 

95 error_description = token_data.get("error_description") 

96 if error_description: 

97 raise OAuthCallbackError(error_description) 

98 if not state: 

99 raise OAuthCallbackError("No state") 

100 

101 try: 

102 state_data = jwt.decode(token=state, secret=settings.SECRET, type="apple_oauth") 

103 except jwt.DecodeError as e: 

104 raise OAuthCallbackError("Invalid state") from e 

105 

106 return_to = state_data.get("return_to", None) 

107 state_user_id = state_data.get("user_id") 

108 

109 state_signup_attribution = state_data.get("signup_attribution") 

110 if state_signup_attribution: 

111 state_signup_attribution = UserSignupAttribution.model_validate( 

112 state_signup_attribution 

113 ) 

114 

115 try: 

116 if ( 

117 is_user(auth_subject) 

118 and state_user_id is not None 

119 and auth_subject.subject.id == uuid.UUID(state_user_id) 

120 ): 

121 is_signup = False 

122 user = await apple_service.link_user( 

123 session, user=auth_subject.subject, token=token_data 

124 ) 

125 else: 

126 user, is_signup = await apple_service.get_updated_or_create( 

127 session, 

128 token=token_data, 

129 signup_attribution=state_signup_attribution, 

130 ) 

131 except AppleServiceError as e: 

132 raise OAuthCallbackError(e.message, e.status_code, return_to=return_to) from e 

133 

134 # Event tracking last to ensure business critical data is stored first 

135 if is_signup: 

136 posthog.user_signup(user, "apple") 

137 await loops_service.user_signup(user) 

138 else: 

139 posthog.user_login(user, "apple") 

140 await loops_service.user_update(session, user) 

141 

142 return await auth_service.get_login_response( 

143 session, request, user, return_to=return_to 

144 )