feat: Add manual OAuth authorization mode for WebUI (#131)

* feat: add manual OAuth flow support in WebUI

* fix: reset add account modal state on close

* feat: display custom API key in startup banner

* fix: move translations to separate files and optimize import API

* fix: remove orphaned model-manager.js and cleanup callback server on manual auth

---------

Co-authored-by: Badri Narayanan S <59133612+badrisnarayanan@users.noreply.github.com>
This commit is contained in:
董飞祥
2026-01-23 21:23:29 +08:00
committed by GitHub
parent 0fa945b069
commit 9992c4ab27
15 changed files with 624 additions and 16 deletions

View File

@@ -0,0 +1,88 @@
/**
* Add Account Modal Component
* Registers itself to window.Components for Alpine.js to consume
*/
window.Components = window.Components || {};
window.Components.addAccountModal = () => ({
manualMode: false,
authUrl: '',
authState: '',
callbackInput: '',
submitting: false,
/**
* Reset all state to initial values
*/
resetState() {
this.manualMode = false;
this.authUrl = '';
this.authState = '';
this.callbackInput = '';
this.submitting = false;
// Close any open details elements
const details = document.querySelectorAll('#add_account_modal details[open]');
details.forEach(d => d.removeAttribute('open'));
},
async copyLink() {
if (!this.authUrl) return;
await navigator.clipboard.writeText(this.authUrl);
Alpine.store('global').showToast(Alpine.store('global').t('linkCopied'), 'success');
},
async initManualAuth(event) {
if (event.target.open && !this.authUrl) {
try {
const password = Alpine.store('global').webuiPassword;
const {
response,
newPassword
} = await window.utils.request('/api/auth/url', {}, password);
if (newPassword) Alpine.store('global').webuiPassword = newPassword;
const data = await response.json();
if (data.status === 'ok') {
this.authUrl = data.url;
this.authState = data.state;
}
} catch (e) {
Alpine.store('global').showToast(e.message, 'error');
}
}
},
async completeManualAuth() {
if (!this.callbackInput || !this.authState) return;
this.submitting = true;
try {
const store = Alpine.store('global');
const {
response,
newPassword
} = await window.utils.request('/api/auth/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
callbackInput: this.callbackInput,
state: this.authState
})
}, store.webuiPassword);
if (newPassword) store.webuiPassword = newPassword;
const data = await response.json();
if (data.status === 'ok') {
store.showToast(store.t('accountAddedSuccess'), 'success');
Alpine.store('data').fetchData();
document.getElementById('add_account_modal').close();
this.resetState();
} else {
store.showToast(data.error || store.t('authFailed'), 'error');
}
} catch (e) {
Alpine.store('global').showToast(e.message, 'error');
} finally {
this.submitting = false;
}
}
});