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

- Uživatel odešle delegovanou Jira operaci.
- Backend podle efektivního
app_user_idnačte jeho delegované Jira údaje. - Pokud údaje chybí nebo je nelze obnovit, backend vrátí HTTP
428s kódemjira_oauth_required. - 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ý.
- Reply Pilot vytvoří náhodný
state, uloží ho do podepsané Flask session a přesměruje prohlížeč na Atlassian consent stránku. - Callback přijme pouze shodný, nejvýše 10 minut starý
state. Autorizačnícodepředá backendu, který ho vymění za access token a rotating refresh token. - Backend přes
accessible-resourcesvybere právě Jira site nastavenou vJIRA_BASE_URLa přes/myselfověří identitu účtu. - 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-workawrite: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:
creatorareporter,- 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: