@@ -4,12 +4,7 @@ import { getRedisClient } from '@/lib/core/config/redis'
44
55const logger = createLogger ( 'OAuthTerminalErrors' )
66
7- /**
8- * Refresh error codes that no retry can recover from: the credential stays dead until
9- * its owner reconnects. `unauthorized_client` is how Atlassian rejects a revoked or
10- * rotated-out refresh token, and under RFC 6749 section 5.2 it otherwise means the
11- * client may not use the refresh grant, which is equally persistent.
12- */
7+ /** Refresh error codes that no retry can recover from: the credential stays dead until its owner reconnects. */
138const TERMINAL_ERRORS = new Set < string > ( [
149 'invalid_refresh_token' ,
1510 'bad_refresh_token' ,
@@ -20,7 +15,17 @@ const TERMINAL_ERRORS = new Set<string>([
2015 'invalid_client' ,
2116 'bad_redirect_uri' ,
2217 'token_revoked' ,
23- 'unauthorized_client' ,
18+ ] )
19+
20+ /**
21+ * Codes terminal only for the providers listed. Atlassian rejects a revoked or rotated-out
22+ * refresh token with `unauthorized_client`; elsewhere that code usually describes the app
23+ * registration, and treating it as terminal would send every credential of the provider to
24+ * reauthorization over one configuration fault.
25+ */
26+ const PROVIDER_TERMINAL_ERRORS : ReadonlyMap < string , ReadonlySet < string > > = new Map ( [
27+ [ 'confluence' , new Set ( [ 'unauthorized_client' ] ) ] ,
28+ [ 'jira' , new Set ( [ 'unauthorized_client' ] ) ] ,
2429] )
2530
2631const DEAD_CACHE_TTL_SEC = 60 * 60
@@ -29,9 +34,13 @@ function deadKey(accountId: string): string {
2934 return `oauth:dead:${ accountId } `
3035}
3136
32- export function isTerminalRefreshError ( code : string | undefined | null ) : boolean {
37+ export function isTerminalRefreshError (
38+ code : string | undefined | null ,
39+ providerId ?: string
40+ ) : boolean {
3341 if ( ! code ) return false
34- return TERMINAL_ERRORS . has ( code )
42+ if ( TERMINAL_ERRORS . has ( code ) ) return true
43+ return providerId !== undefined && ( PROVIDER_TERMINAL_ERRORS . get ( providerId ) ?. has ( code ) ?? false )
3544}
3645
3746export async function markCredentialDead ( accountId : string , code : string ) : Promise < void > {
0 commit comments