Expired PAT Causes Post-Merge Push Failure and Cannot Be Updated via UI (SZ-109)
rk@tigase.net opened 1 month ago

Summary

If the Personal Access Token (PAT) used for an external Git repository (e.g., GitHub) expires, merge operations in Sztab fail during the push step.

There is currently no way to update the stored PAT via the Project Edit UI. The only workaround is to either:

  • Create PATs with long expiration periods, or
  • Manually update the PAT directly in the database.

This is a functional gap and creates operational risk.


Environment

Version: 1.9.2
Component: Sztab + Sztabina
External Git: GitHub (PAT-based authentication)


Steps to Reproduce

  1. Configure a Project with an external Git URL (e.g., GitHub).
  2. Provide a valid PAT.
  3. Allow the PAT to expire.
  4. Attempt a merge (regular, squash, rebase, or ff).
  5. Observe that merge completes locally but push fails.
  6. Navigate to Project → Edit.

Actual Behavior

  • Merge operation in Sztabina succeeds locally.
  • Push to remote fails due to expired PAT.
  • Sztab reports merge failure.
  • Project Edit screen does not allow updating the stored PAT.
  • No recovery path is available via UI.

Expected Behavior

  • Project Edit screen should allow updating Git credentials (username + PAT).
  • System should clearly report authentication failure.
  • Merge operation should not leave repository in inconsistent state.

Impact

  • Users are blocked from completing merges.
  • No self-service recovery path.
  • Requires direct DB intervention.
  • Operationally fragile for short-lived tokens.

Current Workaround

  1. Generate PATs with long expiration times.
  2. Or manually update the PAT in the database.
  3. Restart stack if necessary.

Suggested Fix

  1. Extend Project Edit UI to allow updating Git credentials.
  2. Validate credentials on save (optional).
  3. Improve error messaging on push failure.
  4. Consider detecting 401/403 from remote and surface as "Authentication expired".

Severity

Medium–High

