Skip to content

Commit e63c5ba

Browse files
committed
refactored to avoid recursion as recommended
1 parent 419465e commit e63c5ba

File tree

1 file changed

+80
-79
lines changed

1 file changed

+80
-79
lines changed

src/client/auth.ts

Lines changed: 80 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import pkceChallenge from "pkce-challenge";
2-
import { LATEST_PROTOCOL_VERSION } from "../types.js";
2+
import {LATEST_PROTOCOL_VERSION} from "../types.js";
33
import type {
44
OAuthClientInformation,
55
OAuthClientInformationFull,
@@ -138,101 +138,102 @@ export async function parseErrorResponse(input: Response | string): Promise<OAut
138138
export async function auth(
139139
provider: OAuthClientProvider,
140140
options: { serverUrl: string | URL, authorizationCode?: string },
141-
lastError?: Error
142141
): Promise<AuthResult> {
143-
const { serverUrl, authorizationCode } = options
144142
try {
145-
const metadata = await discoverOAuthMetadata(serverUrl);
146-
147-
// Handle client registration if needed
148-
let clientInformation = await Promise.resolve(provider.clientInformation());
149-
if (!clientInformation) {
150-
if (authorizationCode !== undefined) {
151-
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
152-
}
153-
154-
if (!provider.saveClientInformation) {
155-
throw new Error("OAuth client information must be saveable for dynamic registration");
156-
}
143+
return await authInternal(provider, options);
144+
} catch (error) {
145+
// Handle recoverable error types by invalidating credentials and retrying
146+
if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
147+
await provider.invalidateCredentials?.('all');
148+
return await authInternal(provider, options);
149+
} else if (error instanceof InvalidGrantError) {
150+
await provider.invalidateCredentials?.('tokens');
151+
return await authInternal(provider, options);
152+
}
157153

158-
const fullInformation = await registerClient(serverUrl, {
159-
metadata,
160-
clientMetadata: provider.clientMetadata,
161-
});
154+
// Throw otherwise
155+
throw error
156+
}
157+
}
162158

163-
await provider.saveClientInformation(fullInformation);
164-
clientInformation = fullInformation;
165-
}
159+
async function authInternal(
160+
provider: OAuthClientProvider,
161+
options: { serverUrl: string | URL, authorizationCode?: string },
162+
): Promise<AuthResult> {
163+
const { serverUrl, authorizationCode } = options;
164+
const metadata = await discoverOAuthMetadata(serverUrl);
166165

167-
// Exchange authorization code for tokens
166+
// Handle client registration if needed
167+
let clientInformation = await Promise.resolve(provider.clientInformation());
168+
if (!clientInformation) {
168169
if (authorizationCode !== undefined) {
169-
const codeVerifier = await provider.codeVerifier();
170-
const tokens = await exchangeAuthorization(serverUrl, {
171-
metadata,
172-
clientInformation,
173-
authorizationCode,
174-
codeVerifier,
175-
redirectUri: provider.redirectUrl,
176-
});
177-
178-
await provider.saveTokens(tokens);
179-
return "AUTHORIZED";
170+
throw new Error("Existing OAuth client information is required when exchanging an authorization code");
180171
}
181172

182-
const tokens = await provider.tokens();
183-
184-
// Handle token refresh or new authorization
185-
if (tokens?.refresh_token) {
186-
try {
187-
// Attempt to refresh the token
188-
const newTokens = await refreshAuthorization(serverUrl, {
189-
metadata,
190-
clientInformation,
191-
refreshToken: tokens.refresh_token,
192-
});
193-
194-
await provider.saveTokens(newTokens);
195-
return "AUTHORIZED";
196-
} catch (error) {
197-
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
198-
if (!(error instanceof OAuthError) || error instanceof ServerError) {
199-
console.error("Could not refresh OAuth tokens:", error);
200-
} else {
201-
console.warn(`OAuth token refresh failed: ${JSON.stringify(error.toResponseObject())}`);
202-
throw error
203-
}
204-
}
173+
if (!provider.saveClientInformation) {
174+
throw new Error("OAuth client information must be saveable for dynamic registration");
205175
}
206176

207-
// Start new authorization flow
208-
const {authorizationUrl, codeVerifier} = await startAuthorization(serverUrl, {
177+
const fullInformation = await registerClient(serverUrl, {
178+
metadata,
179+
clientMetadata: provider.clientMetadata,
180+
});
181+
182+
await provider.saveClientInformation(fullInformation);
183+
clientInformation = fullInformation;
184+
}
185+
186+
// Exchange authorization code for tokens
187+
if (authorizationCode !== undefined) {
188+
const codeVerifier = await provider.codeVerifier();
189+
const tokens = await exchangeAuthorization(serverUrl, {
209190
metadata,
210191
clientInformation,
211-
redirectUrl: provider.redirectUrl,
212-
scope: provider.clientMetadata.scope
192+
authorizationCode,
193+
codeVerifier,
194+
redirectUri: provider.redirectUrl,
213195
});
214196

215-
await provider.saveCodeVerifier(codeVerifier);
216-
await provider.redirectToAuthorization(authorizationUrl);
217-
return "REDIRECT";
218-
} catch (error) {
219-
switch ((error as Error).constructor) {
220-
// Don't loop forever if the same type of error recurs
221-
case lastError?.constructor:
197+
await provider.saveTokens(tokens);
198+
return "AUTHORIZED"
199+
}
200+
201+
const tokens = await provider.tokens();
202+
203+
// Handle token refresh or new authorization
204+
if (tokens?.refresh_token) {
205+
try {
206+
// Attempt to refresh the token
207+
const newTokens = await refreshAuthorization(serverUrl, {
208+
metadata,
209+
clientInformation,
210+
refreshToken: tokens.refresh_token,
211+
});
212+
213+
await provider.saveTokens(newTokens);
214+
return "AUTHORIZED"
215+
} catch (error) {
216+
// If this is a ServerError, or an unknown type, log it out and try to continue. Otherwise, escalate so we can fix things and retry.
217+
if (!(error instanceof OAuthError) || error instanceof ServerError) {
218+
console.error("Could not refresh OAuth tokens:", error);
219+
} else {
220+
console.warn(`OAuth token refresh failed: ${JSON.stringify(error.toResponseObject())}`);
222221
throw error;
223-
// Invalid clients mean the entire local state is now invalid, so clear it all then retry
224-
case InvalidClientError:
225-
case UnauthorizedClientError:
226-
await provider.invalidateCredentials?.('all')
227-
return await auth(provider, options, error as Error)
228-
// Invalid grants mean clear the tokens and retry
229-
case InvalidGrantError:
230-
await provider.invalidateCredentials?.('tokens')
231-
return await auth(provider, options, error as Error)
232-
default:
233-
throw error
222+
}
234223
}
235224
}
225+
226+
// Start new authorization flow
227+
const {authorizationUrl, codeVerifier} = await startAuthorization(serverUrl, {
228+
metadata,
229+
clientInformation,
230+
redirectUrl: provider.redirectUrl,
231+
scope: provider.clientMetadata.scope
232+
});
233+
234+
await provider.saveCodeVerifier(codeVerifier);
235+
await provider.redirectToAuthorization(authorizationUrl);
236+
return "REDIRECT"
236237
}
237238

238239
/**

0 commit comments

Comments
 (0)