Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions src/AuthorizationCodeFlow.ts
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -119,7 +120,7 @@ const html = `
* </style>
* ```
*/
export class AuthorizationCodeFlow extends HTMLElement {
export class AuthorizationCodeFlow extends HTMLElement implements CodeProvider {
readonly #mutex = new Mutex
#newModal!: HTMLDialogElement
#switchModal!: HTMLDialogElement
Expand Down Expand Up @@ -194,7 +195,6 @@ export class AuthorizationCodeFlow extends HTMLElement {
this.ownerDocument.defaultView?.removeEventListener("message", onMessage)
signal.removeEventListener("abort", onAbort)
this.#switchModal.close()
this.#authorizationWindow?.close()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First substantial change is not closing the authorization window in the provider.

respondWithCode(message.data)
}

Expand All @@ -218,6 +218,10 @@ export class AuthorizationCodeFlow extends HTMLElement {
return await responseFromPopup
}

cleanup(): void {
this.#authorizationWindow?.close()
}
Comment on lines +221 to +223

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making the AuthorizationCodeFlow class disposable and invoking this in the [Symbol.dispose] function.

This also enables the new using sugar to be used.

This also applies to any other instances of cleanup methods.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to commit this change yesterday:

image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#38


#onSubmit(e: SubmitEvent) {
e.preventDefault()

Expand Down
10 changes: 5 additions & 5 deletions src/BearerTokenProvider.ts
Original file line number Diff line number Diff line change
@@ -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<URL> {
Expand Down Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/CodeProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface CodeProvider {
getCode(authorizationUri: URL, signal: AbortSignal): Promise<string>

cleanup(): void
}
13 changes: 7 additions & 6 deletions src/DPoPTokenProvider.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
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"

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<string, CacheEntry> // 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
Expand Down Expand Up @@ -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 {
Expand All @@ -89,13 +89,14 @@ 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
}
}

this.#codeProvider.cleanup()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is the counterpart: Closing the authorization window from the caller, crucially after a potential fallback in the try above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following https://github.com/solid-contrib/reactive-authentication/pull/37/changes#r3853649238 - this would be a good place to make use of using

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)})
Expand Down
1 change: 0 additions & 1 deletion src/GetCodeCallback.ts

This file was deleted.

10 changes: 5 additions & 5 deletions src/ReactiveFetchWorkerManager.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -15,6 +15,6 @@ export class ReactiveFetchWorkerManager {
}

async #onMessage(e: MessageEvent<string>) {
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?
}
}
2 changes: 1 addition & 1 deletion src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/reactive-fetch-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ async function onFetch(e: FetchEvent): Promise<void> {
}

function upgrade(request: Request, client: Client): Promise<Response> {
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)
}
Expand Down