Jira OAuth 2.0 (3LO)

Reply Pilot používá Jira OAuth 2.0 authorization-code flow (Atlassian 3LO) pro Jira operace vyvolané přihlášeným uživatelem. Backend je posílá s bearer tokenem tohoto uživatele, ne s technickým účtem aplikace.

Přesný rozsah implementace

Audit produkčních Jira call sites rozlišuje dvě cesty:

Cesta Přihlašovací údaje Operace
Interaktivní request s autentizovaným app uživatelem Jeho OAuth 3LO token Jira čtení, vyhledávání uživatele, create/update issue, assignment/unassign, transitions, comments a attachments. Patří sem task workflow, lead import, explicitní refresh tasku a obecné Jira proxy endpointy.
Background proces bez lidského aktéra Technický JIRA_EMAIL/JIRA_API_TOKEN Periodický Jira task sync, automatické Jira operace vyvolané příchozím e-mailem a samostatný modul Jira reports.

Delegovaná cesta nikdy po OAuth chybě neopakuje požadavek s technickým účtem. Technický účet zatím nelze odstranit, protože background allowlist nemá konkrétního přihlášeného uživatele.

Create issue je složená operace: po úspěšném Jira POST /issue se klient pokusí načíst detail vytvořeného issue. Pokud toto obohacující čtení selže požadavkem na nový OAuth souhlas, backend vrátí již získané id a key jako úspěšný výsledek. Nesmí vrátit falešnou 428, protože opakování formuláře by vytvořilo duplicitní ticket.

Obnovitelný chybový kontrakt

Všechny backendové controllery mapují JiraOAuthRequiredException jedním společným handlerem na:

{
  "status": "error",
  "code": "jira_oauth_required",
  "reason": "..."
}

HTTP status je vždy 428 Precondition Required. Chybějící údaje, nepodporovaný nebo nečitelný credential envelope, chybějící refresh token a odmítnutý či neobnovitelný token tak mají stejný kontrakt. Delegovaná operace se po této chybě nesmí opakovat s technickým účtem.

App HTTP klienti rozpoznají obnovitelný stav pouze při současné shodě HTTP 428 a kódu jira_oauth_required; ostatní chyby se dál zpracují jako běžná chyba příslušného klienta. Formuláře zachovají odeslané hodnoty a použijí společnou výzvu Připojit Jira účet v nové kartě. Přechodové akce bez formulářových hodnot nabídnou stejný existující connection flow ve flash zprávě.

Tok uživatele

Jira 3LO delegated operation flow

  1. Uživatel odešle delegovanou Jira operaci.
  2. Backend podle efektivního app_user_id načte jeho delegované Jira údaje.
  3. Pokud údaje chybí nebo je nelze obnovit, backend vrátí HTTP 428 s kódem jira_oauth_required.
  4. UI zachová rozepsaný text a nabídne odkaz Připojit Jira účet. Odkaz se otevírá v nové záložce, aby rozepsaný formulář zůstal zachovaný.
  5. Reply Pilot vytvoří náhodný state, uloží ho do podepsané Flask session a přesměruje prohlížeč na Atlassian consent stránku.
  6. Callback přijme pouze shodný, nejvýše 10 minut starý state. Autorizační code předá backendu, který ho vymění za access token a rotating refresh token.
  7. Backend přes accessible-resources vybere právě Jira site nastavenou v JIRA_BASE_URL a přes /myself ověří identitu účtu.
  8. Po návratu na původní stránku uživatel odešle zachovanou operaci znovu. Backend volá Jira Cloud API s bearer tokenem uživatele, takže Jira eviduje skutečného Atlassian aktéra pro konkrétní delegovanou operaci.

Access token se před použitím automaticky obnoví, pokud do expirace zbývá nejvýše 60 sekund. Atlassian při refreshi vrací rotating refresh token; backend v jedné DB aktualizaci nahradí access i refresh token novými hodnotami.

Profil uživatele

Na /profil je sekce Jira účet pro uživatelské operace. Zobrazuje:

  • stav konfigurace a připojení,
  • Atlassian account ID, display name, email a Jira site,
  • expiraci access tokenu a udělené scopes,
  • odkaz pro nové připojení nebo opakovaný consent,
  • tlačítko pro živé ověření tokenu přes Jira /myself,
  • tlačítko pro explicitní refresh tokenu.

Ověření a refresh jsou testovací a diagnostické akce. Běžná delegovaná operace provádí refresh automaticky.

Uložení a bezpečnost

