Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions app/main-axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,37 @@ function extractJwtFromSetCookie(headers: any): string | null {
return null;
}

function isLocalNetworkHttpsUrl(url: string): boolean {
if (!url.startsWith("https://")) return false;

try {
const host = new URL(url).hostname.replace(/^\[|\]$/g, "");
if (host === "localhost" || host === "::1") return true;

const parts = host.split(".").map((part) => Number(part));
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) {
return false;
}

const [first, second] = parts;
return (
first === 10 ||
first === 127 ||
(first === 169 && second === 254) ||
(first === 172 && second >= 16 && second <= 31) ||
(first === 192 && second === 168)
);
} catch {
return false;
}
}

function isNativeNetworkFailure(error: unknown): boolean {
return (
error instanceof Error && error.message.includes("Network request failed")
);
}

/**
* Detects whether the server URL sits behind a reverse-proxy authentication
* gate (Cloudflare Access, Authelia, etc.) that intercepts requests and serves
Expand Down Expand Up @@ -2272,11 +2303,23 @@ async function loginWithFetch(
password: string,
): Promise<{ data: any; token: string | null }> {
const url = `${baseUrl.replace(/\/$/, "")}/users/login`;
const fetchResponse = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
let fetchResponse: Response;
try {
fetchResponse = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
} catch (error) {
if (isLocalNetworkHttpsUrl(url) && isNativeNetworkFailure(error)) {
throw new ApiError(
"The app could not trust HTTPS for this local IP. Use a DNS name with a valid certificate, a certificate for this IP, or HTTP on a trusted local/VPN network.",
0,
"LOCAL_IP_HTTPS_CERTIFICATE_ERROR",
);
}
throw error;
}

const contentType = (
fetchResponse.headers.get("content-type") || ""
Expand Down
45 changes: 23 additions & 22 deletions plugins/withNetworkSecurityConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ const {
const path = require("path");
const fs = require("fs");

const NETWORK_SECURITY_CONFIG = `<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>

<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">10.0.2.2</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
`;

const withNetworkSecurityConfig = (config) => {
config = withAndroidManifest(config, async (config) => {
const mainApplication = config.modResults.manifest.application[0];
Expand Down Expand Up @@ -38,28 +59,7 @@ const withNetworkSecurityConfig = (config) => {
"network_security_config.xml",
);

const networkSecurityConfig = `<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>

<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">10.0.2.2</domain>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</domain-config>
</network-security-config>
`;

fs.writeFileSync(networkSecurityConfigPath, networkSecurityConfig);
fs.writeFileSync(networkSecurityConfigPath, NETWORK_SECURITY_CONFIG);

return config;
},
Expand All @@ -69,3 +69,4 @@ const withNetworkSecurityConfig = (config) => {
};

module.exports = withNetworkSecurityConfig;
module.exports.NETWORK_SECURITY_CONFIG = NETWORK_SECURITY_CONFIG;
Loading