home: serve the Apple app-site-association for native passkeys (#357)
The native Eleven apps bind passkeys to home.elevenmessenger.com via the `webcredentials` associated domain, which requires this RP to publish an apple-app-site-association naming the apps' team+bundle ids. Content-blind — nothing user-identifying — matching the home server's posture.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 file changed,
+17
−0
home/main.go+17 −0
diff --git a/home/main.go b/home/main.go| index b1cf89f..cafb43f 100644 |
| --- a/home/main.go |
| +++ b/home/main.go |
| @@ -160,6 +160,11 @@ func (s *Server) handler() http.Handler { |
| mux.HandleFunc("GET /restore", s.serveRestore) |
| mux.Handle("GET /static/", noCache(http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))) |
| + // Apple app-site-association: lets the native Eleven apps run passkey |
| + // ceremonies against this RP (the webcredentials service). Content-blind — |
| + // it names only the apps' team+bundle ids, nothing user-specific. |
| + mux.HandleFunc("GET /.well-known/apple-app-site-association", s.serveAASA) |
| + |
| // WebAuthn ceremonies. begin endpoints are rate-limited: they're the open, |
| // anonymous surface (finish requires a signed response, which is its own gate). |
| mux.HandleFunc("POST /api/register/begin", s.rateLimited(s.registerBegin)) |
| @@ -220,6 +225,18 @@ func (s *Server) servePage(w http.ResponseWriter, r *http.Request) { |
| w.Write(s.page) |
| } |
| +// serveAASA publishes the Apple app-site-association so the native Eleven apps |
| +// can bind passkeys to this relying party (the `webcredentials` service). Only |
| +// team+bundle ids appear — nothing user-identifying, consistent with this |
| +// server's content-blind posture. Served at the exact well-known path Apple |
| +// fetches, as application/json with no auth. |
| +func (s *Server) serveAASA(w http.ResponseWriter, r *http.Request) { |
| + w.Header().Set("Content-Type", "application/json") |
| + w.Write([]byte(`{"webcredentials":{"apps":[` + |
| + `"J5NDF5PCD8.com.elevenmessenger.messenger",` + |
| + `"J5NDF5PCD8.com.elevenmessenger.messenger.mac"]}}`)) |
| +} |
| + |
| // serveEnroll serves the enrolment popup. Same CSP as the main page — no inline |
| // scripts, no cross-origin fetches; the only cross-origin move is a postMessage |
| // back to the opener, which CSP doesn't govern. |