Spaces:
Running on Zero
Running on Zero
File size: 9,374 Bytes
e49e31a b4691b7 0e86155 e49e31a b4691b7 0e86155 e49e31a 0e86155 e49e31a 0e86155 e63a295 b4691b7 e63a295 0e86155 e63a295 b4691b7 0e86155 e49e31a 0e86155 e49e31a b4691b7 0e86155 e49e31a 0e86155 e49e31a 0e86155 e49e31a e63a295 e49e31a 0e86155 e49e31a e63a295 e49e31a 0e86155 b4691b7 0e86155 b4691b7 e49e31a 0e86155 e49e31a 0e86155 b4691b7 0e86155 b4691b7 e49e31a 0e86155 e49e31a 0e86155 b4691b7 5e3e294 b4691b7 0e86155 e49e31a b4691b7 0e86155 b4691b7 e63a295 b4691b7 0e86155 e49e31a b4691b7 0e86155 b4691b7 e49e31a 0e86155 b4691b7 0e86155 e49e31a 0e86155 e49e31a 0e86155 e49e31a | 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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 'use strict';
// Official HF PKCE redirect helpers. Only the one-use verifier crosses navigation;
// OAuth access tokens and profile details stay in this page's memory.
(() => {
const node=id=>document.getElementById(id),flowKey='tiga.ald.hf.pkce.v1';
const identityScopes=['openid','profile'],storageScopes=['openid','profile','write-repos'];
let session=null,storageSession=null,locked=false,ready=false,config=null,exchanging=false;
const message=text=>{node('hfIdentity').textContent=text;};
const storageMessage=text=>{node('bucket_auth_status').textContent=text;};
const scopeSet=value=>{
if(typeof value!=='string'||!value.trim())return null;
const items=value.trim().split(/\s+/),set=new Set(items);
return set.size===items.length?set:null;
};
const exactScopes=(value,expected)=>{
const scopes=scopeSet(value);
return !!scopes&&scopes.size===expected.length&&expected.every(scope=>scopes.has(scope));
};
const storageEnabled=()=>ready&&config?.storage_enabled===true&&exactScopes(config.storage_scopes,storageScopes);
function emitChange(){window.dispatchEvent(new Event('hf-auth-change'));}
function render(){
const anySession=!!session||!!storageSession;
node('hfSignIn').hidden=!!session;
node('hfSignIn').disabled=locked||exchanging||!ready;
node('hfAccount').hidden=!session;
node('hfAccount').textContent=session?'Connected as '+session.userInfo.preferred_username:'';
node('hfSignOut').hidden=!anySession;
node('hfSignOut').disabled=locked||exchanging||!anySession;
node('hfSignOut').textContent=session?'Disconnect account':'Disconnect HF access';
node('hfVerify').hidden=!session;
node('hfVerify').disabled=locked||exchanging||!session;
const authorize=node('bucket_authorize');
if(authorize){authorize.hidden=!storageEnabled()||!!storageSession;authorize.disabled=locked||exchanging||!storageEnabled()||!!storageSession;}
if(node('bucket_auth_unavailable'))node('bucket_auth_unavailable').hidden=storageEnabled();
getStorageToken();
if(node('bucket_auth_status'))node('bucket_auth_status').textContent=storageSession
?'HF bucket access authorized as '+storageSession.userInfo.preferred_username+'. Choose your private bucket below.'
:'Not authorized. Bucket storage is optional.';
}
function clearAll(){session=null;storageSession=null;emitChange();render();}
function clearStorage(){
if(!storageSession)return;
if(session===storageSession)session=null;
storageSession=null;emitChange();render();
}
function getStorageToken(){
if(storageSession&&storageSession.accessTokenExpiresAt.getTime()<=Date.now()){
if(session===storageSession)session=null;
storageSession=null;emitChange();storageMessage('HF authorization expired. Sign in or authorize access again to reconnect.');
render();return null;
}
return storageSession?.accessToken??null;
}
window.HFAuth={
get storageEnabled(){return storageEnabled();},
get storageAuthorized(){return !!getStorageToken();},
setBusy(value){locked=value;render();},
credentials(){
if(exchanging)throw Error('Complete HF sign-in before starting GPU');
if(session&&session.accessTokenExpiresAt.getTime()<=Date.now()){
clearAll();message('HF sign-in expired. Sign in again before an account GPU request.');
throw Error('HF sign-in expired; sign in again');
}
return session?{token:session.accessToken}:{};
},
getStorageToken,
getStorageUsername(){return getStorageToken()?storageSession.userInfo.preferred_username:null;},
clearStorageAuthorization:clearStorage,
verify(identity){
if(session&&(!identity.authenticated||identity.auth_path!=='queue-bearer'||identity.username!==session.userInfo.preferred_username))
throw Error('GPU queue visitor identity did not match your HF sign-in; no GPU request started');
if(session)message('Account connection checked. GPU use remains subject to your HF allowance.');
}
};
node('hfSignOut').onclick=()=>{clearAll();message('HF access disconnected from this app. CPU remains available. HF website sign-in is unchanged.');storageMessage('HF access disconnected. Authorize bucket access again to use storage.');};
node('hfVerify').onclick=async()=>{
try{await window.TrainingClient.verifyIdentity();}
catch{message('Account connection check failed. No GPU request started. Disconnect and sign in again, or use CPU.');}
};
async function authorize(purpose){
if(locked||exchanging||!ready)return;
if(purpose==='storage'&&!storageEnabled())return;
if(window.TrainingClient?.hasUnsavedState?.() &&
!(await window.appConfirm('HF authorization navigates away. Selected files, a downloaded result and unsaved editor changes are lost; saved recipes remain. Continue, then reselect or recreate them?',{heading:'Continue to HF authorization',confirmLabel:'Continue to HF'})))return;
exchanging=true;render();
try{
const state={},requested=purpose==='storage'?storageScopes:identityScopes;
const helpers=await import('/hf-oauth.js');
const url=await helpers.oauthLoginUrl({clientId:config.client_id,scopes:requested.join(' '),
redirectUrl:location.origin+'/',localStorage:state});
sessionStorage.setItem(flowKey,JSON.stringify({...state,started:Date.now(),purpose}));
if(purpose==='storage')storageMessage('Opening HF permission request for bucket access. No password or personal token is entered here.');
else message('Opening Hugging Face sign-in. No password or personal token is entered here.');
location.href=url;
}catch{
exchanging=false;render();
const text='HF authorization could not start. Tab-local sign-in storage is required; local export and CPU remain available.';
if(purpose==='storage')storageMessage(text);else message(text);
}
}
node('hfSignIn').onclick=()=>authorize('identity');
node('bucket_authorize').onclick=()=>authorize('storage');
const returned=location.href,params=new URL(returned).searchParams;
const isCallback=params.has('code')||params.has('error');
if(isCallback){history.replaceState(null,'','/');exchanging=true;}
fetch('/api/auth/config',{cache:'no-store',credentials:'same-origin'}).then(async response=>{
if(!response.ok)throw Error();config=await response.json();ready=config.enabled===true;
if(isCallback){
let flow;
try{flow=JSON.parse(sessionStorage.getItem(flowKey));}
finally{sessionStorage.removeItem(flowKey);}
if(!ready||!flow?.nonce||!flow?.codeVerifier||!Number.isFinite(flow.started)||Date.now()-flow.started>300000||flow.started>Date.now()||!['identity','storage'].includes(flow.purpose))throw Error();
if(flow.purpose==='storage'&&!storageEnabled())throw Error();
const helpers=await import('/hf-oauth.js');
const result=await helpers.oauthHandleRedirect({redirectedUrl:returned,nonce:flow.nonce,codeVerifier:flow.codeVerifier});
flow.nonce=null;flow.codeVerifier=null;
const allowed=flow.purpose==='storage'?storageScopes:identityScopes;
if(!exactScopes(result.scope,allowed))throw Error();
if(!result.accessToken||!result.userInfo?.preferred_username||!Number.isFinite(result.accessTokenExpiresAt?.getTime())||result.accessTokenExpiresAt.getTime()<=Date.now())throw Error();
if(flow.purpose==='storage'){
// The optional storage grant includes the ordinary identity scopes. Reuse
// this one page-memory grant for both purposes so the user signs in once.
session=result;storageSession=result;emitChange();
storageMessage('HF bucket access authorized as '+result.userInfo.preferred_username+'. Choose your existing private bucket below.');
}else{
session=result;storageSession=null;emitChange();
message('Account connected. You can choose GPU; your account is checked automatically before the run.');
}
}else{
sessionStorage.removeItem(flowKey);
message(ready?'CPU needs no sign-in. For your account GPU allowance, sign in before choosing files.':
'HF sign-in is not configured on this deployment. CPU remains anonymous.');
storageMessage(storageEnabled()?'Not authorized. Bucket storage is optional.':
'HF storage authorization is unavailable on this deployment. Local bundle export remains available.');
}
}).catch(()=>{
try{sessionStorage.removeItem(flowKey);}catch{}
clearAll();message('HF sign-in unavailable, cancelled, expired, or not verified. No GPU request started; CPU remains anonymous.');
storageMessage('HF authorization unavailable, cancelled, expired, or not verified. Bucket storage remains disconnected; local export is available.');
}).finally(()=>{exchanging=false;render();});
setInterval(()=>{
let changed=false;
if(session&&session.accessTokenExpiresAt.getTime()<=Date.now()){
session=null;message('HF sign-in expired. Sign in again to identify account GPU requests. CPU remains available.');changed=true;
}
if(storageSession&&storageSession.accessTokenExpiresAt.getTime()<=Date.now()){
storageSession=null;storageMessage('HF bucket authorization expired. Authorize access again to reconnect.');changed=true;
}
if(changed){emitChange();render();}
},1000);
render();
})();
|