Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d7a379ce0 |
@@ -26,6 +26,15 @@ export type ClientConfig = {
|
||||
*/
|
||||
elementCallUrl?: string;
|
||||
|
||||
/**
|
||||
* [Gitea #43] Desktop only: the loopback origin the desktop app's local
|
||||
* server also answers on (e.g. "http://127.0.0.1:44548"), to load the
|
||||
* bundled call page from a different origin than the app
|
||||
* ("http://localhost:44548"). Set by cinny-desktop together with the server
|
||||
* and CSP changes it needs; unset keeps the same-origin call page.
|
||||
*/
|
||||
desktopCallOrigin?: string;
|
||||
|
||||
/**
|
||||
* Absolute https URL of the public web app (e.g. https://chat.lotusguild.org).
|
||||
* The desktop app sets it so it can hand calls it can't make to the browser.
|
||||
|
||||
+14
-2
@@ -36,7 +36,11 @@ import { applyCustomAccent, removeCustomAccent } from '../utils/accentColor';
|
||||
import { zIndices } from '../styles/zIndex';
|
||||
import { OIDC_CALLBACK_PATH } from './paths';
|
||||
import { OidcCallback } from './auth/oidc/OidcCallback';
|
||||
import { resolveCallPageUrl, setCallPageUrl } from '../plugins/call/callPageUrl';
|
||||
import {
|
||||
resolveCallPageUrl,
|
||||
resolveDesktopCallPageUrl,
|
||||
setCallPageUrl,
|
||||
} from '../plugins/call/callPageUrl';
|
||||
|
||||
// The emoji families (Twemoji when "Twitter emoji" is on, Twemoji flags on
|
||||
// Windows — see SystemEmojiFeature) must sit before the generic family, or the
|
||||
@@ -223,7 +227,15 @@ function App() {
|
||||
>
|
||||
{(clientConfig) => {
|
||||
// [Gitea #43] Idempotent: where the call page is loaded from.
|
||||
setCallPageUrl(resolveCallPageUrl(clientConfig.elementCallUrl, isTauri()));
|
||||
setCallPageUrl(
|
||||
isTauri()
|
||||
? resolveDesktopCallPageUrl(
|
||||
clientConfig.desktopCallOrigin,
|
||||
window.location.origin,
|
||||
import.meta.env.BASE_URL,
|
||||
)
|
||||
: resolveCallPageUrl(clientConfig.elementCallUrl, false),
|
||||
);
|
||||
return (
|
||||
<ClientConfigProvider value={clientConfig}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { resolveCallPageUrl } from './callPageUrl';
|
||||
import { resolveCallPageUrl, resolveDesktopCallPageUrl } from './callPageUrl';
|
||||
|
||||
const URL_OK = 'https://call.chat.example.org/public/element-call/index.html';
|
||||
|
||||
@@ -38,3 +38,44 @@ test('anything else falls back to the bundled page', () => {
|
||||
'data:text/html,x',
|
||||
].forEach((v) => assert.equal(resolveCallPageUrl(v, false), undefined, String(v)));
|
||||
});
|
||||
|
||||
const APP = 'http://localhost:44548';
|
||||
const PAGE = '/public/element-call/index.html';
|
||||
|
||||
test('desktop: the bundled page from the loopback origin on the same port', () => {
|
||||
assert.equal(
|
||||
resolveDesktopCallPageUrl('http://127.0.0.1:44548', APP, '/'),
|
||||
`http://127.0.0.1:44548${PAGE}`,
|
||||
);
|
||||
assert.equal(
|
||||
resolveDesktopCallPageUrl('http://127.0.0.1:44548/', APP, '/app/'),
|
||||
`http://127.0.0.1:44548/app${PAGE}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('desktop: unset or anything but same-port loopback http keeps the same-origin page', () => {
|
||||
[
|
||||
undefined,
|
||||
'',
|
||||
'http://127.0.0.1:44549',
|
||||
'http://127.0.0.1',
|
||||
'https://127.0.0.1:44548',
|
||||
'http://localhost:44548',
|
||||
'http://[::1]:44548',
|
||||
'http://10.0.0.5:44548',
|
||||
'https://call.chat.lotusguild.org',
|
||||
'http://127.0.0.1:44548/evil/',
|
||||
'http://127.0.0.1:44548/?x=1',
|
||||
'http://user:pw@127.0.0.1:44548',
|
||||
'not a url',
|
||||
42,
|
||||
].forEach((v) => assert.equal(resolveDesktopCallPageUrl(v, APP, '/'), undefined, String(v)));
|
||||
});
|
||||
|
||||
test('desktop: only when the app itself runs on http://localhost (release builds)', () => {
|
||||
const v = 'http://127.0.0.1:44548';
|
||||
assert.equal(resolveDesktopCallPageUrl(v, 'tauri://localhost', '/'), undefined);
|
||||
assert.equal(resolveDesktopCallPageUrl(v, 'http://tauri.localhost', '/'), undefined);
|
||||
assert.equal(resolveDesktopCallPageUrl(v, 'https://chat.lotusguild.org', '/'), undefined);
|
||||
assert.equal(resolveDesktopCallPageUrl(v, 'http://localhost', '/'), undefined);
|
||||
});
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* app loads it from that origin instead, so the call frame can no longer
|
||||
* reach this origin's storage (login token, crypto store) or service worker.
|
||||
*
|
||||
* Web only: the desktop app keeps its bundled copy (its CSP doesn't allow
|
||||
* another frame origin, and a network copy could drift from the bundle).
|
||||
* Anything that isn't an absolute https URL (http only on localhost, for
|
||||
* Web only: the desktop app keeps its bundled copy (a network copy could
|
||||
* drift from the bundle); see resolveDesktopCallPageUrl for how it isolates
|
||||
* it. Anything that isn't an absolute https URL (http only on localhost, for
|
||||
* development) is ignored, so a bad value falls
|
||||
* back to the bundled page instead of breaking calls.
|
||||
*/
|
||||
@@ -28,6 +28,40 @@ export const resolveCallPageUrl = (value: unknown, desktop: boolean): string | u
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* [Gitea #43] Desktop: the bundled call page from a second origin.
|
||||
*
|
||||
* The desktop app is served by its local server at http://localhost:<port>.
|
||||
* The same server answers on http://127.0.0.1:<port>, which is a different
|
||||
* origin (and still a secure context), so loading the bundled call page from
|
||||
* there cuts the call frame off from the app's storage (login token, crypto
|
||||
* store) without a network copy that could drift from the bundle.
|
||||
*
|
||||
* Only used when cinny-desktop sets `desktopCallOrigin` (it ships the server
|
||||
* and CSP changes this needs in the same release), only for a loopback http
|
||||
* origin on the SAME port as the app, and only when the app itself runs on
|
||||
* http://localhost (release builds). Anything else keeps the same-origin page.
|
||||
*/
|
||||
export const resolveDesktopCallPageUrl = (
|
||||
value: unknown,
|
||||
appOrigin: string,
|
||||
basePath: string,
|
||||
): string | undefined => {
|
||||
if (typeof value !== 'string' || value.trim() === '') return undefined;
|
||||
try {
|
||||
const app = new URL(appOrigin);
|
||||
const call = new URL(value);
|
||||
if (app.protocol !== 'http:' || app.hostname !== 'localhost' || !app.port) return undefined;
|
||||
if (call.protocol !== 'http:' || call.hostname !== '127.0.0.1') return undefined;
|
||||
if (call.port !== app.port || call.username || call.password) return undefined;
|
||||
if (call.pathname !== '/' || call.search || call.hash) return undefined;
|
||||
const base = basePath.replace(/\/+$/, '');
|
||||
return `${call.origin}${base}/public/element-call/index.html`;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
let callPageUrl: string | undefined;
|
||||
|
||||
export const setCallPageUrl = (url: string | undefined): void => {
|
||||
|
||||
Reference in New Issue
Block a user