ald-tao-gpu / hf-auth.js
TiGa-RCE's picture
Fix optional OAuth configuration access behind private HF proxy
5e3e294 verified
Raw History Blame Contribute Delete
9.37 kB
'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();
})();