fix(federation): close re-attach final-review findings — client/server domain normalization, merge attachment repoint, empty-domain guard, test hardening
This commit is contained in:
@@ -80,6 +80,38 @@ describe('maybeAutoReattach', () => {
|
||||
useInstanceStore.setState({ instances: [homeConn, detachedConn] });
|
||||
await maybeAutoReattach(detachedConn);
|
||||
expect((homeConn.api as unknown as { auth: { attachProof: ReturnType<typeof vi.fn> } }).auth.attachProof).not.toHaveBeenCalled();
|
||||
expect((detachedConn.api as unknown as { users: { reattach: ReturnType<typeof vi.fn> } }).users.reattach).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('mints the PORTLESS target host for a ported instance origin (matches server extractDomain)', async () => {
|
||||
// Both instances served on a non-443 port. The server binds/verifies the
|
||||
// proof against extractDomain(peer.origin) = new URL(origin).hostname, which
|
||||
// is portless — so the client must mint the portless host too, or the
|
||||
// exchange 401s forever. homeInstance is stored bare (portless hostname).
|
||||
const homeConn = makeInstance({
|
||||
origin: 'https://orbit.test:8443',
|
||||
username: 'youruser',
|
||||
user: { id: 'new-home-1', username: 'youruser' } as User,
|
||||
});
|
||||
const attachProof = vi.fn().mockResolvedValue({ token: 'a'.repeat(64) });
|
||||
(homeConn.api as unknown as { auth: { attachProof: typeof attachProof } }).auth.attachProof = attachProof;
|
||||
|
||||
const updatedUser = { id: 'detached-1', username: 'youruser@orbit.test', federationHomeOrphaned: false, homeInstance: 'orbit.test' } as User;
|
||||
const reattach = vi.fn().mockResolvedValue({ success: true, user: updatedUser });
|
||||
const detachedConn = makeInstance({
|
||||
origin: 'https://nova.test:8443',
|
||||
user: { id: 'detached-1', username: 'youruser@orbit.test', federationHomeOrphaned: true, homeInstance: 'orbit.test' } as User,
|
||||
});
|
||||
(detachedConn.api as unknown as { users: { reattach: typeof reattach } }).users.reattach = reattach;
|
||||
|
||||
useInstanceStore.setState({ instances: [homeConn, detachedConn] });
|
||||
await maybeAutoReattach(detachedConn);
|
||||
|
||||
// Portless — 'nova.test', NOT 'nova.test:8443'.
|
||||
expect(attachProof).toHaveBeenCalledWith('nova.test');
|
||||
expect(reattach).toHaveBeenCalledWith({ token: 'a'.repeat(64) });
|
||||
const stored = useInstanceStore.getState().instances.find(i => i.origin === 'https://nova.test:8443')!;
|
||||
expect(stored.user.federationHomeOrphaned).toBe(false);
|
||||
});
|
||||
|
||||
it('skips when the account is not detached', async () => {
|
||||
|
||||
@@ -155,12 +155,12 @@ export async function maybeAutoReattach(instance: ConnectedInstance): Promise<vo
|
||||
const primaryUser = useAuthStore.getState().user;
|
||||
let homeApi: BackspaceApiClient | null = null;
|
||||
let homeUsername: string | null = null;
|
||||
if (primaryUser && !primaryUser.homeInstance && window.location.host.toLowerCase() === homeDomain) {
|
||||
if (primaryUser && !primaryUser.homeInstance && window.location.hostname.toLowerCase() === homeDomain) {
|
||||
homeApi = api;
|
||||
homeUsername = primaryUser.username;
|
||||
} else {
|
||||
const conn = useInstanceStore.getState().instances.find(
|
||||
(i) => i.status === 'connected' && new URL(i.origin).host.toLowerCase() === homeDomain,
|
||||
(i) => i.status === 'connected' && new URL(i.origin).hostname.toLowerCase() === homeDomain,
|
||||
);
|
||||
if (conn) {
|
||||
homeApi = conn.api;
|
||||
@@ -175,7 +175,10 @@ export async function maybeAutoReattach(instance: ConnectedInstance): Promise<vo
|
||||
if (!detachedBase || detachedBase !== homeBase) return;
|
||||
|
||||
try {
|
||||
const targetHost = new URL(instance.origin).host;
|
||||
// Portless hostname — must match the server's extractDomain(peer.origin)
|
||||
// (new URL(origin).hostname) so the proof's targetDomain binds/verifies on
|
||||
// a non-443 port too. .host would carry the port and 401 forever.
|
||||
const targetHost = new URL(instance.origin).hostname;
|
||||
const { token } = await homeApi.auth.attachProof(targetHost);
|
||||
const res = await instance.api.users.reattach({ token });
|
||||
useInstanceStore.setState((state) => ({
|
||||
|
||||
Reference in New Issue
Block a user