Skip to content

Commit 6488a4a

Browse files
chore: add some docs; linting
1 parent 3009c49 commit 6488a4a

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

src/server/auth-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ export class AuthClient {
796796
codeGrantResponse = await withDPoPNonceRetry(
797797
authorizationCodeGrantRequestCall,
798798
{
799-
isDPoPEnabled: !!(this.useDPoP && this.dpopKeyPair),
799+
isDPoPEnabled: !!dpopHandle,
800800
...this.dpopOptions?.retry
801801
}
802802
);

src/server/dpop-authcode-nonce-retry.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ function createDPoPNonceRetryHandler(keyPairParam: jose.GenerateKeyPairResult) {
6868
};
6969

7070
// Helper to parse DPoP JWT and extract nonce claim
71-
const extractDPoPNonce = (dpopHeader: string | null): { hasNonce: boolean; nonce?: string } => {
71+
const extractDPoPNonce = (
72+
dpopHeader: string | null
73+
): { hasNonce: boolean; nonce?: string } => {
7274
if (!dpopHeader || typeof dpopHeader !== "string") {
7375
return { hasNonce: false };
7476
}
@@ -313,7 +315,9 @@ describe("AuthClient.handleCallback with DPoP Nonce Retry", () => {
313315
// 1. Received the DPoP-Nonce header from the 400 error response
314316
// 2. Extracted the nonce value ("server_nonce_value_123")
315317
// 3. Injected it into the DPoP JWT payload on retry
316-
expect(tokenHandlerState.requests[1].nonce).toBe("server_nonce_value_123");
318+
expect(tokenHandlerState.requests[1].nonce).toBe(
319+
"server_nonce_value_123"
320+
);
317321

318322
// Additional validation: Decode the second request's DPoP JWT and verify
319323
// the payload contains the exact nonce claim

src/utils/dpopUtils.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,26 @@ const applyRetryDelay = async (config?: RetryConfig) => {
8282
* Note: The DPoP handle (oauth4webapi) is stateful and automatically learns nonces
8383
* from the DPoP-Nonce response header. No manual nonce injection is required.
8484
*
85+
* * ## Dual-Path Retry Logic
86+
*
87+
* The wrapper supports TWO different error paths, depending on how the caller
88+
* structures their token request:
89+
*
90+
* ### Path 1: HTTP Request Only (Recommended for Auth Code Flow)
91+
* **When to use:** Wrapping ONLY the HTTP request, not response processing*
92+
* **Error handling:**
93+
* - Nonce errors are detected via `response.status === 400` check (line 135)
94+
* - Non-nonce 400 errors pass through unchanged
95+
* - No exception is thrown; Response is returned for caller to process
96+
*
97+
* ### Path 2: HTTP Request + Response Processing (Used for Refresh/Connection Flows)
98+
* **When to use:** Wrapping both HTTP request AND response processing
99+
*
100+
* * **Error handling:**
101+
* - Nonce errors are detected via `isDPoPNonceError(error)` check (line 150)
102+
* - Non-nonce errors are re-thrown unchanged
103+
* - Caller receives either a successful response or an exception
104+
*
85105
* @template T - The return type of the function being executed
86106
* @param fn - The async function to execute with retry logic
87107
* @param config - Configuration object with retry behavior and DPoP enablement flag
@@ -128,6 +148,19 @@ export async function withDPoPNonceRetry<T>(
128148
return await fn();
129149
}
130150

151+
/**
152+
* PATH 1: Response Object Inspection (Auth Code Flow)
153+
*
154+
* When fn() returns a Response object (not thrown), we check its status.
155+
* If 400 with use_dpop_nonce error, extract nonce from error body and retry.
156+
* This path is used when response processing happens OUTSIDE the wrapper.
157+
*
158+
* PATH 2: Exception Handling (Refresh/Connection Flows)
159+
* When fn() includes response processing that throws, we catch exceptions above.
160+
* Both paths support automatic nonce retry per RFC 9449 Section 8.
161+
*
162+
* @see withDPoPNonceRetry JSDoc for detailed explanation of dual-path retry logic
163+
*/
131164
try {
132165
const response = await fn();
133166

@@ -153,8 +186,7 @@ export async function withDPoPNonceRetry<T>(
153186
await applyRetryDelay(config);
154187
// Retry the request - the DPoP handle automatically learned the nonce
155188
return await fn();
156-
}
157-
else{
189+
} else {
158190
throw error;
159191
}
160192
}

0 commit comments

Comments
 (0)