Skip to content

Tony/685 logout exception#46

Closed
tonypioneer wants to merge 2 commits into
devfrom
tony/685_logout_exception
Closed

Tony/685 logout exception#46
tonypioneer wants to merge 2 commits into
devfrom
tony/685_logout_exception

Conversation

@tonypioneer

Copy link
Copy Markdown
Collaborator

Pull Request Details

Description

Fixed port-in-use crash when a logout/login browser flow is abandoned.

Related Issues

anusii/solidpod#685

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How To Test?

Run SolidUI and follow the steps mentioned in anusii/solidpod#685.
Please note that this PR is for test only. It's not necessary to merge this PR to dev branch.

Checklist

  • Screenshots included here/in linked issue #
  • Changes adhere to the style and coding guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • Any dependent changes have been merged and published in downstream modules
  • The update contains no confidential information
  • The update has no duplicated content
  • No lint check errors are related to these changes (make prep or flutter analyze lib)
  • Integration test dart test output or screenshot included in issue #
  • I tested the PR on these devices:
    • Android
    • iOS
    • Linux
    • MacOS
    • Windows
    • Web
  • I have identified reviewers
  • The PR has been approved by reviewers

Finalising

  • Merge dev into the this branch
  • Resolve any conflicts
  • Add a one line summary into the CHANGELOG.md
  • Push to the git repository and review
  • Merge the PR into dev

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a defensive “loopback listener guard” to prevent port-in-use crashes when an OIDC browser login/logout flow is abandoned and leaves a loopback HTTP listener bound (as reported in anusii/solidpod#685).

Changes:

  • Introduces a platform-conditional releaseStaleLoopbackListeners() utility (IO implementation + web stub).
  • Integrates the guard into SolidAuthManager.login() and SolidAuthManager.logout() to avoid crashing on stale/busy redirect ports.
  • Adds unit tests covering stale listener release behavior and failure when the port is owned by an unrelated server.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/loopback_listener_guard_test.dart Adds tests validating that stale loopback listeners can be released and ports freed.
lib/src/utils/loopback_listener_guard.dart Provides conditional export wiring (IO vs web stub).
lib/src/utils/loopback_listener_guard_stub.dart No-op web implementation of the guard.
lib/src/utils/loopback_listener_guard_io.dart Implements probing and release attempts for fixed loopback redirect ports via HTTP GET.
lib/src/auth/solid_auth_manager.dart Calls the guard before login/logout to avoid port-in-use crashes and to optionally skip remote logout when ports can’t be freed.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +89
test('returns true when the port is already free', () async {
final free = await releaseStaleLoopbackListeners([
Uri.parse('http://localhost:49181/redirect'),
]);
expect(free, isTrue);
expect(await portIsFree(49181), isTrue);
});
Comment on lines +91 to +104
test('releases a stale single-response listener and frees the port',
() async {
const port = 49182;
final released = await bindStaleOidcListener(port, '/redirect');
expect(await portIsFree(port), isFalse);

final free = await releaseStaleLoopbackListeners([
Uri.parse('http://localhost:$port/redirect'),
]);

expect(free, isTrue);
await released.future.timeout(const Duration(seconds: 2));
expect(await portIsFree(port), isTrue);
});
Comment on lines +106 to +121
test('tries each distinct path until the listener is released', () async {
const port = 49183;
final released = await bindStaleOidcListener(port, '/redirect');

// The first URI has the wrong path (the listener answers 404 and
// keeps waiting); the second matches and releases it.

final free = await releaseStaleLoopbackListeners([
Uri.parse('http://localhost:$port/logout'),
Uri.parse('http://localhost:$port/redirect'),
]);

expect(free, isTrue);
await released.future.timeout(const Duration(seconds: 2));
expect(await portIsFree(port), isTrue);
});
Comment on lines +123 to +143
test('returns false when the port is held by an unrelated server',
() async {
const port = 49184;

// An unrelated server answers requests but never closes.

final server = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
unawaited(() async {
await for (final request in server) {
request.response.write('not an oidc listener');
await request.response.close();
}
}());

final free = await releaseStaleLoopbackListeners([
Uri.parse('http://localhost:$port/redirect'),
]);

expect(free, isFalse);
await server.close(force: true);
});
Comment on lines +83 to +87
try {
await http.get(uri).timeout(_releaseRequestTimeout);
} on Object catch (e) {
_log.finer('Release request to $uri failed: $e');
}
Comment on lines +196 to +199
final redirectPortsFree = await releaseStaleLoopbackListeners([
config.redirectUri,
if (config.postLogoutRedirectUri != null) config.postLogoutRedirectUri!,
]);
@gjwgit gjwgit closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SOLIDPOD/SOLIDUI: When attempting to log out and cancel from the app, an exception is shown

3 participants