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
4 changes: 4 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
; typedoc@0.28 peer-caps typescript at 6.x while this project is on ^7.
; Without this, `npm install` of this repo as a git dependency fails ERESOLVE
; before `prepare` ever gets to run.
legacy-peer-deps=true
5 changes: 4 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
}
</script>
<script type="module">
import { DPoPTokenProvider, ReactiveFetchManager } from "./dist/mod.js"
import { DPoPTokenProvider, InsecureConfiguration, ReactiveFetchManager } from "./dist/mod.js"
import "./dist/registerElements.js"

/* Demo talks to a local Community Solid Server over plain HTTP */
InsecureConfiguration.allow()

/* Reactive fetch infrastructure */
const ui = document.querySelector("authorization-code-flow")
const issuerUi = document.querySelector("idp-picker")
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"url": "git+https://github.com/solid-contrib/reactive-authentication.git"
},
"scripts": {
"build": "tsc"
"build": "tsc",
"prepare": "tsc",
"test": "vitest run"
},
"license": "MIT",
"dependencies": {
Expand All @@ -40,7 +42,8 @@
"@types/n3": "^1",
"typedoc": "^0.28.18",
"typedoc-plugin-mdn-links": "^5.1.1",
"typescript": "^7"
"typescript": "^7",
"vitest": "^4.1.8"
},
"engines": {
"node": ">=24.0.0"
Expand Down
33 changes: 32 additions & 1 deletion src/AuthorizationCodeFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,18 @@ export class AuthorizationCodeFlow extends HTMLElement {
this.ownerDocument.defaultView?.removeEventListener("message", onMessage)
signal.removeEventListener("abort", onAbort)
this.#switchModal.close()
this.#authorizationWindow?.close()

// When the server answered a silent (`prompt=none`) attempt with a "user
// interaction needed" error, the caller is about to retry interactively.
// Keep the popup open: the retry's `open()` then NAVIGATES this named
// window, which browsers allow without user activation. Closing it here
// would make the retry create a new window — popup blockers stop that
// (the original click's activation is already consumed), stranding the
// user in the "open new window" dialog.
if (!needsInteraction(message.data)) {
this.#authorizationWindow?.close()
}

respondWithCode(message.data)
}

Expand Down Expand Up @@ -252,3 +263,23 @@ export class AuthorizationCodeFlow extends HTMLElement {
this.#cancelCodeRequest?.call(undefined, new CodeRequestCancelledError(this.#authorizationUri!))
}
}

/**
* Whether the authorization response is one of the OIDC "the user must interact"
* errors — exactly the errors token providers retry interactively right away.
* Anything else (a code, or a terminal error such as `access_denied`) ends the flow.
*/
function needsInteraction(authorizationResponse: unknown): boolean {
if (typeof authorizationResponse !== "string") {
return false
}

let error
try {
error = new URL(authorizationResponse).searchParams.get("error")
} catch {
return false
}

return error === "login_required" || error === "interaction_required" || error === "consent_required"
}
10 changes: 4 additions & 6 deletions src/BearerTokenProvider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as oauth from "oauth4webapi"
import { GetCodeCallback } from "./GetCodeCallback.js"
import { TokenProvider } from "./TokenProvider.js"

// TODO: Configure properly for insecure localhost only
const oauthAllowInsecureRequests = true
import { InsecureConfiguration } from "./InsecureConfiguration.js"

export class BearerTokenProvider implements TokenProvider {
readonly #getCode: GetCodeCallback
Expand Down Expand Up @@ -43,12 +41,12 @@ export class BearerTokenProvider implements TokenProvider {
async upgrade(request: Request): Promise<Request> {
const issuer = await this.#getIssuer(request)

const discoveryResponse = await oauth.discoveryRequest(issuer, {[oauth.allowInsecureRequests]: oauthAllowInsecureRequests})
const discoveryResponse = await oauth.discoveryRequest(issuer, InsecureConfiguration.requestOptions)
const authorizationServer = await oauth.processDiscoveryResponse(issuer, discoveryResponse)

const callbackUri = await this.#getCallback(request)

const registrationResponse = await oauth.dynamicClientRegistrationRequest(authorizationServer, {redirect_uris: [callbackUri]}, {[oauth.allowInsecureRequests]: oauthAllowInsecureRequests})
const registrationResponse = await oauth.dynamicClientRegistrationRequest(authorizationServer, {redirect_uris: [callbackUri]}, InsecureConfiguration.requestOptions)
const clientRegistration = await oauth.processDynamicClientRegistrationResponse(registrationResponse)
const [registeredRedirectUri] = clientRegistration.redirect_uris as string[]
const [registeredResponseType] = clientRegistration.response_types as string[]
Expand Down Expand Up @@ -84,7 +82,7 @@ export class BearerTokenProvider implements TokenProvider {
clientAuth = authenticationMethod(clientSecret)
}

const tokenResponse = await oauth.authorizationCodeGrantRequest(authorizationServer, clientRegistration, clientAuth, authorizationCodeParams, callbackUri, codeVerifier, {[oauth.allowInsecureRequests]: oauthAllowInsecureRequests})
const tokenResponse = await oauth.authorizationCodeGrantRequest(authorizationServer, clientRegistration, clientAuth, authorizationCodeParams, callbackUri, codeVerifier, InsecureConfiguration.requestOptions)

// jwt nonce missing in igrant
// const tokenResult = await oauth.processAuthorizationCodeResponse(authorizationServer, clientRegistration, tokenResponse, {expectedNonce: nonce})
Expand Down
7 changes: 5 additions & 2 deletions src/ClientCredentialsTokenProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as oauth from "oauth4webapi"
import { AuthorizationServer } from "oauth4webapi"
import * as DPoP from "dpop"
import type { TokenProvider } from "./TokenProvider.js"
import { InsecureConfiguration } from "./InsecureConfiguration.js"

export class ClientCredentialsTokenProvider implements TokenProvider {
constructor(private clientId: string, private clientSecret: string) {
Expand Down Expand Up @@ -35,7 +36,8 @@ export class ClientCredentialsTokenProvider implements TokenProvider {
const issuer = await this.#getIssuer(request)

const discoveryResponse = await oauth.discoveryRequest(issuer, {
signal: request.signal
signal: request.signal,
...InsecureConfiguration.requestOptions
})
const authorizationServer = await oauth.processDiscoveryResponse(issuer, discoveryResponse)

Expand All @@ -46,7 +48,8 @@ export class ClientCredentialsTokenProvider implements TokenProvider {

const tokenResponse = await oauth.clientCredentialsGrantRequest(authorizationServer, clientRegistration, this.getClientAuth(authorizationServer, clientRegistration), {scope: "webid"}, {
DPoP: dpop,
signal: request.signal
signal: request.signal,
...InsecureConfiguration.requestOptions
})

const tokenResult = await oauth.processClientCredentialsResponse(authorizationServer, clientRegistration, tokenResponse)
Expand Down
Loading