app_user_jira_profile.jira_oauth_credentials_ciphertext obsahuje jeden verzovaný credential envelope šifrovaný pomocí AES-256-GCM. Obálka obsahuje access token, aktuální rotating refresh token, expiraci, scopes, Jira cloud ID a ověřenou Jira identitu. Autentizovaná šifrovací metadata vážou ciphertext na konkrétní app_user_id, takže obálku nelze pouze přesunout k jinému uživateli. Tokeny se do databáze neukládají v otevřeném textu.

Šifrovací klíč je oddělený runtime secret JIRA_OAUTH_TOKEN_ENCRYPTION_KEY; musí být base64 kódování přesně 32 náhodných bajtů. Klíč se nesmí změnit, dokud existují uložené credential envelopes. Ztráta nebo rotace klíče bez řízené migrace vyžaduje nové připojení Jira účtů.

Client secret a šifrovací klíč patří pouze do SOPS source-of-truth souborů:

  • lokálně secrets/local/reply-pilot-be.env,
  • v produkci secrets/prod/reply-pilot-be.env.

OAuth state váže callback na přihlášenou browser session. Backend navíc identifikuje credential podle aplikací ověřeného app_user_id; prohlížeč neposílá token ani cílové user ID.

Konfigurace Atlassian aplikace

V Atlassian developer console vytvoř OAuth 2.0 integration a povol:

  • callback URL příslušného prostředí,
  • classic scopes read:jira-user, read:jira-work a write:jira-work,
  • offline_access, který se posílá v authorization requestu.

Změna scopes v konfiguraci Reply Pilotu nerozšíří dříve vydaný grant. Uživatel, jehož uložený scope neobsahuje read:jira-work, musí na /profil spustit nové připojení/consent. Samotný refresh tokenu vrací scopes původního grantu a chybějící scope nepřidá.

Runtime proměnné backendu:

Klíč Význam
JIRA_OAUTH_CLIENT_ID OAuth client ID z Atlassian developer console.
JIRA_OAUTH_CLIENT_SECRET OAuth client secret.
JIRA_OAUTH_TOKEN_ENCRYPTION_KEY Base64 kódování 32 náhodných bajtů.
APP_PUBLIC_BASE_URL Veřejný základ Reply Pilot UI; backend z něj odvodí callback ${APP_PUBLIC_BASE_URL}/jira/oauth/callback.
JIRA_BASE_URL Přesná Jira site, kterou musí uživatel v accessible-resources skutečně mít.

Callback URL:

Prostředí Callback
Lokální pilot http://127.0.0.1:9090/jira/oauth/callback
Produkce https://reply-pilot.mathbox.90.cz/jira/oauth/callback

Repozitář podporuje obě prostředí přes jejich vlastní SOPS konfiguraci. Prakticky je bezpečnější použít oddělené Atlassian OAuth klienty pro local/test a production: mají oddělené client secrets, callback konfiguraci a lifecycle. Pokud se použije jeden klient, Atlassian konfigurace musí výslovně přijímat callback právě běžícího prostředí.

Po doplnění hodnot se backend spustí standardním start/deploy workflow. Prázdné client ID nebo client secret znamená stav nenakonfigurováno; technická Jira integrace přitom zůstává funkční.

Stav živého ověření

Dne 2026-07-30 vytvořil lokální delegovaný request dvě testovací Jira issues RP-3507 a RP-3508. V Jira byl u obou pozorován přihlášený OAuth uživatel jako creator i reporter; obě testovací issues byly následně přesunuty do Done.

Tento pokus současně odhalil, že dříve vydaný lokální grant obsahoval read:jira-user, write:jira-work a offline_access, ale ne read:jira-work. Create zápis proto uspěl, zatímco následné čtení detailu bylo odmítnuto.

Po opravě scopes a novém consentu status i živé /myself potvrdily read:jira-user, read:jira-work, write:jira-work a offline_access. Izolovaný acceptance task RP-3509 pak přes Reply Pilot provedl create, comment, attachment upload, unassign, update s reassignmentem a transition do Done. Jira u něj zaznamenala stejného přihlášeného uživatele jako:

  • creator a reporter,
  • autora komentáře a přílohy,
  • autora změny assignee, summary a description,
  • autora přechodu In Progress -> Done.

Dříve provedený live invalid-format test vrátil sdílenou 428 jira_oauth_required. Background smoke následně spustil POST /api/jira/task-sync/run bez user headeru a uspěl přes explicitní technickou no-actor cestu. Živá acceptance je tím uzavřená.

Atlassian kontrakt

Implementace používá:

  • authorization endpoint https://auth.atlassian.com/authorize,
  • token endpoint https://auth.atlassian.com/oauth/token,
  • GET https://api.atlassian.com/oauth/token/accessible-resources,
  • Jira API prefix https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3.

Aktuální externí kontrakt ověřuj v oficiální dokumentaci: