81 lines
5.5 KiB
JavaScript
81 lines
5.5 KiB
JavaScript
const SANSHENG_AUTH_CONFIG={
|
|
apiBase:'http://localhost:19174',
|
|
pcBase:'http://localhost:5173',
|
|
clientId:'sansheng-product-analysis-extension',
|
|
scope:'product.analysis',
|
|
callbackPath:'/extension/authorize'
|
|
};
|
|
|
|
const AUTH_ACCESS_KEY='sansheng_extension_access';
|
|
const AUTH_REFRESH_KEY='sansheng_extension_refresh';
|
|
const AUTH_PENDING_KEY='sansheng_extension_auth_pending';
|
|
const AUTH_LAST_CONTEXT_KEY='sansheng_extension_auth_last_context';
|
|
|
|
function authBase64Url(bytes){return btoa(String.fromCharCode(...bytes)).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')}
|
|
function authRandom(size=32){const bytes=new Uint8Array(size);crypto.getRandomValues(bytes);return authBase64Url(bytes)}
|
|
async function authChallenge(verifier){const digest=await crypto.subtle.digest('SHA-256',new TextEncoder().encode(verifier));return authBase64Url(new Uint8Array(digest))}
|
|
function authCallbackUrl(){return SANSHENG_AUTH_CONFIG.pcBase+SANSHENG_AUTH_CONFIG.callbackPath}
|
|
|
|
async function getExtensionAuth(){
|
|
const session=await chrome.storage.session.get(AUTH_ACCESS_KEY);
|
|
const local=await chrome.storage.local.get(AUTH_REFRESH_KEY);
|
|
return {access:session[AUTH_ACCESS_KEY]||null,refresh:local[AUTH_REFRESH_KEY]||null};
|
|
}
|
|
|
|
async function startExtensionLogin(){
|
|
const verifier=authRandom(48),state=authRandom(24),challenge=await authChallenge(verifier);
|
|
const tabs=await chrome.tabs.query({active:true,currentWindow:true});
|
|
await chrome.storage.local.set({[AUTH_PENDING_KEY]:{verifier,state,createdAt:Date.now(),tabId:tabs?.[0]?.id||0,windowId:tabs?.[0]?.windowId||0}});
|
|
const query=new URLSearchParams({client_id:SANSHENG_AUTH_CONFIG.clientId,redirect_uri:authCallbackUrl(),scope:SANSHENG_AUTH_CONFIG.scope,state,code_challenge:challenge,code_challenge_method:'S256'});
|
|
return chrome.tabs.create({url:`${SANSHENG_AUTH_CONFIG.pcBase}${SANSHENG_AUTH_CONFIG.callbackPath}?${query}`,active:true});
|
|
}
|
|
|
|
async function exchangeExtensionCode(callbackUrl){
|
|
const url=new URL(callbackUrl),code=url.searchParams.get('code'),state=url.searchParams.get('state');
|
|
if(!code||!state)return false;
|
|
const pending=(await chrome.storage.local.get(AUTH_PENDING_KEY))[AUTH_PENDING_KEY];
|
|
if(!pending||pending.state!==state||Date.now()-pending.createdAt>10*60*1000)throw new Error('扩展授权请求已过期,请重新登录');
|
|
const body=new URLSearchParams({grant_type:'authorization_code',client_id:SANSHENG_AUTH_CONFIG.clientId,code,redirect_uri:authCallbackUrl(),code_verifier:pending.verifier});
|
|
const response=await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Token`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body});
|
|
const token=await response.json();
|
|
if(!response.ok||!token.access_token)throw new Error(token.error_description||'扩展登录失败');
|
|
await chrome.storage.session.set({[AUTH_ACCESS_KEY]:{token:token.access_token,type:token.token_type||'Sansheng',expiresAt:Date.now()+(token.expires_in||7200)*1000}});
|
|
await chrome.storage.local.set({[AUTH_REFRESH_KEY]:{token:token.refresh_token,expiresAt:Date.now()+(token.refresh_expires_in||2592000)*1000}});
|
|
await chrome.storage.local.set({[AUTH_LAST_CONTEXT_KEY]:{tabId:pending.tabId||0,windowId:pending.windowId||0,updatedAt:Date.now()}});
|
|
await chrome.storage.local.remove(AUTH_PENDING_KEY);
|
|
return true;
|
|
}
|
|
|
|
async function refreshExtensionToken(){
|
|
const auth=await getExtensionAuth();
|
|
if(!auth.refresh?.token)throw new Error('扩展登录已过期');
|
|
const body=new URLSearchParams({grant_type:'refresh_token',client_id:SANSHENG_AUTH_CONFIG.clientId,refresh_token:auth.refresh.token});
|
|
const response=await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Token`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body});
|
|
const token=await response.json();
|
|
if(!response.ok||!token.access_token)throw new Error(token.error_description||'扩展登录已过期');
|
|
await chrome.storage.session.set({[AUTH_ACCESS_KEY]:{token:token.access_token,type:token.token_type||'Sansheng',expiresAt:Date.now()+(token.expires_in||7200)*1000}});
|
|
await chrome.storage.local.set({[AUTH_REFRESH_KEY]:{token:token.refresh_token,expiresAt:Date.now()+(token.refresh_expires_in||2592000)*1000}});
|
|
return token.access_token;
|
|
}
|
|
|
|
async function extensionApiFetch(path,options={}){
|
|
let auth=await getExtensionAuth();
|
|
if(!auth.access?.token||auth.access.expiresAt-Date.now()<60_000){await refreshExtensionToken();auth=await getExtensionAuth()}
|
|
const send=()=>fetch(SANSHENG_AUTH_CONFIG.apiBase+path,{...options,headers:{...(options.headers||{}),Authorization:`Sansheng ${auth.access.token}`,'Sansheng-Source':'chrome-extension'}});
|
|
let response=await send();
|
|
if(response.status===401){await refreshExtensionToken();auth=await getExtensionAuth();response=await send()}
|
|
return response;
|
|
}
|
|
|
|
async function logoutExtension(){
|
|
const auth=await getExtensionAuth();
|
|
if(auth.refresh?.token){
|
|
const body=new URLSearchParams({client_id:SANSHENG_AUTH_CONFIG.clientId,refresh_token:auth.refresh.token,access_token:auth.access?.token||''});
|
|
await fetch(`${SANSHENG_AUTH_CONFIG.apiBase}/V1/OAuth/Revoke`,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body}).catch(()=>{});
|
|
}
|
|
await chrome.storage.session.remove(AUTH_ACCESS_KEY);
|
|
await chrome.storage.local.remove([AUTH_REFRESH_KEY,AUTH_PENDING_KEY,AUTH_LAST_CONTEXT_KEY]);
|
|
}
|
|
|
|
self.sanshengExtensionAuth={config:SANSHENG_AUTH_CONFIG,get:getExtensionAuth,start:startExtensionLogin,exchange:exchangeExtensionCode,fetch:extensionApiFetch,logout:logoutExtension};
|