Blocks merge workflow once token expires.

  • rk@tigase.net commented 1 month ago

    Task Breakup:

    1. Backend DTO + entity update to allow credential update ~45 minutes • Add fields to ProjectUpdateDto (gitUsername, gitToken) • Update service layer to persist changes • Ensure token is encrypted/handled consistently
      1. Controller wiring + validation ~30–45 minutes • Allow optional update (don’t overwrite if null) • Add clear error message propagation
      2. UI change (Project Edit screen) ~45–60 minutes

    • Add masked PAT field • UX note: “Leave blank to keep existing token” • Ensure it doesn’t echo token back 4. Error handling improvement on push failure ~30 minutes • Detect auth-related push errors • Map to meaningful message (“Authentication failed or token expired”) 5. Regression test (manual + curl) ~30–45 minutes

    Total: ~3 hours focused work.

  • rk@tigase.net commented 1 week ago

    Happy path: MergeByURL handler → service.MergeByURL → inspector.Merge → one of the four merge strategy methods (regularMerge, squashMerge, etc.) → PushWithRetry → Push → e.Git("push", remote, branch) which shells out to git push.

    Where the expired PAT is detected: At the very bottom —

    e.Git("push", ...) 
    

    in engine.go. This runs the real git push process. When the PAT is expired, GitHub returns an HTTP 401/403, and git writes something like fatal: Authentication failed for 'https://...' to stderr and exits non-zero. That stderr comes back in Result.Stderr.

    Current problem: _Push() _sees r.Err != nil, wraps the raw stderr into a generic fmt.Errorf("push failed: %s", stderr), and returns it. PushWithRetry then retries this three times needlessly, and eventually returns "push failed after 3 attempts: push failed: fatal: Authentication failed...". That string propagates all the way up through the merge strategy, through service.MergeByURL, and out of the handler as a http/500 with a cryptic message.

    Where I am adding detection: In _Push() i_tself — the earliest possible point — so the auth failure is classified before it ever leaves engine.go. Everything above it (PushWithRetry, the merge strategies, service.MergeByURL, the handler) then just uses errors.Is to react appropriately rather than string-matching on error messages.

  • rk@tigase.net commented 1 week ago
    rksuma@Ramakrishnans-MacBook-Pro sztab % git checkout -b bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    Switched to a new branch 'bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI'
    rksuma@Ramakrishnans-MacBook-Pro sztab % 
    
    
  • rk@tigase.net changed state to 'In Progress' 1 week ago
    Previous Value Current Value
    Open
    In Progress
  • rk@tigase.net commented 1 week ago
  • rk@tigase.net commented 1 week ago
    rksuma@Ramakrishnans-MacBook-Pro sztab % git checkout wolnosc
    git pull origin wolnosc
    git merge --ff-only bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    git push origin wolnosc
    Switched to branch 'wolnosc'
    Your branch is up to date with 'origin/wolnosc'.
    From https://tigase.dev/sztab
     * branch            wolnosc    -> FETCH_HEAD
    Already up to date.
    Updating c8d111f..a6b16ef
    Fast-forward
     backend/src/main/java/com/sztab/exception/GitCredentialsExpiredException.java    |  12 +++++++
     backend/src/main/java/com/sztab/exception/handlers/GlobalExceptionHandler.java   |  15 +++++++++
     backend/src/main/java/com/sztab/service/impl/PersonalAccessTokenServiceImpl.java |   4 ++-
     backend/src/main/java/com/sztab/sztabina/client/impl/SztabinaClient.java         |  12 ++++++-
     backend/src/test/java/com/sztab/sztabina/client/SztabinaClientMergeTest.java     | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     frontend/src/components/project/EditProjectModal.tsx                             |  19 +++++++++++
     frontend/src/types/project.ts                                                    |   2 ++
     sztabina/exec/engine.go                                                          |  30 +++++++++++++-----
     sztabina/handlers/git_handler.go                                                 |   4 +++
     sztabina/service/gitservice.go                                                   |  13 +++++++-
     10 files changed, 226 insertions(+), 11 deletions(-)
     create mode 100644 backend/src/main/java/com/sztab/exception/GitCredentialsExpiredException.java
     create mode 100644 backend/src/test/java/com/sztab/sztabina/client/SztabinaClientMergeTest.java
    Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
    To https://tigase.dev/sztab.git
       c8d111f..a6b16ef  wolnosc -> wolnosc
    rksuma@Ramakrishnans-MacBook-Pro sztab % git branch -d bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    git push origin --delete bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    Deleted branch bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI (was a6b16ef).
    remote:  
    remote: Create a pull request for 'bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI' by visiting:
    remote:     https://tigase.dev/sztab/~pulls/new?target=1325:wolnosc&source=1325:bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    remote:  
    To https://tigase.dev/sztab.git
     - [deleted]         bugfix/SZ-109-Expired-PAT-Causes-Post-Merge-Push-Failure-and-Cannot-Be-Updated-via-UI
    rksuma@Ramakrishnans-MacBook-Pro sztab % 
    
    
  • rk@tigase.net changed state to 'Closed' 1 week ago
    Previous Value Current Value
    In Progress
    Closed
  • rk@tigase.net commented 1 week ago

    SZ-109 Test Plan to verify this fix

    1. Expired/invalid credentials — merge failure

    • Configure a project with a GitHub/GitLab repo using an expired or revoked PAT
    • Attempt to merge a PR in Sztab
    • Expected: merge fails with a clear message indicating credentials have expired, not a generic 500
    • Expected: failure is fast — no 3-attempt retry delay

    2. Valid credentials — merge succeeds

    • Configure a project with valid credentials
    • Merge a PR
    • Expected: merge succeeds normally, no regression

    3. Credential update via Project Edit UI

    • Open Project Edit for a project with expired credentials
    • Leave the Git Password / Token field blank and save
    • Expected: save succeeds, existing credentials unchanged
    • Open Project Edit again, enter a new valid token, save
    • Retry the merge
    • Expected: merge succeeds with updated credentials

    4. Credential update — invalid new token

    • Enter a deliberately wrong token in the credential field and save
    • Attempt a merge
    • Expected: same clear auth failure message as test 1

    5. Non-credential push failure

    • Simulate a non-auth push failure (e.g. force-push rejected, network error)
    • Expected: generic error message, not misclassified as a credential failure

    6. Read-only git browser with expired credentials

    • Navigate to the file browser for a project with expired credentials
    • Expected: graceful error, not a silent empty result or raw exception
    • Note: flagged as a follow-on during implementation — verify current behaviour and file a separate ticket if still surfacing a raw exception
issue 1 of 1
Type
Bug
Priority
Normal
Assignee
Version
1.9.2
Sprints
n/a
Customer
n/a
Issue Votes (0)
Watchers (3)
Reference
SZ-109
Please wait...
Page is in error, reload to recover