Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/cli/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ The command answers four diagnostic questions: am I signed in, as whom, via whic
"issuer": "https://signin.pipefy.com/realms/pipefy",
"state": "active", // or "refresh-expired" | "needs-login" | "n/a"
"access_expires_at": "2026-05-20T22:14:03Z", // ISO 8601, null for static-bearer
"refresh_expires_at": "2026-06-19T18:02:00Z", // ISO 8601, null when not stored-session
"refresh_expires_at": "2026-06-19T18:02:00Z", // ISO 8601; null when not a stored session, or when the IdP advertises no refresh-token expiry (refresh_expires_in: 0)
"token_rejected": false, // true only when the identity `me` query returned 401
"keychain_backend": "Keyring", // null for non-stored-session sources
"masking_env_vars": [] // env vars masking a stored session, if any
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/pipefy_cli/commands/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,10 @@ def _populate_stored_session(report: AuthStatusReport, oidc: OidcClient) -> None
report.access_expires_at = _iso_expiry(
stale.obtained_at, stale.token.expires_in
)
# refresh_expires_in: 0 is Keycloak's "no advertised expiry" sentinel,
# not a 0-second TTL — treat as no expiry (else it renders "expired").
report.refresh_expires_at = _iso_expiry(
stale.obtained_at, stale.token.refresh_expires_in
stale.obtained_at, stale.token.refresh_expires_in or None
)
raise _StatusExit(
report=report,
Expand All @@ -382,7 +384,7 @@ def _populate_stored_session(report: AuthStatusReport, oidc: OidcClient) -> None
fresh_session.obtained_at, fresh_session.token.expires_in
)
report.refresh_expires_at = _iso_expiry(
fresh_session.obtained_at, fresh_session.token.refresh_expires_in
fresh_session.obtained_at, fresh_session.token.refresh_expires_in or None
)


Expand Down
18 changes: 18 additions & 0 deletions packages/cli/tests/test_auth_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@ def test_status_stored_session_text_output(
assert "Expires:" in result.stdout


def test_status_non_expiring_refresh_token_not_reported_expired(
clean_pipefy_env, saved_cwd, monkeypatch, runner, fake_keyring
):
"""Keycloak advertises a non-expiring refresh token as ``refresh_expires_in: 0``.
That must not be read as a 0-second TTL and rendered as already ``expired``."""
_set_auth_env(monkeypatch)
_seed_session(monkeypatch, refresh_expires_in=0)
session = storage.load_session(issuer=_ISSUER, client_id=_CLIENT_ID)
client = _mock_client_with_me()
with _patch_fresh_session(session), _patch_command_client(client):
json_result = _invoke_status(runner, ["--json"])
text_result = _invoke_status(runner)

assert json_result.exit_code == 0, json_result.stdout
assert json.loads(json_result.stdout)["refresh_expires_at"] is None
assert "Refresh token: expired" not in text_result.stdout


# --------------------------------------------------------------------------- #
# Scenario 2: stored-session, eager refresh runs (we mock the result as fresh) #
# --------------------------------------------------------------------------- #
Expand Down