Skip to content

Keep logout from being undone by an in-flight JWT refresh; tighten docs - #22

Merged
zac merged 5 commits into
mainfrom
auth-env-improvements
Oct 6, 2026
Merged

zac merged 5 commits into
mainfrom
auth-env-improvements

Conversation

@zac

@zac zac commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Bug fix

JWTAuth committed a refresh's tokens even when setTokens(_:) or clearTokens() had replaced them while the refresh was in flight. It also persisted those tokens through onTokensUpdated. A logout during a refresh could silently sign the user back in, and a new login could be overwritten by stale refreshed tokens. With several requests sharing one refresh, a later waiter could also restore tokens after a logout that landed between two waiters resuming.

A refresh now commits only while it is still the pending refresh. The first waiter commits once, and a superseded refresh never commits. A superseded refresh calls neither onTokensUpdated nor onRefreshFailed. Requests waiting on it continue with the current tokens, or fail with .notAuthenticated after a logout.

Known limit, now documented: a persistence callback that has already started cannot be revoked. If a logout lands while onTokensUpdated is still writing, the write can finish after the logout. Apps that need a hard guarantee should have their store reject writes from a signed-out session.

Other changes

  • An empty JWTAuth.Configuration.tokenPrefix sends the bare token instead of a leading space.
  • EnvironmentType.swift is folded into Server.swift. The internal sample ApiServer/TestEndpoint types that shipped inside the library are removed. There is no public API change.
  • The README and DocC now explain that credentials are bound to an environment. A server-wide static let auth suits single-environment apps. Multi-environment apps should pass matching auth: per request.
  • Mocking guide fixes:
    • Removed the nonexistent continuation.endpoint.
    • Made the closure examples Sendable-safe.
    • Added availability annotations to the Combine helper.
    • Replaced the incorrect "no swizzling" claim with an accurate description.
  • The environment: and auth: parameters are documented on every request method. DocC builds with no warnings.

Documentation pass

  • README rewritten. It is shorter and puts installation first. The requirements and the migration notes from 0.4 are now accurate. The links to the GitHub wiki, which doesn't exist, now point to the guides.
  • New DocC Authentication article. It covers server and endpoint declarations, per-request credentials, JWT refresh, logout behavior, custom methods, and errors.
  • Examples and Mocking guides rewritten. Several examples didn't compile. The Mocking guide now documents the real behavior of .fail and .none, typed-error matching inside withMock, a URLProtocol stub for testing refresh, and building requests without sending them.
  • Doc comments.
    • Every public enum case is documented.
    • Comments that described the wrong encoder or decoder are fixed.
    • Spelling is American, with no filler or em-dashes.
  • No code changes. The only non-comment edit is the wording of a debug assertion message.

The README and guide examples were compiled and run against the library. DocC builds with no warnings.

Documentation hosting

The Documentation workflow is removed. It had failed on every run since its trigger moved to releases, because the github-pages environment only allowed deploys from main. As a result, velosmobile.com/Endpoints still shows docs from 2024. A new .spi.yml asks the Swift Package Index to build and host docs for both Endpoints and EndpointsMocking on each release. I checked that both modules build the way the index builds them, with no warnings. The README badges and documentation link now point to the index. The package still needs to be submitted to the index before those links work.

Testing

  • swift test passes on macOS across repeated runs.
  • The new tests in JWTAuthSupersededRefreshTests cover these scenarios:
    • Logout during a refresh.
    • Login during a refresh.
    • A request suspended on a refresh that is then superseded.
    • The multi-waiter race.
    • An empty token prefix.
  • The multi-waiter test failed in 95 of 100 iterations before the fix.
  • Linux has not been run locally. CI covers it.

zac added 5 commits October 6, 2026 08:22
JWTAuth committed a refresh's tokens even when setTokens(_:) or
clearTokens() had replaced them while the refresh was in flight, and
persisted them through onTokensUpdated. A logout during a refresh could
silently sign the user back in. With several requests sharing one refresh,
a later waiter could also restore tokens after a logout that landed
between waiters resuming.

A refresh now commits only while it is still the pending refresh, so the
first waiter commits once and a superseded refresh never does. A
superseded refresh does not call onTokensUpdated or onRefreshFailed.
Requests waiting on it continue with the current tokens, or fail with
notAuthenticated after a logout. A persistence callback that has already
started cannot be revoked, and the docs now say so.

Also:
- An empty JWTAuth tokenPrefix sends the bare token, with no leading space.
- Fold EnvironmentType.swift into Server.swift and remove the internal
  sample ApiServer/TestEndpoint types that shipped in the library.
- Document that credentials are bound to an environment, and how to pair
  per-request environment and auth.
- Fix the Mocking guide: remove the nonexistent continuation.endpoint,
  fix non-Sendable examples, and describe the resume() swizzling accurately.
- Document the environment/auth parameters on every request method, and
  fix DocC warnings.
- README: shorter, with installation up front, a single getting-started
  path, accurate requirements and migration notes from 0.4, and working
  links. The wiki it linked to doesn't exist.
- DocC: new Authentication article. Examples now compile: they use
  ParameterComponents/HeaderComponents, the real multipart types, and the
  correct errorResponse pattern. Mocking documents the actual behavior of
  .fail and .none, typed-error matching inside withMock, a URLProtocol
  example for testing refresh, and building requests without sending them.
- Doc comments: document every public enum case, fix comments that
  described the wrong encoder or decoder, and use American spelling.
  Drop filler and em-dashes.

The README and guide examples were compiled and run against the library
before committing. The only non-comment change is the wording of a debug
assertion message.
The Documentation workflow had failed on every run since the release
trigger was added, so the hosted docs were from 2024. It also built only
the Endpoints module.

.spi.yml asks the Swift Package Index to build and host docs for both
Endpoints and EndpointsMocking on each release. EndpointsMocking gets a
landing page, and its doc comments no longer link to types in the other
module, which separately built archives can't resolve. The README badges
and documentation link now point to the index.
@zac
zac merged commit e3b6fbd into main Oct 6, 2026
2 checks passed
@zac
zac deleted the auth-env-improvements branch October 6, 2026 17:37
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.

1 participant