From 8c32223b72e823e57309285dc3b5f42270920307 Mon Sep 17 00:00:00 2001 From: Samu Lang Date: Tue, 25 Aug 2026 12:31:01 +0200 Subject: [PATCH 1/2] Make code callback an interface --- index.html | 2 +- src/AuthorizationCodeFlow.ts | 3 ++- src/BearerTokenProvider.ts | 10 +++++----- src/CodeProvider.ts | 3 +++ src/DPoPTokenProvider.ts | 12 ++++++------ src/GetCodeCallback.ts | 1 - src/ReactiveFetchWorkerManager.ts | 10 +++++----- src/mod.ts | 2 +- src/reactive-fetch-worker.ts | 4 ++-- 9 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 src/CodeProvider.ts delete mode 100644 src/GetCodeCallback.ts diff --git a/index.html b/index.html index f518d15..9595a13 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@ const clientProvider = new DynamicRegistrationClientProvider const cachingClientProvider = new CachingClientProvider(clientProvider) - const dPoPTokenProvider = new DPoPTokenProvider(callbackUri, ui.getCode.bind(ui), cachingASProvider, cachingClientProvider) + const dPoPTokenProvider = new DPoPTokenProvider(callbackUri, ui, cachingASProvider, cachingClientProvider) const fetch = new ReactiveFetchManager([dPoPTokenProvider]).fetch diff --git a/src/AuthorizationCodeFlow.ts b/src/AuthorizationCodeFlow.ts index 36adfce..4312ddb 100644 --- a/src/AuthorizationCodeFlow.ts +++ b/src/AuthorizationCodeFlow.ts @@ -1,5 +1,6 @@ import { Mutex } from "./Mutex.js" import { CodeRequestCancelledError } from "./CodeRequestCancelledError.js" +import type { CodeProvider } from "./CodeProvider.js" const authorizationWindowName = "oidcAuthentication" const onlyOnce = {once: true} @@ -119,7 +120,7 @@ const html = ` * * ``` */ -export class AuthorizationCodeFlow extends HTMLElement { +export class AuthorizationCodeFlow extends HTMLElement implements CodeProvider { readonly #mutex = new Mutex #newModal!: HTMLDialogElement #switchModal!: HTMLDialogElement diff --git a/src/BearerTokenProvider.ts b/src/BearerTokenProvider.ts index 051af48..52a5259 100644 --- a/src/BearerTokenProvider.ts +++ b/src/BearerTokenProvider.ts @@ -1,15 +1,15 @@ import * as oauth from "oauth4webapi" -import { GetCodeCallback } from "./GetCodeCallback.js" +import { CodeProvider } from "./CodeProvider.js" import { TokenProvider } from "./TokenProvider.js" // TODO: Configure properly for insecure localhost only const oauthAllowInsecureRequests = true export class BearerTokenProvider implements TokenProvider { - readonly #getCode: GetCodeCallback + readonly #codeProvider: CodeProvider - constructor(getCodeCallback: GetCodeCallback) { - this.#getCode = getCodeCallback + constructor(codeProvider: CodeProvider) { + this.#codeProvider = codeProvider } async #getIssuer(request: Request): Promise { @@ -75,7 +75,7 @@ export class BearerTokenProvider implements TokenProvider { // authorizationUrl.searchParams.set("nonce", nonce) // } - const authorizationCodeResponse = await this.#getCode(authorizationUrl, request.signal) + const authorizationCodeResponse = await this.#codeProvider.getCode(authorizationUrl, request.signal) const authorizationCodeParams = oauth.validateAuthResponse(authorizationServer, clientRegistration, new URL(authorizationCodeResponse)) let clientAuth = oauth.None() diff --git a/src/CodeProvider.ts b/src/CodeProvider.ts new file mode 100644 index 0000000..b4025fb --- /dev/null +++ b/src/CodeProvider.ts @@ -0,0 +1,3 @@ +export interface CodeProvider { + getCode(authorizationUri: URL, signal: AbortSignal): Promise +} diff --git a/src/DPoPTokenProvider.ts b/src/DPoPTokenProvider.ts index 17e0123..b652919 100644 --- a/src/DPoPTokenProvider.ts +++ b/src/DPoPTokenProvider.ts @@ -1,6 +1,6 @@ import * as oauth from "oauth4webapi" import * as DPoP from "dpop" -import type { GetCodeCallback } from "./GetCodeCallback.js" +import type { CodeProvider } from "./CodeProvider.js" import type { TokenProvider } from "./TokenProvider.js" import type { AuthorizationServerProvider } from "./AuthorizationServerProvider.js" import { ClientProvider } from "./ClientProvider.js" @@ -8,14 +8,14 @@ import { ClientProvider } from "./ClientProvider.js" type CacheEntry = { created: number, tokenResult: oauth.TokenEndpointResponse, dpopKey: CryptoKeyPair } export class DPoPTokenProvider implements TokenProvider { - readonly #getCode: GetCodeCallback + readonly #codeProvider: CodeProvider readonly #callbackUri: string readonly #cache = new Map // TODO: Take cache from caller readonly #asProvider: AuthorizationServerProvider readonly #clientProvider: ClientProvider - constructor(callbackUri: string, getCodeCallback: GetCodeCallback, asProvider: AuthorizationServerProvider, clientProvider: ClientProvider) { - this.#getCode = getCodeCallback + constructor(callbackUri: string, codeProvider: CodeProvider, asProvider: AuthorizationServerProvider, clientProvider: ClientProvider) { + this.#codeProvider = codeProvider this.#callbackUri = callbackUri this.#asProvider = asProvider this.#clientProvider = clientProvider @@ -73,7 +73,7 @@ export class DPoPTokenProvider implements TokenProvider { } } - const authorizationCodeResponse = await this.#getCode(authorizationUrl, request.signal) + const authorizationCodeResponse = await this.#codeProvider.getCode(authorizationUrl, request.signal) let authorizationCodeParams try { @@ -89,7 +89,7 @@ export class DPoPTokenProvider implements TokenProvider { console.debug("Authorization server requires user interaction, retrying without prompt") authorizationUrl.searchParams.delete("prompt") - const authorizationCodeResponse = await this.#getCode(authorizationUrl, request.signal) + const authorizationCodeResponse = await this.#codeProvider.getCode(authorizationUrl, request.signal) authorizationCodeParams = oauth.validateAuthResponse(authorizationServer, clientRegistration, new URL(authorizationCodeResponse), state) } else { throw e diff --git a/src/GetCodeCallback.ts b/src/GetCodeCallback.ts deleted file mode 100644 index 6d0e672..0000000 --- a/src/GetCodeCallback.ts +++ /dev/null @@ -1 +0,0 @@ -export type GetCodeCallback = (authorizationUri: URL, signal: AbortSignal) => Promise \ No newline at end of file diff --git a/src/ReactiveFetchWorkerManager.ts b/src/ReactiveFetchWorkerManager.ts index ce8b41c..186f039 100644 --- a/src/ReactiveFetchWorkerManager.ts +++ b/src/ReactiveFetchWorkerManager.ts @@ -1,10 +1,10 @@ -import type { GetCodeCallback } from "./GetCodeCallback.js" +import type { CodeProvider } from "./CodeProvider.js" export class ReactiveFetchWorkerManager { - readonly #getCode: GetCodeCallback + readonly #codeProvider: CodeProvider - constructor(getCodeCallback: GetCodeCallback) { - this.#getCode = getCodeCallback + constructor(codeProvider: CodeProvider) { + this.#codeProvider = codeProvider } async register() { @@ -15,6 +15,6 @@ export class ReactiveFetchWorkerManager { } async #onMessage(e: MessageEvent) { - e.ports[0]?.postMessage(await this.#getCode(new URL(e.data), null!)) // TODO: Signal? + e.ports[0]?.postMessage(await this.#codeProvider.getCode(new URL(e.data), null!)) // TODO: Signal? } } diff --git a/src/mod.ts b/src/mod.ts index 7fed984..5441d6c 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -6,7 +6,7 @@ export * from "./ReactiveFetchWorkerManager.js" export * from "./CodeRequestCancelledError.js" export * from "./ReactiveAuthenticationError.js" export * from "./ClientCredentialsTokenProvider.js" -export * from "./GetCodeCallback.js" +export * from "./CodeProvider.js" export * from "./PatternIssuerProvider.js" export * from "./TokenProvider.js" export * from "./IssuerProvider.js" diff --git a/src/reactive-fetch-worker.ts b/src/reactive-fetch-worker.ts index 2e9a310..a1e9904 100644 --- a/src/reactive-fetch-worker.ts +++ b/src/reactive-fetch-worker.ts @@ -28,8 +28,8 @@ async function onFetch(e: FetchEvent): Promise { } function upgrade(request: Request, client: Client): Promise { - const dPoPTokenProvider = new DPoPTokenProvider(undefined!, postEventAndWait.bind(undefined, client), undefined!, undefined!) // TODO: Callback, getIssuer, getClient - const bearerProvider = new BearerTokenProvider(postEventAndWait.bind(undefined, client)) + const dPoPTokenProvider = new DPoPTokenProvider(undefined!, undefined!, undefined!, undefined!) // TODO: Callback, getIssuer, getClient + const bearerProvider = new BearerTokenProvider(undefined!) return new ReactiveAuthenticationClient(self.fetch, [bearerProvider, dPoPTokenProvider]).fetch(request) } From 974de372051e7a068434c976ba58824d46c8ca4f Mon Sep 17 00:00:00 2001 From: Samu Lang Date: Tue, 25 Aug 2026 13:21:12 +0200 Subject: [PATCH 2/2] Caller, not provider, closes authorization window --- src/AuthorizationCodeFlow.ts | 5 ++++- src/CodeProvider.ts | 2 ++ src/DPoPTokenProvider.ts | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/AuthorizationCodeFlow.ts b/src/AuthorizationCodeFlow.ts index 4312ddb..d6b88c2 100644 --- a/src/AuthorizationCodeFlow.ts +++ b/src/AuthorizationCodeFlow.ts @@ -195,7 +195,6 @@ export class AuthorizationCodeFlow extends HTMLElement implements CodeProvider { this.ownerDocument.defaultView?.removeEventListener("message", onMessage) signal.removeEventListener("abort", onAbort) this.#switchModal.close() - this.#authorizationWindow?.close() respondWithCode(message.data) } @@ -219,6 +218,10 @@ export class AuthorizationCodeFlow extends HTMLElement implements CodeProvider { return await responseFromPopup } + cleanup(): void { + this.#authorizationWindow?.close() + } + #onSubmit(e: SubmitEvent) { e.preventDefault() diff --git a/src/CodeProvider.ts b/src/CodeProvider.ts index b4025fb..aac64eb 100644 --- a/src/CodeProvider.ts +++ b/src/CodeProvider.ts @@ -1,3 +1,5 @@ export interface CodeProvider { getCode(authorizationUri: URL, signal: AbortSignal): Promise + + cleanup(): void } diff --git a/src/DPoPTokenProvider.ts b/src/DPoPTokenProvider.ts index b652919..b953ea9 100644 --- a/src/DPoPTokenProvider.ts +++ b/src/DPoPTokenProvider.ts @@ -96,6 +96,7 @@ export class DPoPTokenProvider implements TokenProvider { } } + this.#codeProvider.cleanup() const tokenResponse = await oauth.authorizationCodeGrantRequest(authorizationServer, clientRegistration, this.getClientAuth(authorizationServer.issuer, clientRegistration), authorizationCodeParams, this.#callbackUri, authorizationServer.code_challenge_methods_supported !== undefined ? codeVerifier : oauth.nopkce, {DPoP: dpop, signal: request.signal}) const tokenResult = await oauth.processAuthorizationCodeResponse(authorizationServer, clientRegistration, tokenResponse, {expectedNonce: this.nonceVerificationOverride(authorizationServer.issuer, nonce)})