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

@@ -128,6 +128,79 @@ const tests = [
return 'Add Node button present';
}
},
{
name: 'Accounts view has addAccountModal component',
async run() {
const res = await request('/views/accounts.html');
if (!res.data.includes('addAccountModal')) {
throw new Error('addAccountModal component not found');
}
return 'addAccountModal component present';
}
},
{
name: 'Accounts view has manual auth UI elements',
async run() {
const res = await request('/views/accounts.html');
const elements = ['initManualAuth', 'completeManualAuth', 'callbackInput'];
const missing = elements.filter(el => !res.data.includes(el));
if (missing.length > 0) {
throw new Error(`Missing manual auth elements: ${missing.join(', ')}`);
}
return 'All manual auth UI elements present';
}
},
{
name: 'Auth URL API endpoint works',
async run() {
const res = await request('/api/auth/url');
if (res.status !== 200) {
throw new Error(`Expected 200, got ${res.status}`);
}
const data = JSON.parse(res.data);
if (data.status !== 'ok') {
throw new Error(`API returned status: ${data.status}`);
}
if (!data.url || !data.state) {
throw new Error('Missing url or state in response');
}
return `Auth URL generated with state: ${data.state.substring(0, 8)}...`;
}
},
{
name: 'Auth complete API validates required fields',
async run() {
const res = await request('/api/auth/complete', {
method: 'POST',
body: {}
});
if (res.status !== 400) {
throw new Error(`Expected 400 for missing fields, got ${res.status}`);
}
const data = JSON.parse(res.data);
if (!data.error || !data.error.includes('Missing')) {
throw new Error('Expected error about missing fields');
}
return 'API validates required fields';
}
},
{
name: 'Auth complete API rejects invalid state',
async run() {
const res = await request('/api/auth/complete', {
method: 'POST',
body: { callbackInput: 'fake-code', state: 'invalid-state' }
});
if (res.status !== 400) {
throw new Error(`Expected 400 for invalid state, got ${res.status}`);
}
const data = JSON.parse(res.data);
if (!data.error || !data.error.includes('not found')) {
throw new Error('Expected error about flow not found');
}
return 'API rejects invalid state';
}
},
{
name: 'Account toggle API works',
async run() {