feat: unify EncryptID passkeys across all r*.online apps

Simplify resolveRpId() to always return 'rspace.online' so passkeys
registered from any r*.online domain share the same RP ID. Browsers
use .well-known/webauthn Related Origins to validate cross-domain
passkey usage. This makes one passkey work everywhere.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-20 21:38:26 +00:00
parent cac038ed0d
commit 7210888aed
2 changed files with 11 additions and 29 deletions

View File

@ -297,14 +297,13 @@ export function receiveSyncMessage(
const newDoc = result[0];
const newSyncState = result[1];
const patch = result[2] as { patches: Automerge.Patch[] } | null;
communities.set(slug, newDoc);
peerState.syncState = newSyncState;
// Schedule save if changes were made
const hasPatches = patch && patch.patches && patch.patches.length > 0;
if (hasPatches) {
// Save if the document actually changed (Automerge 2.x receiveSyncMessage
// returns null for patches, so detect changes via object identity instead)
if (newDoc !== doc) {
saveCommunity(slug);
}
@ -319,7 +318,7 @@ export function receiveSyncMessage(
const broadcastToPeers = new Map<string, Uint8Array>();
const communityPeers = peerSyncStates.get(slug);
if (communityPeers && hasPatches) {
if (communityPeers && newDoc !== doc) {
for (const [otherPeerId, otherPeerState] of communityPeers) {
if (otherPeerId !== peerId) {
const [newOtherSyncState, otherMessage] = Automerge.generateSyncMessage(

View File

@ -237,31 +237,14 @@ app.get('/health', async (c) => {
// ============================================================================
/**
* Derive the RP ID from the request's Origin header.
* If the origin is an allowed r* domain, use that domain as the RP ID.
* Falls back to CONFIG.rpId (rspace.online) for rspace.online origins and unknown.
* Resolve RP ID for WebAuthn ceremonies.
*
* Always returns 'rspace.online' so that all passkeys are registered with
* the same RP ID. The .well-known/webauthn endpoint lists Related Origins,
* allowing browsers on other r*.online domains to use these passkeys.
*/
function resolveRpId(c: any): string {
const origin = c.req.header('origin') || c.req.header('referer') || '';
try {
const url = new URL(origin);
const hostname = url.hostname;
// All *.rspace.online subdomains use rspace.online as RP ID
if (hostname.endsWith('.rspace.online') || hostname === 'rspace.online') {
return 'rspace.online';
}
// Check if this origin is in our explicit allowed list
const isAllowed = CONFIG.allowedOrigins.some(o => {
try { return new URL(o).hostname === hostname; } catch { return false; }
});
if (isAllowed && hostname !== 'localhost') {
// For other allowed origins, use their domain as RP ID
return hostname;
}
} catch {
// Invalid origin, fall back to default
}
return CONFIG.rpId;
function resolveRpId(_c: any): string {
return CONFIG.rpId; // Always 'rspace.online'
}
// ============================================================================