Coverage for polar/user/endpoints.py: 97%

36 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 12:42 +0000

1from fastapi import Depends 

2 

3from polar.auth.dependencies import Authenticator, WebUserRead, WebUserWrite 

4from polar.auth.models import AuthSubject 

5from polar.customer_portal.endpoints.downloadables import router as downloadables_router 

6from polar.customer_portal.endpoints.license_keys import router as license_keys_router 

7from polar.customer_portal.endpoints.order import router as order_router 

8from polar.customer_portal.endpoints.subscription import router as subscription_router 

9from polar.models import User 

10from polar.models.user import OAuthPlatform 

11from polar.openapi import APITag 

12from polar.postgres import AsyncSession, get_db_session 

13from polar.routing import APIRouter 

14from polar.user.oauth_service import oauth_account_service 

15from polar.user.service import user as user_service 

16 

17from .schemas import ( 

18 UserDeletionResponse, 

19 UserIdentityVerification, 

20 UserRead, 

21 UserScopes, 

22) 

23 

24router = APIRouter(prefix="/users", tags=["users", APITag.private]) 

25 

26# Include customer portal endpoints for backwards compatibility 

27router.include_router(order_router, deprecated=True, include_in_schema=False) 

28router.include_router(subscription_router, deprecated=True, include_in_schema=False) 

29router.include_router(downloadables_router, deprecated=True, include_in_schema=False) 

30router.include_router(license_keys_router, deprecated=True, include_in_schema=False) 

31 

32 

33@router.get("/me", response_model=UserRead) 

34async def get_authenticated(auth_subject: WebUserRead) -> User: 

35 return auth_subject.subject 

36 

37 

38@router.get("/me/scopes", response_model=UserScopes) 

39async def scopes( 

40 auth_subject: AuthSubject[User] = Depends(Authenticator(allowed_subjects={User})), 

41) -> UserScopes: 

42 return UserScopes(scopes=list(auth_subject.scopes)) 

43 

44 

45@router.post("/me/identity-verification", response_model=UserIdentityVerification) 

46async def create_identity_verification( 

47 auth_subject: WebUserWrite, 

48 session: AsyncSession = Depends(get_db_session), 

49) -> UserIdentityVerification: 

50 return await user_service.create_identity_verification( 

51 session, user=auth_subject.subject 

52 ) 

53 

54 

55@router.delete( 

56 "/me", 

57 response_model=UserDeletionResponse, 

58 responses={ 

59 200: {"description": "Deletion result"}, 

60 }, 

61) 

62async def delete_authenticated_user( 

63 auth_subject: WebUserWrite, 

64 session: AsyncSession = Depends(get_db_session), 

65) -> UserDeletionResponse: 

66 """ 

67 Delete the authenticated user account. 

68 

69 A user can only be deleted if all organizations they are members of have been 

70 deleted first. If the user has active organizations, the response will include 

71 the list of organizations that must be deleted before the user account can be 

72 removed. 

73 

74 When deleted: 

75 - User's email is anonymized 

76 - User's avatar and metadata are cleared 

77 - User's OAuth accounts are deleted (cascade) 

78 - User's Account (payout account) is deleted if present 

79 """ 

80 return await user_service.request_deletion(session, auth_subject.subject) 

81 

82 

83@router.delete( 

84 "/me/oauth-accounts/{platform}", 

85 status_code=204, 

86 responses={ 

87 404: {"description": "OAuth account not found"}, 

88 400: {"description": "Cannot disconnect last authentication method"}, 

89 }, 

90) 

91async def disconnect_oauth_account( 

92 platform: OAuthPlatform, 

93 auth_subject: WebUserWrite, 

94 session: AsyncSession = Depends(get_db_session), 

95) -> None: 

96 """ 

97 Disconnect an OAuth account (GitHub or Google) from the authenticated user. 

98 

99 This allows users to unlink their OAuth provider while keeping their Polar account. 

100 They can still authenticate using other methods (email magic link or other OAuth providers). 

101 

102 Note: You cannot disconnect your last authentication method if your email is not verified. 

103 """ 

104 user = auth_subject.subject 

105 await oauth_account_service.disconnect_platform(session, user, platform)