diff --git a/backend/open_webui/routers/apps.py b/backend/open_webui/routers/apps.py index f3d582a6f5..c735e36995 100644 --- a/backend/open_webui/routers/apps.py +++ b/backend/open_webui/routers/apps.py @@ -385,19 +385,31 @@ async def connect_app_by_id( db: AsyncSession = Depends(get_async_session), ): app = await get_app_by_id(app_id, db=db) - webui_url = str(request.base_url).rstrip('/') + webui_url = str(await Config.get('webui.url') or request.base_url).strip().rstrip('/') try: - validate_app_url(webui_url) origin = urlsplit(webui_url) - if origin.path or origin.query or origin.fragment: - raise ValueError() + if ( + origin.scheme not in ('http', 'https') + or not origin.hostname + or origin.username is not None + or origin.password is not None + or origin.path + or origin.query + or origin.fragment + or '\\' in webui_url + or len(webui_url) > 2048 + or any(c.isspace() for c in webui_url) + ): + raise ValueError('Invalid WebUI origin') host = f'[{origin.hostname}]' if ':' in origin.hostname else origin.hostname.lower() port = origin.port or (443 if origin.scheme == 'https' else 80) suffix = f':{port}' if port != (443 if origin.scheme == 'https' else 80) else '' webui_url = f'{origin.scheme}://{host}{suffix}' except ValueError: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, detail='Connect through your public HTTPS WebUI URL' + status_code=status.HTTP_400_BAD_REQUEST, + detail='Invalid WebUI URL. Set WebUI URL in Settings > Admin > General to an HTTP(S) origin ' + 'without a path, query, fragment or credentials.', ) from None if form_data.api_key is not None: app.api_key = get_app_encryption_cipher().encrypt(form_data.api_key.get_secret_value().encode()).decode() diff --git a/src/lib/components/admin/Settings/Apps/AppModal.svelte b/src/lib/components/admin/Settings/Apps/AppModal.svelte index 0c3b71446a..1d13b6105f 100644 --- a/src/lib/components/admin/Settings/Apps/AppModal.svelte +++ b/src/lib/components/admin/Settings/Apps/AppModal.svelte @@ -13,6 +13,8 @@ import Modal from '$lib/components/common/Modal.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; import Search from '$lib/components/icons/Search.svelte'; + import Refresh from '$lib/components/icons/Refresh.svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; import Switch from '$lib/components/common/Switch.svelte'; import AccessButton from '$lib/components/common/AccessButton.svelte'; @@ -93,9 +95,16 @@ showScopes = true; } else { if (manual) { - manifest = { ...manifest, id: manifest.id || slug, name, requested_scopes: scopes }; + manifest = { + ...manifest, + id: manifest.id || slug, + name: app ? manifest.name : name, + requested_scopes: [...new Set([...manifest.requested_scopes, ...scopes])] + }; + manifestUpdated = !!app && JSON.stringify(manifest) !== JSON.stringify(app.data.manifest); } if (!app) { + manifest = { ...manifest, name }; app = await createNewApp(localStorage.token, { manifest, url, @@ -141,7 +150,7 @@ let opened = false; async function initialize() { manifestUpdated = false; - manual = false; + manual = !!app; showScopes = false; scopeQuery = ''; url = app?.data.url ?? ''; @@ -174,7 +183,7 @@ bind:accessGrants={grants} accessRoles={['read']} labels={{ - title: $i18n.t('Who can use this app?'), + title: $i18n.t('Access'), public: $i18n.t('Everyone'), private: $i18n.t('Restricted'), read: $i18n.t('Use'), @@ -305,7 +314,7 @@ bind:value={apiKey} required={!app?.configured} maxlength="4096" - placeholder={app?.configured ? '••••••••' : ''} + placeholder={app?.configured ? '••••••••' : $i18n.t('API Key')} /> {:else} @@ -315,7 +324,6 @@ id="app-name" class={inputClass} bind:value={name} - disabled={!app && !manual} required maxlength="100" /> {/if} -

{url}

- {#if app && !manual} - - - {/if} +
+

{url}

+ {#if app} + + + + {/if} +
{#if manifestUpdated}

{$i18n.t('Saving approves the updated app identity and selected scopes.')} @@ -399,7 +412,7 @@ bind:value={apiKey} required={!app?.configured} maxlength="4096" - placeholder={app?.configured ? '••••••••' : ''} + placeholder={app?.configured ? '••••••••' : $i18n.t('API Key')} /> @@ -425,7 +438,7 @@ { showGrants = true; diff --git a/src/lib/components/channel/Apps.svelte b/src/lib/components/channel/Apps.svelte deleted file mode 100644 index f679f3ad51..0000000000 --- a/src/lib/components/channel/Apps.svelte +++ /dev/null @@ -1,82 +0,0 @@ - - -

-

Apps

- {#each joined as app} -
- {app.name} - {app.is_active ? 'App' : 'Inactive'} - {#if canManage}{/if} -
- {:else}

- No apps have been invited. -

{/each} - {#if canManage && available.length} -
- - -
- {/if} -
diff --git a/src/lib/components/channel/ChannelInfoModal.svelte b/src/lib/components/channel/ChannelInfoModal.svelte index 96fe0bb0b6..c6c703fa48 100644 --- a/src/lib/components/channel/ChannelInfoModal.svelte +++ b/src/lib/components/channel/ChannelInfoModal.svelte @@ -1,13 +1,12 @@ {#if channel} - +
@@ -118,7 +142,9 @@
{ showAddMembersModal = true; } @@ -132,7 +158,6 @@ sort={channel?.type !== 'dm'} />
- {#if $config?.features?.enable_apps && channel.type !== 'dm'}{/if}
diff --git a/src/lib/components/channel/ChannelInfoModal/AddMembersModal.svelte b/src/lib/components/channel/ChannelInfoModal/AddMembersModal.svelte index 60ce1f765d..02654548eb 100644 --- a/src/lib/components/channel/ChannelInfoModal/AddMembersModal.svelte +++ b/src/lib/components/channel/ChannelInfoModal/AddMembersModal.svelte @@ -1,9 +1,11 @@