File size: 3,107 Bytes
1fff71f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b211df8
 
 
 
1fff71f
 
5148b3d
 
 
 
 
1fff71f
 
5148b3d
 
1fff71f
 
 
b211df8
1fff71f
018ba2e
 
1fff71f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
"""
HuggingFace OAuth authentication service.
Feature: 012-profile-contact-ui
"""

import os

from authlib.integrations.flask_client import OAuth


class AuthService:
    """HuggingFace OAuth service."""

    def __init__(self, app=None):
        self.oauth = OAuth()
        self.hf = None
        if app:
            self.init_app(app)

    def init_app(self, app):
        """Initialize OAuth with Flask app."""
        self.oauth.init_app(app)

        # Get OAuth URLs from environment (allows mock OAuth for local dev)
        authorization_url = os.getenv(
            "HF_AUTHORIZATION_URL",
            "https://huggingface.co/oauth/authorize"
        )
        token_url = os.getenv(
            "HF_TOKEN_URL",
            "https://huggingface.co/oauth/token"
        )
        userinfo_url = os.getenv(
            "HF_USERINFO_URL",
            "https://huggingface.co/oauth/userinfo"
        )
        jwks_uri = os.getenv(
            "HF_JWKS_URI",
            "https://huggingface.co/oauth/jwks"
        )

        # Register OAuth provider (HuggingFace or mock)
        # HF Spaces provides OAUTH_CLIENT_ID/SECRET when hf_oauth: true
        # Local dev uses HF_CLIENT_ID/SECRET
        client_id = os.getenv("OAUTH_CLIENT_ID") or os.getenv("HF_CLIENT_ID")
        client_secret = os.getenv("OAUTH_CLIENT_SECRET") or os.getenv("HF_CLIENT_SECRET")
        
        self.hf = self.oauth.register(
            name="huggingface",
            client_id=client_id,
            client_secret=client_secret,
            authorize_url=authorization_url,
            access_token_url=token_url,
            userinfo_endpoint=userinfo_url,
            jwks_uri=jwks_uri,
            client_kwargs={"scope": "openid profile email"},
            # Set update_token to None to avoid unnecessary token updates
            update_token=None,
        )

    def get_authorization_url(self, redirect_uri: str) -> str:
        """
        Get HuggingFace OAuth authorization URL.

        Args:
            redirect_uri: Callback URL for OAuth flow

        Returns:
            Authorization URL string
        """
        if not self.hf:
            raise RuntimeError("OAuth not initialized. Call init_app() first.")

        return self.hf.authorize_redirect(redirect_uri)

    def fetch_token(self, **kwargs):
        """
        Exchange authorization code for access token.

        Returns:
            Token dict with access_token, refresh_token, etc.
        """
        if not self.hf:
            raise RuntimeError("OAuth not initialized. Call init_app() first.")

        return self.hf.authorize_access_token(**kwargs)

    def fetch_userinfo(self, token):
        """
        Fetch user information using access token.

        Args:
            token: Access token dict

        Returns:
            User info dict with sub, name, preferred_username, picture, email
        """
        if not self.hf:
            raise RuntimeError("OAuth not initialized. Call init_app() first.")

        return self.hf.userinfo(token=token)


# Global auth service instance
auth_service = AuthService()