aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android/app/src/main/kotlin
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-14 13:34:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-14 13:34:47 +0200
commit51705056a578d2649a989bf14a056fbcfae58a6b (patch)
treedfd078aba518427387fca29b9d43c3723b030008 /packages/meshbay-android/app/src/main/kotlin
parentdaec4bc6a2a9f9e038e5269586b06018ff327a27 (diff)
downloadmeshbay-51705056a578d2649a989bf14a056fbcfae58a6b.tar.gz
feat(hub): replace reCAPTCHA with a self-hosted proof of work
Sign-up and passphrase reset sent every visitor's address and browser details to Google, which needed a consent the sign-up form could not ask for freely. The hub now issues its own challenge (ALTCHA's scheme, signed with a key derived from the hub's private key, single-use, 30 minutes) and the page solves it with SubtleCrypto while the form is filled in. - GET /v1/users/captcha issues a challenge; 404 captcha_disabled when off. - [captcha] enabled / max_number replace site_key / secret_key, which are now ignored with a warning; MESHBAY_CAPTCHA_ENABLED replaces the old variables. - No Google origin left in the CSP of the hub, the desktop shell or the Android shell; captcha_site_key leaves /v1/hub/info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-android/app/src/main/kotlin')
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt7
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/shell/UiAssets.kt14
2 files changed, 7 insertions, 14 deletions
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
index 699e0c3..6bd2a3f 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
@@ -198,9 +198,8 @@ class MainActivity : Activity() {
return docs.firstNotNullOfOrNull { it.serve(path) } ?: refused()
}
if (url.host == UiAssets.HOST) return loader.shouldInterceptRequest(url) ?: refused()
- // reCAPTCHA (sign-up) and nothing else goes to the network from
- // the page; the policy says the same, this is the second wall.
- if (UiAssets.isRecaptcha(url.host) && url.scheme == "https") return null
+ // Nothing goes to the network from the page; the policy says
+ // the same, this is the second wall.
if (url.scheme == "blob" || url.scheme == "data") return null
return refused()
}
@@ -211,7 +210,7 @@ class MainActivity : Activity() {
// The hub must never become the document origin. A link out
// opens in the person's browser, not in a window holding keys.
if (request.isForMainFrame && (url.scheme == "https" || url.scheme == "http")) openExternally(url)
- return !(UiAssets.isRecaptcha(url.host) && !request.isForMainFrame)
+ return true
}
}
web.webChromeClient = object : WebChromeClient() {
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/shell/UiAssets.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/shell/UiAssets.kt
index 2300668..0721db3 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/shell/UiAssets.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/shell/UiAssets.kt
@@ -45,10 +45,6 @@ class UiAssets(private val context: Context) : WebViewAssetLoader.PathHandler {
const val PREFIX = "/ui/"
const val START = "$ORIGIN${PREFIX}index.html"
- // reCAPTCHA gates sign-up here as it does in a browser and on the
- // desktop; these two hosts and no others.
- private const val RECAPTCHA_SRC = "https://www.google.com https://www.gstatic.com"
-
/**
* meshbay-client/src/main.js's CSP, directive for directive
* (test_android_shell.py holds them together). `'wasm-unsafe-eval'` is
@@ -56,22 +52,20 @@ class UiAssets(private val context: Context) : WebViewAssetLoader.PathHandler {
*/
val CSP = listOf(
"default-src 'none'",
- "script-src 'self' 'wasm-unsafe-eval' $RECAPTCHA_SRC",
+ "script-src 'self' 'wasm-unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
- "img-src 'self' data: blob: $RECAPTCHA_SRC",
+ "img-src 'self' data: blob:",
"media-src 'self' blob:",
"font-src 'self'",
- "connect-src 'self' $RECAPTCHA_SRC",
+ "connect-src 'self'",
"worker-src 'self'",
"object-src blob:",
- "frame-src blob: $RECAPTCHA_SRC",
+ "frame-src blob:",
"frame-ancestors 'none'",
"base-uri 'none'",
"form-action 'none'",
).joinToString("; ")
- fun isRecaptcha(host: String?) = host == "www.google.com" || host == "www.gstatic.com"
-
fun contentType(name: String): String = when (File(name).extension.lowercase()) {
"html" -> "text/html"
"js", "mjs" -> "text/javascript"