mirror of https://github.com/vladmandic/automatic
wrap all internal api calls with auth check and use token when possible
Signed-off-by: Vladimir Mandic <mandic00@live.com>pull/4401/head^2
parent
f383a2babc
commit
8fb037d4d4
|
|
@ -50,6 +50,7 @@
|
|||
},
|
||||
"globals": {
|
||||
"panzoom": "readonly",
|
||||
"authFetch": "readonly",
|
||||
"log": "readonly",
|
||||
"debug": "readonly",
|
||||
"error": "readonly",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ TBD
|
|||
- update global lint rules
|
||||
- chrono: switch to official pipeline
|
||||
- pipeline: add optional preprocess and postprocess hooks
|
||||
- auth: wrap all internal api calls with auth check and use token when possible
|
||||
- **Fixes**
|
||||
- hires: strength save/load in metadata, thanks @awsr
|
||||
- imgi2img: fix initial scale tab, thanks @awsr
|
||||
|
|
|
|||
5
TODO.md
5
TODO.md
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
- <https://github.com/users/vladmandic/projects>
|
||||
|
||||
## Kanvas
|
||||
|
||||
- server-side mask handling vs ui mask handling
|
||||
- outpaint visible image edge seam
|
||||
|
||||
## Internal
|
||||
|
||||
- UI: New inpaint/outpaint interface
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
let user = null;
|
||||
let token = null;
|
||||
|
||||
async function authFetch(url, options = {}) {
|
||||
if (!token) {
|
||||
const res = await fetch(`${window.subpath}/token`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
user = data.user;
|
||||
token = data.token;
|
||||
}
|
||||
}
|
||||
if (user && token) {
|
||||
if (!options.headers) options.headers = {};
|
||||
const encoded = btoa(`${user}:${token}`);
|
||||
options.headers.Authorization = `Basic ${encoded}`;
|
||||
}
|
||||
const res = await fetch(url, options);
|
||||
return res;
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ async function modelCardClick(id) {
|
|||
log('modelCardClick id', id);
|
||||
const el = gradioApp().getElementById('model-details') || gradioApp().getElementById('civitai_models_output') || gradioApp().getElementById('models_outcome');
|
||||
if (!el) return;
|
||||
const res = await fetch(`${window.api}/civitai?model_id=${encodeURI(id)}`);
|
||||
const res = await authFetch(`${window.api}/civitai?model_id=${encodeURI(id)}`);
|
||||
if (!res || res.status !== 200) {
|
||||
error(`modelCardClick: id=${id} status=${res ? res.status : 'unknown'}`);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ const getStatus = async () => {
|
|||
log('progressInternal:', data);
|
||||
if (el) el.innerText += '\nProgress internal:\n' + JSON.stringify(data, null, 2); // eslint-disable-line prefer-template
|
||||
}
|
||||
res = await fetch('./sdapi/v1/progress?skip_current_image=true', { method: 'GET', headers });
|
||||
res = await authFetch('./sdapi/v1/progress?skip_current_image=true', { method: 'GET', headers });
|
||||
if (res?.ok) {
|
||||
data = await res.json();
|
||||
log('progressAPI:', data);
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ function selectHistory(id) {
|
|||
const headers = new Headers();
|
||||
headers.set('Content-Type', 'application/json');
|
||||
const init = { method: 'POST', body: { name: id }, headers };
|
||||
fetch(`${window.api}/history`, { method: 'POST', body: JSON.stringify({ name: id }), headers });
|
||||
authFetch(`${window.api}/history`, { method: 'POST', body: JSON.stringify({ name: id }), headers });
|
||||
}
|
||||
|
||||
let enDirty = false;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ async function delayFetchThumb(fn) {
|
|||
while (outstanding > 16) await new Promise((resolve) => setTimeout(resolve, 50)); // eslint-disable-line no-promise-executor-return
|
||||
outstanding++;
|
||||
const ts = Date.now().toString();
|
||||
const res = await fetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}&ts=${ts}`, { priority: 'low' });
|
||||
const res = await authFetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}&ts=${ts}`, { priority: 'low' });
|
||||
if (!res.ok) {
|
||||
error(`fetchThumb: ${res.statusText}`);
|
||||
outstanding--;
|
||||
|
|
@ -552,7 +552,7 @@ async function fetchFilesHT(evt) {
|
|||
updateStatusWithSort(`Folder: ${evt.target.name} | in-progress`);
|
||||
let numFiles = 0;
|
||||
|
||||
const res = await fetch(`${window.api}/browser/files?folder=${encodeURI(evt.target.name)}`);
|
||||
const res = await authFetch(`${window.api}/browser/files?folder=${encodeURI(evt.target.name)}`);
|
||||
if (!res || res.status !== 200) {
|
||||
updateStatusWithSort(`Folder: ${evt.target.name} | failed: ${res?.statusText}`);
|
||||
return;
|
||||
|
|
@ -639,7 +639,7 @@ async function pruneImages() {
|
|||
|
||||
async function galleryVisible() {
|
||||
// if (el.folders.children.length > 0) return;
|
||||
const res = await fetch(`${window.api}/browser/folders`);
|
||||
const res = await authFetch(`${window.api}/browser/folders`);
|
||||
if (!res || res.status !== 200) return;
|
||||
el.folders.innerHTML = '';
|
||||
url = res.url.split('/sdapi')[0].replace('http', 'ws'); // update global url as ws need fqdn
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ async function updateGPU() {
|
|||
const gpuEl = document.getElementById('gpu');
|
||||
const gpuTable = document.getElementById('gpu-table');
|
||||
try {
|
||||
const res = await fetch(`${window.api}/gpu`);
|
||||
const res = await authFetch(`${window.api}/gpu`);
|
||||
if (!res.ok) {
|
||||
clearInterval(gpuInterval);
|
||||
gpuEl.style.display = 'none';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const ioTypes = ['load', 'save'];
|
|||
|
||||
function refreshHistory() {
|
||||
log('refreshHistory');
|
||||
fetch(`${window.api}/history`, { priority: 'low' }).then((res) => {
|
||||
authFetch(`${window.api}/history`, { priority: 'low' }).then((res) => {
|
||||
const timeline = document.getElementById('history_timeline');
|
||||
const table = document.getElementById('history_table');
|
||||
timeline.innerHTML = '';
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ async function createSplash() {
|
|||
}
|
||||
const imgEl = `<div id="spash-img" class="splash-img" alt="logo" style="background-image: url(file=html/logo-bg-${dark ? 'dark' : 'light'}.jpg), url(file=html/logo-bg-${num}.jpg); background-blend-mode: ${dark ? 'multiply' : 'lighten'}"></div>`;
|
||||
document.getElementById('splash').insertAdjacentHTML('afterbegin', imgEl);
|
||||
fetch(`${window.api}/motd`)
|
||||
authFetch(`${window.api}/motd`)
|
||||
.then((res) => res.text())
|
||||
.then((text) => {
|
||||
const motdEl = document.getElementById('motd');
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ async function logMonitor() {
|
|||
if (!logMonitorEl) return;
|
||||
const atBottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight);
|
||||
try {
|
||||
const res = await fetch(`${window.api}/log?clear=True`);
|
||||
const res = await authFetch(`${window.api}/log?clear=True`);
|
||||
if (res?.ok) {
|
||||
logMonitorStatus = true;
|
||||
const lines = await res.json();
|
||||
|
|
@ -123,7 +123,7 @@ async function initLogMonitor() {
|
|||
</table>
|
||||
`;
|
||||
el.style.display = 'none';
|
||||
fetch(`${window.api}/start?agent=${encodeURI(navigator.userAgent)}`);
|
||||
authFetch(`${window.api}/start?agent=${encodeURI(navigator.userAgent)}`);
|
||||
logMonitor();
|
||||
log('initLogMonitor');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@ const xhrInternal = (xhrObj, data, handler = undefined, errorHandler = undefined
|
|||
try {
|
||||
const json = JSON.parse(xhrObj.responseText);
|
||||
if (handler) handler(json);
|
||||
} catch (e) {
|
||||
error(`xhr.onreadystatechange: ${e}`);
|
||||
} catch {
|
||||
// error(`xhr.onreadystatechange: ${e}`);
|
||||
}
|
||||
} else {
|
||||
err(`xhr.onreadystatechange: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
|
||||
// err(`xhr.onreadystatechange: state=${xhrObj.readyState} status=${xhrObj.status} response=${xhrObj.responseText}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ async function updateIndicator(online, data, msg) {
|
|||
|
||||
async function monitorConnection() {
|
||||
try {
|
||||
const res = await fetch(`${window.api}/version`);
|
||||
const res = await authFetch(`${window.api}/version`);
|
||||
const data = await res.json();
|
||||
const url = res.url.split('/sdapi')[0].replace('http', 'ws'); // update global url as ws need fqdn
|
||||
const ws = new WebSocket(`${url}/queue/join`);
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ async function initModels() {
|
|||
const el = gradioApp().getElementById('main_info');
|
||||
const en = gradioApp().getElementById('txt2img_extra_networks');
|
||||
if (!el || !en) return;
|
||||
const req = await fetch(`${window.api}/sd-models`);
|
||||
const req = await authFetch(`${window.api}/sd-models`);
|
||||
const res = req.ok ? await req.json() : [];
|
||||
log('initModels', res.length);
|
||||
const ready = () => `
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ function monitorServerStatus() {
|
|||
function restartReload() {
|
||||
document.body.style = 'background: #222222; font-size: 1rem; font-family:monospace; margin-top:20%; color:lightgray; text-align:center';
|
||||
document.body.innerHTML = '<h1>Server shutdown in progress...</h1>';
|
||||
fetch(`${window.api}/progress?skip_current_image=true`)
|
||||
authFetch(`${window.api}/progress?skip_current_image=true`)
|
||||
.then((res) => setTimeout(restartReload, 1000))
|
||||
.catch((e) => setTimeout(monitorServerStatus, 500));
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@ class Api:
|
|||
from modules.civitai import api_civitai
|
||||
api_civitai.register_api()
|
||||
|
||||
def add_api_route(self, path: str, fn, **kwargs):
|
||||
if self.credentials:
|
||||
def add_api_route(self, path: str, fn, auth: bool = True, **kwargs):
|
||||
if auth and self.credentials:
|
||||
deps = list(kwargs.get('dependencies', []))
|
||||
deps.append(Depends(self.auth))
|
||||
kwargs['dependencies'] = deps
|
||||
|
|
@ -132,6 +132,9 @@ class Api:
|
|||
if credentials.username in self.credentials:
|
||||
if compare_digest(credentials.password, self.credentials[credentials.username]):
|
||||
return True
|
||||
if hasattr(self.app, 'tokens') and (self.app.tokens is not None):
|
||||
if credentials.password in self.app.tokens.keys():
|
||||
return True
|
||||
shared.log.error(f'API authentication: user="{credentials.username}" password="{credentials.password}"')
|
||||
raise HTTPException(status_code=401, detail="Unauthorized", headers={"WWW-Authenticate": "Basic"})
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ def setup_middleware(app: FastAPI, cmd_opts):
|
|||
if err['code'] == 404 and 'file=html/' in req.url.path: # dont spam with locales
|
||||
return JSONResponse(status_code=err['code'], content=jsonable_encoder(err))
|
||||
|
||||
log.error(f"API error: {req.method}: {req.url} {err}")
|
||||
if not any([req.url.path.endswith(x) for x in ignore_endpoints]): # noqa C419 # pylint: disable=use-a-generator
|
||||
log.error(f"API error: {req.method}: {req.url} {err}")
|
||||
|
||||
if not isinstance(e, HTTPException) and err['error'] != 'TypeError': # do not print backtrace on known httpexceptions
|
||||
errors.display(e, 'HTTP API', [anyio, fastapi, uvicorn, starlette])
|
||||
|
|
|
|||
|
|
@ -115,10 +115,10 @@ def init_api():
|
|||
return JSONResponse(obj)
|
||||
|
||||
shared.api.add_api_route("/sdapi/v1/network", get_network, methods=["GET"])
|
||||
shared.api.add_api_route("/sdapi/v1/network/thumb", fetch_file, methods=["GET"])
|
||||
shared.api.add_api_route("/sdapi/v1/network/metadata", get_metadata, methods=["GET"])
|
||||
shared.api.add_api_route("/sdapi/v1/network/info", get_info, methods=["GET"])
|
||||
shared.api.add_api_route("/sdapi/v1/network/desc", get_desc, methods=["GET"])
|
||||
shared.api.add_api_route("/sdapi/v1/network/thumb", fetch_file, methods=["GET"], auth=False)
|
||||
shared.api.add_api_route("/sdapi/v1/network/metadata", get_metadata, methods=["GET"], auth=False)
|
||||
shared.api.add_api_route("/sdapi/v1/network/info", get_info, methods=["GET"], auth=False)
|
||||
shared.api.add_api_route("/sdapi/v1/network/desc", get_desc, methods=["GET"], auth=False)
|
||||
|
||||
|
||||
class DateTimeEncoder(json.JSONEncoder):
|
||||
|
|
|
|||
Loading…
Reference in New Issue