fix: resolve connectToRemote identity for federated users
Federated users (e.g. youruser@nova browsing orbit) could not add instances via Settings → Connections because connectToRemote assumed window.location.host was the home instance, producing a double-@ username like youruser@nova.ddns.net@orbit.ddns.net that failed server validation. Now derives trueHomeHost/bareUsername/trueHomeUserId from the user's actual homeInstance fields and branches the auth flow: login-only with bare username when targeting home, register with correct namespacing for third-party remotes. Also skips profile sync to home instance since it's the source of truth.
This commit is contained in:
@@ -163,18 +163,43 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Verify password against HOME instance
|
// Step 1: Verify password against the instance we're currently browsing
|
||||||
const { valid } = await api.users.verifyPassword(password);
|
const { valid } = await api.users.verifyPassword(password);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
throw new Error('Incorrect password');
|
throw new Error('Incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Try register on remote, then fall back to login
|
// Step 2: Compute the user's true home identity
|
||||||
const homeInstance = window.location.host;
|
// If we're a federated user (e.g. youruser@nova browsing orbit),
|
||||||
|
// homeInstance points to the real home, not window.location.host.
|
||||||
|
const trueHomeHost = currentUser.homeInstance ?? window.location.host;
|
||||||
|
const bareUsername = currentUser.username.includes('@')
|
||||||
|
? currentUser.username.split('@')[0]!
|
||||||
|
: currentUser.username;
|
||||||
|
const trueHomeUserId = currentUser.homeUserId ?? currentUser.id;
|
||||||
|
const targetHost = new URL(origin).host;
|
||||||
|
const targetIsHome = targetHost === trueHomeHost;
|
||||||
|
|
||||||
const tempClient = createApiClient(origin, () => null);
|
const tempClient = createApiClient(origin, () => null);
|
||||||
|
|
||||||
let response: AuthResponse | null = null;
|
let response: AuthResponse | null = null;
|
||||||
const finalUsername = `${currentUser.username}@${homeInstance}`;
|
let finalUsername: string;
|
||||||
|
|
||||||
|
if (targetIsHome) {
|
||||||
|
// Target IS the user's home instance — they already have a native account.
|
||||||
|
// Just login with bare username, no registration or homeInstance params.
|
||||||
|
finalUsername = bareUsername;
|
||||||
|
try {
|
||||||
|
response = await tempClient.auth.login({
|
||||||
|
username: bareUsername,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
throw new DifferentPasswordError(bareUsername);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Target is a remote/third-party instance — register as user@homeHost
|
||||||
|
finalUsername = `${bareUsername}@${trueHomeHost}`;
|
||||||
|
|
||||||
// 2a: Attempt registration with namespaced username
|
// 2a: Attempt registration with namespaced username
|
||||||
try {
|
try {
|
||||||
@@ -182,8 +207,8 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
username: finalUsername,
|
username: finalUsername,
|
||||||
password,
|
password,
|
||||||
displayName: displayName || currentUser.displayName || undefined,
|
displayName: displayName || currentUser.displayName || undefined,
|
||||||
homeInstance,
|
homeInstance: trueHomeHost,
|
||||||
homeUserId: currentUser.id,
|
homeUserId: trueHomeUserId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = (err as Error).message;
|
const message = (err as Error).message;
|
||||||
@@ -206,11 +231,12 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
// Namespaced login failed — try legacy plain username as fallback
|
// Namespaced login failed — try legacy plain username as fallback
|
||||||
try {
|
try {
|
||||||
response = await tempClient.auth.login({
|
response = await tempClient.auth.login({
|
||||||
username: currentUser.username,
|
username: bareUsername,
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
throw new DifferentPasswordError(currentUser.username);
|
throw new DifferentPasswordError(bareUsername);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,10 +268,13 @@ export const useInstanceStore = create<InstanceState>((set, get) => ({
|
|||||||
// Open WebSocket connection to the remote instance
|
// Open WebSocket connection to the remote instance
|
||||||
connectInstance(origin, response.token);
|
connectInstance(origin, response.token);
|
||||||
|
|
||||||
// Sync full home profile to new remote (fire-and-forget)
|
// Sync home profile to new remote (fire-and-forget).
|
||||||
|
// Skip when target is home — home is the source of truth for profile data.
|
||||||
|
if (!targetIsHome) {
|
||||||
syncProfileToRemote(instance).catch((err) => {
|
syncProfileToRemote(instance).catch((err) => {
|
||||||
console.warn(`[ProfileSync] Initial sync to ${origin} failed:`, err);
|
console.warn(`[ProfileSync] Initial sync to ${origin} failed:`, err);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Sync instance list to all instances (fire-and-forget)
|
// Sync instance list to all instances (fire-and-forget)
|
||||||
get().syncInstanceList().catch(() => {});
|
get().syncInstanceList().catch(() => {});
|
||||||
|
|||||||
Reference in New Issue
Block a user