File size: 5,992 Bytes
22665fb
3f84555
22665fb
 
f91a47c
69b83cc
22665fb
b3e7f02
 
 
 
 
 
 
22665fb
 
 
69b83cc
 
b3e7f02
 
69b83cc
b3e7f02
69b83cc
 
 
 
ea67b4c
b3e7f02
283947d
f91a47c
69b83cc
 
22665fb
b3e7f02
 
9b42726
b3e7f02
f91a47c
b3e7f02
f91a47c
b3e7f02
 
 
f91a47c
 
 
b3e7f02
f91a47c
b3e7f02
 
8ddcc75
 
b3e7f02
 
8ddcc75
b3e7f02
 
8ddcc75
b3e7f02
 
8ddcc75
b3e7f02
 
ea67b4c
8ddcc75
 
 
ea67b4c
8ddcc75
 
b3e7f02
22665fb
 
b3e7f02
 
9b42726
 
22665fb
 
b3e7f02
 
ea67b4c
b3e7f02
ea67b4c
 
 
b3e7f02
ea67b4c
 
d18f624
b3e7f02
8ddcc75
b3e7f02
 
 
 
 
 
 
 
 
 
 
ea67b4c
b3e7f02
 
 
 
 
 
 
 
 
 
ea67b4c
b3e7f02
69b83cc
 
b3e7f02
69b83cc
b3e7f02
 
ea67b4c
22665fb
ea67b4c
 
 
b3e7f02
ea67b4c
b3e7f02
22665fb
 
 
 
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>OAuth in a static Space (Vanilla JS)</title>
    <style>
      body { font-family: sans-serif; background: white; color: #222; margin: 0; padding: 2rem; }
      .card { max-width: 420px; margin: 0 auto; background: #fff; border-radius: 16px; box-shadow: 0 2px 8px #0001; padding: 2rem; border: 1px solid #eee; }
      h1 { font-size: 20px; margin-top: 0; }
      p { color: #666; font-size: 14px; }
      #status { margin-top: 1rem; color: #28a745; }
      button, img { margin-top: 1rem; cursor: pointer; }
      pre { background: #f8f9fa; padding: 1em; border-radius: 8px; margin-top: 1rem; font-size: 12px; }
    </style>
  </head>
  <body>
    <div class="card">
      <h1>OAuth in a static Space (Vanilla JS)</h1>
      <p>Demonstration of Hugging Face OAuth authentication with PKCE in vanilla JS.</p>
      
      <img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-xl-dark.svg"
           alt="Sign in with Hugging Face" id="signin" style="display:none;max-width:100%;">
      <button id="signout" style="display:none;">Sign out</button>
      <div id="status"></div>
      <pre id="userinfo" style="display:none"></pre>
    </div>
    <script>
      // OAuth configuration - automatically injected by HF Spaces
      const CLIENT_ID = window.huggingface?.variables?.OAUTH_CLIENT_ID;
      const REDIRECT_URI = window.location.origin + window.location.pathname;
      const HF_OAUTH_URL = 'https://huggingface.co/oauth/authorize';
      const HF_TOKEN_URL = 'https://huggingface.co/oauth/token';

      // UI helpers
      const showLoggedIn = (userinfo) => {
        document.getElementById('signin').style.display = 'none';
        document.getElementById('signout').style.display = 'block';
        document.getElementById('status').textContent = 'Logged in!';
        document.getElementById('userinfo').style.display = 'block';
        document.getElementById('userinfo').textContent = userinfo;
      };
      const showLoggedOut = () => {
        document.getElementById('signin').style.display = 'block';
        document.getElementById('signout').style.display = 'none';
        document.getElementById('status').textContent = '';
        document.getElementById('userinfo').style.display = 'none';
      };

      // PKCE helpers
      const generateCodeVerifier = () => {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return btoa(String.fromCharCode(...array)).replace(/[+/=]/g, m => ({'+':'-','/':'_','=':''}[m]));
      };
      
      const generateCodeChallenge = async (verifier) => {
        const data = new TextEncoder().encode(verifier);
        const digest = await crypto.subtle.digest('SHA-256', data);
        return btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/[+/=]/g, m => ({'+':'-','/':'_','=':''}[m]));
      };

      // Event handlers
      document.getElementById('signin').onclick = async () => {
        const state = Math.random().toString(36).slice(2);
        const codeVerifier = generateCodeVerifier();
        const codeChallenge = await generateCodeChallenge(codeVerifier);
        
        localStorage.setItem('hf_oauth_state', state);
        localStorage.setItem('hf_oauth_code_verifier', codeVerifier);
        
        window.location = `${HF_OAUTH_URL}?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=openid%20profile&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
      };

      document.getElementById('signout').onclick = () => {
        localStorage.clear();
        showLoggedOut();
        window.history.replaceState({}, '', window.location.pathname);
      };

      // OAuth callback handler
      window.onload = async () => {
        const params = new URLSearchParams(window.location.search);
        
        if (params.has('code') && params.has('state')) {
          const state = params.get('state');
          if (state !== localStorage.getItem('hf_oauth_state')) {
            document.getElementById('status').textContent = 'Security error detected.';
            return;
          }
          
          document.getElementById('status').textContent = 'Exchanging code...';
          
          try {
            // Exchange code for token
            const tokenResp = await fetch(HF_TOKEN_URL, {
              method: 'POST',
              headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
              body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: params.get('code'),
                redirect_uri: REDIRECT_URI,
                code_verifier: localStorage.getItem('hf_oauth_code_verifier')
              })
            });
            
            const { access_token } = await tokenResp.json();
            
            // Fetch user info
            const userResp = await fetch('https://huggingface.co/oauth/userinfo', {
              headers: { Authorization: `Bearer ${access_token}` }
            });
            const userinfo = await userResp.json();
            
            // Save and display
            const userinfoStr = JSON.stringify(userinfo, null, 2);
            localStorage.setItem('hf_oauth_token', access_token);
            localStorage.setItem('hf_oauth_userinfo', userinfoStr);
            showLoggedIn(userinfoStr);
            
            window.history.replaceState({}, '', window.location.pathname);
          } catch (error) {
            document.getElementById('status').textContent = `OAuth error: ${error.message}`;
            showLoggedOut();
          }
          return;
        }

        // Check existing session
        const userinfo = localStorage.getItem('hf_oauth_userinfo');
        userinfo ? showLoggedIn(userinfo) : showLoggedOut();
      };
    </script>
  </body>
</html>