diff --git a/notNeededPackages.json b/notNeededPackages.json index 2b9ee4cddf20bc..bf65bf4a112bfe 100644 --- a/notNeededPackages.json +++ b/notNeededPackages.json @@ -8743,6 +8743,10 @@ "libraryName": "zookeeper", "asOfVersion": "4.6.0" }, + "zoomist": { + "libraryName": "zoomist", + "asOfVersion": "2.0.1" + }, "zrender": { "libraryName": "zrender", "asOfVersion": "5.0.0" diff --git a/types/akamai-edgeworkers/index.d.ts b/types/akamai-edgeworkers/index.d.ts index 15113bce95bc3d..b74f2d34dbf4ef 100644 --- a/types/akamai-edgeworkers/index.d.ts +++ b/types/akamai-edgeworkers/index.d.ts @@ -976,12 +976,15 @@ declare module "http-request" { * - `headers` Request headers to specify. * - `body` The request payload. * - `timeout` The request timeout, in milliseconds. + * - `preserveEncoding` Whether to preserve the original encoding of the response body. Default is false and + * response bodies are decoded. */ function httpRequest(url: string, options?: { method?: string | undefined; headers?: { [others: string]: string | string[] } | undefined; body?: RequestBody | undefined; timeout?: number | undefined; + preserveEncoding?: boolean | undefined; }): Promise; /** @@ -1003,12 +1006,14 @@ declare module "http-request" { /** * Returns a Promise that resolves to a string containing the * response body. Note that the body is buffered in memory. + * Will automatically decode recognized content-encodings even if `preserveEncoding:true` was specified */ text(): Promise; /** * Parses the body of the response as JSON. The response is buffered * and `JSON.parse()` is run on the text. + * Will automatically decode recognized content-encodings even if `preserveEncoding:true` was specified */ json(): Promise; } @@ -1020,7 +1025,7 @@ declare module "http-request" { * [WHATWG Streams Standard]: https://streams.spec.whatwg.org */ declare module "streams" { - interface ReadableStream extends EW.ReadableStreamEW { + interface ReadableStream extends EW.ReadableStreamEW { } const ReadableStream: { @@ -1032,7 +1037,7 @@ declare module "streams" { new(underlyingSource?: EW.UnderlyingSource, strategy?: EW.QueuingStrategy): ReadableStream; }; - interface WritableStream extends EW.WritableStreamEW { + interface WritableStream extends EW.WritableStreamEW { } const WritableStream: { @@ -1043,6 +1048,13 @@ declare module "streams" { interface ReadableStreamDefaultController extends EW.ReadableStreamDefaultControllerEW { } + type BufferSource = ArrayBufferView | ArrayBuffer; + + interface GenericTransformStream { + readonly readable: ReadableStream; + readonly writable: WritableStream; + } + interface TransformStream { readonly readable: ReadableStream; readonly writable: WritableStream; @@ -1104,9 +1116,30 @@ declare module "streams" { new(options: { highWaterMark: number }): ByteLengthQueuingStrategy; }; + type CompressionFormat = "brotli" | "deflate" | "deflate-raw" | "gzip"; + + interface CompressionStream extends GenericTransformStream { + } + + const CompressionStream: { + prototype: CompressionStream; + new(format: CompressionFormat): CompressionStream; + }; + + interface DecompressionStream extends GenericTransformStream { + } + + const DecompressionStream: { + prototype: DecompressionStream; + new(format: CompressionFormat): DecompressionStream; + }; + export { ByteLengthQueuingStrategy, + CompressionFormat, + CompressionStream, CountQueuingStrategy, + DecompressionStream, ReadableStream, ReadableStreamDefaultController, TransformStream, diff --git a/types/akamai-edgeworkers/test/akamai-edgeworkers-global.test.ts b/types/akamai-edgeworkers/test/akamai-edgeworkers-global.test.ts index 80f8d0a7b6fc42..d1f7e588391e91 100644 --- a/types/akamai-edgeworkers/test/akamai-edgeworkers-global.test.ts +++ b/types/akamai-edgeworkers/test/akamai-edgeworkers-global.test.ts @@ -13,7 +13,7 @@ export function onClientRequest(request: EW.IngressClientRequest) { testHeaders(request.getHeaders()); // Exercise EW.ClientRequest.getVariable() - request.respondWith(505, [], "Missing get-variable-present"); + request.getVariable("variableName"); request.respondWith(505, { no: "bad" }, "Expected var to be missing"); diff --git a/types/akamai-edgeworkers/test/http-request-tests.ts b/types/akamai-edgeworkers/test/http-request-tests.ts index 60c2ddfcfc4f90..51e6ac44985c51 100644 --- a/types/akamai-edgeworkers/test/http-request-tests.ts +++ b/types/akamai-edgeworkers/test/http-request-tests.ts @@ -9,6 +9,7 @@ httpRequest("url", {}); httpRequest("url", { headers: { "Accept-Encoding": "zz" } }); httpRequest("url", { method: "POST", body: "post payload" }); httpRequest("url", { timeout: 9 }); +httpRequest("url", { preserveEncoding: true }); httpRequest("url", { method: "POST", body: new ReadableStream({ diff --git a/types/akamai-edgeworkers/test/stream.ts b/types/akamai-edgeworkers/test/stream.ts index f6276f99c370b0..a605ca283ee7e2 100644 --- a/types/akamai-edgeworkers/test/stream.ts +++ b/types/akamai-edgeworkers/test/stream.ts @@ -1,9 +1,13 @@ import { createResponse } from "create-response"; +import { HtmlRewritingStream } from "html-rewriter"; import { httpRequest } from "http-request"; +import { CompressionStream, DecompressionStream } from "streams"; import { TextDecoderStream } from "text-encode-transform"; +const TEMPLATE_URL = "http://techjam.edgekey-staging.net/templates/index1MB.html"; + export function responseProviderBufferedResponse(request: EW.ResponseProviderRequest) { - return httpRequest("http://techjam.edgekey-staging.net/templates/index1MB.html").then(response => { + return httpRequest(TEMPLATE_URL).then(response => { const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); const body = ""; return reader.read().then(function accumulate({ done, value }) { @@ -11,3 +15,33 @@ export function responseProviderBufferedResponse(request: EW.ResponseProviderReq }); }); } + +export function responseProviderCompressedResponse(request: EW.ResponseProviderRequest) { + return httpRequest(TEMPLATE_URL).then(response => { + let compressed = response.body.pipeThrough(new CompressionStream("gzip")); + return createResponse(response.status, { "content-encoding": "gzip" }, compressed); + }); +} + +export function responseProviderRewriteCompressed(request: EW.ResponseProviderRequest) { + const rewriter = new HtmlRewritingStream(); + rewriter.onElement("head", el => { + el.append(""); + }); + + return httpRequest(TEMPLATE_URL, { preserveEncoding: true }).then(response => { + let stream = response.body; + const contentEncoding = response.getHeader("content-encoding")?.[0]; + const decompress = contentEncoding == "gzip"; + + const inject = request.getVariable("PMUSER_INJECT_BEACON"); + if (inject === "true") { + stream = decompress ? stream.pipeThrough(new DecompressionStream("gzip")) : stream; + stream = stream.pipeThrough(rewriter); + stream = stream.pipeThrough(new CompressionStream("gzip")); + return createResponse(response.status, { "content-encoding": "gzip" }, stream); + } else { + return createResponse(response.status, {}, stream); + } + }); +} diff --git a/types/googlepay/googlepay-tests.ts b/types/googlepay/googlepay-tests.ts index 481f1f9295fc0c..9ac1aa9c0a09d5 100644 --- a/types/googlepay/googlepay-tests.ts +++ b/types/googlepay/googlepay-tests.ts @@ -218,6 +218,103 @@ function getGooglePaymentDataConfiguration(): google.payments.api.PaymentDataReq }; } +function getGoogleRecurringPaymentDataConfiguration(): google.payments.api.PaymentDataRequest { + const recurringTransactionInfo: google.payments.api.RecurringTransactionInfo = { + currencyCode: "USD", + countryCode: "US", + immediateTotalPrice: "0.00", + managementUrl: "https://example.com/account", + immediateDisplayItems: [{ + label: "Due today", + type: "LINE_ITEM", + price: "0.00", + status: "FINAL", + }], + recurrenceItems: [{ + label: "Monthly subscription", + priceStatus: "FINAL", + recurrencePeriod: "MONTH", + recurrencePeriodCount: 1, + price: "9.99", + }], + introductoryPeriodInfo: { + introductoryPeriodEndDateTime: "2026-09-30T23:59:59Z", + label: "Free trial", + totalPrice: "0.00", + }, + }; + + return { + apiVersion: 2, + apiVersionMinor: 0, + allowedPaymentMethods, + merchantInfo: { + merchantId: "01234567890123456789", + merchantName: "Example Merchant", + }, + recurringTransactionInfo, + }; +} + +function getGoogleDeferredPaymentDataConfiguration(): google.payments.api.PaymentDataRequest { + const deferredTransactionInfo: google.payments.api.DeferredTransactionInfo = { + currencyCode: "USD", + countryCode: "US", + immediateTotalPrice: "0.00", + managementUrl: "https://example.com/bookings", + immediateDisplayItems: [{ + label: "Due today", + type: "LINE_ITEM", + price: "0.00", + status: "FINAL", + }], + billingDateTime: "2026-09-30T23:59:59Z", + priceStatus: "FINAL", + label: "Pay later", + price: "49.99", + }; + + return { + apiVersion: 2, + apiVersionMinor: 0, + allowedPaymentMethods, + merchantInfo: { + merchantId: "01234567890123456789", + merchantName: "Example Merchant", + }, + deferredTransactionInfo, + }; +} + +function getGoogleAutomaticReloadPaymentDataConfiguration(): google.payments.api.PaymentDataRequest { + const automaticReloadTransactionInfo: google.payments.api.AutomaticReloadTransactionInfo = { + currencyCode: "USD", + countryCode: "US", + immediateTotalPrice: "10.00", + managementUrl: "https://example.com/balance", + immediateDisplayItems: [{ + label: "Reload today", + type: "LINE_ITEM", + price: "10.00", + status: "FINAL", + }], + minimumBalanceAmount: "5.00", + reloadAmount: "25.00", + label: "Transit card reload", + }; + + return { + apiVersion: 2, + apiVersionMinor: 0, + allowedPaymentMethods, + merchantInfo: { + merchantId: "01234567890123456789", + merchantName: "Example Merchant", + }, + automaticReloadTransactionInfo, + }; +} + function prefetchGooglePaymentData() { const client = getGooglePaymentsClient(); client.prefetchPaymentData(getGooglePaymentDataConfiguration()); diff --git a/types/googlepay/index.d.ts b/types/googlepay/index.d.ts index aa344b1941b1e7..f6957e4ac5e1b6 100644 --- a/types/googlepay/index.d.ts +++ b/types/googlepay/index.d.ts @@ -78,9 +78,53 @@ declare namespace google.payments.api { /** * Detailed information about the transaction. * - * This field is required. + * Exactly one of [[PaymentDataRequest.transactionInfo]], + * [[PaymentDataRequest.recurringTransactionInfo]], + * [[PaymentDataRequest.deferredTransactionInfo]], or + * [[PaymentDataRequest.automaticReloadTransactionInfo]] must be + * present. + */ + transactionInfo?: TransactionInfo | undefined; + + /** + * Details about enrollment for a recurring transaction. + * + * Exactly one of [[PaymentDataRequest.transactionInfo]], + * [[PaymentDataRequest.recurringTransactionInfo]], + * [[PaymentDataRequest.deferredTransactionInfo]], or + * [[PaymentDataRequest.automaticReloadTransactionInfo]] must be + * present. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#RecurringTransactionInfo RecurringTransactionInfo} + */ + recurringTransactionInfo?: RecurringTransactionInfo | undefined; + + /** + * Details about enrollment for a deferred transaction. + * + * Exactly one of [[PaymentDataRequest.transactionInfo]], + * [[PaymentDataRequest.recurringTransactionInfo]], + * [[PaymentDataRequest.deferredTransactionInfo]], or + * [[PaymentDataRequest.automaticReloadTransactionInfo]] must be + * present. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#DeferredTransactionInfo DeferredTransactionInfo} */ - transactionInfo: TransactionInfo; + deferredTransactionInfo?: DeferredTransactionInfo | undefined; + + /** + * Details about enrollment for automatic reload of a stored balance + * account. + * + * Exactly one of [[PaymentDataRequest.transactionInfo]], + * [[PaymentDataRequest.recurringTransactionInfo]], + * [[PaymentDataRequest.deferredTransactionInfo]], or + * [[PaymentDataRequest.automaticReloadTransactionInfo]] must be + * present. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#AutomaticReloadTransactionInfo AutomaticReloadTransactionInfo} + */ + automaticReloadTransactionInfo?: AutomaticReloadTransactionInfo | undefined; /** * Offers available for redemption that can be used with the current @@ -1090,6 +1134,514 @@ declare namespace google.payments.api { displayItems?: DisplayItem[] | undefined; } + /** + * Details about enrollment for a recurring transaction. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#RecurringTransactionInfo RecurringTransactionInfo} + */ + interface RecurringTransactionInfo { + /** + * ISO 4217 alphabetic currency code. + * + * This is a required field. + */ + currencyCode: string; + + /** + * ISO 3166-1 alpha-2 country code where the transaction is processed. + * + * Merchants must specify the acquirer bank country code. + * + * This is a required field. + */ + countryCode: string; + + /** + * Correlation ID to refer to this transaction. + * + * A unique ID that identifies a facilitation attempt. Merchants can use + * an existing ID or generate a specific one for Google Pay facilitation + * attempts. + * + * This field is optional, but highly encouraged for troubleshooting. + */ + transactionId?: string | undefined; + + /** + * A merchant or Payment Service Provider (PSP) URL to which token + * lifecycle updates are sent. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + tokenUpdateUrl?: string | undefined; + + /** + * A merchant URL where the user can manage their subscription in the + * future. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + managementUrl?: string | undefined; + + /** + * A localized billing agreement of cancellation terms and information + * on how to manage the recurring transaction. + * + * Limited to 300 characters. + * + * This field is optional. + */ + billingAgreement?: string | undefined; + + /** + * Total price of the transaction due today. + * + * It must be non-negative numeric with optional precision of two + * decimal places. It includes any one-time items and any recurring + * transaction that is charged immediately. It can be `"0.00"` for + * transactions where the user is not charged immediately, such as a + * free trial. + * + * This is a required field. + */ + immediateTotalPrice: string; + + /** + * A breakdown of one-time items that are immediately charged, such as + * service setup fees. + * + * Must not include regular recurring charges or an introductory + * period. + * + * This field is optional. + */ + immediateDisplayItems?: DisplayItem[] | undefined; + + /** + * An introductory period for the recurring transaction. + * + * A time bound period with no recurrence. Pricing can be free or a + * single flat fee. + * + * This field is optional. + */ + introductoryPeriodInfo?: IntroductoryPeriodInfo | undefined; + + /** + * Recurrence periods that detail billing frequency and pricing. + * + * This is a required field. + */ + recurrenceItems: RecurrencePeriodItem[]; + } + + /** + * An introductory period for a recurring transaction. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#IntroductoryPeriodInfo IntroductoryPeriodInfo} + */ + interface IntroductoryPeriodInfo { + /** + * Start time of the introductory service period in RFC 3339 format. + * + * Optional for introductory periods that start immediately. This + * property represents the service period for the introductory period + * and not necessarily the billing time for the recurring transaction. + * + * If specified, the time must be greater than the current time and + * before + * [[IntroductoryPeriodInfo.introductoryPeriodEndDateTime|`introductoryPeriodEndDateTime`]]. + * + * This field is optional. + */ + introductoryPeriodStartDateTime?: string | undefined; + + /** + * End time of the introductory service period in RFC 3339 format. + * + * This property represents the service period for the introductory + * period and not necessarily the billing time for the recurring + * transaction. + * + * This is a required field. + */ + introductoryPeriodEndDateTime: string; + + /** + * Short label for the introductory period. + * + * For example, `"7 Day Free Trial"`. Limited to 100 characters. + * + * This is a required field. + */ + label: string; + + /** + * Price of the introductory period. + * + * Must be non-negative numeric with optional precision of two decimal + * places. It can be `"0.00"` for a free period. + * + * This is a required field. + */ + totalPrice: string; + + /** + * Line items that compose the introductory period. + * + * This field is optional. + */ + displayItems?: DisplayItem[] | undefined; + } + + /** + * A recurrence period for a recurring transaction. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#RecurrencePeriodItem RecurrencePeriodItem} + */ + interface RecurrencePeriodItem { + /** + * First billing time for the recurring transaction period in RFC 3339 + * format. + * + * Optional for billing that starts immediately. This property + * represents the billing time and not necessarily the service period. + * + * If an [[RecurringTransactionInfo.introductoryPeriodInfo|`introductoryPeriodInfo`]] + * is specified in the parent + * [[RecurringTransactionInfo|`RecurringTransactionInfo`]], this + * `billingInitialDateTime` must be specified and equal to or after the + * [[IntroductoryPeriodInfo.introductoryPeriodEndDateTime|`introductoryPeriodEndDateTime`]] + * of the [[IntroductoryPeriodInfo|`IntroductoryPeriodInfo`]]. + * + * This field is optional. + */ + billingInitialDateTime?: string | undefined; + + /** + * Final billing time for the recurring transaction period in RFC 3339 + * format. + * + * Must be `null` for a perpetual recurring transaction. This property + * represents the billing time and not necessarily the service period. + * + * This field is optional. + */ + billingFinalDateTime?: string | null | undefined; + + /** + * Short label for the recurring transaction period. + * + * For example, `"Premium Streaming Monthly Plan"`. Limited to 100 + * characters. + * + * This is a required field. + */ + label: string; + + /** + * Status of the price. + * + * This is a required field. + */ + priceStatus: TotalPriceStatus; + + /** + * Period of the recurrence. + * + * This is a required field. + */ + recurrencePeriod: RecurrencePeriod; + + /** + * Number of periods between billing cycles. + * + * For example, a monthly recurring transaction would be `1` with a + * [[RecurrencePeriodItem.recurrencePeriod|`recurrencePeriod`]] of + * [[RecurrencePeriod|`MONTH`]]. + * + * Must be a positive integer. + * + * This is a required field. + */ + recurrencePeriodCount: number; + + /** + * Price of the recurring transaction period. + * + * Must be present if [[RecurrencePeriodItem.priceStatus]] is + * [[TotalPriceStatus|`FINAL`]] or [[TotalPriceStatus|`ESTIMATED`]]. + * Must be null if [[RecurrencePeriodItem.priceStatus]] is + * [[TotalPriceStatus|`NOT_CURRENTLY_KNOWN`]]. + */ + price?: string | null | undefined; + + /** + * Line items that compose the recurring transaction. + * + * Example situations include a recurring transaction that can be + * broken down into the base fee and taxes. + * + * This field is optional. + */ + displayItems?: DisplayItem[] | undefined; + } + + /** + * Details about enrollment for a deferred transaction. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#DeferredTransactionInfo DeferredTransactionInfo} + */ + interface DeferredTransactionInfo { + /** + * ISO 4217 alphabetic currency code. + * + * This is a required field. + */ + currencyCode: string; + + /** + * ISO 3166-1 alpha-2 country code where the transaction is processed. + * + * Merchants must specify the acquirer bank country code. + * + * This is a required field. + */ + countryCode: string; + + /** + * Correlation ID to refer to this transaction. + * + * A unique ID that identifies a facilitation attempt. Merchants can use + * an existing ID or generate a specific one for Google Pay facilitation + * attempts. + * + * This field is optional. + */ + transactionId?: string | undefined; + + /** + * A merchant or Payment Service Provider (PSP) URL to which token + * lifecycle updates are sent. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + tokenUpdateUrl?: string | undefined; + + /** + * A merchant URL where the user can manage their transaction in the + * future. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + managementUrl?: string | undefined; + + /** + * A localized billing agreement of cancellation terms and information + * on how to manage the deferred transaction. + * + * Limited to 300 characters. + * + * This field is optional. + */ + billingAgreement?: string | undefined; + + /** + * Total price of the transaction due today. + * + * Includes any one-time items, such as a reservation deposit. It can + * be `"0.00"` for transactions where the user is not charged + * immediately. Must be non-negative numeric with optional precision of + * two decimal places. + * + * This is a required field. + */ + immediateTotalPrice: string; + + /** + * A breakdown of one-time items that are immediately charged. + * + * This field is optional. + */ + immediateDisplayItems?: DisplayItem[] | undefined; + + /** + * Time at which the transaction is processed in RFC 3339 format. + * + * Must be greater than the current time. + * + * This is a required field. + */ + billingDateTime: string; + + /** + * Status of the price. + * + * This is a required field. + */ + priceStatus: TotalPriceStatus; + + /** + * Short label for the transaction. + * + * For example, `"Hotel Room Reservation"`. Limited to 100 + * characters. + * + * This is a required field. + */ + label: string; + + /** + * Price of the transaction charged in the future, if known. + * + * Must be present if [[DeferredTransactionInfo.priceStatus]] is + * [[TotalPriceStatus|`FINAL`]] or [[TotalPriceStatus|`ESTIMATED`]]. + * Must be null if [[DeferredTransactionInfo.priceStatus]] is + * [[TotalPriceStatus|`NOT_CURRENTLY_KNOWN`]]. + */ + price?: string | null | undefined; + + /** + * Line items that compose the deferred transaction. + * + * Example situations include a transaction that can be broken down into + * the base fee and taxes. + * + * This field is optional. + */ + displayItems?: DisplayItem[] | undefined; + } + + /** + * Details about enrollment for automatic reload of a stored balance + * account. + * + * @see {@link https://developers.google.com/pay/api/web/reference/request-objects#AutomaticReloadTransactionInfo AutomaticReloadTransactionInfo} + */ + interface AutomaticReloadTransactionInfo { + /** + * ISO 4217 alphabetic currency code. + * + * This is a required field. + */ + currencyCode: string; + + /** + * ISO 3166-1 alpha-2 country code where the transaction is processed. + * + * Merchants must specify the acquirer bank country code. + * + * This is a required field. + */ + countryCode: string; + + /** + * Correlation ID to refer to this transaction. + * + * A unique ID that identifies a facilitation attempt. Merchants can use + * an existing ID or generate a specific one for Google Pay facilitation + * attempts. + * + * This field is optional. + */ + transactionId?: string | undefined; + + /** + * A merchant or Payment Service Provider (PSP) URL to which token + * lifecycle updates are sent. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + tokenUpdateUrl?: string | undefined; + + /** + * A merchant URL where the user can manage their transaction in the + * future. + * + * It must be an HTTPS endpoint with no trailing slash, no parameters + * and no URL fragments. + * + * This field is optional. + */ + managementUrl?: string | undefined; + + /** + * A localized billing agreement of cancellation terms and information + * on how to manage the automatic reload transaction. + * + * Limited to 300 characters. + * + * This field is optional. + */ + billingAgreement?: string | undefined; + + /** + * Total price of the transaction due today. + * + * Includes any one-time items. For example, the customer is performing + * an initial reload along with setting up the automatic reload. It can + * be `"0.00"` for transactions where the user is not charged + * immediately. Must be non-negative numeric with optional precision of + * two decimal places. + * + * This is a required field. + */ + immediateTotalPrice: string; + + /** + * One-time items that are immediately charged, such as service setup + * fees. + * + * This field is optional. + */ + immediateDisplayItems?: DisplayItem[] | undefined; + + /** + * Minimum balance limit at which the automatic reload is triggered. + * + * Must be non-negative numeric with optional precision of two decimal + * places. It can be `"0.00"`. + * + * This is a required field. + */ + minimumBalanceAmount: string; + + /** + * Amount the user's payment credential is charged when the automatic + * reload is triggered. + * + * Must be non-zero and non-negative numeric with optional precision of + * two decimal places. + * + * This is a required field. + */ + reloadAmount: string; + + /** + * Short label for the transaction. + * + * For example, `"Gift Card Reload"`. Limited to 100 characters. + * + * This is a required field. + */ + label: string; + } + /** * Data for a payment method. */ @@ -1674,6 +2226,11 @@ declare namespace google.payments.api { */ type TotalPriceStatus = "NOT_CURRENTLY_KNOWN" | "ESTIMATED" | "FINAL"; + /** + * Period of a recurring transaction. + */ + type RecurrencePeriod = "YEAR" | "MONTH" | "WEEK" | "DAY"; + /** * The options for checkout. * diff --git a/types/gorilla-engine/components/DragContainer.d.ts b/types/gorilla-engine/components/DragContainer.d.ts index 537bdf634dc0a2..309d1b2c0bceef 100755 --- a/types/gorilla-engine/components/DragContainer.d.ts +++ b/types/gorilla-engine/components/DragContainer.d.ts @@ -1,19 +1,30 @@ declare namespace GorillaEngine.UI { + /** Properties for a source in an internal or operating-system drag operation. */ interface DragContainerProps extends Common, Bounds, Clickable { + /** Group used to match this source with compatible drag targets. */ dragGroup: string; + /** Data made available to an internal drag target. */ dragContent: string; + /** Name of the action invoked when an external drag starts. */ externalDragStartAction: string; + /** Name of the action invoked when an external drag ends. */ externalDragEndAction: string; + /** Whether an external destination may move, rather than only copy, dragged files. */ destinationCanMoveDraggedFiles: boolean; + /** File paths supplied to an external drag operation. */ externallyDraggedFiles: any; + /** Name of the action invoked while a drag operation is in progress. */ onDragging: string; + /** Name of the action invoked when an external drag starts. */ onExternalDragStart: string; + /** Name of the action invoked when an external drag ends. */ onExternalDragEnd: string; } // tslint:disable-next-line:no-empty-interface interface DragContainer extends DragContainerProps {} + /** A component that provides data or files for drag-and-drop operations. */ class DragContainer extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/DragTarget.d.ts b/types/gorilla-engine/components/DragTarget.d.ts index dced27de4c8f86..c9840ea988af80 100755 --- a/types/gorilla-engine/components/DragTarget.d.ts +++ b/types/gorilla-engine/components/DragTarget.d.ts @@ -1,9 +1,13 @@ declare namespace GorillaEngine.UI { + /** Properties for a target that receives internal drag operations. */ interface DragTargetProps extends Common, Bounds, Clickable { + /** Group of drag sources accepted by this target. */ dragGroup: string; + /** Name of the action invoked when compatible content is dropped. */ onDrop: string; } + /** A component that receives content from a compatible drag container. */ class DragTarget extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/DropZone.d.ts b/types/gorilla-engine/components/DropZone.d.ts index 29fb640bcdb53e..277be66a94177a 100755 --- a/types/gorilla-engine/components/DropZone.d.ts +++ b/types/gorilla-engine/components/DropZone.d.ts @@ -1,13 +1,21 @@ declare namespace GorillaEngine.UI { + /** Properties for a target that receives files from the operating system. */ interface DropZoneProps extends Common, Bounds, Clickable { + /** + * File patterns accepted by the drop zone, such as `"*.wav"`. + * Include `"*"` to accept every file type. + */ acceptedFileTypes: string[] | string; + /** Name of the action invoked when files are dragged over the component. */ onDraggedFiles: string; + /** Name of the action invoked when a file is dropped. */ onDroppedFile: string; } // tslint:disable-next-line:no-empty-interface interface DropZone extends DropZoneProps {} + /** A component that accepts files dragged from the operating system. */ class DropZone extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/Knob.d.ts b/types/gorilla-engine/components/Knob.d.ts index 1e092b55dccde2..b937a484c08a45 100755 --- a/types/gorilla-engine/components/Knob.d.ts +++ b/types/gorilla-engine/components/Knob.d.ts @@ -1,7 +1,10 @@ declare namespace GorillaEngine.UI { + /** + * Properties for a rotary control that edits a numeric value. + */ interface KnobProps extends Common, Bounds, Clickable, Background, Skinnable, MIDILearn, Highlight { /** - * The text displayed for component + * The text displayed by the control. */ text: string; /** @@ -21,6 +24,9 @@ declare namespace GorillaEngine.UI { */ stepSize: number; + /** + * Whether the control runs from its maximum value to its minimum value. + */ inverted: boolean; /** * If `true`, the knob's value can be adjusted using the mouse scroll wheel. @@ -30,7 +36,9 @@ declare namespace GorillaEngine.UI { * The path to a single image used for the knob's appearance. */ image: string; - /** TODO:: this is rather a slider prop than a knob prop + /** + * Whether clicking a slider track moves its handle to the pointer position. + * This property only affects controls displayed as sliders. */ snapsToMousePosition: boolean; /** @@ -45,6 +53,9 @@ declare namespace GorillaEngine.UI { // tslint:disable-next-line:no-empty-interface interface Knob extends KnobProps {} + /** + * A rotary control for editing numeric values. + */ class Knob extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/Label.d.ts b/types/gorilla-engine/components/Label.d.ts index 21fcb6038b3f3a..304d56b47be5f2 100755 --- a/types/gorilla-engine/components/Label.d.ts +++ b/types/gorilla-engine/components/Label.d.ts @@ -1,4 +1,7 @@ declare namespace GorillaEngine.UI { + /** + * Properties for a read-only text label. + */ interface LabelProps extends Common, Bounds, Background, Font, Clickable, Margin, Keyable { /** * The initial text to display in the label. Doesn't update at runtime. @@ -8,13 +11,16 @@ declare namespace GorillaEngine.UI { * The text to display in the label. Updates at runtime. */ text: string | number; + /** Format string applied to the displayed value. */ format: string; + /** Whether the label can display multiple lines of text. */ multiLine: boolean; + /** Whether the text is scaled to fit the label's bounds. */ stretchText: boolean; } /** - * Use a Label to... + * A read-only control for displaying text or formatted values. */ class Label extends Component { constructor(options: Partial); diff --git a/types/gorilla-engine/components/LevelMeter.d.ts b/types/gorilla-engine/components/LevelMeter.d.ts index db42633d54df29..a151df1d951b32 100755 --- a/types/gorilla-engine/components/LevelMeter.d.ts +++ b/types/gorilla-engine/components/LevelMeter.d.ts @@ -1,4 +1,5 @@ declare namespace GorillaEngine.UI { + /** Properties for a mono or stereo audio level meter. */ interface LevelMeterProps extends Common, Bounds, Background, Skinnable { /** * If `true`, the level meter will display and hold the highest peak level reached @@ -62,13 +63,9 @@ declare namespace GorillaEngine.UI { * The thickness of the level meter's indicator. */ indicatorThickness: number; - /** TODO:: - * I have no idea that this does - */ + /** Start of the normalized meter range displayed by the control. */ visibleSectionStart: number; - /** TODO:: - * I have no idea that this does - */ + /** End of the normalized meter range displayed by the control. */ visibleSectionEnd: number; /** * The direction of the level meter. This can be "horizontal" or "vertical". @@ -87,6 +84,7 @@ declare namespace GorillaEngine.UI { // tslint:disable-next-line:no-empty-interface interface LevelMeter extends LevelMeterProps {} + /** A component that displays the current audio signal level. */ class LevelMeter extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/ListBox.d.ts b/types/gorilla-engine/components/ListBox.d.ts index d06d45ddcf7940..91724166a9bca8 100755 --- a/types/gorilla-engine/components/ListBox.d.ts +++ b/types/gorilla-engine/components/ListBox.d.ts @@ -9,7 +9,7 @@ declare namespace GorillaEngine.UI { */ horizontalMargin?: number; /** - * The bakcground color of the items in the list box. + * The background color of the items in the list box. */ cellColor?: string; /** diff --git a/types/gorilla-engine/components/LottieAnimation.d.ts b/types/gorilla-engine/components/LottieAnimation.d.ts index f8a96c102b6736..c6036c4e824722 100755 --- a/types/gorilla-engine/components/LottieAnimation.d.ts +++ b/types/gorilla-engine/components/LottieAnimation.d.ts @@ -1,4 +1,5 @@ declare namespace GorillaEngine.UI { + /** Properties and playback methods for a Lottie animation. */ interface LottieAnimationProps extends Common, Bounds { /** * The file path to the Lottie animation JSON file. @@ -40,13 +41,15 @@ declare namespace GorillaEngine.UI { */ setFrame(frame: number): void; /** - * TODO:: No idea what this does - * @param frame the target frame to set the animation to. + * Sets the target frame used by animated controls. + * @param frame Target frame between zero and the animation's last frame. */ setTargetFrame(frame: number): void; /** - * TODO:: No idea what this does - * @param frame the target frame to set the animation to. + * Maps a value from the supplied range to the animation's frame range and displays that frame. + * @param value Value to map. + * @param min Lower bound of the value range. + * @param max Upper bound of the value range. */ setFrameFromLinearTransform(value: number, min: number, max: number): void; setProperties( @@ -66,6 +69,7 @@ declare namespace GorillaEngine.UI { ): void; } + /** A component that renders and controls a Lottie animation. */ class LottieAnimation extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/Pad.d.ts b/types/gorilla-engine/components/Pad.d.ts index 2389707d7a4e1d..9204972bb5cbf7 100755 --- a/types/gorilla-engine/components/Pad.d.ts +++ b/types/gorilla-engine/components/Pad.d.ts @@ -5,7 +5,7 @@ declare namespace GorillaEngine.UI { */ midiNote: number; /** - * The veloctiy of the pad when pressed and not controlled by midi + * The velocity sent when the pad is pressed without MIDI input. */ velocity: number; /** diff --git a/types/gorilla-engine/components/ScrollView.d.ts b/types/gorilla-engine/components/ScrollView.d.ts index 0cc4abf7c9a80b..6bc6ff64a19e2b 100755 --- a/types/gorilla-engine/components/ScrollView.d.ts +++ b/types/gorilla-engine/components/ScrollView.d.ts @@ -1,4 +1,5 @@ declare namespace GorillaEngine.UI { + /** Properties and scrolling methods for a viewport containing child components. */ interface ScrollViewProps extends Common, Bounds, Scrollable { /** * The thickness of the scrollbar in pixels. @@ -14,8 +15,8 @@ declare namespace GorillaEngine.UI { * @default false */ hideHorizontalScrollbar: boolean; - /** TODO:: No idea if this is correct - * If true, the scroll view will ignore keypress events. + /** + * Whether the scroll view declines keyboard input instead of handling it for scrolling. * @default false */ ignoreKeypressEvent: boolean; @@ -32,6 +33,7 @@ declare namespace GorillaEngine.UI { setScrollPositionProportionately(xPos: number, yPos: number): void; } + /** A scrollable viewport for child components. */ class ScrollView extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/components/Slider.d.ts b/types/gorilla-engine/components/Slider.d.ts index 7ed4548897f77b..8ee3c49c744932 100755 --- a/types/gorilla-engine/components/Slider.d.ts +++ b/types/gorilla-engine/components/Slider.d.ts @@ -1,4 +1,7 @@ declare namespace GorillaEngine.UI { + /** + * Properties for a numeric control with a handle that moves along a track. + */ interface SliderProps extends KnobProps { /** * The direction of the slider. @@ -9,20 +12,21 @@ declare namespace GorillaEngine.UI { */ thumbImage: string; - /** - * TODO:: no idea what this does - */ + /** Bounds of the slider track relative to the control. */ sliderBounds: { + /** Horizontal offset of the track in pixels. */ x: number; + /** Vertical offset of the track in pixels. */ y: number; + /** Width of the track in pixels. */ width: number; + /** Height of the track in pixels. */ height: number; }; } /** - * Use a slider to - * - enable users to control numeric values by dragging up/down or left/right + * A control for editing numeric values by dragging horizontally or vertically. */ class Slider extends Component { constructor(options: Partial); diff --git a/types/gorilla-engine/components/TextBox.d.ts b/types/gorilla-engine/components/TextBox.d.ts index 1e4ae3432192af..cd9b8b0af411a2 100755 --- a/types/gorilla-engine/components/TextBox.d.ts +++ b/types/gorilla-engine/components/TextBox.d.ts @@ -1,4 +1,7 @@ declare namespace GorillaEngine.UI { + /** + * Properties for an editable or read-only text field. + */ interface TextBoxProps extends Font, Clickable, Bounds, Background, Margin, Keyable { /** * The initial text value of the text box. @@ -8,8 +11,8 @@ declare namespace GorillaEngine.UI { * The placeholder text of the text box. */ placeholder: string; - /** TODO:: check if it works - * An additional suffix displayed in the textbox without alterting the text value. + /** + * A suffix displayed after the text without changing the text value. */ unit: string; /** @@ -41,8 +44,8 @@ declare namespace GorillaEngine.UI { */ multiLine: boolean; - /** TODO:: what is even the point of this? - * If true, the text box will not allow user input and will be read-only. + /** + * Whether user input is disabled while the text remains selectable and readable. */ readOnly: boolean; /** @@ -58,10 +61,15 @@ declare namespace GorillaEngine.UI { * If acceptedDataType is numeric, this is the maximum value allowed in the text box. */ maxValue: number; + /** Zero-based index of the first selected character. */ highlightStart: number; + /** Zero-based index after the last selected character. */ highlightEnd: number; + /** Name of the action invoked when a key is pressed while the text box has focus. */ keyDownAction: string; + /** Name of the action invoked after the user changes the text. */ textChangedAction: string; + /** Whether text changes are committed when the text box loses keyboard focus. */ focusLostUpdate: boolean; } @@ -69,6 +77,9 @@ declare namespace GorillaEngine.UI { grabKeyboardFocus(): void; } + /** + * A text field for displaying and editing string or numeric input. + */ class TextBox extends Component { constructor(props: Partial); } diff --git a/types/gorilla-engine/components/Trigger.d.ts b/types/gorilla-engine/components/Trigger.d.ts index 1bc1e9823182ee..889de2089772ec 100755 --- a/types/gorilla-engine/components/Trigger.d.ts +++ b/types/gorilla-engine/components/Trigger.d.ts @@ -1,16 +1,29 @@ declare namespace GorillaEngine.UI { + /** + * Properties for a momentary button that invokes an action when clicked. + */ interface TriggerProps extends Common, Bounds, Font, Clickable, Background, KeyboardFocus, Keyable { + /** Text displayed on the button. */ text: string; + /** Whether the button omits its normal pressed-state behavior. */ isDumb: boolean; + /** Images used for the button's interaction states. */ images: { + /** Image displayed in the normal state. */ normal?: string; + /** Image displayed while the pointer is over the button. */ hover?: string; + /** Image displayed while the button is pressed. */ down?: string; }; + /** Lottie animation associated with the button. */ animation: LottieAnimation; } // tslint:disable-next-line:no-empty-interface interface Trigger extends TriggerProps {} + /** + * A momentary button that invokes an action when clicked. + */ class Trigger extends Component { constructor(options: Partial); } diff --git a/types/gorilla-engine/gorilla-engine-tests.ts b/types/gorilla-engine/gorilla-engine-tests.ts index 81b1a455923f1d..d996a4098ebe2f 100644 --- a/types/gorilla-engine/gorilla-engine-tests.ts +++ b/types/gorilla-engine/gorilla-engine-tests.ts @@ -17,3 +17,8 @@ const slider = new GorillaEngine.UI.Slider({ id: "slider", x: 0, y: 2 }); const mappingEditor = new GorillaEngine.UI.MappingEditor({ id: "myNewMappingEditor", x: 3, y: 2 }); const levelMeter = new GorillaEngine.UI.LevelMeter({ id: "myLevelMeter", x: 0, y: 2 }); + +const instrument = GorillaEngine.createEmptyInstrument(); + +GorillaEngine.loadBlob("/tmp/Test_part1.blob", "0123456789ABCDEF0123456789ABCDEF"); +GorillaEngine.setActiveInstrument(instrument, 2, true); diff --git a/types/gorilla-engine/index.d.ts b/types/gorilla-engine/index.d.ts index 952821c911145e..53c4bd572696fa 100755 --- a/types/gorilla-engine/index.d.ts +++ b/types/gorilla-engine/index.d.ts @@ -471,11 +471,17 @@ declare namespace GorillaEngine { function getManufacturerName(): string; function quitApplication(): void; /** - * Load blob at the specified path - * @param blobPath the load blob - * @throws if the blob could not be loaded e.g. it is not there or decryption failed + * Loads an instrument blob from the file system. + * + * The returned blob can be used to enumerate and load its instruments. Blob exports + * with separate headers must be loaded by passing the path to the first blob part. + * + * @param blobPath Absolute path to the blob file or the first part of a split blob. + * @param encryptionKey Encryption key used to decrypt the blob, when required. + * @returns The loaded blob. + * @throws If the file cannot be found or decrypted. */ - function loadBlob(blobPath: string): Blob; + function loadBlob(blobPath: string, encryptionKey?: string): Blob; function getPluginNRTB(enable: boolean): void; /** * Method updates the list of the automatable parameters note it does not work for all DAWS @@ -516,12 +522,20 @@ declare namespace GorillaEngine { function initialiseSpliceRTO(pluginName?: string): any; function disposeInstrument(instrument: Instrument): void; /** - * Activates an instrument, i.e. route MIDI to the instrument and send Audio from the isntrument to - * the DAW. Currently only one instrument can be active. If there was - * another instrument active before, it will get deativated. - * @param instrument the instrument activate + * Activates an instrument for MIDI input and audio output. + * + * Only one instrument can be active at a time. Activating an instrument deactivates + * the previously active instrument. + * + * @param instrument The instrument to activate. + * @param streamBufferSizeSeconds Streaming buffer size in seconds. Omit to keep the current size. + * @param waitUntilPreloaded Whether to wait for sample preloading to complete before returning. */ - function setActiveInstrument(instrument: Instrument): void; + function setActiveInstrument( + instrument: Instrument, + streamBufferSizeSeconds?: number, + waitUntilPreloaded?: boolean, + ): void; /** * Create an empty dummy instrument. It can be modified with e.g. {@link Instrument.setModuleAtPath} * @returns the empty instrument. @@ -534,8 +548,23 @@ declare namespace GorillaEngine { * @returns the instrument */ function LoadInstrumentFromFile(instFilePath: string): Instrument; - function setSessionSaveCallback(callback: (state: string) => string, instance: any): void; - function setSessionLoadCallback(callback: (state: string) => string, instance: any): void; + /** + * Registers the callback used to serialize application state when the host saves its session. + * + * @param callback Called with additional state managed by Gorilla Engine. It must return the + * application state to store in the host session. + * @param instance Optional object used as `this` when the callback runs. + * @returns `true` when the callback was registered. + */ + function setSessionSaveCallback(callback: (state: string) => string, instance?: object): boolean; + /** + * Registers the callback used to restore application state when the host loads its session. + * + * @param callback Called with the application state previously returned by the save callback. + * @param instance Optional object used as `this` when the callback runs. + * @returns `true` when the callback was registered. + */ + function setSessionLoadCallback(callback: (state: string) => string, instance?: object): boolean; function setParametersDirty(dirty: boolean): void; function areParametersDirty(): boolean; /** diff --git a/types/gorilla-engine/interfaces/Component.d.ts b/types/gorilla-engine/interfaces/Component.d.ts index 481bf528e6ad7f..739ef06362137c 100755 --- a/types/gorilla-engine/interfaces/Component.d.ts +++ b/types/gorilla-engine/interfaces/Component.d.ts @@ -1,11 +1,19 @@ declare namespace GorillaEngine.UI { + /** Base class for controls in the Gorilla Engine UI component tree. */ abstract class Component { + /** Unique identifier used to look up the component. */ id: string; + /** Components directly contained by this component. */ children: Component[]; + /** Component that contains this component. */ parent: Component; + /** Registers a handler for an event emitted by the component. */ on(event: string, handler: any): void; + /** Adds a component after the existing children. */ appendChild(child: Component): void; + /** Removes a direct child component. */ removeChild(child: Component): void; + /** Inserts a component immediately before another direct child. */ insertBefore(child: Component, beforeChild: Component): void; } } diff --git a/types/gorilla-engine/interfaces/KeyboardFocus.d.ts b/types/gorilla-engine/interfaces/KeyboardFocus.d.ts index 4ba0f12e0f1583..f6fa23c260da3d 100755 --- a/types/gorilla-engine/interfaces/KeyboardFocus.d.ts +++ b/types/gorilla-engine/interfaces/KeyboardFocus.d.ts @@ -1,7 +1,6 @@ declare namespace GorillaEngine.UI { /** - * KeyboardFocus interface for Gorilla Engine UI. - * Defines how a cotrol appears when it has the keyboard focus + * Defines how a control appears when it has keyboard focus. */ interface KeyboardFocus { keyboardFocus: { diff --git a/types/oidc-provider/index.d.ts b/types/oidc-provider/index.d.ts index 5ec732ae51261f..b7821cee6ef96e 100644 --- a/types/oidc-provider/index.d.ts +++ b/types/oidc-provider/index.d.ts @@ -10,17 +10,6 @@ import Koa = require("koa"); export {}; export type CanBePromise = Promise | T; -export type FindAccount = ( - ctx: KoaContextWithOIDC, - sub: string, - token?: AuthorizationCode | AccessToken | DeviceCode | BackchannelAuthenticationRequest | PreAuthorizedCode, -) => CanBePromise; -export type TokenFormat = "opaque" | "jwt"; -export type FapiProfile = "1.0 Final" | "2.0"; - -export type TTLFunction = WithClient extends true - ? (ctx: KoaContextWithOIDC, token: T, client: Client) => number - : (ctx: KoaContextWithOIDC, token: T) => number; export interface UnknownObject { [key: string]: unknown; @@ -54,87 +43,6 @@ export interface JWKS { keys: ReadonlyArray; } -export interface AuthorizationDetail extends UnknownObject { - type: string; -} - -export interface JWTVerificationResult { - protectedHeader: UnknownObject; - payload: UnknownObject; - key: crypto.KeyObject | crypto.webcrypto.CryptoKey; -} - -export interface KeyAttestation { - jwt: string; - attestedKeys: readonly JWK[]; - payload: UnknownObject; -} - -export interface OpenID4VCIProofType { - proof_signing_alg_values_supported?: readonly string[] | undefined; - key_attestations_required?: - | { - key_storage?: readonly string[] | undefined; - user_authentication?: readonly string[] | undefined; - } - | undefined; - [key: string]: unknown; -} - -export interface OpenID4VCICredentialConfiguration { - format: string; - scope?: string | undefined; - cryptographic_binding_methods_supported?: readonly "jwk"[] | undefined; - proof_types_supported?: - | { - jwt?: OpenID4VCIProofType | undefined; - attestation?: OpenID4VCIProofType | undefined; - } - | undefined; - [key: string]: unknown; -} - -export interface OpenID4VCIMetadata extends UnknownObject { - batch_credential_issuance?: - | { - batch_size: number; - [key: string]: unknown; - } - | undefined; -} - -export type OpenID4VCIProofs = - | { - jwt: readonly string[]; - key_attestation?: KeyAttestation | undefined; - attestation?: never; - } - | { - attestation: KeyAttestation; - jwt?: never; - key_attestation?: never; - }; - -export interface OpenID4VCICredentialContext { - credentialConfigurationId: string; - credentialConfiguration: OpenID4VCICredentialConfiguration; - credentialIdentifier?: string | undefined; - client: Client; - account: Account; - grant: Grant; - accessToken: AccessToken; -} - -export interface OpenID4VCIIssueCredentialContext extends OpenID4VCICredentialContext { - body: UnknownObject; - proofs?: OpenID4VCIProofs | undefined; -} - -export interface OpenID4VCICredentialResponse extends UnknownObject { - credentials: readonly unknown[]; - notification_id?: string | undefined; -} - export interface AllClientMetadata { client_id?: string | undefined; redirect_uris?: readonly string[] | undefined; @@ -588,6 +496,7 @@ declare class DeviceCode extends BaseToken { grantId: string; client: Client; deviceInfo: UnknownObject; + rar?: AuthorizationDetail[] | undefined; [key: string]: unknown; }); @@ -615,6 +524,7 @@ declare class DeviceCode extends BaseToken { sessionUid?: string | undefined; expiresWithSession?: boolean | undefined; grantId: string; + rar?: AuthorizationDetail[] | undefined; attestationJkt?: string | undefined; consumed: unknown; @@ -681,18 +591,24 @@ declare class ClientCredentials extends BaseToken { constructor(properties: { client: Client; resourceServer?: ResourceServerInstance | undefined; - scope: string; + aud?: string | string[] | undefined; + scope?: string | undefined; + rar?: AuthorizationDetail[] | undefined; [key: string]: unknown; }); readonly kind: "ClientCredentials"; scope?: string | undefined; extra?: UnknownObject | undefined; - aud: string | string[]; + aud?: string | string[] | undefined; readonly tokenType: string; "x5t#S256"?: string | undefined; jkt?: string | undefined; resourceServer?: ResourceServerInstance | undefined; + rar?: AuthorizationDetail[] | undefined; + setAudience(audience: string | string[]): void; + setThumbprint(prop: "x5t", input: string | crypto.X509Certificate): void; + setThumbprint(prop: "jkt", input: string): void; isSenderConstrained(): boolean; } @@ -719,7 +635,7 @@ declare class AccessToken extends BaseToken { resourceServer?: ResourceServerInstance | undefined; claims?: ClaimsParameter | undefined; aud?: string | string[] | undefined; - scope: string; + scope?: string | undefined; sid?: string | undefined; sessionUid?: string | undefined; expiresWithSession?: boolean | undefined; @@ -733,7 +649,7 @@ declare class AccessToken extends BaseToken { readonly kind: "AccessToken"; accountId: string; resourceServer?: ResourceServerInstance | undefined; - aud: string | string[]; + aud?: string | string[] | undefined; claims?: ClaimsParameter | undefined; extra?: UnknownObject | undefined; grantId: string; @@ -747,6 +663,9 @@ declare class AccessToken extends BaseToken { "x5t#S256"?: string | undefined; jkt?: string | undefined; + setAudience(audience: string | string[]): void; + setThumbprint(prop: "x5t", input: string | crypto.X509Certificate): void; + setThumbprint(prop: "jkt", input: string): void; isSenderConstrained(): boolean; static revokeByGrantId(grantId: string): Promise; @@ -909,43 +828,6 @@ export type { Session, }; -export interface ResourceServer { - scope: string; - audience?: string | undefined; - accessTokenTTL?: number | undefined; - accessTokenFormat?: TokenFormat | undefined; - jwt?: - | { - sign?: - | false - | { - alg?: AsymmetricSigningAlgorithm | undefined; - kid?: string | undefined; - } - | { - alg: SymmetricSigningAlgorithm; - key: crypto.KeyObject | crypto.webcrypto.CryptoKey | Buffer; - kid?: string | undefined; - } - | undefined; - encrypt?: - | false - | { - alg: EncryptionAlgValues; - enc: EncryptionEncValues; - key: crypto.KeyObject | crypto.webcrypto.CryptoKey | Buffer; - kid?: string | undefined; - } - | undefined; - } - | undefined; -} - -export interface ResourceServerInstance extends ResourceServer { - readonly scopes: Set; - identifier(): string; -} - declare class OIDCContext { constructor(ctx: Koa.Context); readonly route: string; @@ -1023,102 +905,275 @@ export type KoaContextWithOIDC = Koa.ParameterizedContext< } >; -export type TLSClientAuthProperty = - | "tls_client_auth_subject_dn" - | "tls_client_auth_san_dns" - | "tls_client_auth_san_uri" - | "tls_client_auth_san_ip" - | "tls_client_auth_san_email"; +export interface TokenEndpointGrantParameters extends UnknownObject { + grant_type: string; + scope?: string | undefined; + resource?: string | string[] | undefined; + authorization_details?: string | undefined; +} -export interface AccountClaims { - sub: string; +/** Context passed to a handler registered with `Provider.registerGrantType()`. */ +export type TokenEndpointGrantContext = KoaContextWithOIDC & { + oidc: OIDCContext & { + readonly provider: Provider; + readonly client: Client; + readonly params: TokenEndpointGrantParameters & Params; + readonly resourceServers: { [identifier: string]: ResourceServerInstance }; + }; +}; - [key: string]: unknown; -} +/* eslint-disable @typescript-eslint/no-invalid-void-type */ +// BEGIN GENERATED OIDC-PROVIDER CONTRACTS +// oidc-provider types artifact "9.12.0"; schema 1; sha256 781afaad6598727ec6dbbfea7486752924eb51914dd4cac579f9244a84041e9a +export type FindAccount = ( + ctx: KoaContextWithOIDC, + sub: string, + token?: + | AuthorizationCode + | AccessToken + | RefreshToken + | DeviceCode + | BackchannelAuthenticationRequest + | PreAuthorizedCode, +) => CanBePromise; +export type TokenFormat = "opaque" | "jwt"; +export type FapiProfile = "1.0 Final" | "2.0"; -export interface Account { - accountId: string; - claims: ( - use: string, - scope: string, - claims: { [key: string]: null | ClaimsParameterMember }, - rejected: string[], - ) => CanBePromise; - [key: string]: unknown; +/** + * A synchronous TTL policy callback. The returned number of seconds must be a + * positive safe integer. + */ +export type TTLFunction = WithClient extends true + ? (ctx: KoaContextWithOIDC, token: T, client: Client) => number + : (ctx: KoaContextWithOIDC, token: T) => number; + +export interface AuthorizationDetail extends UnknownObject { + type: string; } -export type RotateRegistrationAccessTokenFunction = (ctx: KoaContextWithOIDC) => CanBePromise; -export type IssueRegistrationAccessTokenFunction = (ctx: KoaContextWithOIDC) => CanBePromise; +export interface JWTVerificationResult { + protectedHeader: UnknownObject; + payload: UnknownObject; + key: crypto.KeyObject | crypto.webcrypto.CryptoKey; +} -export interface ErrorOut { - error: string; - error_description?: string | undefined; - scope?: string | undefined; - state?: string | undefined; +export interface KeyAttestation { + jwt: string; + attestedKeys: readonly JWK[]; + payload: UnknownObject; } -export interface AdapterPayload extends AllClientMetadata { - accountId?: string | undefined; - acr?: string | undefined; - amr?: string[] | undefined; - aud?: string | string[] | undefined; - authorizations?: +export interface OpenID4VCIProofType { + proof_signing_alg_values_supported?: readonly string[] | undefined; + key_attestations_required?: | { - [clientId: string]: ClientAuthorizationState; + key_storage?: readonly string[] | undefined; + user_authentication?: readonly string[] | undefined; } | undefined; - authTime?: number | undefined; - claims?: ClaimsParameter | undefined; - cid?: string | undefined; - clientId?: string | undefined; - codeChallenge?: string | undefined; - codeChallengeMethod?: string | undefined; - consumed?: any; - deviceCode?: string | undefined; - deviceInfo?: UnknownObject | undefined; - error?: string | undefined; - errorDescription?: string | undefined; - exp?: number | undefined; - expiresWithSession?: boolean | undefined; - extra?: UnknownObject | undefined; - format?: string | undefined; - grantId?: string | undefined; - gty?: string | undefined; - iat?: number | undefined; - iiat?: number | undefined; - inFlight?: boolean | undefined; - jti?: string | undefined; - kind?: string | undefined; - lastSubmission?: InteractionResults | undefined; - loginTs?: number | undefined; - nonce?: string | undefined; - parJti?: string | undefined; - params?: UnknownObject | undefined; - policies?: string[] | undefined; - prompt?: PromptDetail | undefined; - redirectUri?: string | undefined; - request?: string | undefined; - rar?: AuthorizationDetail[] | undefined; - resource?: string | string[] | undefined; - result?: InteractionResults | undefined; - returnTo?: string | undefined; - rotations?: number | undefined; + [key: string]: unknown; +} + +export interface OpenID4VCICredentialConfiguration { + format: string; scope?: string | undefined; - session?: + cryptographic_binding_methods_supported?: readonly "jwk"[] | undefined; + proof_types_supported?: | { - accountId?: string | undefined; - acr?: string | undefined; - amr?: string[] | undefined; - cookie?: string | undefined; - uid?: string | undefined; + jwt?: OpenID4VCIProofType | undefined; + attestation?: OpenID4VCIProofType | undefined; } | undefined; - sessionUid?: string | undefined; - sid?: string | undefined; - trusted?: string[] | undefined; - attestationJkt?: string | undefined; - dpopJkt?: string | undefined; - iss?: string | undefined; + [key: string]: unknown; +} + +export interface OpenID4VCIMetadata extends UnknownObject { + batch_credential_issuance?: + | { + batch_size: number; + [key: string]: unknown; + } + | undefined; +} + +export type OpenID4VCIProofs = + | { + jwt: readonly string[]; + key_attestation?: KeyAttestation | undefined; + attestation?: never; + } + | { + attestation: KeyAttestation; + jwt?: never; + key_attestation?: never; + }; + +export interface OpenID4VCICredentialContext { + credentialConfigurationId: string; + credentialConfiguration: OpenID4VCICredentialConfiguration; + credentialIdentifier?: string | undefined; + client: Client; + account: Account; + grant: Grant; + accessToken: AccessToken; +} + +export interface OpenID4VCIIssueCredentialContext extends OpenID4VCICredentialContext { + body: UnknownObject; + proofs?: OpenID4VCIProofs | undefined; +} + +export interface OpenID4VCICredentialResponse extends UnknownObject { + credentials: readonly unknown[]; + notification_id?: string | undefined; +} + +export interface ResourceServer { + scope: string; + audience?: string | undefined; + /** A positive safe integer number of seconds. */ + accessTokenTTL?: number | undefined; + accessTokenFormat?: TokenFormat | undefined; + jwt?: + | { + sign?: + | false + | { + alg?: AsymmetricSigningAlgorithm | undefined; + kid?: string | undefined; + } + | { + alg: SymmetricSigningAlgorithm; + key: crypto.KeyObject | crypto.webcrypto.CryptoKey | Buffer; + kid?: string | undefined; + } + | undefined; + encrypt?: + | false + | { + alg: EncryptionAlgValues; + enc: EncryptionEncValues; + key: crypto.KeyObject | crypto.webcrypto.CryptoKey | Buffer; + kid?: string | undefined; + } + | undefined; + } + | undefined; +} + +export interface ResourceServerInstance extends ResourceServer { + readonly scopes: Set; + identifier(): string; +} + +export type TLSClientAuthProperty = + | "tls_client_auth_subject_dn" + | "tls_client_auth_san_dns" + | "tls_client_auth_san_uri" + | "tls_client_auth_san_ip" + | "tls_client_auth_san_email"; + +export interface AccountClaims { + sub: string; + + [key: string]: unknown; +} + +export interface Account { + accountId: string; + claims: ( + use: string, + scope: string, + claims: { [key: string]: null | ClaimsParameterMember }, + rejected: string[], + ) => CanBePromise; + [key: string]: unknown; +} + +export type RotateRegistrationAccessTokenFunction = Exclude< + ( + | boolean + | ((ctx: KoaContextWithOIDC) => CanBePromise) + ), + boolean +>; +export type IssueRegistrationAccessTokenFunction = Exclude< + ( + | boolean + | ((ctx: KoaContextWithOIDC) => CanBePromise) + ), + boolean +>; + +export interface ErrorOut { + error: string; + error_description?: string | undefined; + scope?: string | undefined; + state?: string | undefined; +} + +export interface AdapterPayload extends AllClientMetadata { + accountId?: string | undefined; + acr?: string | undefined; + amr?: string[] | undefined; + aud?: string | string[] | undefined; + authorizations?: + | { + [clientId: string]: ClientAuthorizationState; + } + | undefined; + authTime?: number | undefined; + claims?: ClaimsParameter | undefined; + cid?: string | undefined; + clientId?: string | undefined; + codeChallenge?: string | undefined; + codeChallengeMethod?: string | undefined; + consumed?: any; + deviceCode?: string | undefined; + deviceInfo?: UnknownObject | undefined; + error?: string | undefined; + errorDescription?: string | undefined; + exp?: number | undefined; + expiresWithSession?: boolean | undefined; + extra?: UnknownObject | undefined; + format?: string | undefined; + grantId?: string | undefined; + gty?: string | undefined; + iat?: number | undefined; + iiat?: number | undefined; + inFlight?: boolean | undefined; + jti?: string | undefined; + kind?: string | undefined; + lastSubmission?: InteractionResults | undefined; + loginTs?: number | undefined; + nonce?: string | undefined; + parJti?: string | undefined; + params?: UnknownObject | undefined; + policies?: string[] | undefined; + prompt?: PromptDetail | undefined; + redirectUri?: string | undefined; + request?: string | undefined; + rar?: AuthorizationDetail[] | undefined; + resource?: string | string[] | undefined; + result?: InteractionResults | undefined; + returnTo?: string | undefined; + rotations?: number | undefined; + scope?: string | undefined; + session?: + | { + accountId?: string | undefined; + acr?: string | undefined; + amr?: string[] | undefined; + cookie?: string | undefined; + uid?: string | undefined; + } + | undefined; + sessionUid?: string | undefined; + sid?: string | undefined; + trusted?: string[] | undefined; + attestationJkt?: string | undefined; + dpopJkt?: string | undefined; + iss?: string | undefined; state?: UnknownObject | undefined; transient?: boolean | undefined; uid?: string | undefined; @@ -1149,7 +1204,9 @@ export interface CookiesSetOptions { domain?: string | undefined; secure?: boolean | undefined; httpOnly?: boolean | undefined; - sameSite?: "strict" | "lax" | "none" | undefined; + partitioned?: boolean | undefined; + priority?: "low" | "medium" | "high" | undefined; + sameSite?: boolean | "strict" | "lax" | "none" | undefined; signed?: boolean | undefined; overwrite?: boolean | undefined; } @@ -1164,133 +1221,1641 @@ export type JsonArray = JsonValue[]; export type JsonPrimitive = string | number | boolean | null; export type JsonValue = JsonPrimitive | JsonObject | JsonArray; -export interface RichAuthorizationRequestType { - validate: ( - ctx: KoaContextWithOIDC, - detail: AuthorizationDetail, - client: Client, - ) => CanBePromise; -} +type RichAuthorizationRequestTypeBase = ({ + [type: string]: { + validate: ( + ctx: KoaContextWithOIDC, + detail: AuthorizationDetail, + client: Client, + ) => CanBePromise; + }; +})[string]; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface RichAuthorizationRequestType extends RichAuthorizationRequestTypeBase {} + +export type AuthorizationDetailsForGrantSource = ( + ctx: KoaContextWithOIDC, + source: AuthorizationCode | DeviceCode, +) => CanBePromise; -export interface RichAuthorizationRequestsConfiguration { +export type AuthorizationDetailsForAccessToken = ( + ctx: KoaContextWithOIDC, + token: AccessToken | ClientCredentials, + source: + | AuthorizationCode + | BackchannelAuthenticationRequest + | DeviceCode + | PreAuthorizedCode + | RefreshToken + | undefined, + grantType: string, +) => CanBePromise; + +export type AuthorizationDetailsForIntrospection = ( + ctx: KoaContextWithOIDC, + token: AccessToken | ClientCredentials | RefreshToken, +) => CanBePromise; + +export interface RichAuthorizationRequestsConfigurationBase { enabled?: boolean | undefined; - ack?: string | undefined; + /** + * Specifies the authorization details type identifiers that shall be supported by the authorization server. Each + * type identifier MUST have an associated validation function that defines the required structure and constraints + * for authorization details of that specific type according to authorization server policy. The validation function + * is responsible for rejecting unknown fields as well as missing or invalid type-specific fields with + * `errors.InvalidAuthorizationDetails`. + */ types?: Readonly> | undefined; - rarForAuthorizationCode?: - | ((ctx: KoaContextWithOIDC) => CanBePromise) + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AuthorizationCode or DeviceCode grant source is + * persisted when Rich Authorization Request details were requested or granted. The function shall apply + * authorization server policy to the requested and granted details and return the authorization details to store in + * the grant source, or undefined. An empty array is treated as undefined. + */ + authorizationDetailsForGrantSource?: AuthorizationDetailsForGrantSource | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AccessToken or ClientCredentials token is persisted + * whenever Rich Authorization Request details were requested or inherited from the grant source. The function shall + * perform type-specific grant comparison, client policy enforcement, resource-specific filtering, and any response + * enrichment. It shall return the exact authorization details assigned to the access token and returned from the + * token endpoint, or undefined. An empty array is treated as undefined. `source` is the exchanged grant source, or + * undefined for client credentials; `grantType` is the exact token request `grant_type` value, including full URN + * values. To reject client-provided authorization details, throw `errors.InvalidAuthorizationDetails`. + */ + authorizationDetailsForAccessToken?: AuthorizationDetailsForAccessToken | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked when a token containing Rich Authorization Request details is + * introspected. It shall apply authorization server policy for the requesting party and return the authorization + * details to include as the top-level `authorization_details` introspection response member, or undefined. An empty + * array is treated as undefined. + */ + authorizationDetailsForIntrospection?: AuthorizationDetailsForIntrospection | undefined; +} + +export interface RichAuthorizationRequestsDisabledConfiguration extends RichAuthorizationRequestsConfigurationBase { + enabled?: false | undefined; +} + +export interface RichAuthorizationRequestsInactiveConfiguration extends RichAuthorizationRequestsConfigurationBase { + enabled: boolean; + /** + * Specifies the authorization details type identifiers that shall be supported by the authorization server. Each + * type identifier MUST have an associated validation function that defines the required structure and constraints + * for authorization details of that specific type according to authorization server policy. The validation function + * is responsible for rejecting unknown fields as well as missing or invalid type-specific fields with + * `errors.InvalidAuthorizationDetails`. + */ + types?: Readonly> | undefined; +} + +export interface RichAuthorizationRequestsActiveConfiguration extends RichAuthorizationRequestsConfigurationBase { + enabled: boolean; + /** + * Specifies the authorization details type identifiers that shall be supported by the authorization server. Each + * type identifier MUST have an associated validation function that defines the required structure and constraints + * for authorization details of that specific type according to authorization server policy. The validation function + * is responsible for rejecting unknown fields as well as missing or invalid type-specific fields with + * `errors.InvalidAuthorizationDetails`. + */ + types: Readonly>; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AuthorizationCode or DeviceCode grant source is + * persisted when Rich Authorization Request details were requested or granted. The function shall apply + * authorization server policy to the requested and granted details and return the authorization details to store in + * the grant source, or undefined. An empty array is treated as undefined. + */ + authorizationDetailsForGrantSource: AuthorizationDetailsForGrantSource; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AccessToken or ClientCredentials token is persisted + * whenever Rich Authorization Request details were requested or inherited from the grant source. The function shall + * perform type-specific grant comparison, client policy enforcement, resource-specific filtering, and any response + * enrichment. It shall return the exact authorization details assigned to the access token and returned from the + * token endpoint, or undefined. An empty array is treated as undefined. `source` is the exchanged grant source, or + * undefined for client credentials; `grantType` is the exact token request `grant_type` value, including full URN + * values. To reject client-provided authorization details, throw `errors.InvalidAuthorizationDetails`. + */ + authorizationDetailsForAccessToken: AuthorizationDetailsForAccessToken; +} + +export type RichAuthorizationRequestsConfiguration = + | RichAuthorizationRequestsDisabledConfiguration + | RichAuthorizationRequestsInactiveConfiguration + | RichAuthorizationRequestsActiveConfiguration; + +export interface FapiDisabledConfiguration { + enabled?: false | undefined; + /** + * Specifies the FAPI profile version that shall be applied for security policy enforcement. The authorization + * server shall implement the behaviors defined in the selected profile specification. Supported values include: + * + * - '2.0' - The authorization server shall implement behaviors from + * [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-security-profile-2_0-final.html) + * - '1.0 Final' - The authorization server shall implement behaviors from + * [FAPI 1.0 Security Profile - Part 2: Advanced](https://openid.net/specs/openid-financial-api-part-2-1_0-final.html) + * - Function - A function that shall be invoked with arguments `(ctx, client)` to determine the profile + * contextually. The function shall return one of the supported profile values or undefined when FAPI behaviors + * should be ignored for the current request context. + */ + profile?: + | ( + | FapiProfile + | (( + ctx: KoaContextWithOIDC, + client: Client, + ) => FapiProfile | undefined) + ) | undefined; - rarForBackchannelResponse?: +} + +export interface FapiEnabledConfiguration { + enabled: boolean; + /** + * Specifies the FAPI profile version that shall be applied for security policy enforcement. The authorization + * server shall implement the behaviors defined in the selected profile specification. Supported values include: + * + * - '2.0' - The authorization server shall implement behaviors from + * [FAPI 2.0 Security Profile](https://openid.net/specs/fapi-security-profile-2_0-final.html) + * - '1.0 Final' - The authorization server shall implement behaviors from + * [FAPI 1.0 Security Profile - Part 2: Advanced](https://openid.net/specs/openid-financial-api-part-2-1_0-final.html) + * - Function - A function that shall be invoked with arguments `(ctx, client)` to determine the profile + * contextually. The function shall return one of the supported profile values or undefined when FAPI behaviors + * should be ignored for the current request context. + */ + profile: + | FapiProfile + | (( + ctx: KoaContextWithOIDC, + client: Client, + ) => FapiProfile | undefined); +} + +export type FapiConfiguration = FapiDisabledConfiguration | FapiEnabledConfiguration; + +export type CIBATriggerAuthenticationDevice = ( + ctx: KoaContextWithOIDC, + request: BackchannelAuthenticationRequest, + account: Account, + client: Client, +) => CanBePromise; + +export type CIBAValidateRequestContext = ( + ctx: KoaContextWithOIDC, + requestContext?: string, +) => CanBePromise; + +export type CIBAVerifyUserCode = ( + ctx: KoaContextWithOIDC, + account: Account, + userCode?: string, +) => CanBePromise; + +export interface CIBAConfigurationBase { + enabled?: boolean | undefined; + /** + * Specifies the token delivery modes supported by this authorization server. The following delivery modes are + * defined: + * - `poll` - Client polls the token endpoint for completion + * - `ping` - Authorization server notifies client of completion via HTTP callback + */ + deliveryModes?: readonly CIBADeliveryMode[] | ReadonlySet | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to initiate authentication and authorization processes on the + * end-user's Authentication Device as defined in the CIBA specification. This function is executed after accepting + * the backchannel authentication request but before transmitting the response to the requesting client. + * + * Upon successful end-user authentication, implementations shall use `provider.backchannelResult()` to complete the + * Consumption Device login process. + */ + triggerAuthenticationDevice?: CIBATriggerAuthenticationDevice | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a deployment. + * + * Specifies a helper function that shall be invoked to validate the `binding_message` parameter according to + * authorization server policy. This function MUST reject invalid binding messages by throwing appropriate error + * instances. + * + * **Recommendation:** Use `throw new errors.InvalidBindingMessage('validation error message')` when the + * binding_message violates authorization server policy. + * + * **Recommendation:** Use `return undefined` when a binding_message is not required by policy and was not provided + * in the request. + */ + validateBindingMessage?: | (( ctx: KoaContextWithOIDC, - resourceServer: ResourceServerInstance, - ) => CanBePromise) + bindingMessage?: string, + ) => CanBePromise) | undefined; - rarForCodeResponse?: + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to validate the `request_context` parameter according to + * authorization server policy. This function MUST enforce policy requirements for request context validation and + * reject non-compliant requests. + * + * **Recommendation:** Use `throw new errors.InvalidRequest('validation error message')` when the request_context is + * required by policy but missing or invalid. + * + * **Recommendation:** Use `return undefined` when a request_context is not required by policy and was not provided + * in the request. + */ + validateRequestContext?: CIBAValidateRequestContext | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to process the `login_hint_token` parameter and extract the + * corresponding accountId value for request processing. This function MUST validate token expiration and format + * according to authorization server policy. Returning `undefined` causes the request to fail because no end-user + * could be identified. + * + * **Recommendation:** Use `throw new errors.ExpiredLoginHintToken('validation error message')` when the + * login_hint_token has expired. + * + * **Recommendation:** Use `throw new errors.InvalidRequest('validation error message')` when the login_hint_token + * format or content is invalid. + * + * **Recommendation:** Use `return undefined` when the accountId cannot be determined from the provided + * login_hint_token. + */ + processLoginHintToken?: | (( ctx: KoaContextWithOIDC, - resourceServer: ResourceServerInstance, - ) => CanBePromise) + loginHintToken?: string, + ) => CanBePromise) | undefined; - rarForIntrospectionResponse?: + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to process the `login_hint` parameter and extract the + * corresponding accountId value for request processing. This function MUST validate the hint format and content + * according to authorization server policy. Returning `undefined` causes the request to fail because no end-user + * could be identified. + * + * **Recommendation:** Use `throw new errors.InvalidRequest('validation error message')` when the login_hint format + * or content is invalid. + * + * **Recommendation:** Use `return undefined` when the accountId cannot be determined from the provided login_hint. + */ + processLoginHint?: | (( ctx: KoaContextWithOIDC, - token: AccessToken | ClientCredentials | RefreshToken, - ) => CanBePromise) + loginHint?: string, + ) => CanBePromise) + | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to verify the presence and validity of the `user_code` + * parameter when required by authorization server policy. + * + * **Recommendation:** Use `throw new errors.MissingUserCode('validation error message')` when user_code is required + * by policy but was not provided. + * + * **Recommendation:** Use `throw new errors.InvalidUserCode('validation error message')` when the provided + * user_code value is invalid or does not meet policy requirements. + * + * **Recommendation:** Use `return undefined` when no user_code was provided and it is not required by authorization + * server policy. + */ + verifyUserCode?: CIBAVerifyUserCode | undefined; +} + +export interface CIBADisabledConfiguration extends CIBAConfigurationBase { + enabled?: false | undefined; +} + +export interface CIBAEnabledConfiguration extends CIBAConfigurationBase { + enabled: boolean; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to initiate authentication and authorization processes on the + * end-user's Authentication Device as defined in the CIBA specification. This function is executed after accepting + * the backchannel authentication request but before transmitting the response to the requesting client. + * + * Upon successful end-user authentication, implementations shall use `provider.backchannelResult()` to complete the + * Consumption Device login process. + */ + triggerAuthenticationDevice: CIBATriggerAuthenticationDevice; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to validate the `request_context` parameter according to + * authorization server policy. This function MUST enforce policy requirements for request context validation and + * reject non-compliant requests. + * + * **Recommendation:** Use `throw new errors.InvalidRequest('validation error message')` when the request_context is + * required by policy but missing or invalid. + * + * **Recommendation:** Use `return undefined` when a request_context is not required by policy and was not provided + * in the request. + */ + validateRequestContext: CIBAValidateRequestContext; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to verify the presence and validity of the `user_code` + * parameter when required by authorization server policy. + * + * **Recommendation:** Use `throw new errors.MissingUserCode('validation error message')` when user_code is required + * by policy but was not provided. + * + * **Recommendation:** Use `throw new errors.InvalidUserCode('validation error message')` when the provided + * user_code value is invalid or does not meet policy requirements. + * + * **Recommendation:** Use `return undefined` when no user_code was provided and it is not required by authorization + * server policy. + */ + verifyUserCode: CIBAVerifyUserCode; +} + +export type CIBAConfiguration = CIBADisabledConfiguration | CIBAEnabledConfiguration; + +export type MTLSGetCertificate = ( + ctx: KoaContextWithOIDC, +) => crypto.X509Certificate | string | undefined; +export type MTLSCertificateAuthorized = (ctx: KoaContextWithOIDC) => boolean; +export type MTLSCertificateSubjectMatches = ( + ctx: KoaContextWithOIDC, + property: TLSClientAuthProperty, + expected: string, +) => boolean; + +export interface MTLSConfigurationBase { + enabled?: boolean | undefined; + /** + * Specifies whether Certificate-Bound Access Tokens shall be enabled as defined in RFC 8705 sections 3 and 4. When + * enabled, the authorization server shall expose the client's `tls_client_certificate_bound_access_tokens` metadata + * property for mutual TLS certificate binding functionality. + */ + certificateBoundAccessTokens?: boolean | undefined; + /** + * Specifies whether Self-Signed Certificate Mutual TLS client authentication shall be enabled as defined in RFC + * 8705 section 2.2. When enabled, the authorization server shall support the `self_signed_tls_client_auth` + * authentication method within the server's `clientAuthMethods` configuration. + */ + selfSignedTlsClientAuth?: boolean | undefined; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. When + * enabled, the authorization server shall support the `tls_client_auth` authentication method within the server's + * `clientAuthMethods` configuration. + */ + tlsClientAuth?: boolean | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the client certificate used in the current request. + * Returning `undefined` causes authentication to fail wherever a certificate is required. + */ + getCertificate?: MTLSGetCertificate | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate used in the request + * is verified and originates from a trusted Certificate Authority for the requesting client. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `true` accepts the certificate trust result; `false` rejects client authentication. + */ + certificateAuthorized?: MTLSCertificateAuthorized | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate subject used in the + * request matches the registered client property according to authorization server policy. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `property` is the registered `tls_client_auth_*` metadata name and `expected` is its registered value. `true` + * accepts the subject match; `false` rejects client authentication. + */ + certificateSubjectMatches?: MTLSCertificateSubjectMatches | undefined; +} + +export interface MTLSDisabledConfiguration extends MTLSConfigurationBase { + enabled?: false | undefined; +} + +export interface MTLSEnabledWithoutCertificateConfiguration extends MTLSConfigurationBase { + enabled: boolean; + /** + * Specifies whether Certificate-Bound Access Tokens shall be enabled as defined in RFC 8705 sections 3 and 4. When + * enabled, the authorization server shall expose the client's `tls_client_certificate_bound_access_tokens` metadata + * property for mutual TLS certificate binding functionality. + */ + certificateBoundAccessTokens?: false | undefined; + /** + * Specifies whether Self-Signed Certificate Mutual TLS client authentication shall be enabled as defined in RFC + * 8705 section 2.2. When enabled, the authorization server shall support the `self_signed_tls_client_auth` + * authentication method within the server's `clientAuthMethods` configuration. + */ + selfSignedTlsClientAuth?: false | undefined; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. When + * enabled, the authorization server shall support the `tls_client_auth` authentication method within the server's + * `clientAuthMethods` configuration. + */ + tlsClientAuth?: false | undefined; +} + +export type MTLSEnabledCertificateConfiguration = + & MTLSConfigurationBase + & { + enabled: boolean; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. + * When enabled, the authorization server shall support the `tls_client_auth` authentication method within the + * server's `clientAuthMethods` configuration. + */ + tlsClientAuth?: false | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the client certificate used in the current + * request. Returning `undefined` causes authentication to fail wherever a certificate is required. + */ + getCertificate: MTLSGetCertificate; + } + & ( + | { + /** + * Specifies whether Certificate-Bound Access Tokens shall be enabled as defined in RFC 8705 sections 3 and + * 4. When enabled, the authorization server shall expose the client's + * `tls_client_certificate_bound_access_tokens` metadata property for mutual TLS certificate binding + * functionality. + */ + certificateBoundAccessTokens: true; + } + | { + /** + * Specifies whether Self-Signed Certificate Mutual TLS client authentication shall be enabled as defined in + * RFC 8705 section 2.2. When enabled, the authorization server shall support the + * `self_signed_tls_client_auth` authentication method within the server's `clientAuthMethods` + * configuration. + */ + selfSignedTlsClientAuth: true; + } + ); + +export interface MTLSEnabledClientAuthenticationConfiguration extends MTLSConfigurationBase { + enabled: boolean; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. When + * enabled, the authorization server shall support the `tls_client_auth` authentication method within the server's + * `clientAuthMethods` configuration. + */ + tlsClientAuth: true; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the client certificate used in the current request. + * Returning `undefined` causes authentication to fail wherever a certificate is required. + */ + getCertificate: MTLSGetCertificate; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate used in the request + * is verified and originates from a trusted Certificate Authority for the requesting client. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `true` accepts the certificate trust result; `false` rejects client authentication. + */ + certificateAuthorized: MTLSCertificateAuthorized; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate subject used in the + * request matches the registered client property according to authorization server policy. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `property` is the registered `tls_client_auth_*` metadata name and `expected` is its registered value. `true` + * accepts the subject match; `false` rejects client authentication. + */ + certificateSubjectMatches: MTLSCertificateSubjectMatches; +} + +export type MTLSEnabledDynamicCertificateFlagsConfiguration = + & MTLSConfigurationBase + & { + enabled: boolean; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the client certificate used in the current + * request. Returning `undefined` causes authentication to fail wherever a certificate is required. + */ + getCertificate: MTLSGetCertificate; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. + * When enabled, the authorization server shall support the `tls_client_auth` authentication method within the + * server's `clientAuthMethods` configuration. + */ + tlsClientAuth?: false | undefined; + } + & ( + | { + /** + * Specifies whether Certificate-Bound Access Tokens shall be enabled as defined in RFC 8705 sections 3 and + * 4. When enabled, the authorization server shall expose the client's + * `tls_client_certificate_bound_access_tokens` metadata property for mutual TLS certificate binding + * functionality. + */ + certificateBoundAccessTokens: boolean; + } + | { + /** + * Specifies whether Self-Signed Certificate Mutual TLS client authentication shall be enabled as defined in + * RFC 8705 section 2.2. When enabled, the authorization server shall support the + * `self_signed_tls_client_auth` authentication method within the server's `clientAuthMethods` + * configuration. + */ + selfSignedTlsClientAuth: boolean; + } + ); + +export interface MTLSEnabledDynamicTlsClientAuthConfiguration extends MTLSConfigurationBase { + enabled: boolean; + /** + * Specifies whether PKI Mutual TLS client authentication shall be enabled as defined in RFC 8705 section 2.1. When + * enabled, the authorization server shall support the `tls_client_auth` authentication method within the server's + * `clientAuthMethods` configuration. + */ + tlsClientAuth: boolean; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the client certificate used in the current request. + * Returning `undefined` causes authentication to fail wherever a certificate is required. + */ + getCertificate: MTLSGetCertificate; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate used in the request + * is verified and originates from a trusted Certificate Authority for the requesting client. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `true` accepts the certificate trust result; `false` rejects client authentication. + */ + certificateAuthorized: MTLSCertificateAuthorized; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to determine whether the client certificate subject used in the + * request matches the registered client property according to authorization server policy. This validation is + * exclusively used for the `tls_client_auth` client authentication method. + * + * `property` is the registered `tls_client_auth_*` metadata name and `expected` is its registered value. `true` + * accepts the subject match; `false` rejects client authentication. + */ + certificateSubjectMatches: MTLSCertificateSubjectMatches; +} + +export type MTLSConfiguration = + | MTLSDisabledConfiguration + | MTLSEnabledWithoutCertificateConfiguration + | MTLSEnabledCertificateConfiguration + | MTLSEnabledClientAuthenticationConfiguration + | MTLSEnabledDynamicCertificateFlagsConfiguration + | MTLSEnabledDynamicTlsClientAuthConfiguration; + +export type AttestationSignaturePublicKey = ( + ctx: KoaContextWithOIDC, + header: UnknownObject, + payload: UnknownObject, + client: Client, +) => CanBePromise; + +export interface AttestClientAuthConfigurationBase { + enabled?: boolean | undefined; + ack?: string | undefined; + /** + * Specifies whether Client Attestation shall be accepted or required as an additional security signal alongside + * regular client authentication. Use `optional` to validate the signal when it is present, or `required` to require + * the OAuth-Client-Attestation and OAuth-Client-Attestation-PoP headers after the client is identified. This uses + * the `attestation_pop_jwt` method and does not enable DPoP combined mode. + */ + additionalSecuritySignal?: false | "optional" | "required" | undefined; + /** + * Specifies the cryptographic secret value used for generating server-provided challenges. This value MUST be a + * 32-byte Buffer instance to ensure sufficient entropy for secure challenge generation. Challenges are derived from + * this secret rather than stored; the same value MUST be configured on all instances of a deployment and kept + * stable across restarts. + */ + challengeSecret?: Buffer | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the public key used for Client Attestation JWT + * signature verification. At the point of this function's invocation, only the JWT format has been validated; no + * cryptographic or claims verification has occurred. + * + * The authorization server uses the resolved key to verify the Client Attestation JWT signature. An unsupported or + * invalid key rejects client authentication. + */ + getAttestationSignaturePublicKey?: AttestationSignaturePublicKey | undefined; + /** + * Specifies a helper function that shall be invoked to perform additional validation of the Client Attestation JWT + * and Client Attestation Proof-of-Possession JWT beyond the specification requirements. This enables enforcement of + * extension profiles, deployment-specific policies, or additional security constraints. + * + * At the point of invocation, both JWTs have undergone signature verification and standard validity claim + * validation. The function may throw errors to reject non-compliant attestations or return successfully to indicate + * acceptance of the client authentication attempt or additional security signal. + */ + assertAttestationJwtAndPop?: + | (( + ctx: KoaContextWithOIDC, + attestation: JWTVerificationResult, + pop: JWTVerificationResult, + client: Client, + ) => CanBePromise) + | undefined; +} + +export interface AttestClientAuthDisabledConfiguration extends AttestClientAuthConfigurationBase { + enabled?: false | undefined; +} + +export interface AttestClientAuthEnabledConfiguration extends AttestClientAuthConfigurationBase { + enabled: boolean; + /** + * Specifies the cryptographic secret value used for generating server-provided challenges. This value MUST be a + * 32-byte Buffer instance to ensure sufficient entropy for secure challenge generation. Challenges are derived from + * this secret rather than stored; the same value MUST be configured on all instances of a deployment and kept + * stable across restarts. + */ + challengeSecret: Buffer; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to retrieve the public key used for Client Attestation JWT + * signature verification. At the point of this function's invocation, only the JWT format has been validated; no + * cryptographic or claims verification has occurred. + * + * The authorization server uses the resolved key to verify the Client Attestation JWT signature. An unsupported or + * invalid key rejects client authentication. + */ + getAttestationSignaturePublicKey: AttestationSignaturePublicKey; +} + +export type AttestClientAuthConfiguration = + | AttestClientAuthDisabledConfiguration + | AttestClientAuthEnabledConfiguration; + +export type OpenID4VCIIssueCredential = ( + ctx: KoaContextWithOIDC, + details: OpenID4VCIIssueCredentialContext, +) => CanBePromise; + +export type OpenID4VCIKeyAttestationSignaturePublicKey = ( + ctx: KoaContextWithOIDC, + issuer: string, + header: UnknownObject, + client: Client, +) => CanBePromise; + +export interface OpenID4VCIConfigurationBase { + enabled?: boolean | undefined; + ack?: string | undefined; + /** + * Specifies the cryptographic secret used to generate and validate OpenID4VCI `c_nonce` challenges exposed by the + * nonce endpoint. This value MUST be a 32-byte Buffer instance. `c_nonce` values are derived from this secret + * rather than stored; the same value MUST be configured on all instances of a deployment and kept stable across + * restarts. + */ + nonceSecret?: Buffer | undefined; + /** + * Specifies whether the OpenID4VCI Pre-Authorized Code Flow shall be enabled. When enabled, the authorization + * server shall accept `urn:ietf:params:oauth:grant-type:pre-authorized_code` grant type exchanges at the token + * endpoint. Clients using this grant type must have it registered in their `grant_types` client metadata. + * + * Pre-authorized codes represent issuance authorization obtained through means outside of the protocol exchanges + * defined by this framework. Creating them, together with their underlying Grant, is an application-level concern, + * as is delivering them to the Wallet inside a Credential Offer. + * + * Pre-authorized codes are single-use. An optional Transaction Code (`txCode` property, a string) may be attached + * to a pre-authorized code, in which case the Wallet-provided `tx_code` parameter presence is validated before the + * code is consumed and its value is then compared in constant time, a failed comparison revokes the pre-authorized + * code and its underlying Grant. + * + * No ID Token is issued as part of this grant type's token exchange. + */ + preAuthorizedCodeGrant?: boolean | undefined; + /** + * Free-form object with additional top-level members to be merged into the Credential Issuer Metadata response. + */ + metadata?: OpenID4VCIMetadata | undefined; + /** + * Specifies static Credential Issuer metadata values for `credential_configurations_supported`. + */ + credentialConfigurationsSupported?: + | Readonly> + | undefined; + /** + * Specifies a helper function that shall be invoked to resolve the value the Access Token's `aud` claim must equal + * in order to access the Credential Endpoint. It shall return a non-empty string. + * + * The default derives the Credential Endpoint URL from the incoming request, which only resolves consistently when + * the Credential Endpoint and the Token Endpoint are served on the same host. Deployments serving the Credential + * Endpoint on another host, such as a mutual-TLS host, shall return a fixed absolute URL from this helper. + * + * Whatever this helper returns MUST equal the resource indicator the Access Token was issued for; this helper and + * the `features.resourceIndicators` configuration are two halves of the same contract. Note that OpenID4VCI + * recommends the Credential Issuer Identifier (`ctx.oidc.issuer`) as the `resource` parameter value, which is + * another value that does not vary with the host serving the request. + */ + credentialEndpointExpectedAudience?: + | ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; - rarForRefreshTokenResponse?: + /** + * Specifies a helper function that shall be invoked at runtime to decide whether a specific credential + * configuration is currently issuable for the current request context. + */ + credentialConfigurationPolicy?: + | (( + ctx: KoaContextWithOIDC, + details: OpenID4VCICredentialContext, + ) => CanBePromise) + | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to perform actual credential issuance and return credential + * response payloads. By the time this function is called all proof signatures, algorithms, types, required claims + * (`iat`, `nonce`, `aud`), and `c_nonce` challenges have already been validated. + * + * The returned object MUST contain a non-empty `credentials` array. The provider emits that array and the optional + * string `notification_id`; a missing or empty credentials array is a contract error. + * + * When `proofs` is present it contains a single key whose name is the proof type: + * + * - `jwt` The value is the original array of compact JWS strings. When the JWT proof(s) contain a `key_attestation` + * JOSE header parameter, the pre-parsed key attestation data is available as `proofs.key_attestation` with: + * - `jwt` {string} The Key Attestation JWT compact serialization. + * - `attestedKeys` {Object[]} The `attested_keys` claim (array of JWK objects). + * - `payload` {Object} The full Key Attestation JWT payload, including optional claims such as `key_storage`, + * `user_authentication`, and `certification`. + * + * - `attestation` The value is a pre-parsed object with: + * - `jwt` {string} The original Key Attestation JWT compact serialization. + * - `attestedKeys` {Object[]} The `attested_keys` claim (array of JWK objects). + * - `payload` {Object} The full Key Attestation JWT payload, including optional claims such as `key_storage`, + * `user_authentication`, and `certification` when present. If `key_attestations_required` is configured for the + * credential configuration, the required claims have been validated to contain at least one matching value + * before this function is called. + */ + issueCredential?: OpenID4VCIIssueCredential | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function used to resolve the public key for verifying Key Attestation JWT (typ + * `key-attestation+jwt`) signatures when the `attestation` proof type is used. At the point of invocation the JWT + * format and `iss` claim presence have been validated; no cryptographic or further claims verification has occurred + * yet. + * + * An unsupported or invalid key causes the credential endpoint to respond with `invalid_proof`. + */ + getKeyAttestationSignaturePublicKey?: OpenID4VCIKeyAttestationSignaturePublicKey | undefined; +} + +export interface OpenID4VCIDisabledConfiguration extends OpenID4VCIConfigurationBase { + enabled?: false | undefined; +} + +export interface OpenID4VCIEnabledConfiguration extends OpenID4VCIConfigurationBase { + enabled: boolean; + /** + * Specifies the cryptographic secret used to generate and validate OpenID4VCI `c_nonce` challenges exposed by the + * nonce endpoint. This value MUST be a 32-byte Buffer instance. `c_nonce` values are derived from this secret + * rather than stored; the same value MUST be configured on all instances of a deployment and kept stable across + * restarts. + */ + nonceSecret: Buffer; + /** + * Specifies static Credential Issuer metadata values for `credential_configurations_supported`. + */ + credentialConfigurationsSupported: Readonly>; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to perform actual credential issuance and return credential + * response payloads. By the time this function is called all proof signatures, algorithms, types, required claims + * (`iat`, `nonce`, `aud`), and `c_nonce` challenges have already been validated. + * + * The returned object MUST contain a non-empty `credentials` array. The provider emits that array and the optional + * string `notification_id`; a missing or empty credentials array is a contract error. + * + * When `proofs` is present it contains a single key whose name is the proof type: + * + * - `jwt` The value is the original array of compact JWS strings. When the JWT proof(s) contain a `key_attestation` + * JOSE header parameter, the pre-parsed key attestation data is available as `proofs.key_attestation` with: + * - `jwt` {string} The Key Attestation JWT compact serialization. + * - `attestedKeys` {Object[]} The `attested_keys` claim (array of JWK objects). + * - `payload` {Object} The full Key Attestation JWT payload, including optional claims such as `key_storage`, + * `user_authentication`, and `certification`. + * + * - `attestation` The value is a pre-parsed object with: + * - `jwt` {string} The original Key Attestation JWT compact serialization. + * - `attestedKeys` {Object[]} The `attested_keys` claim (array of JWK objects). + * - `payload` {Object} The full Key Attestation JWT payload, including optional claims such as `key_storage`, + * `user_authentication`, and `certification` when present. If `key_attestations_required` is configured for the + * credential configuration, the required claims have been validated to contain at least one matching value + * before this function is called. + */ + issueCredential: OpenID4VCIIssueCredential; +} + +export type OpenID4VCIConfiguration = OpenID4VCIDisabledConfiguration | OpenID4VCIEnabledConfiguration; + +interface IntrospectionFeatureBase { + enabled?: boolean | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a deployment. + * + * Specifies a helper function that shall be invoked to determine whether the requesting client or resource server + * is authorized to introspect the specified token. This function enables enforcement of fine-grained access control + * policies for token introspection operations according to authorization server security requirements. + * + * `true` includes the token's active response; `false` returns the normal inactive response without revealing + * whether the token exists. The default permits confidential clients and only permits public clients to introspect + * their own tokens. + */ + allowedPolicy?: | (( ctx: KoaContextWithOIDC, - resourceServer: ResourceServerInstance, - ) => CanBePromise) + client: Client, + token: AccessToken | ClientCredentials | RefreshToken, + ) => CanBePromise) | undefined; } +interface IntrospectionDisabledFeature extends IntrospectionFeatureBase { + enabled?: false | undefined; +} + +interface IntrospectionPossiblyEnabledFeature extends IntrospectionFeatureBase { + enabled: boolean; +} + +type RichAuthorizationRequestsActiveWithIntrospection = RichAuthorizationRequestsActiveConfiguration & { + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked when a token containing Rich Authorization Request details is + * introspected. It shall apply authorization server policy for the requesting party and return the authorization + * details to include as the top-level `authorization_details` introspection response member, or undefined. An empty + * array is treated as undefined. + */ + authorizationDetailsForIntrospection: AuthorizationDetailsForIntrospection; +}; + +type RichAuthorizationRequestsEnabledByOpenID4VCI = RichAuthorizationRequestsConfigurationBase & { + enabled: boolean; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AuthorizationCode or DeviceCode grant source is + * persisted when Rich Authorization Request details were requested or granted. The function shall apply + * authorization server policy to the requested and granted details and return the authorization details to store in + * the grant source, or undefined. An empty array is treated as undefined. + */ + authorizationDetailsForGrantSource: AuthorizationDetailsForGrantSource; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked before an AccessToken or ClientCredentials token is persisted + * whenever Rich Authorization Request details were requested or inherited from the grant source. The function shall + * perform type-specific grant comparison, client policy enforcement, resource-specific filtering, and any response + * enrichment. It shall return the exact authorization details assigned to the access token and returned from the + * token endpoint, or undefined. An empty array is treated as undefined. `source` is the exchanged grant source, or + * undefined for client credentials; `grantType` is the exact token request `grant_type` value, including full URN + * values. To reject client-provided authorization details, throw `errors.InvalidAuthorizationDetails`. + */ + authorizationDetailsForAccessToken: AuthorizationDetailsForAccessToken; +}; + +type RichAuthorizationRequestsEnabledByOpenID4VCIWithIntrospection = RichAuthorizationRequestsEnabledByOpenID4VCI & { + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked when a token containing Rich Authorization Request details is + * introspected. It shall apply authorization server policy for the requesting party and return the authorization + * details to include as the top-level `authorization_details` introspection response member, or undefined. An empty + * array is treated as undefined. + */ + authorizationDetailsForIntrospection: AuthorizationDetailsForIntrospection; +}; + +type ConditionalRichAuthorizationRequestFeatures = + | { + /** + * [OpenID for Verifiable Credential Issuance 1.0](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-final.html) + * + * This is an experimental feature. + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `issueCredential` + * - `getKeyAttestationSignaturePublicKey` + * + * Specifies whether OpenID4VCI core capabilities shall be enabled. When enabled, the authorization server shall + * expose the Credential Issuer Metadata, Credential Endpoint, and Nonce Endpoint routes, and perform protocol + * validation for issuance requests. Supported proof types are `jwt` and `attestation`. The `attestation` proof + * type relies on Key Attestation JWTs signed by a Wallet Provider; use `getKeyAttestationSignaturePublicKey` to + * resolve the attester's public key. + * + * Credential Offer is an application-level concern outside the scope of the framework. The Issuer constructs + * the Credential Offer JSON object (containing `credential_issuer`, `credential_configuration_ids`, and + * `grants`) and delivers it to the Wallet via a custom URL scheme redirect (same-device) or QR code + * (cross-device). The `issuer_state` authorization parameter, included in the offer's + * `grants.authorization_code` object and sent back by the Wallet in the authorization request, should be + * registered via `extraParams` with a validator callback. Once registered, it becomes available in + * `ctx.oidc.params` and is included in the interaction session details automatically. The Wallet's + * `credential_offer_endpoint` client metadata can be supported via `extraClientMetadata` if needed. The + * `metadata` configuration property below can be used to add any additional Credential Issuer Metadata members. + * + * Access to the Credential Endpoint requires an Access Token issued through a user-facing authorization grant + * (e.g. Authorization Code). The token MUST use the `opaque` format and its audience MUST equal the value + * returned by the `credentialEndpointExpectedAudience` helper. Deployments shall use the + * `features.resourceIndicators` mechanism to configure that same value as a resource indicator. Use the + * `defaultResource` helper to detect credential-requesting authorization requests and return it as the resource + * so that the client needs not to use the `resource` parameter. Use the `useGrantedResource` helper to return + * `true` so that the issued Access Token targets the Credential Endpoint rather than the UserInfo Endpoint. + */ + openid4vci: OpenID4VCIEnabledConfiguration; + /** + * [RFC7662](https://www.rfc-editor.org/info/rfc7662/) - OAuth 2.0 Token Introspection + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `allowedPolicy` + * + * Specifies whether OAuth 2.0 Token Introspection capabilities shall be enabled. When enabled, the + * authorization server shall expose a token introspection endpoint that allows authorized clients and resource + * servers to query the metadata and status of the following token types: + * - Opaque access tokens + * - Refresh tokens + */ + introspection: IntrospectionPossiblyEnabledFeature; + /** + * [RFC9396](https://www.rfc-editor.org/info/rfc9396/) - OAuth 2.0 Rich Authorization Requests + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `authorizationDetailsForGrantSource` + * - `authorizationDetailsForAccessToken` + * - `authorizationDetailsForIntrospection` + * + * Specifies whether Rich Authorization Request capabilities shall be enabled. When enabled, the authorization + * server shall support the `authorization_details` parameter at the authorization and token endpoints to enable + * issuing Access Tokens with fine-grained authorization data and enhanced authorization scope control. + * + * This provider profile requires `features.resourceIndicators` and supports authorization requests whose + * response type contains `code` but not `token`. Deployments handling sensitive authorization details SHOULD + * use JAR or PAR, sanitize all consent presentation, compare string values exactly without Unicode + * normalization, and disclose details to clients and Resource Servers only as required by policy. + */ + richAuthorizationRequests?: + | RichAuthorizationRequestsDisabledConfiguration + | RichAuthorizationRequestsEnabledByOpenID4VCIWithIntrospection + | undefined; + } + | { + /** + * [OpenID for Verifiable Credential Issuance 1.0](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-final.html) + * + * This is an experimental feature. + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `issueCredential` + * - `getKeyAttestationSignaturePublicKey` + * + * Specifies whether OpenID4VCI core capabilities shall be enabled. When enabled, the authorization server shall + * expose the Credential Issuer Metadata, Credential Endpoint, and Nonce Endpoint routes, and perform protocol + * validation for issuance requests. Supported proof types are `jwt` and `attestation`. The `attestation` proof + * type relies on Key Attestation JWTs signed by a Wallet Provider; use `getKeyAttestationSignaturePublicKey` to + * resolve the attester's public key. + * + * Credential Offer is an application-level concern outside the scope of the framework. The Issuer constructs + * the Credential Offer JSON object (containing `credential_issuer`, `credential_configuration_ids`, and + * `grants`) and delivers it to the Wallet via a custom URL scheme redirect (same-device) or QR code + * (cross-device). The `issuer_state` authorization parameter, included in the offer's + * `grants.authorization_code` object and sent back by the Wallet in the authorization request, should be + * registered via `extraParams` with a validator callback. Once registered, it becomes available in + * `ctx.oidc.params` and is included in the interaction session details automatically. The Wallet's + * `credential_offer_endpoint` client metadata can be supported via `extraClientMetadata` if needed. The + * `metadata` configuration property below can be used to add any additional Credential Issuer Metadata members. + * + * Access to the Credential Endpoint requires an Access Token issued through a user-facing authorization grant + * (e.g. Authorization Code). The token MUST use the `opaque` format and its audience MUST equal the value + * returned by the `credentialEndpointExpectedAudience` helper. Deployments shall use the + * `features.resourceIndicators` mechanism to configure that same value as a resource indicator. Use the + * `defaultResource` helper to detect credential-requesting authorization requests and return it as the resource + * so that the client needs not to use the `resource` parameter. Use the `useGrantedResource` helper to return + * `true` so that the issued Access Token targets the Credential Endpoint rather than the UserInfo Endpoint. + */ + openid4vci: OpenID4VCIEnabledConfiguration; + /** + * [RFC7662](https://www.rfc-editor.org/info/rfc7662/) - OAuth 2.0 Token Introspection + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `allowedPolicy` + * + * Specifies whether OAuth 2.0 Token Introspection capabilities shall be enabled. When enabled, the + * authorization server shall expose a token introspection endpoint that allows authorized clients and resource + * servers to query the metadata and status of the following token types: + * - Opaque access tokens + * - Refresh tokens + */ + introspection?: IntrospectionDisabledFeature | undefined; + /** + * [RFC9396](https://www.rfc-editor.org/info/rfc9396/) - OAuth 2.0 Rich Authorization Requests + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `authorizationDetailsForGrantSource` + * - `authorizationDetailsForAccessToken` + * - `authorizationDetailsForIntrospection` + * + * Specifies whether Rich Authorization Request capabilities shall be enabled. When enabled, the authorization + * server shall support the `authorization_details` parameter at the authorization and token endpoints to enable + * issuing Access Tokens with fine-grained authorization data and enhanced authorization scope control. + * + * This provider profile requires `features.resourceIndicators` and supports authorization requests whose + * response type contains `code` but not `token`. Deployments handling sensitive authorization details SHOULD + * use JAR or PAR, sanitize all consent presentation, compare string values exactly without Unicode + * normalization, and disclose details to clients and Resource Servers only as required by policy. + */ + richAuthorizationRequests?: + | RichAuthorizationRequestsDisabledConfiguration + | RichAuthorizationRequestsEnabledByOpenID4VCI + | undefined; + } + | { + /** + * [OpenID for Verifiable Credential Issuance 1.0](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-final.html) + * + * This is an experimental feature. + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `issueCredential` + * - `getKeyAttestationSignaturePublicKey` + * + * Specifies whether OpenID4VCI core capabilities shall be enabled. When enabled, the authorization server shall + * expose the Credential Issuer Metadata, Credential Endpoint, and Nonce Endpoint routes, and perform protocol + * validation for issuance requests. Supported proof types are `jwt` and `attestation`. The `attestation` proof + * type relies on Key Attestation JWTs signed by a Wallet Provider; use `getKeyAttestationSignaturePublicKey` to + * resolve the attester's public key. + * + * Credential Offer is an application-level concern outside the scope of the framework. The Issuer constructs + * the Credential Offer JSON object (containing `credential_issuer`, `credential_configuration_ids`, and + * `grants`) and delivers it to the Wallet via a custom URL scheme redirect (same-device) or QR code + * (cross-device). The `issuer_state` authorization parameter, included in the offer's + * `grants.authorization_code` object and sent back by the Wallet in the authorization request, should be + * registered via `extraParams` with a validator callback. Once registered, it becomes available in + * `ctx.oidc.params` and is included in the interaction session details automatically. The Wallet's + * `credential_offer_endpoint` client metadata can be supported via `extraClientMetadata` if needed. The + * `metadata` configuration property below can be used to add any additional Credential Issuer Metadata members. + * + * Access to the Credential Endpoint requires an Access Token issued through a user-facing authorization grant + * (e.g. Authorization Code). The token MUST use the `opaque` format and its audience MUST equal the value + * returned by the `credentialEndpointExpectedAudience` helper. Deployments shall use the + * `features.resourceIndicators` mechanism to configure that same value as a resource indicator. Use the + * `defaultResource` helper to detect credential-requesting authorization requests and return it as the resource + * so that the client needs not to use the `resource` parameter. Use the `useGrantedResource` helper to return + * `true` so that the issued Access Token targets the Credential Endpoint rather than the UserInfo Endpoint. + */ + openid4vci?: OpenID4VCIDisabledConfiguration | undefined; + /** + * [RFC7662](https://www.rfc-editor.org/info/rfc7662/) - OAuth 2.0 Token Introspection + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `allowedPolicy` + * + * Specifies whether OAuth 2.0 Token Introspection capabilities shall be enabled. When enabled, the + * authorization server shall expose a token introspection endpoint that allows authorized clients and resource + * servers to query the metadata and status of the following token types: + * - Opaque access tokens + * - Refresh tokens + */ + introspection: IntrospectionPossiblyEnabledFeature; + /** + * [RFC9396](https://www.rfc-editor.org/info/rfc9396/) - OAuth 2.0 Rich Authorization Requests + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `authorizationDetailsForGrantSource` + * - `authorizationDetailsForAccessToken` + * - `authorizationDetailsForIntrospection` + * + * Specifies whether Rich Authorization Request capabilities shall be enabled. When enabled, the authorization + * server shall support the `authorization_details` parameter at the authorization and token endpoints to enable + * issuing Access Tokens with fine-grained authorization data and enhanced authorization scope control. + * + * This provider profile requires `features.resourceIndicators` and supports authorization requests whose + * response type contains `code` but not `token`. Deployments handling sensitive authorization details SHOULD + * use JAR or PAR, sanitize all consent presentation, compare string values exactly without Unicode + * normalization, and disclose details to clients and Resource Servers only as required by policy. + */ + richAuthorizationRequests?: + | RichAuthorizationRequestsDisabledConfiguration + | RichAuthorizationRequestsInactiveConfiguration + | RichAuthorizationRequestsActiveWithIntrospection + | undefined; + } + | { + /** + * [OpenID for Verifiable Credential Issuance 1.0](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-final.html) + * + * This is an experimental feature. + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `issueCredential` + * - `getKeyAttestationSignaturePublicKey` + * + * Specifies whether OpenID4VCI core capabilities shall be enabled. When enabled, the authorization server shall + * expose the Credential Issuer Metadata, Credential Endpoint, and Nonce Endpoint routes, and perform protocol + * validation for issuance requests. Supported proof types are `jwt` and `attestation`. The `attestation` proof + * type relies on Key Attestation JWTs signed by a Wallet Provider; use `getKeyAttestationSignaturePublicKey` to + * resolve the attester's public key. + * + * Credential Offer is an application-level concern outside the scope of the framework. The Issuer constructs + * the Credential Offer JSON object (containing `credential_issuer`, `credential_configuration_ids`, and + * `grants`) and delivers it to the Wallet via a custom URL scheme redirect (same-device) or QR code + * (cross-device). The `issuer_state` authorization parameter, included in the offer's + * `grants.authorization_code` object and sent back by the Wallet in the authorization request, should be + * registered via `extraParams` with a validator callback. Once registered, it becomes available in + * `ctx.oidc.params` and is included in the interaction session details automatically. The Wallet's + * `credential_offer_endpoint` client metadata can be supported via `extraClientMetadata` if needed. The + * `metadata` configuration property below can be used to add any additional Credential Issuer Metadata members. + * + * Access to the Credential Endpoint requires an Access Token issued through a user-facing authorization grant + * (e.g. Authorization Code). The token MUST use the `opaque` format and its audience MUST equal the value + * returned by the `credentialEndpointExpectedAudience` helper. Deployments shall use the + * `features.resourceIndicators` mechanism to configure that same value as a resource indicator. Use the + * `defaultResource` helper to detect credential-requesting authorization requests and return it as the resource + * so that the client needs not to use the `resource` parameter. Use the `useGrantedResource` helper to return + * `true` so that the issued Access Token targets the Credential Endpoint rather than the UserInfo Endpoint. + */ + openid4vci?: OpenID4VCIDisabledConfiguration | undefined; + /** + * [RFC7662](https://www.rfc-editor.org/info/rfc7662/) - OAuth 2.0 Token Introspection + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `allowedPolicy` + * + * Specifies whether OAuth 2.0 Token Introspection capabilities shall be enabled. When enabled, the + * authorization server shall expose a token introspection endpoint that allows authorized clients and resource + * servers to query the metadata and status of the following token types: + * - Opaque access tokens + * - Refresh tokens + */ + introspection?: IntrospectionDisabledFeature | undefined; + /** + * [RFC9396](https://www.rfc-editor.org/info/rfc9396/) - OAuth 2.0 Rich Authorization Requests + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by a + * deployment before use. + * - `authorizationDetailsForGrantSource` + * - `authorizationDetailsForAccessToken` + * - `authorizationDetailsForIntrospection` + * + * Specifies whether Rich Authorization Request capabilities shall be enabled. When enabled, the authorization + * server shall support the `authorization_details` parameter at the authorization and token endpoints to enable + * issuing Access Tokens with fine-grained authorization data and enhanced authorization scope control. + * + * This provider profile requires `features.resourceIndicators` and supports authorization requests whose + * response type contains `code` but not `token`. Deployments handling sensitive authorization details SHOULD + * use JAR or PAR, sanitize all consent presentation, compare string values exactly without Unicode + * normalization, and disclose details to clients and Resource Servers only as required by policy. + */ + richAuthorizationRequests?: RichAuthorizationRequestsConfiguration | undefined; + }; + export interface Configuration { + /** + * Authentication Context Class References + * + * An array of strings representing the Authentication Context Class References that this authorization server + * supports. + */ acrValues?: readonly string[] | ReadonlySet | undefined; + /** + * Storage Adapter + * + * Specifies the storage adapter implementation for persisting authorization server state. The default + * implementation provides a basic in-memory adapter suitable for development and testing purposes only. When this + * process is restarted, all stored information will be lost. Production deployments MUST provide a custom adapter + * implementation that persists data to external storage (e.g., database, Redis, etc.). + * + * The adapter constructor will be instantiated for each model type when first accessed. + * + * @see [The expected interface](https://github.com/panva/node-oidc-provider/blob/main/example/my_adapter.js) + * + * @see [Example MongoDB adapter implementation](https://github.com/panva/node-oidc-provider/discussions/1308) + * + * @see [Example Redis adapter implementation](https://github.com/panva/node-oidc-provider/discussions/1309) + * + * @see [Example Redis w/ JSON Adapter](https://github.com/panva/node-oidc-provider/discussions/1310) + * + * @see [Default in-memory adapter implementation](https://github.com/panva/node-oidc-provider/blob/main/lib/adapters/memory_adapter.js) + * + * @see [Community Contributed Adapter Archive](https://github.com/panva/node-oidc-provider/discussions/1311) + */ adapter?: AdapterConstructor | AdapterFactory | undefined; + /** + * Available Claims + * + * Describes the claims that this authorization server may be able to supply values for. + * + * It is used to achieve two different things related to claims: + * - which additional claims are available to RPs (configure as `{ claimName: null }`) + * - which claims fall under what scope (configure `{ scopeName: ['claim', 'another-claim'] }`) + * + * @see [Configuring OpenID Connect 1.0 Standard Claims](https://github.com/panva/node-oidc-provider/discussions/1299) + */ claims?: | { - [key: string]: null | readonly string[]; + [key: string]: null | readonly string[] | Readonly>; } | undefined; + /** + * Cross-Origin Resource Sharing (CORS) + * + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a deployment. + * + * Specifies a function that determines whether Cross-Origin Resource Sharing (CORS) requests shall be permitted + * based on the requesting client. This function is invoked for each actual CORS request to evaluate the client's + * authorization to access the authorization server from the specified origin. + * + * @see [Configuring Client Metadata-based CORS Origin allow list](https://github.com/panva/node-oidc-provider/discussions/1298) + */ clientBasedCORS?: ((ctx: KoaContextWithOIDC, origin: string, client: Client) => boolean) | undefined; + /** + * Statically Configured Clients + * + * An array of client metadata objects representing statically configured OAuth 2.0 and OpenID Connect clients. + * These clients are persistent, do not expire, and remain available throughout the authorization server's lifetime. + * For dynamic client resolution, the authorization server will invoke the adapter's `find` method when encountering + * unregistered client identifiers. + * + * To restrict the authorization server to only statically configured clients and disable dynamic registration, + * configure the adapter to return falsy values for client lookup operations (e.g., `return Promise.resolve()`). + * + * Each client's metadata shall be validated according to the specifications in which the respective properties are + * defined. + */ clients?: readonly ClientMetadata[] | undefined; formats?: | { - bitsOfOpaqueRandomness?: number | ((ctx: KoaContextWithOIDC, model: BaseModel) => number) | undefined; + /** + * Specifies the entropy configuration for opaque token generation. The value shall be an integer (or a + * function returning an integer) that determines the cryptographic strength of generated opaque tokens. The + * resulting opaque token length shall be calculated as `Math.ceil(i / Math.log2(n))` where `i` is the + * specified bit count and `n` is the number of symbols in the encoding alphabet (64 characters in the + * base64url character set used by this implementation). + */ + bitsOfOpaqueRandomness?: (number | ((ctx: KoaContextWithOIDC, model: BaseModel) => number)) | undefined; + /** + * Specifies customizer functions that shall be invoked immediately before issuing structured Access Tokens + * to enable modification of token headers and payload claims according to authorization server policy. + * These functions shall be called during the token formatting process to apply deployment-specific + * customizations to the token structure before signing. Customize the supplied `jwt.header` and + * `jwt.payload` objects in place; a customizer's return value is ignored. + */ customizers?: - | { + | ({ jwt?: | (( ctx: KoaContextWithOIDC, token: AccessToken | ClientCredentials, parts: JWTStructured, - ) => CanBePromise) + ) => CanBePromise) | undefined; - } + }) | undefined; } | undefined; + /** + * Default Client Metadata + * + * Specifies default client metadata values that shall be applied when properties are not explicitly provided during + * Dynamic Client Registration or for statically configured clients. This configuration allows override of the + * authorization server's built-in default values for any supported client metadata property. + */ clientDefaults?: AllClientMetadata | undefined; + /** + * Clock Skew Tolerance + * + * Specifies the maximum acceptable clock skew tolerance (in seconds) for validating time-sensitive operations, + * including JWT validation for Request Objects and other timestamp-based security mechanisms. + * + * **Recommendation:** This value should be kept as small as possible while accommodating expected clock drift + * between the authorization server and client systems. + */ clockTolerance?: number | undefined; + /** + * ID Token Claims Conformance + * + * [`OIDC Core 1.0` - Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0-errata2.html#ScopeClaims) + * defines that claims requested using the `scope` parameter are only returned from the UserInfo Endpoint unless the + * `response_type` is `id_token`. + * + * Despite this configuration, the ID Token always includes claims requested using the `scope` parameter when the + * userinfo endpoint is disabled, or when issuing an Access Token not applicable for access to the userinfo + * endpoint. + */ conformIdTokenClaims?: boolean | undefined; + /** + * HTTP Cookie Configuration + * + * Configuration for HTTP cookies used to maintain User-Agent state throughout the authorization flow. These + * settings conform to the + * [cookies module interface specification](https://github.com/pillarjs/cookies/tree/0.9.1?tab=readme-ov-file#cookiessetname--values--options) + * . The `maxAge` and `expires` properties are ignored; cookie lifetimes are instead controlled via the + * `ttl.Session` and `ttl.Interaction` configuration parameters. + */ cookies?: | { + /** + * Specifies the HTTP cookie names used for state management during the authorization flow. + */ names?: | { session?: string | undefined; interaction?: string | undefined; resume?: string | undefined; - state?: string | undefined; } | undefined; + /** + * Options for long-term cookies. + */ long?: CookiesSetOptions | undefined; + /** + * Options for short-term cookies. + */ short?: CookiesSetOptions | undefined; + /** + * [Keygrip](https://www.npmjs.com/package/keygrip) signing keys used for cookie signing to prevent + * tampering. You may also pass your own KeyGrip instance. + * + * **Recommendation:** Rotate regularly (by prepending new keys) with a reasonable interval and keep a + * reasonable history of keys to allow for returning user session cookies to still be valid and re-signed. + */ keys?: ReadonlyArray | undefined | KeyGrip; } | undefined; + /** + * Extending the Discovery Document + * + * Pass additional properties to this object to extend the discovery document. + * + * Note: Standard discovery properties derived from the provider's configuration cannot be overridden through this + * object. + */ discovery?: UnknownObject | undefined; + /** + * HTTP POST Method Support + * + * Specifies whether HTTP POST method support shall be enabled at the Authorization Endpoint and the Logout Endpoint + * (if enabled). When enabled, the authorization server shall accept POST requests at these endpoints in addition to + * the standard GET requests. This configuration may only be used when the `cookies.long.sameSite` configuration + * value is `none`. + */ enableHttpPostMethods?: boolean | undefined; + /** + * Additional Authorization Request Parameters + * + * Specifies additional parameters that shall be recognized by the authorization, device authorization, backchannel + * authentication, and pushed authorization request endpoints. These extended parameters become available in + * `ctx.oidc.params` and are passed to interaction session details for processing. + * + * This configuration accepts either an iterable object (array or Set of strings) for simple parameter registration, + * or a plain object with string properties representing parameter names and values being validation functions + * (synchronous or asynchronous) for the corresponding parameter values. + * + * Parameter validators are executed regardless of the parameter's presence or value, enabling validation of + * parameter presence as well as assignment of default values. When the value is `null` or `undefined`, the + * parameter is registered without validation constraints. + * + * Note: These validators execute during the final phase of the request validation process. Modifications to other + * parameters (such as assigning default values) will not trigger re-validation of the entire request. + */ extraParams?: - | readonly string[] - | ReadonlySet - | { - [param: string]: + | (readonly string[] | ReadonlySet | { + [name: string]: | null - | ((ctx: KoaContextWithOIDC, value: string | undefined, client: Client) => CanBePromise); - } + | (( + ctx: KoaContextWithOIDC, + value: string | undefined, + client: Client, + ) => CanBePromise); + }) | undefined; - assertJwtClientAuthClaimsAndHeader?: ( - ctx: KoaContextWithOIDC, - claims: Record, - header: Record, - client: Client, - ) => CanBePromise; + /** + * JWT Client Authentication Assertion Validation + * + * Specifies a helper function that shall be invoked to perform additional validation of JWT Client Authentication + * assertion Claims Set and Header beyond the requirements mandated by the specification. This function enables + * enforcement of deployment-specific security policies and extended validation logic for `private_key_jwt` and + * `client_secret_jwt` client authentication methods according to authorization server requirements. + * + * `claims` and `header` are structured clones of the verified assertion Claims Set and JOSE Header, so mutating + * them does not alter the verified assertion. Throw an appropriate error to fail authentication. The default + * additionally requires an exact issuer identifier audience when the FAPI 2.0 profile applies. + */ + assertJwtClientAuthClaimsAndHeader?: + | (( + ctx: KoaContextWithOIDC, + claims: Record, + header: Record, + client: Client, + ) => CanBePromise) + | undefined; + /** + * Feature Configurations + * + * Specifies the authorization server feature capabilities that shall be enabled or disabled. This configuration + * controls the availability of optional OAuth 2.0 and OpenID Connect extensions, experimental specifications, and + * proprietary enhancements. + * + * Certain features may be designated as experimental implementations. When experimental features are enabled, the + * authorization server will emit warnings to indicate that breaking changes may occur in future releases. These + * changes will be published as minor version updates of the oidc-provider module. + * + * To suppress experimental feature warnings and ensure configuration validation against breaking changes, + * implementations shall acknowledge the specific experimental feature version using the acknowledgment mechanism + * demonstrated in the example below. When an unacknowledged breaking change is detected, the authorization server + * configuration will throw an error during instantiation. + */ features?: | { + /** + * Development-only Interaction Views + * + * Enables development-only interaction views that provide pre-built user interface components for rapid + * prototyping and testing of authorization flows. These views accept any username (used as the subject + * claim value) and any password for authentication, bypassing production-grade security controls. + * + * Production deployments MUST disable this feature and implement proper end-user authentication and + * authorization mechanisms. These development views MUST NOT be used in production environments as they + * provide no security guarantees and accept arbitrary credentials. + */ devInteractions?: | { enabled?: boolean | undefined; } | undefined; + /** + * [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0-errata2.html#ClaimsParameter) - + * Requesting Claims using the "claims" Request Parameter + * + * Specifies whether the `claims` request parameter shall be enabled for authorization requests. When + * enabled, the authorization server shall accept and process the `claims` parameter to enable fine-grained + * control over which claims are returned in ID Tokens and from the UserInfo Endpoint. + */ claimsParameter?: | { enabled?: boolean | undefined; + /** + * Specifies a helper function that shall be invoked to perform additional validation of the + * `claims` parameter. This function enables enforcement of deployment-specific policies, security + * constraints, or extended claim validation logic according to authorization server requirements. + * + * The function may throw errors to reject non-compliant claims requests or return successfully to + * indicate acceptance of the claims parameter content. + */ assertClaimsParameter?: | (( ctx: KoaContextWithOIDC, @@ -1301,14 +2866,50 @@ export interface Configuration { } | undefined; + /** + * [draft-ietf-oauth-client-id-metadata-document-02](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-02.html) + * - OAuth Client ID Metadata Document (CIMD) + * + * This is an experimental feature. + * + * Specifies whether the authorization server shall support resolving client metadata from Client Identifier + * URLs used as `client_id` values. When enabled, if a `client_id` is an HTTPS URL conforming to the + * specification's requirements, the authorization server shall fetch the Client ID Metadata Document from + * that URL and use it as the client's registration data, without requiring prior client registration. + */ clientIdMetadataDocument?: | { enabled?: boolean | undefined; ack?: string | undefined; + /** + * Specifies a helper function that shall be invoked before fetching a Client ID Metadata Document + * from a Client Identifier URL. This function enables enforcement of domain allowlisting, rate + * limiting, or other security policies. Return `true` to allow the fetch, or `false` to reject the + * `client_id`. + */ allowFetch?: - | ((ctx: KoaContextWithOIDC, clientId: string) => CanBePromise) + | (( + ctx: KoaContextWithOIDC, + clientId: string, + ) => CanBePromise) + | undefined; + /** + * Specifies a helper function that shall be invoked every time a client resolved from a Client ID + * Metadata Document is about to be used, including when served from cache. This function enables + * per-request evaluation of trust and authorization policies for metadata-document-resolved + * clients. Return `true` to allow the client, or `false` to reject it. + */ + allowClient?: + | (( + ctx: KoaContextWithOIDC, + client: Client, + ) => CanBePromise) | undefined; - allowClient?: ((ctx: KoaContextWithOIDC, client: Client) => CanBePromise) | undefined; + /** + * Specifies the minimum and maximum cache duration bounds (in seconds) applied to HTTP cache + * headers when caching fetched Client ID Metadata Documents. Cache-Control and Expires response + * headers are respected within these bounds. + */ cacheDuration?: | { min?: number | undefined; @@ -1318,28 +2919,53 @@ export interface Configuration { } | undefined; + /** + * [RFC6749](https://www.rfc-editor.org/info/rfc6749/#section-1.3.4) - Client Credentials + * + * Specifies whether the Client Credentials grant type shall be enabled. When enabled, the authorization + * server shall accept `grant_type=client_credentials` requests at the token endpoint, allowing clients to + * obtain access tokens. + */ clientCredentials?: | { enabled?: boolean | undefined; } | undefined; - introspection?: - | { - enabled?: boolean | undefined; - allowedPolicy?: - | (( - ctx: KoaContextWithOIDC, - client: Client, - token: AccessToken | ClientCredentials | RefreshToken, - ) => CanBePromise) - | undefined; - } - | undefined; - + /** + * [RFC7009](https://www.rfc-editor.org/info/rfc7009/) - OAuth 2.0 Token Revocation + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `allowedPolicy` + * + * Specifies whether Token Revocation capabilities shall be enabled. When enabled, the authorization server + * shall expose a token revocation endpoint that allows authorized clients to notify the authorization + * server that a particular token is no longer needed. This feature supports revocation of the following + * token types: + * - Opaque access tokens + * - Refresh tokens + */ revocation?: | { enabled?: boolean | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies a helper function that shall be invoked to determine whether the requesting client or + * resource server is authorized to revoke the specified token. This function enables enforcement of + * fine-grained access control policies for token revocation operations according to authorization + * server security requirements. + * + * `true` permits revocation; `false` leaves the token unchanged and returns the normal revocation + * response. By default, a client may revoke its own tokens; a mismatched public client is denied + * without an error and a mismatched confidential client is rejected. + */ allowedPolicy?: | (( ctx: KoaContextWithOIDC, @@ -1350,63 +2976,235 @@ export interface Configuration { } | undefined; + /** + * [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0-errata2.html#UserInfo) - UserInfo + * Endpoint + * + * Specifies whether the UserInfo Endpoint shall be enabled. When enabled, the authorization server shall + * expose a UserInfo endpoint that returns claims about the authenticated end-user. Access to this endpoint + * requires an opaque Access Token with at least `openid` scope that does not have a Resource Server + * audience. + */ userinfo?: | { enabled?: boolean | undefined; } | undefined; + /** + * [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0-errata2.html#UserInfo) - JWT UserInfo + * Endpoint Responses + * + * Specifies whether JWT-formatted UserInfo endpoint responses shall be enabled. When enabled, the + * authorization server shall support returning UserInfo responses as signed and/or encrypted JSON Web + * Tokens, providing enhanced security and integrity protection for end-user claims transmission. This + * feature shall also enable the relevant client metadata parameters for configuring JWT signing and/or + * encryption algorithms according to client requirements. + */ jwtUserinfo?: | { enabled?: boolean | undefined; } | undefined; + /** + * JWE Encryption + * + * Specifies whether encryption capabilities shall be enabled. When enabled, the authorization server shall + * support accepting and issuing encrypted tokens involved in its other enabled capabilities. + */ encryption?: | { enabled?: boolean | undefined; } | undefined; + /** + * [OIDC Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0-errata2.html) + * and [RFC7591](https://www.rfc-editor.org/info/rfc7591/) - OAuth 2.0 Dynamic Client Registration Protocol + * + * Specifies whether Dynamic Client Registration capabilities shall be enabled. When enabled, the + * authorization server shall expose a client registration endpoint that allows clients to dynamically + * register themselves with the authorization server at runtime, enabling automated client onboarding and + * configuration management. + */ registration?: | { enabled?: boolean | undefined; + /** + * Specifies whether the registration endpoint shall require an initial access token as + * authorization for client registration requests. This configuration controls access to the dynamic + * registration functionality. Supported values include: + * - `string` - The authorization server shall validate the provided bearer token against this + * static initial access token value + * - `boolean` - When true, the authorization server shall require adapter-backed initial access + * tokens; when false, registration requests are processed without initial access tokens. + */ initialAccessToken?: boolean | string | undefined; + /** + * Specifies registration and registration management policies that shall be applied to client + * metadata properties during dynamic registration operations. Policies are synchronous or + * asynchronous functions assigned to Initial Access Tokens that execute before standard client + * property validations. Multiple policies may be assigned to an Initial Access Token, and by + * default, the same policies shall transfer to the Registration Access Token. Policy functions may + * throw errors to reject registration requests or modify the client properties object before + * validation. + * + * **Recommendation:** Referenced policies MUST always be present when encountered on a token; an + * AssertionError will be thrown inside the request context if a policy is not found, resulting in a + * 500 Server Error. + * + * **Recommendation:** The same policies will be assigned to the Registration Access Token after a + * successful validation. If you wish to assign different policies to the Registration Access Token: + * `` `js + * // inside your final ran policy + * ctx.oidc.entities.RegistrationAccessToken.policies = ['update-policy']; + * `` ` + */ policies?: - | { - [key: string]: ( + | ({ + [name: string]: ( ctx: KoaContextWithOIDC, metadata: ClientMetadata, - ) => CanBePromise; // eslint-disable-line @typescript-eslint/no-invalid-void-type - } + ) => CanBePromise; + }) | undefined; + /** + * Specifies a helper function that shall be invoked to generate random client identifiers during + * dynamic client registration operations. This function enables customization of client identifier + * generation according to authorization server requirements and conventions. + */ idFactory?: ((ctx: KoaContextWithOIDC) => string) | undefined; + /** + * Specifies a helper function that shall be invoked to generate random client secrets during + * dynamic client registration operations. This function enables customization of client secret + * generation according to authorization server security requirements and entropy specifications. + */ secretFactory?: ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; - issueRegistrationAccessToken?: IssueRegistrationAccessTokenFunction | boolean | undefined; + /** + * Specifies whether a registration access token shall be issued upon successful client + * registration. This configuration determines if clients receive tokens for subsequent registration + * management operations. Supported values include: + * - `true` - Registration access tokens shall be issued for all successful registrations + * - `false` - Registration access tokens shall not be issued + * - Function - A function that shall be invoked to dynamically determine token issuance based on + * request context and authorization server policy + */ + issueRegistrationAccessToken?: + | ( + | boolean + | ((ctx: KoaContextWithOIDC) => CanBePromise) + ) + | undefined; } | undefined; + /** + * [RFC7592](https://www.rfc-editor.org/info/rfc7592/) - OAuth 2.0 Dynamic Client Registration Management + * Protocol + * + * Specifies whether Dynamic Client Registration Management capabilities shall be enabled. When enabled, the + * authorization server shall expose Update and Delete operations as defined in RFC 7592, allowing clients + * to modify or remove their registration entries using Registration Access Tokens for client lifecycle + * management operations. + */ registrationManagement?: | { enabled?: boolean | undefined; - rotateRegistrationAccessToken?: RotateRegistrationAccessTokenFunction | boolean | undefined; + /** + * Specifies whether registration access token rotation shall be enabled as a security policy for + * client registration management operations. When token rotation is active, the authorization + * server shall discard the current Registration Access Token upon successful update operations and + * issue a new token, returning it to the client with the Registration Update Response. + * + * Supported values include: + * - `false` - Registration access tokens shall not be rotated and remain valid after use + * - `true` - Registration access tokens shall be rotated when used for management operations + * - Function - A function that shall be invoked to dynamically determine whether rotation should + * occur based on request context and authorization server policy + */ + rotateRegistrationAccessToken?: + | ( + | boolean + | ((ctx: KoaContextWithOIDC) => CanBePromise) + ) + | undefined; } | undefined; + /** + * [RFC8628](https://www.rfc-editor.org/info/rfc8628/) - OAuth 2.0 Device Authorization Grant (Device Flow) + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `userCodeInputSource` + * - `userCodeConfirmSource` + * - `successSource` + * + * Specifies whether the OAuth 2.0 Device Authorization Grant shall be enabled. When enabled, the + * authorization server shall support the device authorization flow, enabling OAuth clients on + * input-constrained devices to obtain user authorization by directing the user to perform the authorization + * flow on a secondary device with richer input and display capabilities. + */ deviceFlow?: | { enabled?: boolean | undefined; + /** + * Specifies the character set used for generating user codes in the device authorization flow. This + * configuration determines the alphabet from which user codes are constructed. Supported values + * include: + * - `base-20` - Uses characters BCDFGHJKLMNPQRSTVWXZ (excludes easily confused characters) + * - `digits` - Uses characters 0123456789 (numeric only) + */ charset?: "base-20" | "digits" | undefined; + /** + * Specifies the template pattern used for generating user codes in the device authorization flow. + * The authorization server shall replace `*` characters with random characters from the configured + * charset, while `-` (dash) and ` ` (space) characters may be included for enhanced readability. + * Refer to RFC 8628 for guidance on minimal recommended entropy requirements for user code + * generation. + */ mask?: string | undefined; + /** + * Specifies a helper function that shall be invoked to extract device-specific information from + * device authorization endpoint requests. The extracted information becomes available during the + * end-user confirmation screen to assist users in verifying that the authorization request + * originated from a device in their possession. This enhances security by enabling users to confirm + * device identity before granting authorization. + */ deviceInfo?: ((ctx: KoaContextWithOIDC) => UnknownObject) | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies the HTML source that shall be rendered when the device flow feature displays a user + * code input prompt to the User-Agent. This template is presented during the device authorization + * flow when the authorization server requires the end-user to enter a device-generated user code + * for verification. + */ userCodeInputSource?: | (( ctx: KoaContextWithOIDC, form: string, out?: ErrorOut, err?: errors.OIDCProviderError | Error, - ) => CanBePromise) // eslint-disable-line @typescript-eslint/no-invalid-void-type + ) => CanBePromise) | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies the HTML source that shall be rendered when the device flow feature displays a + * confirmation prompt to the User-Agent. This template is presented after successful user code + * validation to confirm the authorization request before proceeding with the device authorization + * flow. + */ userCodeConfirmSource?: | (( ctx: KoaContextWithOIDC, @@ -1414,77 +3212,155 @@ export interface Configuration { client: Client, deviceInfo: UnknownObject, userCode: string, - ) => CanBePromise) // eslint-disable-line @typescript-eslint/no-invalid-void-type + ) => CanBePromise) | undefined; - successSource?: ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; // eslint-disable-line @typescript-eslint/no-invalid-void-type + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies the HTML source that shall be rendered when the device flow feature displays a success + * page to the User-Agent. This template is presented upon successful completion of the device + * authorization flow to inform the end-user that authorization has been granted to the requesting + * device. + */ + successSource?: ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; } | undefined; + /** + * [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0-errata2.html#RequestObject) and + * [RFC9101](https://www.rfc-editor.org/info/rfc9101/#name-passing-a-request-object-by) - Passing a Request + * Object by Value (JAR) + * + * Specifies whether Request Object capabilities shall be enabled. When enabled, the authorization server + * shall support the use and validation of the `request` parameter for conveying authorization request + * parameters as JSON Web Tokens, providing enhanced security and integrity protection for authorization + * requests. + */ requestObjects?: | { enabled?: boolean | undefined; + /** + * Specifies whether the use of signed request objects shall be mandatory for all authorization + * requests as an authorization server security policy. When enabled, the authorization server shall + * reject authorization requests that do not include a signed Request Object JWT. + */ requireSignedRequestObject?: boolean | undefined; - assertJwtClaimsAndHeader?: ( - ctx: KoaContextWithOIDC, - claims: Record, - header: Record, - client: Client, - ) => CanBePromise; + /** + * Specifies a helper function that shall be invoked to perform additional validation of the Request + * Object JWT Claims Set and Header beyond the standard JAR specification requirements. This + * function enables enforcement of deployment-specific policies, security constraints, or extended + * validation logic according to authorization server requirements. + */ + assertJwtClaimsAndHeader?: + | (( + ctx: KoaContextWithOIDC, + claims: Record, + header: Record, + client: Client, + ) => CanBePromise) + | undefined; } | undefined; + /** + * [RFC9449](https://www.rfc-editor.org/info/rfc9449/) - OAuth 2.0 Demonstration of Proof-of-Possession at + * the Application Layer (DPoP) + * + * Specifies whether sender-constraining of OAuth 2.0 tokens through application-level proof-of-possession + * mechanisms shall be enabled. + */ dPoP?: | { enabled?: boolean | undefined; + /** + * Specifies the cryptographic secret value used for generating server-provided DPoP nonces. When + * provided, this value MUST be a 32-byte Buffer instance to ensure sufficient entropy for secure + * nonce generation. Nonces are derived from this secret rather than stored; the same value MUST be + * configured on all instances of a deployment and kept stable across restarts. + */ nonceSecret?: Buffer | undefined; - requireNonce?: (ctx: KoaContextWithOIDC) => boolean; + /** + * Specifies a function that determines whether a DPoP nonce shall be required for + * proof-of-possession validation in the current request context. This function is invoked during + * DPoP proof validation to enforce nonce requirements based on authorization server policy. + */ + requireNonce?: ((ctx: KoaContextWithOIDC) => boolean) | undefined; + /** + * Specifies whether DPoP Proof replay shall be permitted by the authorization server. When set to + * false, the server enforces strict replay protection by rejecting previously used DPoP proofs, + * enhancing security against replay attacks. + */ allowReplay?: boolean; } | undefined; + /** + * [OIDC Back-Channel Logout 1.0](https://openid.net/specs/openid-connect-backchannel-1_0-final.html) + * + * Specifies whether Back-Channel Logout capabilities shall be enabled. When enabled, the authorization + * server shall support propagating end-user logout events to clients that were involved throughout the + * lifetime of the terminated session. + */ backchannelLogout?: | { enabled?: boolean | undefined; } | undefined; - fapi?: - | { - enabled?: boolean | undefined; - profile?: FapiProfile | ((ctx: KoaContextWithOIDC, client: Client) => FapiProfile) | undefined; - } - | undefined; - - ciba?: - | { - enabled?: boolean | undefined; - deliveryModes?: readonly CIBADeliveryMode[] | ReadonlySet | undefined; - triggerAuthenticationDevice?: - | (( - ctx: KoaContextWithOIDC, - request: BackchannelAuthenticationRequest, - account: Account, - client: Client, - ) => CanBePromise) - | undefined; - validateBindingMessage?: - | ((ctx: KoaContextWithOIDC, bindingMessage?: string) => CanBePromise) - | undefined; - validateRequestContext?: - | ((ctx: KoaContextWithOIDC, requestContext?: string) => CanBePromise) - | undefined; - processLoginHintToken?: - | ((ctx: KoaContextWithOIDC, loginHintToken?: string) => CanBePromise) - | undefined; - processLoginHint?: - | ((ctx: KoaContextWithOIDC, loginHint?: string) => CanBePromise) - | undefined; - verifyUserCode?: - | ((ctx: KoaContextWithOIDC, userCode?: string) => CanBePromise) - | undefined; - } - | undefined; - + /** + * FAPI Security Profiles + * + * Specifies whether FAPI Security Profile capabilities shall be enabled. When enabled, the authorization + * server shall implement additional security behaviors defined in FAPI specifications that cannot be + * achieved through other configuration options. + */ + fapi?: FapiConfiguration | undefined; + + /** + * [OIDC Client Initiated Backchannel Authentication Flow (CIBA)](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-final.html) + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by + * a deployment before use. + * - `triggerAuthenticationDevice` + * - `validateRequestContext` + * - `processLoginHintToken` + * - `processLoginHint` + * - `verifyUserCode` + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `validateBindingMessage` + * + * Specifies whether Core `CIBA` Flow shall be enabled. When combined with `features.fapi` and + * `features.requestObjects` this also enables + * [Financial-grade API: Client Initiated Backchannel Authentication Profile - Implementers Draft 01](https://openid.net/specs/openid-financial-api-ciba-ID1.html) + * as well. + */ + ciba?: CIBAConfiguration | undefined; + + /** + * [draft-sakimura-oauth-wmrm-01](https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-01) - OAuth 2.0 Web + * Message Response Mode + * + * This is an experimental feature. + * + * Specifies whether Web Message Response Mode capabilities shall be enabled. When enabled, the + * authorization server shall support the `web_message` response mode for returning authorization responses + * via HTML5 Web Messaging. The implementation shall support only Simple Mode operation; authorization + * requests containing Relay Mode parameters will be rejected. + * + * **Recommendation:** Although a general advice to use a `helmet` (e.g. for + * [express](https://www.npmjs.com/package/helmet), [koa](https://www.npmjs.com/package/koa-helmet)) it is + * especially advised for your interaction views routes if Web Message Response Mode is enabled in your + * deployment. You will have to experiment with removal of the Cross-Origin-Embedder-Policy and + * Cross-Origin-Opener-Policy headers at various endpoints throughout the authorization request end-user + * journey to finalize this feature. + */ webMessageResponseMode?: | { enabled?: boolean | undefined; @@ -1492,57 +3368,193 @@ export interface Configuration { } | undefined; + /** + * [RFC9701](https://www.rfc-editor.org/info/rfc9701/) - JWT Response for OAuth Token Introspection + * + * Specifies whether JWT-formatted token introspection responses shall be enabled. When enabled, the + * authorization server shall support issuing introspection responses as JSON Web Tokens, providing enhanced + * security and integrity protection for token metadata transmission between authorized parties. + */ jwtIntrospection?: | { enabled?: boolean | undefined; } | undefined; + /** + * [JWT Secured Authorization Response Mode (JARM)](https://openid.net/specs/oauth-v2-jarm-errata1.html) + * + * Specifies whether JWT Secured Authorization Response Mode capabilities shall be enabled. When enabled, + * the authorization server shall support encoding authorization responses as JSON Web Tokens, providing + * cryptographic protection and integrity assurance for authorization response parameters. + */ jwtResponseModes?: | { enabled?: boolean | undefined; } | undefined; + /** + * [RFC9126](https://www.rfc-editor.org/info/rfc9126/) - OAuth 2.0 Pushed Authorization Requests (PAR) + * + * Specifies whether Pushed Authorization Request capabilities shall be enabled. When enabled, the + * authorization server shall expose a pushed authorization request endpoint that allows clients to lodge + * authorization request parameters at the authorization server prior to redirecting end-users to the + * authorization endpoint, enhancing security by removing the need to transmit parameters via query string + * parameters. + */ pushedAuthorizationRequests?: | { + /** + * Specifies whether PAR usage shall be mandatory for all authorization requests as an authorization + * server security policy. When enabled, the authorization server shall reject authorization + * endpoint requests that do not utilize the pushed authorization request mechanism. + */ requirePushedAuthorizationRequests?: boolean | undefined; + /** + * Specifies whether unregistered redirect_uri values shall be permitted for authenticated clients + * using PAR that do not utilize a sector_identifier_uri. This configuration enables dynamic + * redirect URI specification within the security constraints of the pushed authorization request + * mechanism. + */ allowUnregisteredRedirectUris?: boolean | undefined; enabled?: boolean | undefined; } | undefined; + /** + * [OIDC RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html) + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `postLogoutSuccessSource` + * - `logoutSource` + * + * Specifies whether RP-Initiated Logout capabilities shall be enabled. When enabled, the authorization + * server shall support logout requests initiated by relying parties, allowing clients to request + * termination of end-user sessions. + */ rpInitiatedLogout?: | { enabled?: boolean | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies the HTML source that shall be rendered when an RP-Initiated Logout request concludes + * successfully but no `post_logout_redirect_uri` was provided by the requesting client. This + * template shall be presented to inform the end-user that the logout operation has completed + * successfully and provide appropriate post-logout guidance. + */ postLogoutSuccessSource?: - | ((ctx: KoaContextWithOIDC) => CanBePromise) // eslint-disable-line @typescript-eslint/no-invalid-void-type + | ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; + /** + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a + * deployment. + * + * Specifies the HTML source that shall be rendered when RP-Initiated Logout displays a confirmation + * prompt to the User-Agent. This template shall be presented to request explicit end-user + * confirmation before proceeding with the logout operation, ensuring user awareness and consent for + * session termination. + */ logoutSource?: - | ((ctx: KoaContextWithOIDC, form: string) => CanBePromise) // eslint-disable-line @typescript-eslint/no-invalid-void-type - | undefined; - } - | undefined; - - mTLS?: - | { - enabled?: boolean | undefined; - certificateBoundAccessTokens?: boolean | undefined; - selfSignedTlsClientAuth?: boolean | undefined; - tlsClientAuth?: boolean | undefined; - getCertificate?: - | ((ctx: KoaContextWithOIDC) => crypto.X509Certificate | string | undefined) - | undefined; - certificateAuthorized?: ((ctx: KoaContextWithOIDC) => boolean) | undefined; - certificateSubjectMatches?: - | ((ctx: KoaContextWithOIDC, property: TLSClientAuthProperty, expected: string) => boolean) + | (( + ctx: KoaContextWithOIDC, + form: string, + ) => CanBePromise) | undefined; } | undefined; + /** + * [RFC8705](https://www.rfc-editor.org/info/rfc8705/) - OAuth 2.0 Mutual TLS Client Authentication and + * Certificate Bound Access Tokens (MTLS) + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by + * a deployment before use. + * - `getCertificate` + * - `certificateAuthorized` + * - `certificateSubjectMatches` + * + * Specifies whether Mutual TLS capabilities shall be enabled. The authorization server supports three + * distinct capabilities that require separate configuration settings within this feature's configuration + * object. Implementations MUST provide deployment-specific helper functions for certificate validation and + * processing operations. + */ + mTLS?: MTLSConfiguration | undefined; + + /** + * [RFC8707](https://www.rfc-editor.org/info/rfc8707/) - Resource Indicators for OAuth 2.0 + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by + * a deployment before use. + * - `getResourceServerInfo` + * + * Specifies whether Resource Indicator capabilities shall be enabled. When enabled, the authorization + * server shall support the `resource` parameter at the authorization and token endpoints to enable issuing + * Access Tokens for specific Resource Servers (APIs) with enhanced audience control and scope management. + * + * The authorization server implements the following resource indicator processing rules: + * - Multiple resource parameters may be present during Authorization Code Flow, Device Authorization Grant, + * and Backchannel Authentication Requests, but only a single audience for an Access Token is permitted. + * - Authorization and Authentication Requests that result in an Access Token being issued by the + * Authorization Endpoint MUST only contain a single resource (or one MUST be resolved using the + * `defaultResource` helper). + * - Client Credentials grant MUST only contain a single resource parameter. + * - During Authorization Code / Refresh Token / Device Code / Backchannel Authentication Request exchanges, + * if the exchanged code/token does not include the `'openid'` scope and only has a single resource then + * the resource parameter may be omitted - an Access Token for the single resource is returned. + * - During Authorization Code / Refresh Token / Device Code / Backchannel Authentication Request exchanges, + * if the exchanged code/token does not include the `'openid'` scope and has multiple resources then the + * resource parameter MUST be provided (or one MUST be resolved using the `defaultResource` helper). An + * Access Token for the provided/resolved resource is returned. + * - (with userinfo endpoint enabled and useGrantedResource helper returning falsy) During Authorization + * Code / Refresh Token / Device Code exchanges, if the exchanged code/token includes the `'openid'` scope + * and no resource parameter is present - an Access Token for the UserInfo Endpoint is returned. + * - (with userinfo endpoint enabled and useGrantedResource helper returning truthy) During Authorization + * Code / Refresh Token / Device Code exchanges, even if the exchanged code/token includes the `'openid'` + * scope and only has a single resource then the resource parameter may be omitted - an Access Token for + * the single resource is returned. + * - (with userinfo endpoint disabled) During Authorization Code / Refresh Token / Device Code exchanges, if + * the exchanged code/token includes the `'openid'` scope and only has a single resource then the resource + * parameter may be omitted - an Access Token for the single resource is returned. + * - Issued Access Tokens shall always only contain scopes that are defined on the respective Resource + * Server (returned from `features.resourceIndicators.getResourceServerInfo`). + */ resourceIndicators?: | { enabled?: boolean | undefined; + /** + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before + * use. + * + * Specifies a helper function that shall be invoked to load information about a Resource Server + * (API) and determine whether the client is authorized to request scopes for that particular + * resource. This function enables resource-specific scope validation and Access Token configuration + * according to authorization server policy. + * + * A returned `accessTokenTTL` must be a positive safe integer number of seconds. Fractional, + * non-finite, non-positive, and unsafe integer values are rejected. + * + * Reject unauthorized resource indicators with `errors.InvalidTarget`. + * + * **Recommendation:** Only allow client's pre-registered resource values. To pre-register these you + * shall use the `extraClientMetadata` configuration option to define a custom metadata and use that + * to implement your policy using this function. + */ getResourceServerInfo?: | (( ctx: KoaContextWithOIDC, @@ -1550,6 +3562,16 @@ export interface Configuration { client: Client, ) => CanBePromise) | undefined; + /** + * Specifies a helper function that shall be invoked to determine the default resource indicator for + * a request when none is provided by the client during the authorization request or when multiple + * resources are provided/resolved and only a single one is required during an Access Token Request. + * This function enables authorization server policy-based resource selection according to + * deployment requirements. + * + * `oneOf`, when present, contains the candidate resource indicators. Leaving that array unresolved + * causes a request that requires one target to fail with `invalid_target`. + */ defaultResource?: | (( ctx: KoaContextWithOIDC, @@ -1557,6 +3579,22 @@ export interface Configuration { oneOf?: readonly string[] | undefined, ) => CanBePromise) | undefined; + /** + * Specifies a helper function that shall be invoked to determine whether an already granted + * resource indicator should be used without being explicitly requested by the client during the + * Token Endpoint request. This function enables flexible resource selection policies for token + * issuance operations. + * + * `true` permits the already granted resource to be selected when the request omits `resource`; + * `false` does not. + * + * **Recommendation:** Use `return true` when it's allowed for a client to skip providing the + * "resource" parameter at the Token Endpoint. + * + * **Recommendation:** Use `return false` (default) when it's required for a client to explicitly + * provide a "resource" parameter at the Token Endpoint or when other indication dictates an Access + * Token for the UserInfo Endpoint should be returned. + */ useGrantedResource?: | (( ctx: KoaContextWithOIDC, @@ -1571,85 +3609,126 @@ export interface Configuration { } | undefined; - richAuthorizationRequests?: RichAuthorizationRequestsConfiguration | undefined; - + /** + * [OIDC Relying Party Metadata Choices 1.0](https://openid.net/specs/openid-connect-rp-metadata-choices-1_0-final.html) + * + * Specifies whether Relying Party Metadata Choices capabilities shall be enabled. When enabled, the + * authorization server shall support the following multi-valued input parameters metadata from the Relying + * Party Metadata Choices draft, provided that their underlying feature is also enabled: + * + * - subject_types_supported + * - id_token_signing_alg_values_supported + * - id_token_encryption_alg_values_supported + * - id_token_encryption_enc_values_supported + * - userinfo_signing_alg_values_supported + * - userinfo_encryption_alg_values_supported + * - userinfo_encryption_enc_values_supported + * - request_object_signing_alg_values_supported + * - request_object_encryption_alg_values_supported + * - request_object_encryption_enc_values_supported + * - token_endpoint_auth_methods_supported + * - token_endpoint_auth_signing_alg_values_supported + * - introspection_signing_alg_values_supported + * - introspection_encryption_alg_values_supported + * - introspection_encryption_enc_values_supported + * - authorization_signing_alg_values_supported + * - authorization_encryption_alg_values_supported + * - authorization_encryption_enc_values_supported + * - backchannel_authentication_request_signing_alg_values_supported + */ rpMetadataChoices?: { enabled?: boolean | undefined; } | undefined; + /** + * External Signing Support + * + * This is an experimental feature. + * + * Specifies whether external signing capabilities shall be enabled. When enabled, the authorization server + * shall support the use of `ExternalSigningKey` class instances in place of private JWK entries within the + * `jwks.keys` configuration array. This feature enables Digital Signature Algorithm operations (such as + * PS256, ES256, or other supported algorithms) to be performed by external cryptographic services, + * including Key Management Services (KMS) and Hardware Security Modules (HSM), providing enhanced security + * for private key material through externalized signing operations. + * + * @see [KMS integration with AWS Key Management Service](https://github.com/panva/node-oidc-provider/discussions/1316) + */ externalSigningSupport?: { enabled?: boolean | undefined; ack?: string | undefined; - [key: string]: any; - } | undefined; - - attestClientAuth?: { - enabled?: boolean | undefined; - ack?: string | undefined; - additionalSecuritySignal?: false | "optional" | "required" | undefined; - challengeSecret?: Buffer | undefined; - getAttestationSignaturePublicKey?: - | (( - ctx: KoaContextWithOIDC, - header: UnknownObject, - payload: UnknownObject, - client: Client, - ) => CanBePromise) - | undefined; - assertAttestationJwtAndPop?: - | (( - ctx: KoaContextWithOIDC, - attestation: JWTVerificationResult, - pop: JWTVerificationResult, - client: Client, - ) => CanBePromise) - | undefined; } | undefined; - openid4vci?: - | { - enabled?: boolean | undefined; - ack?: string | undefined; - nonceSecret?: Buffer | undefined; - preAuthorizedCodeGrant?: boolean | undefined; - metadata?: OpenID4VCIMetadata | undefined; - credentialConfigurationsSupported?: - | Record - | undefined; - credentialEndpointExpectedAudience?: - | ((ctx: KoaContextWithOIDC) => CanBePromise) - | undefined; - credentialConfigurationPolicy?: - | (( - ctx: KoaContextWithOIDC, - details: OpenID4VCICredentialContext, - ) => CanBePromise) - | undefined; - issueCredential?: - | (( - ctx: KoaContextWithOIDC, - details: OpenID4VCIIssueCredentialContext, - ) => CanBePromise) - | undefined; - getKeyAttestationSignaturePublicKey?: - | (( - ctx: KoaContextWithOIDC, - issuer: string, - header: UnknownObject, - client: Client, - ) => CanBePromise) - | undefined; - } - | undefined; - } + /** + * [draft-ietf-oauth-attestation-based-client-auth-10](https://www.ietf.org/archive/id/draft-ietf-oauth-attestation-based-client-auth-10.html) + * - OAuth 2.0 Attestation-Based Client Authentication + * + * This is an experimental feature. + * + * **Important:** + * + * The following default helper implementations in this option include placeholders and MUST be replaced by + * a deployment before use. + * - `getAttestationSignaturePublicKey` + * + * Specifies whether Attestation-Based Client Authentication capabilities shall be enabled. When enabled, + * the authorization server shall support the `attest_jwt_client_auth` authentication method within the + * server's `clientAuthMethods` configuration. This mechanism enables Client Instances to authenticate using + * a Client Attestation JWT issued by a trusted Client Attester and a corresponding Client Attestation + * Proof-of-Possession JWT generated by the Client Instance. It can also enable Client Attestation as an + * additional security signal alongside existing Client Authentication methods using the + * `attestation_pop_jwt` Proof-of-Possession method. + */ + attestClientAuth?: AttestClientAuthConfiguration | undefined; + } & ConditionalRichAuthorizationRequestFeatures | undefined; + /** + * Additional Access Token Claims + * + * Specifies a helper function that shall be invoked to add additional claims to Access Tokens during the token + * issuance process. For opaque Access Tokens, the returned claims shall be stored in the authorization server + * storage under the `extra` property and shall be returned by the introspection endpoint as top-level claims. For + * JWT-formatted Access Tokens, the returned claims shall be included as top-level claims within the JWT payload. + * Claims returned by this function will not overwrite pre-existing top-level claims in the token. + */ extraTokenClaims?: - | ((ctx: KoaContextWithOIDC, token: AccessToken | ClientCredentials) => CanBePromise) + | (( + ctx: KoaContextWithOIDC, + token: AccessToken | ClientCredentials, + ) => CanBePromise) | undefined; - fetch?: typeof fetch; + /** + * Fetching External Resources + * + * Specifies a function that shall be invoked whenever the authorization server needs to make calls to external + * HTTPS resources. The interface and expected return value shall conform to the + * [Fetch API specification](https://fetch.spec.whatwg.org/) + * [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) standard. + * + * Before each invocation the authorization server sets the following fetch options: + * - `signal` to `AbortSignal.timeout(2500)` + * - `headers` to a new `Headers` instance with the `user-agent` header set to an empty string in order to remove + * the default one + * - `dispatcher` to a custom `undici.Agent` that rejects connections to private, loopback, and other + * non-globally-routable IP addresses, preventing Server-Side Request Forgery (SSRF) + */ + fetch?: + | (( + input: string | URL | Request, + init?: RequestInit, + ) => Promise) + | undefined; + /** + * Fetch Response Body Size Limits + * + * Specifies per-purpose maximum response body size limits (in bytes) for external HTTPS resource fetches. When a + * limit is defined for a given purpose, the authorization server will bail out early on `Content-Length` header + * values exceeding the limit and will also abort reading the response body when the accumulated size exceeds the + * limit. Purposes with a limit of `Infinity` will not enforce any size restriction. + */ fetchResponseBodyLimits?: | { "client_id metadata document"?: number | undefined; @@ -1659,30 +3738,126 @@ export interface Configuration { } | undefined; + /** + * Session-Bound Token Expiration + * + * Specifies a helper function that shall be invoked to determine whether authorization codes, device codes, or + * authorization-endpoint-returned opaque access tokens shall be bound to the end-user session. When session binding + * is enabled, this policy shall be applied to all opaque tokens issued from the authorization code, device code, or + * subsequent refresh token exchanges. When artifacts are session-bound, their originating session will be loaded by + * its unique identifier every time the artifacts are encountered. Session-bound artifacts shall be effectively + * revoked when the end-user logs out, providing automatic cleanup of token state upon session termination. + * + * `true` binds the artifact to the current session; `false` leaves it usable after logout and marks the client + * authorization as persisting logout. The default returns `false` only when the source includes the + * `offline_access` scope. + */ expiresWithSession?: - | ((ctx: KoaContextWithOIDC, token: AccessToken | AuthorizationCode | DeviceCode) => CanBePromise) + | (( + ctx: KoaContextWithOIDC, + source: AccessToken | AuthorizationCode | DeviceCode, + ) => CanBePromise) | undefined; + /** + * Refresh Token Issuance Policy + * + * Specifies a helper function that shall be invoked to determine whether a refresh token shall be issued during + * token endpoint operations. This function enables policy-based control over refresh token issuance according to + * authorization server requirements, client capabilities, and granted scope values. + * + * `true` issues a refresh token and `false` does not. The default requires both the `refresh_token` grant type and + * the `offline_access` scope. + */ issueRefreshToken?: | (( ctx: KoaContextWithOIDC, client: Client, - code: AuthorizationCode | DeviceCode | BackchannelAuthenticationRequest | PreAuthorizedCode, + source: AuthorizationCode | DeviceCode | BackchannelAuthenticationRequest | PreAuthorizedCode, ) => CanBePromise) | undefined; + /** + * JSON Web Key Set (JWKS) + * + * Specifies the JSON Web Key Set that shall be used by the authorization server for cryptographic signing and + * decryption operations. The key set MUST be provided in + * [JWK Set format](https://www.rfc-editor.org/info/rfc7517/#section-5) as defined in RFC 7517. All keys within the + * set MUST be private keys. + * + * Supported key types include: + * + * - RSA + * - OKP (Ed25519 and X25519 subtypes) + * - EC (P-256, P-384, and P-521 curves) + * + * **Recommendation:** Be sure to follow best practices for distributing private keying material and secrets for + * your respective target deployment environment. + * + * **Recommendation:** The following action order is recommended when rotating signing keys on a distributed + * deployment with rolling reloads in place. + * + * 1. push new keys at the very end of the "keys" array in your JWKS, this means the keys will become available for + * verification should they be encountered but not yet used for signing + * 2. reload all your processes + * 3. move your new key to the very front of the "keys" array in your JWKS, this means the key will be used for + * signing after reload + * 4. reload all your processes + */ jwks?: JWKS | undefined; + /** + * Supported response_type Values + * + * Specifies the response_type values supported by this authorization server. In accordance with RFC 9700 (OAuth 2.0 + * Security Best Current Practice), the default configuration excludes response types that result in access tokens + * being issued directly by the authorization endpoint. + */ responseTypes?: readonly ResponseType[] | undefined; + /** + * Grant Revocation Policy + * + * Specifies a helper function that shall be invoked to determine whether an underlying Grant entry shall be revoked + * in addition to the specific token or code being processed. This function enables enforcement of grant revocation + * policies according to authorization server security requirements. The function is invoked in the following + * contexts: + * - RP-Initiated Logout + * - Opaque Access Token Revocation + * - Refresh Token Revocation + * - Authorization Code re-use + * - Device Code re-use + * - Backchannel Authentication Request re-use + * - Rotated Refresh Token re-use + * + * The current route and token models are available from `ctx.oidc`. `true` destroys the underlying Grant after its + * related token artifacts are revoked and emits `grant.revoked`; `false` preserves the Grant. The default preserves + * the Grant only when revoking an AccessToken at the revocation endpoint. + */ revokeGrantPolicy?: ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; + /** + * [RFC7636](https://www.rfc-editor.org/info/rfc7636/) - Proof Key for Code Exchange (PKCE) + * + * Specifies the PKCE configuration, such as a policy check on the required use of PKCE. + */ pkce?: | { + /** + * Configures if and when the authorization server requires clients to use `PKCE`. This helper is called + * whenever an authorization request lacks the code_challenge parameter. `false` allows the request to + * continue without PKCE, while `true` rejects it. + */ required?: ((ctx: KoaContextWithOIDC, client: Client) => boolean) | undefined; } | undefined; + /** + * Endpoint URL Paths + * + * Defines the URL path mappings for authorization server endpoints. All route values are relative and shall begin + * with a forward slash ("/") character. + */ routes?: | { authorization?: string | undefined; @@ -1702,101 +3877,425 @@ export interface Configuration { } | undefined; + /** + * Supported OAuth 2.0 Scope Values + * + * Specifies additional OAuth 2.0 scope values that this authorization server shall support and advertise in its + * discovery document. Resource Server-specific scopes shall be configured via the `features.resourceIndicators` + * mechanism. + */ scopes?: readonly string[] | ReadonlySet | undefined; + /** + * Subject Identifier Types + * + * Specifies the array of Subject Identifier types that this authorization server shall support for end-user + * identification purposes. When only `pairwise` is supported, it shall become the default `subject_type` client + * metadata value. Supported identifier types shall include: + * - `public` - provides the same subject identifier value to all clients + * - `pairwise` - provides a unique subject identifier value per client to enhance privacy + */ subjectTypes?: readonly SubjectTypes[] | ReadonlySet | undefined; + /** + * Pairwise Subject Identifier Generation + * + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a helper function that shall be invoked to generate pairwise subject identifier values for ID Tokens + * and UserInfo responses, as specified in OpenID Connect Core 1.0. This function enables privacy-preserving subject + * identifier generation that provides unique identifiers per client while maintaining consistent identification for + * the same end-user across requests to the same client. + * + * The returned identifier MUST be a non-empty string that is stable for the same account and sector identifier + * while remaining unlinkable across sectors. + * + * **Recommendation:** Implementations should employ memoization or caching mechanisms when this function may be + * invoked multiple times with identical arguments within a single request. + */ pairwiseIdentifier?: - | ((ctx: KoaContextWithOIDC, accountId: string, client: Client) => CanBePromise) + | (( + ctx: KoaContextWithOIDC, + accountId: string, + client: Client, + ) => CanBePromise) | undefined; + /** + * Supported Client Authentication Methods + * + * Specifies the client authentication methods that this authorization server shall support for authenticating + * clients at the token endpoint and other authenticated endpoints. + */ clientAuthMethods?: readonly ClientAuthMethod[] | ReadonlySet | undefined; + /** + * Artifact Expirations (TTL) + * + * **Important:** + * + * The following default helper implementations in this option are intended as starting points and SHOULD be + * customized by a deployment. + * - `AccessToken` + * - `BackchannelAuthenticationRequest` + * - `ClientCredentials` + * - `DeviceCode` + * - `Grant` + * - `IdToken` + * - `Interaction` + * - `PreAuthorizedCode` + * - `RefreshToken` + * - `Session` + * + * Specifies the Time-To-Live (TTL) values that shall be applied to various artifacts within the authorization + * server. Every static value and every synchronous callback result MUST be a positive safe integer number of + * seconds (`Number.isSafeInteger(value) && value > 0`). Zero, negative, fractional, `NaN`, infinite, and unsafe + * integer values are rejected. TypeScript represents this contract as `number`, so these constraints are enforced + * when the Provider is constructed for static values and whenever a configured callback is evaluated for dynamic + * values. + * + * **Recommendation:** Token TTL values should be set to the minimum duration necessary for the intended use case to + * minimize security exposure. + * + * **Recommendation:** For refresh tokens requiring extended lifetimes, consider utilizing the `rotateRefreshToken` + * configuration option, which extends effective token lifetime through rotation rather than extended initial TTL + * values. + */ ttl?: - | { - AccessToken?: TTLFunction | number | undefined; - AuthorizationCode?: TTLFunction | number | undefined; - ClientCredentials?: TTLFunction | number | undefined; - DeviceCode?: TTLFunction | number | undefined; - BackchannelAuthenticationRequest?: TTLFunction | number | undefined; - PreAuthorizedCode?: TTLFunction | number | undefined; - IdToken?: TTLFunction | number | undefined; - RefreshToken?: TTLFunction | number | undefined; - Interaction?: TTLFunction | number | undefined; - Session?: TTLFunction | number | undefined; - Grant?: TTLFunction | number | undefined; - + | ({ + AccessToken?: + | number + | ((ctx: KoaContextWithOIDC, token: AccessToken, client: Client) => number) + | undefined; + AuthorizationCode?: + | number + | ((ctx: KoaContextWithOIDC, code: AuthorizationCode, client: Client) => number) + | undefined; + BackchannelAuthenticationRequest?: + | number + | ((ctx: KoaContextWithOIDC, request: BackchannelAuthenticationRequest, client: Client) => number) + | undefined; + ClientCredentials?: + | number + | ((ctx: KoaContextWithOIDC, token: ClientCredentials, client: Client) => number) + | undefined; + DeviceCode?: number | ((ctx: KoaContextWithOIDC, code: DeviceCode, client: Client) => number) | undefined; + Grant?: number | ((ctx: KoaContextWithOIDC, grant: Grant) => number) | undefined; + IdToken?: number | ((ctx: KoaContextWithOIDC, token: IdToken, client: Client) => number) | undefined; + Interaction?: number | ((ctx: KoaContextWithOIDC, interaction: Interaction) => number) | undefined; + PreAuthorizedCode?: number | ((ctx: KoaContextWithOIDC, code: PreAuthorizedCode) => number) | undefined; + RefreshToken?: + | number + | ((ctx: KoaContextWithOIDC, token: RefreshToken, client: Client) => number) + | undefined; + Session?: number | ((ctx: KoaContextWithOIDC, session: Session) => number) | undefined; [key: string]: unknown; - } + }) | undefined; + /** + * Loading Existing Grants + * + * Helper function invoked to load existing authorization grants that may be used to resolve an Authorization + * Request without requiring additional end-user interaction. The default implementation attempts to load grants + * based on the interaction result's `consent.grantId` property, falling back to the existing grantId for the + * requesting client in the current session. + */ loadExistingGrant?: ((ctx: KoaContextWithOIDC) => CanBePromise) | undefined; + /** + * Custom Client Metadata Properties + * + * Specifies the configuration for custom client metadata properties that shall be supported by the authorization + * server for client registration and metadata validation purposes. This configuration enables extension of standard + * OAuth 2.0 and OpenID Connect client metadata with deployment-specific properties. Existing standards-defined + * properties are snakeCased on a Client instance (e.g. `client.redirectUris`), while new properties defined by this + * configuration shall be available with their names verbatim (e.g. `client['urn:example:client:my-property']`). + */ extraClientMetadata?: | { + /** + * Specifies an array of property names that clients shall be allowed to have defined within their client + * metadata during registration and management operations. Each property name listed here extends the + * standard client metadata schema according to authorization server policy. + */ properties?: readonly string[] | undefined; + /** + * Specifies a validator function that shall be executed in order once for every property defined in + * `extraClientMetadata.properties`, regardless of its value or presence in the client metadata passed + * during registration or update operations. The function MUST be synchronous; async validators and any + * returned thenable are rejected during runtime. To modify the current client metadata values (for the + * current key or any other) simply modify the passed in `metadata` argument within the validator function. + * `ctx` is provided for registration and update requests and is `undefined` for other Client construction + * paths. + */ validator?: | (( - ctx: KoaContextWithOIDC, + ctx: KoaContextWithOIDC | undefined, key: string, value: unknown, metadata: ClientMetadata, - // eslint-disable-next-line @typescript-eslint/no-invalid-void-type ) => void | undefined) | undefined; } | undefined; + /** + * Refresh Token Rotation Policy + * + * Specifies the refresh token rotation policy that shall be applied by the authorization server when refresh tokens + * are used. This configuration determines whether and under what conditions refresh tokens shall be rotated. + * Supported values include: + * - `false` - refresh tokens shall not be rotated and their initial expiration date is final + * - `true` - refresh tokens shall be rotated when used, with the current token marked as consumed and a new one + * issued with new TTL; when a consumed refresh token is encountered an error shall be returned and the whole + * token chain (grant) is revoked + * - `function` - a function returning true/false that shall be invoked to determine whether rotation should occur + * based on request context and authorization server policy + * + * The default configuration value implements a sensible refresh token rotation policy that: + * - only allows refresh tokens to be rotated (have their TTL prolonged by issuing a new one) for one year + * - otherwise always rotates public client tokens that are not sender-constrained + * - otherwise only rotates tokens if they're being used close to their expiration (>= 70% TTL passed) + * + * The RefreshToken and Client are available as `ctx.oidc.entities.RefreshToken` and `ctx.oidc.entities.Client`. + * `true` consumes the presented token and issues a rotated refresh token; `false` continues without rotation. A + * configured literal Boolean applies that decision without invoking a function. + */ rotateRefreshToken?: ((ctx: KoaContextWithOIDC) => CanBePromise) | boolean | undefined; + /** + * Error Response Rendering + * + * **Important:** + * + * The default helper implementation is intended as a starting point and SHOULD be customized by a deployment. + * + * Specifies a function that shall be invoked to present error responses to the User-Agent during authorization + * server operations. This function enables customization of error presentation according to deployment-specific + * user interface requirements. + */ renderError?: | (( ctx: KoaContextWithOIDC, out: ErrorOut, error: errors.OIDCProviderError | Error, - ) => CanBePromise) // eslint-disable-line @typescript-eslint/no-invalid-void-type + ) => CanBePromise) | undefined; + /** + * Redirect URI Parameter Omission for Single Registered URI + * + * Specifies whether clients may omit the `redirect_uri` parameter in authorization requests when only a single + * redirect URI is registered in their client metadata. When enabled, the authorization server shall automatically + * use the sole registered redirect URI for clients that have exactly one URI configured. + * + * When disabled, all authorization requests MUST explicitly include the `redirect_uri` parameter regardless of the + * number of registered redirect URIs. + */ allowOmittingSingleRegisteredRedirectUri?: boolean | undefined; + /** + * Query Parameter Access Tokens + * + * Controls whether access tokens may be transmitted via URI query parameters. Several OAuth 2.0 and OpenID Connect + * profiles require that access tokens be transmitted exclusively via the Authorization header. When set to false, + * the authorization server shall reject requests attempting to transmit access tokens via query parameters. + */ acceptQueryParamAccessTokens?: boolean | undefined; + /** + * End-User Interaction Policy + * + * Specifies the configuration for interaction policy and end-user redirection that shall be applied to determine + * when user interaction is required during the authorization process. This configuration enables customization of + * authentication and consent flows according to deployment-specific requirements. + */ interactions?: | { - policy?: readonly interactionPolicy.Prompt[] | undefined; - url?: ((ctx: KoaContextWithOIDC, interaction: Interaction) => CanBePromise) | undefined; + /** + * Specifies the structure of Prompts and their associated checks that shall be applied during authorization + * request processing. The policy is formed by Prompt and Check class instances that define the conditions + * under which user interaction is required. The default policy implementation provides a fresh instance + * that can be customized, and the relevant classes are exported for configuration purposes. All checks + * belonging to a Prompt are evaluated concurrently. Checks within the same Prompt MUST NOT depend on + * evaluation order or on mutations performed by another check. + */ + policy?: (readonly interactionPolicy.Prompt[]) | undefined; + /** + * Specifies a function that shall be invoked to determine the destination URL for redirecting the + * User-Agent when user interaction is required during authorization processing. This function enables + * customization of the interaction endpoint location and may return both absolute and relative URLs + * according to deployment requirements. + */ + url?: + | (( + ctx: KoaContextWithOIDC, + interaction: Interaction, + ) => CanBePromise) + | undefined; } | undefined; + /** + * Account Loading and Claims Resolution + * + * **Important:** + * + * The default helper implementation is a placeholder and MUST be replaced by a deployment before use. + * + * Specifies a function that shall be invoked to load an account and retrieve its available claims during + * authorization server operations. This function enables the authorization server to resolve end-user account + * information based on the provided account identifier. The returned Account contains an `accountId` property and a + * `claims()` method that returns the claims supported by the issuer; `claims()` may also be asynchronous. Return + * `undefined` when the account cannot be loaded. + */ findAccount?: FindAccount | undefined; + /** + * Sector Identifier URI Validation + * + * Specifies a function that shall be invoked to determine whether the sectorIdentifierUri of a client being loaded, + * registered, or updated should be fetched and its contents validated against the client metadata. + */ sectorIdentifierUriValidate?: ((client: Client) => boolean) | undefined; + /** + * Supported JSON Web Algorithms (JWA) + * + * Specifies the JSON Web Algorithm (JWA) values supported by this authorization server for various cryptographic + * operations, as defined in RFC 7518 and related specifications. + */ enabledJWA?: | { + /** + * JWE "alg" Algorithm values the authorization server supports for JWT Authorization response (`JARM`) + * encryption + */ authorizationEncryptionAlgValues?: readonly EncryptionAlgValues[] | undefined; + /** + * JWE "enc" Content Encryption Algorithm values the authorization server supports to encrypt JWT + * Authorization Responses (`JARM`) with + */ authorizationEncryptionEncValues?: readonly EncryptionEncValues[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to sign JWT Authorization Responses (`JARM`) + * with + */ authorizationSigningAlgValues?: readonly SigningAlgorithm[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to verify signed DPoP proof JWTs with + */ dPoPSigningAlgValues?: readonly AsymmetricSigningAlgorithm[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to verify signed Client Attestation and + * Client Attestation PoP JWTs with + */ attestSigningAlgValues?: readonly AsymmetricSigningAlgorithm[] | undefined; + /** + * JWE "alg" Algorithm values the authorization server supports for ID Token encryption + */ idTokenEncryptionAlgValues?: readonly EncryptionAlgValues[] | undefined; + /** + * JWE "enc" Content Encryption Algorithm values the authorization server supports to encrypt ID Tokens with + */ idTokenEncryptionEncValues?: readonly EncryptionEncValues[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to sign ID Tokens with. + */ idTokenSigningAlgValues?: readonly SigningAlgorithmWithNone[] | undefined; + /** + * JWE "alg" Algorithm values the authorization server supports for JWT Introspection response encryption + */ introspectionEncryptionAlgValues?: readonly EncryptionAlgValues[] | undefined; + /** + * JWE "enc" Content Encryption Algorithm values the authorization server supports to encrypt JWT + * Introspection responses with + */ introspectionEncryptionEncValues?: readonly EncryptionEncValues[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to sign JWT Introspection responses with + */ introspectionSigningAlgValues?: readonly SigningAlgorithmWithNone[] | undefined; + /** + * JWE "alg" Algorithm values the authorization server supports to receive encrypted Request Objects (`JAR`) + * with + */ requestObjectEncryptionAlgValues?: readonly EncryptionAlgValues[] | undefined; + /** + * JWE "enc" Content Encryption Algorithm values the authorization server supports to decrypt Request + * Objects (`JAR`) with + */ requestObjectEncryptionEncValues?: readonly EncryptionEncValues[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to receive signed Request Objects (`JAR`) + * with + */ requestObjectSigningAlgValues?: readonly SigningAlgorithmWithNone[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports for signed JWT Client Authentication + * (`private_key_jwt` and `client_secret_jwt`) + */ clientAuthSigningAlgValues?: readonly SigningAlgorithm[] | undefined; + /** + * JWE "alg" Algorithm values the authorization server supports for UserInfo Response encryption + */ userinfoEncryptionAlgValues?: readonly EncryptionAlgValues[] | undefined; + /** + * JWE "enc" Content Encryption Algorithm values the authorization server supports to encrypt UserInfo + * responses with + */ userinfoEncryptionEncValues?: readonly EncryptionEncValues[] | undefined; + /** + * JWS "alg" Algorithm values the authorization server supports to sign UserInfo responses with + */ userinfoSigningAlgValues?: readonly SigningAlgorithmWithNone[] | undefined; } | undefined; } +export class ExternalSigningKey { + get alg(): string | undefined; + get crv(): string | undefined; + get e(): string | undefined; + get key_ops(): string[] | undefined; + get kid(): string | undefined; + get kty(): string; + get n(): string | undefined; + get pub(): string | undefined; + get use(): "sig"; + get x(): string | undefined; + get x5c(): string[] | undefined; + get y(): string | undefined; + + keyObject(): Promise | crypto.KeyObject; + + sign(data: Uint8Array): Promise | Uint8Array; +} + +interface ProviderAdditionalEventMap { + "backchannel_authentication.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "challenge.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "code_verification.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "credential.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "device_authorization.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "device_authorization.success": (ctx: KoaContextWithOIDC, body: UnknownObject) => void; + "device_resume.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "end_session_confirm.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "end_session_success.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "initial_access_token.destroyed": (token: InitialAccessToken) => void; + "initial_access_token.saved": (token: InitialAccessToken) => void; + "openid_credential_issuer.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; + "pre_authorized_code.consumed": (code: PreAuthorizedCode) => void; + "pre_authorized_code.destroyed": (code: PreAuthorizedCode) => void; + "pre_authorized_code.saved": (code: PreAuthorizedCode) => void; +} +// END GENERATED OIDC-PROVIDER CONTRACTS +/* eslint-enable @typescript-eslint/no-invalid-void-type */ + export interface HttpOptions { signal?: AbortSignal | undefined; agent?: http.Agent | https.Agent | undefined; @@ -1867,30 +4366,6 @@ export interface InteractionResults { [key: string]: unknown; } -interface ProviderAdditionalEventMap { - "backchannel_authentication.error": ( - ctx: KoaContextWithOIDC, - err: errors.OIDCProviderError, - ) => void; - "challenge.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "code_verification.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "credential.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "device_authorization.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "device_authorization.success": (ctx: KoaContextWithOIDC, body: UnknownObject) => void; - "device_resume.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "end_session_confirm.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "end_session_success.error": (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void; - "initial_access_token.destroyed": (token: InitialAccessToken) => void; - "initial_access_token.saved": (token: InitialAccessToken) => void; - "openid_credential_issuer.error": ( - ctx: KoaContextWithOIDC, - err: errors.OIDCProviderError, - ) => void; - "pre_authorized_code.consumed": (code: PreAuthorizedCode) => void; - "pre_authorized_code.destroyed": (code: PreAuthorizedCode) => void; - "pre_authorized_code.saved": (code: PreAuthorizedCode) => void; -} - export default class Provider extends Koa { constructor(issuer: string, configuration?: Configuration); @@ -1903,6 +4378,7 @@ export default class Provider extends Koa { static get ctx(): KoaContextWithOIDC | undefined; + // BEGIN GENERATED OIDC-PROVIDER MEMBERS urlFor(name: string, options?: UnknownObject): string; pathFor(name: string, options?: UnknownObject & { mountPath?: string | undefined }): string; cookieName(type: string): string; @@ -1949,9 +4425,9 @@ export default class Provider extends Koa { res: http.ServerResponse | http2.Http2ServerResponse, ): Promise; - registerGrantType( + registerGrantType( name: string, - handler: (ctx: KoaContextWithOIDC, next: () => Promise) => CanBePromise, + handler: (ctx: TokenEndpointGrantContext) => CanBePromise, params?: string | readonly string[] | ReadonlySet, duplicates?: string | readonly string[] | ReadonlySet, ): void; @@ -1960,99 +4436,105 @@ export default class Provider extends Koa { addListener(event: "access_token.destroyed", listener: (accessToken: AccessToken) => void): this; addListener(event: "access_token.saved", listener: (accessToken: AccessToken) => void): this; addListener(event: "access_token.issued", listener: (accessToken: AccessToken) => void): this; - addListener(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; - addListener(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; addListener(event: "authorization_code.consumed", listener: (authorizationCode: AuthorizationCode) => void): this; - addListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; - addListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; - addListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + addListener(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; + addListener(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; + addListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; addListener( - event: "backchannel_authentication_request.saved", - listener: (request: BackchannelAuthenticationRequest) => void, + event: "authorization.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( - event: "backchannel_authentication_request.destroyed", - listener: (request: BackchannelAuthenticationRequest) => void, + event: "authorization.success", + listener: (ctx: KoaContextWithOIDC, response?: UnknownObject) => void, ): this; addListener( - event: "backchannel_authentication_request.consumed", - listener: (request: BackchannelAuthenticationRequest) => void, + event: "backchannel.error", + listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, ): this; - addListener(event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void): this; - addListener(event: "client_credentials.saved", listener: (clientCredentials: ClientCredentials) => void): this; - addListener(event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void): this; - addListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; - addListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; - addListener(event: "session.destroyed", listener: (session: Session) => void): this; - addListener(event: "session.saved", listener: (session: Session) => void): this; - addListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; - addListener(event: "grant.saved", listener: (grant: Grant) => void): this; - addListener(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; - addListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; addListener( - event: "pushed_authorization_request.destroyed", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, + event: "backchannel.success", + listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, ): this; addListener( - event: "pushed_authorization_request.saved", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, + event: "backchannel_authentication_request.consumed", + listener: (request: BackchannelAuthenticationRequest) => void, ): this; addListener( - event: "registration_access_token.destroyed", - listener: (registrationAccessToken: RegistrationAccessToken) => void, + event: "backchannel_authentication_request.destroyed", + listener: (request: BackchannelAuthenticationRequest) => void, ): this; addListener( - event: "registration_access_token.saved", - listener: (registrationAccessToken: RegistrationAccessToken) => void, + event: "backchannel_authentication_request.saved", + listener: (request: BackchannelAuthenticationRequest) => void, ): this; - addListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; - addListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; - addListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; - addListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; - addListener(event: "authorization.success", listener: (ctx: KoaContextWithOIDC) => void): this; + addListener(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + addListener(event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void): this; + addListener(event: "client_credentials.saved", listener: (clientCredentials: ClientCredentials) => void): this; + addListener(event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void): this; + addListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + addListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; + addListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; addListener( - event: "authorization.error", + event: "discovery.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - addListener(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; addListener( event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + addListener(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; + addListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; + addListener(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + addListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; + addListener(event: "grant.saved", listener: (grant: Grant) => void): this; addListener(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; + addListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; addListener(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; + addListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; addListener( event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void, ): this; - addListener(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - addListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; addListener( - event: "backchannel.success", - listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, + event: "introspection.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + addListener(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; + addListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; addListener( - event: "backchannel.error", - listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + event: "pushed_authorization_request.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( event: "pushed_authorization_request.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; addListener( - event: "pushed_authorization_request.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "pushed_authorization_request.destroyed", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; addListener( - event: "registration_update.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, + event: "pushed_authorization_request.saved", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; + addListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; + addListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; + addListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; addListener( - event: "registration_update.error", + event: "registration_access_token.destroyed", + listener: (registrationAccessToken: RegistrationAccessToken) => void, + ): this; + addListener( + event: "registration_access_token.saved", + listener: (registrationAccessToken: RegistrationAccessToken) => void, + ): this; + addListener( + event: "registration_create.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( - event: "registration_delete.success", + event: "registration_create.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; addListener( @@ -2060,51 +4542,55 @@ export default class Provider extends Koa { listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( - event: "registration_create.success", + event: "registration_delete.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; addListener( - event: "registration_create.error", + event: "registration_read.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( - event: "introspection.error", + event: "registration_update.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; addListener( - event: "registration_read.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_update.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; - addListener(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; addListener( - event: "discovery.error", + event: "revocation.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + addListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; + addListener(event: "session.destroyed", listener: (session: Session) => void): this; + addListener(event: "session.saved", listener: (session: Session) => void): this; addListener( event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - addListener( - event: "revocation.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, - ): this; addListener( event: Event, listener: ProviderAdditionalEventMap[Event], ): this; - addListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; - on(event: "access_token.destroyed", listener: (accessToken: AccessToken) => void): this; on(event: "access_token.saved", listener: (accessToken: AccessToken) => void): this; on(event: "access_token.issued", listener: (accessToken: AccessToken) => void): this; - on(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; - on(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; on(event: "authorization_code.consumed", listener: (authorizationCode: AuthorizationCode) => void): this; - on(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; - on(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; - on(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + on(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; + on(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; + on(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; + on(event: "authorization.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "authorization.success", listener: (ctx: KoaContextWithOIDC, response?: UnknownObject) => void): this; on( - event: "backchannel_authentication_request.saved", + event: "backchannel.error", + listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + ): this; + on( + event: "backchannel.success", + listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, + ): this; + on( + event: "backchannel_authentication_request.consumed", listener: (request: BackchannelAuthenticationRequest) => void, ): this; on( @@ -2112,20 +4598,39 @@ export default class Provider extends Koa { listener: (request: BackchannelAuthenticationRequest) => void, ): this; on( - event: "backchannel_authentication_request.consumed", + event: "backchannel_authentication_request.saved", listener: (request: BackchannelAuthenticationRequest) => void, ): this; + on(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; on(event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void): this; on(event: "client_credentials.saved", listener: (clientCredentials: ClientCredentials) => void): this; on(event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void): this; - on(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; - on(event: "interaction.saved", listener: (interaction: Interaction) => void): this; - on(event: "session.destroyed", listener: (session: Session) => void): this; - on(event: "session.saved", listener: (session: Session) => void): this; + on(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + on(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; + on(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; + on(event: "discovery.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; on(event: "grant.destroyed", listener: (grant: Grant) => void): this; + on(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; on(event: "grant.saved", listener: (grant: Grant) => void): this; + on(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; + on(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; + on(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; + on(event: "interaction.saved", listener: (interaction: Interaction) => void): this; + on(event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void): this; + on(event: "introspection.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; on(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; on(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; + on( + event: "pushed_authorization_request.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; + on( + event: "pushed_authorization_request.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, + ): this; on( event: "pushed_authorization_request.destroyed", listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, @@ -2134,6 +4639,9 @@ export default class Provider extends Koa { event: "pushed_authorization_request.saved", listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; + on(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; + on(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; + on(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; on( event: "registration_access_token.destroyed", listener: (registrationAccessToken: RegistrationAccessToken) => void, @@ -2142,76 +4650,56 @@ export default class Provider extends Koa { event: "registration_access_token.saved", listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; - on(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; - on(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; - on(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; - on(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; - on(event: "authorization.success", listener: (ctx: KoaContextWithOIDC) => void): this; - on(event: "authorization.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - on(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; - on(event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - on(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; - on(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; - on(event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void): this; - on(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - on(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; - on( - event: "backchannel.success", - listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, - ): this; - on( - event: "backchannel.error", - listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, - ): this; on( - event: "pushed_authorization_request.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, - ): this; - on( - event: "pushed_authorization_request.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, - ): this; - on(event: "registration_update.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; - on( - event: "registration_update.error", + event: "registration_create.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - on(event: "registration_delete.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; + on(event: "registration_create.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; on( event: "registration_delete.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - on(event: "registration_create.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; + on(event: "registration_delete.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; on( - event: "registration_create.error", + event: "registration_read.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - on(event: "introspection.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; on( - event: "registration_read.error", + event: "registration_update.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - on(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - on(event: "discovery.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - on(event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "registration_update.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; on(event: "revocation.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + on(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; + on(event: "session.destroyed", listener: (session: Session) => void): this; + on(event: "session.saved", listener: (session: Session) => void): this; + on(event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; on( event: Event, listener: ProviderAdditionalEventMap[Event], ): this; - on(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; - once(event: "access_token.destroyed", listener: (accessToken: AccessToken) => void): this; once(event: "access_token.saved", listener: (accessToken: AccessToken) => void): this; once(event: "access_token.issued", listener: (accessToken: AccessToken) => void): this; - once(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; - once(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; once(event: "authorization_code.consumed", listener: (authorizationCode: AuthorizationCode) => void): this; - once(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; - once(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; - once(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + once(event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void): this; + once(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; + once(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; once( - event: "backchannel_authentication_request.saved", + event: "authorization.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; + once(event: "authorization.success", listener: (ctx: KoaContextWithOIDC, response?: UnknownObject) => void): this; + once( + event: "backchannel.error", + listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + ): this; + once( + event: "backchannel.success", + listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, + ): this; + once( + event: "backchannel_authentication_request.consumed", listener: (request: BackchannelAuthenticationRequest) => void, ): this; once( @@ -2219,20 +4707,42 @@ export default class Provider extends Koa { listener: (request: BackchannelAuthenticationRequest) => void, ): this; once( - event: "backchannel_authentication_request.consumed", + event: "backchannel_authentication_request.saved", listener: (request: BackchannelAuthenticationRequest) => void, ): this; + once(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; once(event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void): this; once(event: "client_credentials.saved", listener: (clientCredentials: ClientCredentials) => void): this; once(event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void): this; - once(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; - once(event: "interaction.saved", listener: (interaction: Interaction) => void): this; - once(event: "session.destroyed", listener: (session: Session) => void): this; - once(event: "session.saved", listener: (session: Session) => void): this; + once(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + once(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; + once(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; + once(event: "discovery.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + once(event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + once(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; once(event: "grant.destroyed", listener: (grant: Grant) => void): this; + once(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + once(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; once(event: "grant.saved", listener: (grant: Grant) => void): this; + once(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; + once(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; + once(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; + once(event: "interaction.saved", listener: (interaction: Interaction) => void): this; + once(event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void): this; + once( + event: "introspection.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; once(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; once(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; + once( + event: "pushed_authorization_request.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; + once( + event: "pushed_authorization_request.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, + ): this; once( event: "pushed_authorization_request.destroyed", listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, @@ -2241,6 +4751,9 @@ export default class Provider extends Koa { event: "pushed_authorization_request.saved", listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; + once(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; + once(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; + once(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; once( event: "registration_access_token.destroyed", listener: (registrationAccessToken: RegistrationAccessToken) => void, @@ -2249,88 +4762,65 @@ export default class Provider extends Koa { event: "registration_access_token.saved", listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; - once(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; - once(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; - once(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; - once(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; - once(event: "authorization.success", listener: (ctx: KoaContextWithOIDC) => void): this; - once( - event: "authorization.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, - ): this; - once(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; - once(event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - once(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; - once(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; - once(event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void): this; - once(event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - once(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; - once( - event: "backchannel.success", - listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, - ): this; - once( - event: "backchannel.error", - listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, - ): this; - once( - event: "pushed_authorization_request.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, - ): this; - once( - event: "pushed_authorization_request.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, - ): this; - once(event: "registration_update.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; - once( - event: "registration_update.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, - ): this; - once(event: "registration_delete.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; once( - event: "registration_delete.error", + event: "registration_create.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; once(event: "registration_create.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; once( - event: "registration_create.error", + event: "registration_delete.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + once(event: "registration_delete.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; once( - event: "introspection.error", + event: "registration_read.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; once( - event: "registration_read.error", + event: "registration_update.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - once(event: "jwks.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - once(event: "discovery.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; - once(event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + once(event: "registration_update.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void): this; once(event: "revocation.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; + once(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; + once(event: "session.destroyed", listener: (session: Session) => void): this; + once(event: "session.saved", listener: (session: Session) => void): this; + once(event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void): this; once( event: Event, listener: ProviderAdditionalEventMap[Event], ): this; - once(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; - prependListener(event: "access_token.destroyed", listener: (accessToken: AccessToken) => void): this; prependListener(event: "access_token.saved", listener: (accessToken: AccessToken) => void): this; prependListener(event: "access_token.issued", listener: (accessToken: AccessToken) => void): this; - prependListener(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; + prependListener( + event: "authorization_code.consumed", + listener: (authorizationCode: AuthorizationCode) => void, + ): this; prependListener( event: "authorization_code.destroyed", listener: (authorizationCode: AuthorizationCode) => void, ): this; + prependListener(event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void): this; + prependListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; + prependListener( + event: "authorization.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; + prependListener( + event: "authorization.success", + listener: (ctx: KoaContextWithOIDC, response?: UnknownObject) => void, + ): this; + prependListener( + event: "backchannel.error", + listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + ): this; prependListener( - event: "authorization_code.consumed", - listener: (authorizationCode: AuthorizationCode) => void, + event: "backchannel.success", + listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, ): this; - prependListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; - prependListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; - prependListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; prependListener( - event: "backchannel_authentication_request.saved", + event: "backchannel_authentication_request.consumed", listener: (request: BackchannelAuthenticationRequest) => void, ): this; prependListener( @@ -2338,94 +4828,81 @@ export default class Provider extends Koa { listener: (request: BackchannelAuthenticationRequest) => void, ): this; prependListener( - event: "backchannel_authentication_request.consumed", + event: "backchannel_authentication_request.saved", listener: (request: BackchannelAuthenticationRequest) => void, ): this; + prependListener( + event: "jwks.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; prependListener( event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void, ): this; prependListener(event: "client_credentials.saved", listener: (clientCredentials: ClientCredentials) => void): this; prependListener(event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void): this; - prependListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; - prependListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; - prependListener(event: "session.destroyed", listener: (session: Session) => void): this; - prependListener(event: "session.saved", listener: (session: Session) => void): this; - prependListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; - prependListener(event: "grant.saved", listener: (grant: Grant) => void): this; - prependListener(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; - prependListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; - prependListener( - event: "pushed_authorization_request.destroyed", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, - ): this; - prependListener( - event: "pushed_authorization_request.saved", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, - ): this; - prependListener( - event: "registration_access_token.destroyed", - listener: (registrationAccessToken: RegistrationAccessToken) => void, - ): this; + prependListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + prependListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; + prependListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; prependListener( - event: "registration_access_token.saved", - listener: (registrationAccessToken: RegistrationAccessToken) => void, + event: "discovery.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - prependListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; - prependListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; - prependListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; - prependListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; - prependListener(event: "authorization.success", listener: (ctx: KoaContextWithOIDC) => void): this; prependListener( - event: "authorization.error", + event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; + prependListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; prependListener( - event: "end_session.error", + event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + prependListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; + prependListener(event: "grant.saved", listener: (grant: Grant) => void): this; prependListener(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; + prependListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; prependListener(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; + prependListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; prependListener( event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void, ): this; prependListener( - event: "grant.error", + event: "introspection.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - prependListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; - prependListener( - event: "backchannel.success", - listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, - ): this; + prependListener(event: "replay_detection.destroyed", listener: (replayDetection: ReplayDetection) => void): this; + prependListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; prependListener( - event: "backchannel.error", - listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + event: "pushed_authorization_request.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( event: "pushed_authorization_request.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependListener( - event: "pushed_authorization_request.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "pushed_authorization_request.destroyed", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; prependListener( - event: "registration_update.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, + event: "pushed_authorization_request.saved", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; + prependListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; + prependListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; + prependListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; prependListener( - event: "registration_update.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_access_token.destroyed", + listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; prependListener( - event: "registration_delete.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, + event: "registration_access_token.saved", + listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; prependListener( - event: "registration_delete.error", + event: "registration_create.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( @@ -2433,44 +4910,45 @@ export default class Provider extends Koa { listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependListener( - event: "registration_create.error", + event: "registration_delete.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( - event: "introspection.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_delete.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependListener( event: "registration_read.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( - event: "jwks.error", + event: "registration_update.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( - event: "discovery.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_update.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependListener( - event: "userinfo.error", + event: "revocation.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + prependListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; + prependListener(event: "session.destroyed", listener: (session: Session) => void): this; + prependListener(event: "session.saved", listener: (session: Session) => void): this; prependListener( - event: "revocation.error", + event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependListener( event: Event, listener: ProviderAdditionalEventMap[Event], ): this; - prependListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; - prependOnceListener(event: "access_token.destroyed", listener: (accessToken: AccessToken) => void): this; prependOnceListener(event: "access_token.saved", listener: (accessToken: AccessToken) => void): this; prependOnceListener(event: "access_token.issued", listener: (accessToken: AccessToken) => void): this; prependOnceListener( - event: "authorization_code.saved", + event: "authorization_code.consumed", listener: (authorizationCode: AuthorizationCode) => void, ): this; prependOnceListener( @@ -2478,14 +4956,28 @@ export default class Provider extends Koa { listener: (authorizationCode: AuthorizationCode) => void, ): this; prependOnceListener( - event: "authorization_code.consumed", + event: "authorization_code.saved", listener: (authorizationCode: AuthorizationCode) => void, ): this; - prependOnceListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; - prependOnceListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; - prependOnceListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + prependOnceListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; prependOnceListener( - event: "backchannel_authentication_request.saved", + event: "authorization.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; + prependOnceListener( + event: "authorization.success", + listener: (ctx: KoaContextWithOIDC, response?: UnknownObject) => void, + ): this; + prependOnceListener( + event: "backchannel.error", + listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + ): this; + prependOnceListener( + event: "backchannel.success", + listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, + ): this; + prependOnceListener( + event: "backchannel_authentication_request.consumed", listener: (request: BackchannelAuthenticationRequest) => void, ): this; prependOnceListener( @@ -2493,9 +4985,13 @@ export default class Provider extends Koa { listener: (request: BackchannelAuthenticationRequest) => void, ): this; prependOnceListener( - event: "backchannel_authentication_request.consumed", + event: "backchannel_authentication_request.saved", listener: (request: BackchannelAuthenticationRequest) => void, ): this; + prependOnceListener( + event: "jwks.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + ): this; prependOnceListener( event: "client_credentials.destroyed", listener: (clientCredentials: ClientCredentials) => void, @@ -2508,88 +5004,71 @@ export default class Provider extends Koa { event: "client_credentials.issued", listener: (clientCredentials: ClientCredentials) => void, ): this; - prependOnceListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; - prependOnceListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; - prependOnceListener(event: "session.destroyed", listener: (session: Session) => void): this; - prependOnceListener(event: "session.saved", listener: (session: Session) => void): this; - prependOnceListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; - prependOnceListener(event: "grant.saved", listener: (grant: Grant) => void): this; - prependOnceListener( - event: "replay_detection.destroyed", - listener: (replayDetection: ReplayDetection) => void, - ): this; - prependOnceListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; - prependOnceListener( - event: "pushed_authorization_request.destroyed", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, - ): this; - prependOnceListener( - event: "pushed_authorization_request.saved", - listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, - ): this; - prependOnceListener( - event: "registration_access_token.destroyed", - listener: (registrationAccessToken: RegistrationAccessToken) => void, - ): this; + prependOnceListener(event: "device_code.consumed", listener: (deviceCode: DeviceCode) => void): this; + prependOnceListener(event: "device_code.destroyed", listener: (deviceCode: DeviceCode) => void): this; + prependOnceListener(event: "device_code.saved", listener: (deviceCode: DeviceCode) => void): this; prependOnceListener( - event: "registration_access_token.saved", - listener: (registrationAccessToken: RegistrationAccessToken) => void, + event: "discovery.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - prependOnceListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; - prependOnceListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; - prependOnceListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; - prependOnceListener(event: "authorization.accepted", listener: (ctx: KoaContextWithOIDC) => void): this; - prependOnceListener(event: "authorization.success", listener: (ctx: KoaContextWithOIDC) => void): this; prependOnceListener( - event: "authorization.error", + event: "end_session.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener(event: "end_session.success", listener: (ctx: KoaContextWithOIDC) => void): this; + prependOnceListener(event: "grant.destroyed", listener: (grant: Grant) => void): this; prependOnceListener( - event: "end_session.error", + event: "grant.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + prependOnceListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; + prependOnceListener(event: "grant.saved", listener: (grant: Grant) => void): this; prependOnceListener(event: "grant.success", listener: (ctx: KoaContextWithOIDC) => void): this; + prependOnceListener(event: "interaction.destroyed", listener: (interaction: Interaction) => void): this; prependOnceListener(event: "interaction.ended", listener: (ctx: KoaContextWithOIDC) => void): this; + prependOnceListener(event: "interaction.saved", listener: (interaction: Interaction) => void): this; prependOnceListener( event: "interaction.started", listener: (ctx: KoaContextWithOIDC, interaction: PromptDetail) => void, ): this; prependOnceListener( - event: "grant.error", + event: "introspection.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; - prependOnceListener(event: "grant.revoked", listener: (ctx: KoaContextWithOIDC, grantId: string) => void): this; prependOnceListener( - event: "backchannel.success", - listener: (ctx: KoaContextWithOIDC, client: Client, accountId: string, sid: string) => void, + event: "replay_detection.destroyed", + listener: (replayDetection: ReplayDetection) => void, ): this; + prependOnceListener(event: "replay_detection.saved", listener: (replayDetection: ReplayDetection) => void): this; prependOnceListener( - event: "backchannel.error", - listener: (ctx: KoaContextWithOIDC, err: Error, client: Client, accountId: string, sid: string) => void, + event: "pushed_authorization_request.error", + listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( event: "pushed_authorization_request.success", listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependOnceListener( - event: "pushed_authorization_request.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "pushed_authorization_request.destroyed", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; prependOnceListener( - event: "registration_update.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, + event: "pushed_authorization_request.saved", + listener: (pushedAuthorizationRequest: PushedAuthorizationRequest) => void, ): this; + prependOnceListener(event: "refresh_token.consumed", listener: (refreshToken: RefreshToken) => void): this; + prependOnceListener(event: "refresh_token.destroyed", listener: (refreshToken: RefreshToken) => void): this; + prependOnceListener(event: "refresh_token.saved", listener: (refreshToken: RefreshToken) => void): this; prependOnceListener( - event: "registration_update.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_access_token.destroyed", + listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; prependOnceListener( - event: "registration_delete.success", - listener: (ctx: KoaContextWithOIDC, client: Client) => void, + event: "registration_access_token.saved", + listener: (registrationAccessToken: RegistrationAccessToken) => void, ): this; prependOnceListener( - event: "registration_delete.error", + event: "registration_create.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( @@ -2597,39 +5076,42 @@ export default class Provider extends Koa { listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependOnceListener( - event: "registration_create.error", + event: "registration_delete.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( - event: "introspection.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_delete.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependOnceListener( event: "registration_read.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( - event: "jwks.error", + event: "registration_update.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( - event: "discovery.error", - listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, + event: "registration_update.success", + listener: (ctx: KoaContextWithOIDC, client: Client) => void, ): this; prependOnceListener( - event: "userinfo.error", + event: "revocation.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; + prependOnceListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; + prependOnceListener(event: "session.destroyed", listener: (session: Session) => void): this; + prependOnceListener(event: "session.saved", listener: (session: Session) => void): this; prependOnceListener( - event: "revocation.error", + event: "userinfo.error", listener: (ctx: KoaContextWithOIDC, err: errors.OIDCProviderError) => void, ): this; prependOnceListener( event: Event, listener: ProviderAdditionalEventMap[Event], ): this; - prependOnceListener(event: "server_error", listener: (ctx: KoaContextWithOIDC, err: Error) => void): this; // tslint:enable:unified-signatures + // END GENERATED OIDC-PROVIDER MEMBERS readonly Grant: typeof Grant; readonly Client: typeof Client; @@ -2655,11 +5137,12 @@ export default class Provider extends Koa { readonly Interaction: typeof Interaction; } +// BEGIN GENERATED OIDC-PROVIDER RELATED CONTRACTS declare class Checks extends Array { - get(name: string): interactionPolicy.Check | undefined; - remove(name: string): void; + get(reason: string): interactionPolicy.Check | undefined; + remove(reason: string): void; clear(): void; - add(prompt: interactionPolicy.Check, index?: number): void; + add(check: interactionPolicy.Check, index?: number): void; } export namespace interactionPolicy { @@ -2679,19 +5162,19 @@ export namespace interactionPolicy { description: string, error: string, check: (ctx: KoaContextWithOIDC) => CanBePromise, - details?: (ctx: KoaContextWithOIDC) => CanBePromise, + details?: (ctx: KoaContextWithOIDC) => CanBePromise, ); constructor( reason: string, description: string, check: (ctx: KoaContextWithOIDC) => CanBePromise, - details?: (ctx: KoaContextWithOIDC) => CanBePromise, + details?: (ctx: KoaContextWithOIDC) => CanBePromise, ); reason: string; description: string; - error: string; - details: (ctx: KoaContextWithOIDC) => CanBePromise; + error: string | undefined; + details: (ctx: KoaContextWithOIDC) => CanBePromise; check: (ctx: KoaContextWithOIDC) => CanBePromise; } @@ -2699,13 +5182,13 @@ export namespace interactionPolicy { constructor(info: { name: string; requestable?: boolean | undefined }, ...checks: Check[]); constructor( info: { name: string; requestable?: boolean | undefined }, - details: (ctx: KoaContextWithOIDC) => CanBePromise, + details: (ctx: KoaContextWithOIDC) => CanBePromise, ...checks: Check[] ); name: string; requestable: boolean; - details?: ((ctx: KoaContextWithOIDC) => Promise) | undefined; + details: (ctx: KoaContextWithOIDC) => CanBePromise; checks: Checks; } @@ -2882,24 +5365,6 @@ export namespace errors { constructor(description?: string, options?: string | OIDCProviderErrorOptions); } } - -export class ExternalSigningKey { - get alg(): string | undefined; - get crv(): string | undefined; - get e(): string | undefined; - get key_ops(): string[] | undefined; - get kid(): string | undefined; - get kty(): string; - get n(): string | undefined; - get pub(): string | undefined; - get use(): "sig"; - get x(): string | undefined; - get x5c(): string[] | undefined; - get y(): string | undefined; - - keyObject(): Promise | crypto.KeyObject; - - sign(data: Uint8Array): Promise | Uint8Array; -} +// END GENERATED OIDC-PROVIDER RELATED CONTRACTS export { Provider }; diff --git a/types/oidc-provider/lib/helpers/grants.d.ts b/types/oidc-provider/lib/helpers/grants.d.ts new file mode 100644 index 00000000000000..9ebf7c29a39413 --- /dev/null +++ b/types/oidc-provider/lib/helpers/grants.d.ts @@ -0,0 +1,205 @@ +import type * as crypto from "node:crypto"; + +import type Provider from "../../index.js"; +import type { + AccessToken, + Account, + AuthorizationDetail, + ClaimsParameter, + ClientCredentials, + errors, + Grant, + RefreshToken, + ResourceServerInstance, + TokenEndpointGrantContext, +} from "../../index.js"; + +/** + * This module is intended for custom grant implementations and is not covered + * by semantic versioning conventions. Its exports, signatures, and behavior + * may change in any release. Making this subpath an explicit package export + * would only preserve access; it would not change this compatibility policy. + */ + +/** @experimental Not covered by semantic versioning conventions. */ +export interface ClientBoundGrantSource { + clientId?: string | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface ConsumableGrantSource { + consumed?: unknown; + grantId?: string | undefined; + consume(): Promise; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface GrantSourceModel { + find(value: string, options: { ignoreExpiration: true }): Promise; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface ResourceGrantSource { + readonly scopes: Set; + claims?: ClaimsParameter | undefined; + resource?: string | string[] | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface AuthorizationDetailsSource { + rar?: readonly AuthorizationDetail[] | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface DPoPValidationResult { + thumbprint: string; + jti: string; + iat: number; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface SenderConstraints { + certificate?: string | crypto.X509Certificate | undefined; + dPoP?: DPoPValidationResult | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export type OIDCProviderErrorConstructor = new(...args: any[]) => errors.OIDCProviderError; + +/** @experimental Not covered by semantic versioning conventions. */ +export type ReservedTokenResponseParameter = + | "access_token" + | "authorization_details" + | "expires_in" + | "id_token" + | "issued_token_type" + | "refresh_token" + | "scope" + | "token_type"; + +/** @experimental Not covered by semantic versioning conventions. */ +export interface TokenResponseInput> { + accessToken: string; + tokenType: string; + authorizationDetails?: readonly AuthorizationDetail[] | undefined; + expiresIn?: number | undefined; + idToken?: string | undefined; + issuedTokenType?: string | undefined; + parameters?: + | (Parameters & Partial>) + | undefined; + refreshToken?: string | undefined; + scope?: string | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export interface TokenResponse { + access_token: string; + token_type: string; + authorization_details?: readonly AuthorizationDetail[] | undefined; + expires_in?: number | undefined; + id_token?: string | undefined; + issued_token_type?: string | undefined; + refresh_token?: string | undefined; + scope?: string | undefined; +} + +/** @experimental Not covered by semantic versioning conventions. */ +export function findGrantSource( + provider: Provider, + ctx: TokenEndpointGrantContext, + Model: GrantSourceModel, + value: string, + label: string, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function consumeGrantSource( + provider: Provider, + ctx: TokenEndpointGrantContext, + source: Source, + label: string, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function validateGrant( + provider: Provider, + ctx: TokenEndpointGrantContext, + grantId: string, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function findAccount( + provider: Provider, + ctx: TokenEndpointGrantContext, + accountId: string, + source?: object | undefined, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function validateSenderConstraints( + provider: Provider, + ctx: TokenEndpointGrantContext, + ErrorClass?: OIDCProviderErrorConstructor, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function applySenderConstraints( + provider: Provider, + ctx: TokenEndpointGrantContext, + token: AccessToken | ClientCredentials, + constraints: SenderConstraints, + ErrorClass?: OIDCProviderErrorConstructor, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function validateClientScope( + provider: Provider, + ctx: TokenEndpointGrantContext, + scopes?: string | Iterable, +): Set; + +/** @experimental Not covered by semantic versioning conventions. */ +export function resolveRequestedResources( + provider: Provider, + ctx: TokenEndpointGrantContext, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function resolveAndApplyResource( + provider: Provider, + ctx: TokenEndpointGrantContext, + source: ResourceGrantSource, + token: AccessToken, + grant: Grant, + scope?: Set | undefined, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function applyAuthorizationDetails( + provider: Provider, + ctx: TokenEndpointGrantContext, + token: AccessToken | ClientCredentials, + source?: AuthorizationDetailsSource | undefined, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function shouldIssueRefreshToken( + provider: Provider, + ctx: TokenEndpointGrantContext, + source: object, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function applyRefreshTokenBindings( + provider: Provider, + ctx: TokenEndpointGrantContext, + accessToken: AccessToken, + refreshToken: RefreshToken, +): Promise; + +/** @experimental Not covered by semantic versioning conventions. */ +export function buildTokenResponse>( + provider: Provider, + input: TokenResponseInput, +): TokenResponse & Omit; diff --git a/types/oidc-provider/oidc-provider-tests.ts b/types/oidc-provider/oidc-provider-tests.ts index 8fc2ad052d6c06..ae069d06aa9937 100644 --- a/types/oidc-provider/oidc-provider-tests.ts +++ b/types/oidc-provider/oidc-provider-tests.ts @@ -5,6 +5,7 @@ import * as crypto from "node:crypto"; import KeyGrip from "keygrip"; import Provider from "oidc-provider"; import * as oidc from "oidc-provider"; +import * as grantHelpers from "oidc-provider/lib/helpers/grants.js"; oidc.errors.AccessDenied.name; @@ -19,6 +20,320 @@ new Provider("https://op.example.com", { }, }); +new Provider("https://op.example.com", { + cookies: { + long: { + // @ts-expect-error Cookie priority has a finite set of supported values. + priority: "urgent", + }, + }, +}); + +const getAttestationSignaturePublicKey: oidc.AttestationSignaturePublicKey = async () => + crypto.generateKeyPairSync( + "ec", + { namedCurve: "P-256" }, + ).publicKey; +const issueCredential: oidc.OpenID4VCIIssueCredential = async () => ({ credentials: ["credential"] }); +const authorizationDetailsForGrantSource: oidc.AuthorizationDetailsForGrantSource = async () => undefined; +const authorizationDetailsForAccessToken: oidc.AuthorizationDetailsForAccessToken = async () => undefined; +const authorizationDetailsForIntrospection: oidc.AuthorizationDetailsForIntrospection = async () => undefined; +const paymentAuthorizationDetail: oidc.RichAuthorizationRequestType = { + validate(_ctx: oidc.KoaContextWithOIDC, detail: oidc.AuthorizationDetail) { + detail.type.substring(0); + }, +}; +const optionalAuthorizationDetailTypes = undefined as + | Readonly> + | undefined; +const dynamicallyEnabled: boolean = true; + +new Provider("https://op.example.com", { + features: { + attestClientAuth: { + enabled: true, + challengeSecret: Buffer.alloc(32), + getAttestationSignaturePublicKey, + }, + ciba: { + enabled: true, + triggerAuthenticationDevice() {}, + validateRequestContext() {}, + verifyUserCode() {}, + }, + fapi: { + enabled: true, + profile: "2.0", + }, + mTLS: { + enabled: true, + tlsClientAuth: true, + getCertificate: () => undefined, + certificateAuthorized: () => true, + certificateSubjectMatches: () => true, + }, + openid4vci: { + enabled: true, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { + credential: { format: "example" }, + }, + issueCredential, + }, + introspection: { enabled: true }, + richAuthorizationRequests: { + enabled: true, + types: { payment: paymentAuthorizationDetail }, + authorizationDetailsForGrantSource, + authorizationDetailsForAccessToken, + authorizationDetailsForIntrospection, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + attestClientAuth: { + enabled: dynamicallyEnabled, + challengeSecret: Buffer.alloc(32), + getAttestationSignaturePublicKey, + }, + ciba: { + enabled: dynamicallyEnabled, + triggerAuthenticationDevice() {}, + validateRequestContext() {}, + verifyUserCode() {}, + }, + fapi: { + enabled: dynamicallyEnabled, + profile: () => undefined, + }, + mTLS: { + enabled: dynamicallyEnabled, + tlsClientAuth: dynamicallyEnabled, + getCertificate: () => undefined, + certificateAuthorized: () => true, + certificateSubjectMatches: () => true, + }, + openid4vci: { + enabled: dynamicallyEnabled, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { + credential: { format: "example" }, + }, + issueCredential, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + mTLS: { + enabled: true, + certificateBoundAccessTokens: true, + getCertificate: () => undefined, + }, + richAuthorizationRequests: { + enabled: true, + types: {}, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + mTLS: { enabled: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + mTLS: { + enabled: dynamicallyEnabled, + certificateBoundAccessTokens: dynamicallyEnabled, + getCertificate: () => undefined, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + openid4vci: { + enabled: true, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { + credential: { format: "example" }, + }, + issueCredential, + }, + richAuthorizationRequests: { + enabled: true, + types: {}, + authorizationDetailsForGrantSource, + authorizationDetailsForAccessToken, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + openid4vci: { + enabled: true, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { + credential: { format: "example" }, + }, + issueCredential, + }, + richAuthorizationRequests: { + enabled: true, + types: optionalAuthorizationDetailTypes, + authorizationDetailsForGrantSource, + authorizationDetailsForAccessToken, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error enabled attestClientAuth requires its challenge secret and key resolver + attestClientAuth: { enabled: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error the challenge secret does not make the attestation key resolver optional + attestClientAuth: { enabled: true, challengeSecret: Buffer.alloc(32) }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error active RAR with introspection requires an introspection projection policy + introspection: { enabled: true }, + richAuthorizationRequests: { + enabled: true, + types: { payment: paymentAuthorizationDetail }, + authorizationDetailsForGrantSource, + authorizationDetailsForAccessToken, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + openid4vci: { + // @ts-expect-error OpenID4VCI activates an otherwise empty enabled RAR configuration + enabled: true, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { + credential: { format: "example" }, + }, + issueCredential, + }, + richAuthorizationRequests: { + enabled: true, + types: {}, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error enabled CIBA requires its always-invoked application callbacks + ciba: { enabled: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error verifyUserCode remains mandatory when the other CIBA callbacks are configured + ciba: { + enabled: true, + triggerAuthenticationDevice() {}, + validateRequestContext() {}, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error enabled FAPI requires a profile + fapi: { enabled: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error enabled OpenID4VCI requires a nonce secret, configurations, and issuance callback + openid4vci: { enabled: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + openid4vci: { + // @ts-expect-error issueCredential remains mandatory when the other OpenID4VCI values are configured + enabled: true, + nonceSecret: Buffer.alloc(32), + credentialConfigurationsSupported: { credential: { format: "example" } }, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error certificate-bound access tokens require a certificate resolver + mTLS: { enabled: true, certificateBoundAccessTokens: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error self-signed TLS client authentication requires a certificate resolver + mTLS: { enabled: true, selfSignedTlsClientAuth: true }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error tls_client_auth requires all three certificate callbacks + mTLS: { enabled: true, tlsClientAuth: true, getCertificate: () => undefined }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error a dynamic certificate-bound flag still requires a certificate resolver + mTLS: { + enabled: dynamicallyEnabled, + certificateBoundAccessTokens: dynamicallyEnabled, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error a dynamic tls_client_auth flag still requires all three certificate callbacks + mTLS: { + enabled: dynamicallyEnabled, + tlsClientAuth: dynamicallyEnabled, + getCertificate: () => undefined, + }, + }, +}); + +new Provider("https://op.example.com", { + features: { + // @ts-expect-error a non-empty RAR type map requires grant-source and access-token policies + richAuthorizationRequests: { + enabled: true, + types: { payment: paymentAuthorizationDetail }, + }, + }, +}); + new Provider("https://op.example.com", { assertJwtClientAuthClaimsAndHeader(ctx, claims, header, client) { ctx.oidc.issuer.substring(0); @@ -41,12 +356,15 @@ new oidc.Provider("https://op.example.com", { token.iat.toFixed(); parts.header = { foo: "bar" }; parts.payload.foo = "bar"; - return parts; }, }, }, }); +const findAccountRefreshToken = null as unknown as oidc.RefreshToken; +const findAccountToken: Parameters[2] = findAccountRefreshToken; +findAccountToken?.iat.toFixed(); + new oidc.Provider("https://op.example.com", { pkce: { required: () => false, @@ -205,6 +523,10 @@ const provider = new oidc.Provider("https://op.example.com", { acr: null, foo: null, bar: ["bar"], + address: { + formatted: null, + country: null, + }, }, clientBasedCORS(ctx: oidc.KoaContextWithOIDC, origin, client: oidc.Client) { ctx.oidc.issuer.substring(0); @@ -229,14 +551,19 @@ const provider = new oidc.Provider("https://op.example.com", { cookies: { names: { session: "_foo", + // @ts-expect-error `state` is not a configurable cookie name. + state: "_state", }, long: { + partitioned: true, + priority: "high", sameSite: "none", secure: true, }, short: { httpOnly: true, - sameSite: "lax", + priority: "low", + sameSite: true, }, keys: ["foo", Buffer.from("bar")], }, @@ -261,7 +588,6 @@ const provider = new oidc.Provider("https://op.example.com", { token.iat.toFixed(); parts.header = { foo: "bar" }; parts.payload.foo = "bar"; - return parts; }, }, }, @@ -357,8 +683,11 @@ const provider = new oidc.Provider("https://op.example.com", { }, extraClientMetadata: { properties: ["foo", "bar"], - validator(ctx: oidc.KoaContextWithOIDC, key, value, metadata: oidc.ClientMetadata) { + validator(ctx, key, value, metadata: oidc.ClientMetadata) { + const optionalContext: oidc.KoaContextWithOIDC | undefined = ctx; + // @ts-expect-error The validator context may be undefined. ctx.oidc.issuer.substring(0); + optionalContext?.oidc.issuer.substring(0); metadata.client_id.substring(0); key.substring(0); metadata.foo = "bar"; @@ -623,15 +952,43 @@ const provider = new oidc.Provider("https://op.example.com", { }, }); +const firstInteractionCheck = new oidc.interactionPolicy.Check("first", "first check", () => false); +const secondInteractionCheck = new oidc.interactionPolicy.Check("second", "second check", () => false); +const interactionPrompt = new oidc.interactionPolicy.Prompt( + { name: "step-up" }, + () => ({ method: "webauthn" }), + firstInteractionCheck, +); +const selectAccountPrompt = new oidc.interactionPolicy.Prompt({ name: "select_account" }); +const configuredInteractionPolicy = oidc.interactionPolicy.base(); +const interactionPolicyContext = null as unknown as oidc.KoaContextWithOIDC; + +firstInteractionCheck.error?.substring(0); +configuredInteractionPolicy.get("login")?.name.substring(0); +configuredInteractionPolicy.remove("missing"); +configuredInteractionPolicy.add(interactionPrompt); +configuredInteractionPolicy.clear(); + +interactionPrompt.checks.get("first")?.description.substring(0); +interactionPrompt.checks.remove("missing"); +interactionPrompt.checks.add(secondInteractionCheck); +interactionPrompt.checks.clear(); +Promise.resolve(interactionPrompt.details(interactionPolicyContext)).then(details => details?.method); +Promise.resolve(selectAccountPrompt.details(interactionPolicyContext)).then(details => details?.method); + +// @ts-expect-error policy collections only accept Prompt instances +configuredInteractionPolicy.add(firstInteractionCheck); +// @ts-expect-error check collections only accept Check instances +interactionPrompt.checks.add(interactionPrompt); + provider.on("access_token.saved", (accessToken: oidc.AccessToken) => { accessToken.jti.substring(0); }); provider.registerGrantType( "urn:example", - async (ctx: oidc.KoaContextWithOIDC, next) => { + async (ctx: oidc.TokenEndpointGrantContext) => { ctx.oidc.route.substring(0); - return next(); }, ["foo", "bar"], ["foo"], @@ -646,6 +1003,21 @@ provider.on("authorization.accepted", (ctx: oidc.KoaContextWithOIDC) => { ctx.oidc.cookies.set("key", "value", { signed: true, sameSite: "strict" }); }); +const authorizationSuccessListener = (ctx: oidc.KoaContextWithOIDC, response?: oidc.UnknownObject) => { + ctx.oidc.route.substring(0); + response?.state; +}; + +provider.on("authorization.success", (ctx, response) => { + ctx.oidc.route.substring(0); + const optionalResponse: oidc.UnknownObject | undefined = response; + optionalResponse?.state; +}); +provider.addListener("authorization.success", authorizationSuccessListener); +provider.once("authorization.success", authorizationSuccessListener); +provider.prependListener("authorization.success", authorizationSuccessListener); +provider.prependOnceListener("authorization.success", authorizationSuccessListener); + provider.on("interaction.started", (ctx: oidc.KoaContextWithOIDC, prompt: oidc.PromptDetail) => { ctx.oidc.route.substring(0); prompt.name.substring(0); @@ -715,7 +1087,12 @@ provider.OIDCContext.prototype.clientJwtAuthExpectedAudience = function clientJw new Provider("", { features: { - externalSigningSupport: { enabled: true, ack: "" }, + externalSigningSupport: { + enabled: true, + ack: "", + // @ts-expect-error externalSigningSupport has a finite configuration schema. + custom: true, + }, }, jwks: { keys: [ @@ -868,6 +1245,23 @@ preAuthorizedCode.txCode?.substring(0); preAuthorizedCode.consume().then(console.log); provider.PreAuthorizedCode.revokeByGrantId("grant").then(console.log); +const deviceCode = new provider.DeviceCode({ + client: null as unknown as oidc.Client, + deviceInfo: {}, + grantId: "grant", + params: {}, + userCode: "ABCD-EFGH", + rar: [{ type: "payment", actions: ["initiate"] }], +}); +deviceCode.rar?.[0].type.substring(0); + +const clientCredentials = new provider.ClientCredentials({ + client: null as unknown as oidc.Client, + scope: "api:read", + rar: [{ type: "payment", actions: ["read"] }], +}); +clientCredentials.rar?.[0].type.substring(0); + const rarGrant = new provider.Grant({ clientId: "client", accountId: "account" }); rarGrant.addRar({ type: "openid_credential", credential_configuration_id: "org.iso.18013.5.1.mDL" }); @@ -904,6 +1298,10 @@ const immutableConfiguration = { acrValues: ["urn:example:bronze"], claims: { profile: ["name", "family_name"], + address: { + formatted: null, + country: null, + }, }, clients: [ { @@ -1098,25 +1496,26 @@ new Provider("https://op.example.com", { }, }, }, - rarForAuthorizationCode(ctx) { - return ctx.oidc.grant?.rar; - }, - rarForBackchannelResponse(ctx, resourceServer) { - resourceServer.identifier().substring(0); - resourceServer.scopes.has("api:read"); - return ctx.oidc.grant?.rar; - }, - rarForCodeResponse(ctx, resourceServer) { - resourceServer.identifier().substring(0); - return ctx.oidc.grant?.rar; + authorizationDetailsForGrantSource(ctx, source) { + ctx.oidc.issuer.substring(0); + if (source.kind === "DeviceCode") { + source.userCode.substring(0); + } else { + source.redirectUri?.substring(0); + } + return source.rar; }, - rarForIntrospectionResponse(ctx, token) { + authorizationDetailsForAccessToken(ctx, token, source, grantType) { + ctx.oidc.issuer.substring(0); token.jti.substring(0); - return ctx.oidc.grant?.rar; + source?.jti.substring(0); + grantType.substring(0); + return token.rar; }, - rarForRefreshTokenResponse(ctx, resourceServer) { - resourceServer.identifier().substring(0); - return ctx.oidc.grant?.rar; + authorizationDetailsForIntrospection(ctx, token) { + ctx.oidc.issuer.substring(0); + token.jti.substring(0); + return token.rar; }, }, }, @@ -1139,6 +1538,158 @@ provider.registerGrantType( new Set(["resource"]) as ReadonlySet, ); +interface TokenExchangeParameters { + subject_token: string; + subject_token_type: string; + actor_token?: string | undefined; + actor_token_type?: string | undefined; + audience?: string | string[] | undefined; +} + +provider.registerGrantType( + "urn:ietf:params:oauth:grant-type:token-exchange", + async ctx => { + ctx.oidc.provider.issuer.substring(0); + ctx.oidc.client.clientId.substring(0); + ctx.oidc.params.grant_type.substring(0); + ctx.oidc.params.subject_token.substring(0); + ctx.oidc.params.subject_token_type.substring(0); + ctx.oidc.params.actor_token?.substring(0); + ctx.oidc.params.audience?.valueOf(); + Object.values(ctx.oidc.resourceServers).map(resource => resource.identifier()); + + const source = await grantHelpers.findGrantSource( + provider, + ctx, + provider.AccessToken, + ctx.oidc.params.subject_token, + "subject token", + ); + const clientBoundSource: grantHelpers.ClientBoundGrantSource = source; + clientBoundSource.clientId?.substring(0); + const sourceModel: grantHelpers.GrantSourceModel = provider.AccessToken; + sourceModel.find("access-token", { ignoreExpiration: true }).then(found => found?.jti.substring(0)); + const code = await grantHelpers.findGrantSource( + provider, + ctx, + provider.AuthorizationCode, + "authorization-code", + "authorization code", + ); + const consumableSource: grantHelpers.ConsumableGrantSource = code; + consumableSource.grantId?.substring(0); + await grantHelpers.consumeGrantSource(provider, ctx, code, "authorization code"); + + const grant = await grantHelpers.validateGrant(provider, ctx, source.grantId); + const account = await grantHelpers.findAccount(provider, ctx, source.accountId, source); + account?.accountId.substring(0); + + const scopes = grantHelpers.validateClientScope(provider, ctx); + grantHelpers.validateClientScope(provider, ctx, "api:read api:write"); + grantHelpers.validateClientScope(provider, ctx, ["api:read"]); + const resources = await grantHelpers.resolveRequestedResources(provider, ctx); + resources.map(resource => resource.identifier()); + + const accessToken = new provider.AccessToken({ + accountId: source.accountId, + client: ctx.oidc.client, + grantId: source.grantId, + gty: ctx.oidc.params.grant_type, + }); + accessToken.setAudience("https://api.example.com"); + accessToken.setAudience(["https://api.example.com"]); + accessToken.setThumbprint("jkt", "thumbprint"); + accessToken.setThumbprint("x5t", "certificate"); + accessToken.setThumbprint("x5t", new crypto.X509Certificate(Buffer.alloc(0))); + + const resource = await grantHelpers.resolveAndApplyResource( + provider, + ctx, + source, + accessToken, + grant, + scopes, + ); + resource?.substring(0); + const resourceSource: grantHelpers.ResourceGrantSource = source; + resourceSource.scopes.has("api:read"); + const authorizationDetailsSource: grantHelpers.AuthorizationDetailsSource = source; + authorizationDetailsSource.rar?.[0].type.substring(0); + + const constraints: grantHelpers.SenderConstraints = await grantHelpers.validateSenderConstraints( + provider, + ctx, + oidc.errors.InvalidGrant, + ); + constraints.dPoP?.thumbprint.substring(0); + constraints.dPoP?.jti.substring(0); + constraints.dPoP?.iat.toFixed(); + await grantHelpers.applySenderConstraints(provider, ctx, accessToken, constraints, oidc.errors.InvalidRequest); + await grantHelpers.applyAuthorizationDetails(provider, ctx, accessToken, source); + + const refreshToken = new provider.RefreshToken({ + accountId: source.accountId, + client: ctx.oidc.client, + grantId: source.grantId, + gty: ctx.oidc.params.grant_type, + scope: accessToken.scope || "", + }); + if (await grantHelpers.shouldIssueRefreshToken(provider, ctx, source)) { + await grantHelpers.applyRefreshTokenBindings(provider, ctx, accessToken, refreshToken); + } + + const responseInput: grantHelpers.TokenResponseInput<{ transaction_id: string }> = { + accessToken: "serialized-access-token", + expiresIn: accessToken.expiration, + issuedTokenType: "urn:ietf:params:oauth:token-type:access_token", + parameters: { + transaction_id: "transaction-id", + }, + scope: accessToken.scope, + tokenType: accessToken.tokenType, + }; + const response = grantHelpers.buildTokenResponse(provider, responseInput); + response.access_token.substring(0); + response.token_type.substring(0); + response.transaction_id.substring(0); + const standardResponse: grantHelpers.TokenResponse = response; + standardResponse.issued_token_type?.substring(0); + const reservedMember: grantHelpers.ReservedTokenResponseParameter = "access_token"; + reservedMember.substring(0); + + const clientCredentials = new provider.ClientCredentials({ client: ctx.oidc.client }); + clientCredentials.setAudience("https://api.example.com"); + clientCredentials.setThumbprint("jkt", "thumbprint"); + await grantHelpers.applyAuthorizationDetails(provider, ctx, clientCredentials); + + const errorConstructor: grantHelpers.OIDCProviderErrorConstructor = oidc.errors.InvalidGrant; + errorConstructor.name.substring(0); + const dpopResult: grantHelpers.DPoPValidationResult | undefined = constraints.dPoP; + dpopResult?.thumbprint.substring(0); + + // @ts-expect-error The provider argument must be an oidc-provider instance. + grantHelpers.validateClientScope({}, ctx); + grantHelpers.buildTokenResponse(provider, { + accessToken: "serialized-access-token", + tokenType: "Bearer", + // @ts-expect-error Extension parameters cannot override reserved response members. + parameters: { + access_token: "replacement", + }, + }); + // @ts-expect-error tokenType is required. + grantHelpers.buildTokenResponse(provider, { accessToken: "serialized-access-token" }); + }, + [ + "subject_token", + "subject_token_type", + "actor_token", + "actor_token_type", + "audience", + ], + ["audience"], +); + const resourceServer = new provider.ResourceServer("https://api.example.com", { scope: "api:read", }); diff --git a/types/oidc-provider/package.json b/types/oidc-provider/package.json index fd90cd433bf315..c72723b200da84 100644 --- a/types/oidc-provider/package.json +++ b/types/oidc-provider/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@types/oidc-provider", - "version": "9.11.9999", + "version": "9.12.9999", "projects": [ "https://github.com/panva/node-oidc-provider" ], diff --git a/types/oidc-provider/scripts/README.md b/types/oidc-provider/scripts/README.md new file mode 100644 index 00000000000000..e9077bb59033e9 --- /dev/null +++ b/types/oidc-provider/scripts/README.md @@ -0,0 +1,124 @@ +# oidc-provider declaration mirror + +`update-types.mjs` copies the declaration artifact produced by oidc-provider +into fixed generated regions in `index.d.ts` and the generated +`lib/helpers/grants.d.ts` file. Configuration structure, extension methods, +events, interaction policy declarations, errors, and grant implementation +helpers therefore share a canonical upstream source. + +Run it from `types/oidc-provider` and pass an explicit oidc-provider checkout: + +```sh +node scripts/update-types.mjs /path/to/oidc-provider +``` + +Alternatively, consume a previously generated artifact without a provider +checkout: + +```sh +node scripts/update-types.mjs --artifact /path/to/oidc-provider-types.json +``` + +Use `--check` to verify that the committed declarations are current without +writing them: + +```sh +node scripts/update-types.mjs --check /path/to/oidc-provider +``` + +`--check` also accepts `--artifact`. It intentionally compares the exact +mirror, including artifact provenance, and requires matching provider and DT +major/minor version lines. + +Use `--status` to decide whether a release warrants a DT pull request: + +```sh +node scripts/update-types.mjs --status --artifact /path/to/oidc-provider-types.json +``` + +It writes deterministic JSON to stdout. The comparison formats current and +prospective declarations and ignores only artifact provenance. Declaration or +generated JSDoc changes require an update, as does a new provider major. +Patch-only and minor-only releases do not. A required update advances the DT +package to the provider's `X.Y.9999`; artifacts older than DT's tracked +major/minor line are reported and skipped. Normal update mode follows this +policy, while exact `--check` may still reject provenance drift that does not +warrant a pull request. + +The script invokes `node docs/update-configuration.js --types-json` in the +provider checkout. It validates the artifact schema, exact provider version, +and SHA-256 content hash before changing any declaration. That provenance is +persisted in the generated contracts region, so `--check` also detects an +upstream patch release or source change whose formatted declarations happen to +be identical. + +The script uses only Node.js built-ins. It formats `index.d.ts` with the dprint +installation from the DefinitelyTyped repository and copies the grant-helper +declaration byte-for-byte, so that repository's dependencies must be installed. +Generated regions and files must not be edited by hand. + +The updater integration checks accept the same provider checkout: + +```sh +node scripts/update-types.test.mjs /path/to/oidc-provider +``` + +Release handoffs can run the same checks without provider source: + +```sh +node scripts/update-types.test.mjs --artifact /path/to/oidc-provider-types.json +``` + +## Release synchronization + +`sync-release.mjs` turns a provider release artifact into an isolated, +validated DefinitelyTyped commit. Run it from any directory in this checkout +with a stable provider tag and exactly one artifact source: + +```sh +node types/oidc-provider/scripts/sync-release.mjs \ + --tag v9.12.0 \ + --run-id 123456789 + +node types/oidc-provider/scripts/sync-release.mjs \ + --tag v9.12.0 \ + --artifact /path/to/oidc-provider-types.json +``` + +The run ID form downloads the `oidc-provider-types-v9.12.0` artifact from +`panva/node-oidc-provider` and requires its sole top-level file to be +`oidc-provider-types.json`. Before changing anything, the script verifies the +GitHub CLI login, the `origin` fork and official `upstream` remote, Git author +identity, Node.js, and pnpm. The authenticated GitHub user must own `origin`. + +The script fetches `upstream/master`, creates a temporary Git worktree, installs +only the root tooling and `@types/oidc-provider` dependency closure with a +no-lockfile workspace install, and asks `update-types.mjs --status` whether the +release needs a declaration update. When it does, the script applies the +artifact and runs: + +- the updater's exact `--check` mode; +- the updater artifact/status integration checks; +- dprint over `types/oidc-provider`; +- `pnpm test oidc-provider`. + +The updater's reported file list must exactly match Git, and every change must +remain below `types/oidc-provider`. A successful preparation leaves the local +branch `oidc-provider-v9.12.0` containing one commit titled +`[oidc-provider] sync declarations for v9.12.0`; the temporary worktree is then +removed. Inspect and submit that retained branch using the commands printed by +the script. The submission instruction reruns the helper with the same artifact +source so it can validate and reuse the prepared branch before pushing. + +Pass `--submit` to push the branch without force and open the deterministic PR +against `DefinitelyTyped/DefinitelyTyped:master`. Existing open or merged PRs +for the release branch are successful no-ops. A closed, unmerged PR or a remote +branch without a PR requires manual recovery and is never overwritten or +adopted. A matching prepared local branch can be reused safely. + +Temporary state is preserved on every failure. Pass `--keep-temp` to preserve +it after success as well. The focused orchestration tests are run separately: + +```sh +node --test types/oidc-provider/scripts/sync-release.test.mjs +``` diff --git a/types/oidc-provider/scripts/sync-release.mjs b/types/oidc-provider/scripts/sync-release.mjs new file mode 100644 index 00000000000000..7f0c54e606771f --- /dev/null +++ b/types/oidc-provider/scripts/sync-release.mjs @@ -0,0 +1,837 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process"; +import { lstatSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, posix, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; + +const STATUS_SCHEMA_VERSION = 1; +const UPSTREAM_REPOSITORY = "DefinitelyTyped/DefinitelyTyped"; +const PROVIDER_REPOSITORY = "panva/node-oidc-provider"; +const PACKAGE_PATH = "types/oidc-provider"; +const HASH_PATTERN = /^[a-f0-9]{64}$/; +const VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/; +const TAG_PATTERN = /^v(\d+\.\d+\.\d+)$/; +const STATUS_REASONS = new Set(["declarations", "major-version"]); +function usage() { + return "usage: node scripts/sync-release.mjs --tag vX.Y.Z (--run-id | --artifact ) [--submit] [--keep-temp]"; +} + +function fail(message) { + throw new Error(message); +} + +function optionValue(argv, index, option) { + const value = argv[index + 1]; + if (!value || value.startsWith("--")) fail(`${option} requires a value`); + return value; +} + +export function parseArguments(argv, cwd = process.cwd()) { + const parsed = { + artifact: undefined, + keepTemp: false, + runId: undefined, + submit: false, + tag: undefined, + }; + + for (let index = 0; index < argv.length; index += 1) { + const argument = argv[index]; + switch (argument) { + case "--artifact": { + if (parsed.artifact !== undefined) fail("--artifact may only be specified once"); + parsed.artifact = resolve(cwd, optionValue(argv, index, argument)); + index += 1; + break; + } + case "--keep-temp": + if (parsed.keepTemp) fail("--keep-temp may only be specified once"); + parsed.keepTemp = true; + break; + case "--run-id": + if (parsed.runId !== undefined) fail("--run-id may only be specified once"); + parsed.runId = optionValue(argv, index, argument); + index += 1; + break; + case "--submit": + if (parsed.submit) fail("--submit may only be specified once"); + parsed.submit = true; + break; + case "--tag": + if (parsed.tag !== undefined) fail("--tag may only be specified once"); + parsed.tag = optionValue(argv, index, argument); + index += 1; + break; + default: + fail(`unknown argument ${JSON.stringify(argument)}\n${usage()}`); + } + } + + if (!parsed.tag) fail(`--tag is required\n${usage()}`); + if (!TAG_PATTERN.test(parsed.tag)) fail("--tag must have the form vX.Y.Z"); + if ((parsed.artifact === undefined) === (parsed.runId === undefined)) { + fail(`exactly one of --run-id or --artifact is required\n${usage()}`); + } + if (parsed.runId !== undefined && !/^[1-9]\d*$/.test(parsed.runId)) { + fail("--run-id must be a positive GitHub Actions run ID"); + } + + return parsed; +} + +export function releasePlan(tag) { + const match = TAG_PATTERN.exec(tag); + if (!match) fail("tag must have the form vX.Y.Z"); + const version = match[1]; + return { + artifactName: `oidc-provider-types-${tag}`, + branch: `oidc-provider-${tag}`, + commitTitle: `[oidc-provider] sync declarations for ${tag}`, + tag, + version, + }; +} + +function commandDescription(command, args) { + return [command, ...args].map((part) => JSON.stringify(part)).join(" "); +} + +function shellQuote(value) { + if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value; + return `'${value.replaceAll("'", `'"'"'`)}'`; +} + +export function shellCommand(command, args) { + return [command, ...args].map(shellQuote).join(" "); +} + +export function runCommand(command, args, { + allowStatuses = [0], + capture = true, + cwd, +} = {}) { + const result = spawnSync(command, args, { + cwd, + encoding: "utf8", + maxBuffer: 32 * 1024 * 1024, + stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit", + }); + if (result.error) { + fail(`could not run ${commandDescription(command, args)}: ${result.error.message}`); + } + if (!allowStatuses.includes(result.status)) { + const diagnostic = capture ? (result.stderr || result.stdout || "").trim() : ""; + fail( + `${commandDescription(command, args)} exited with status ${result.status}${ + diagnostic ? `: ${diagnostic}` : "" + }`, + ); + } + return { + status: result.status, + stderr: result.stderr || "", + stdout: result.stdout || "", + }; +} + +function output(execute, command, args, options) { + return execute(command, args, options).stdout.trim(); +} + +export function githubRepository(remote) { + const patterns = [ + /^git@github\.com:([^/\s:]+)\/([^/\s?#:]+?)(?:\.git)?\/?$/, + /^ssh:\/\/git@github\.com\/([^/\s:]+)\/([^/\s?#:]+?)(?:\.git)?\/?$/, + /^https:\/\/github\.com\/([^/\s:]+)\/([^/\s?#:]+?)(?:\.git)?\/?$/, + ]; + for (const pattern of patterns) { + const match = pattern.exec(remote); + if (match) return { owner: match[1], repository: match[2] }; + } + fail("remote must use an exact github.com HTTPS or SSH URL"); +} + +function assertNodeVersion(version) { + const match = VERSION_PATTERN.exec(version); + if (!match) fail(`could not parse Node.js version ${JSON.stringify(version)}`); + const major = Number(match[1]); + const minor = Number(match[2]); + if (major < 20 || (major === 20 && minor < 17)) { + fail(`Node.js 20.17.0 or newer is required, found ${version}`); + } +} + +function preflight(packageDirectory, execute) { + assertNodeVersion(process.versions.node); + + const repositoryDirectory = output( + execute, + "git", + ["-C", packageDirectory, "rev-parse", "--show-toplevel"], + ); + const packageRelative = relative(repositoryDirectory, packageDirectory).split(sep).join("/"); + if (packageRelative !== PACKAGE_PATH) { + fail(`sync-release must be run from ${PACKAGE_PATH} in a DefinitelyTyped checkout`); + } + + output(execute, "git", ["--version"]); + output(execute, "gh", ["--version"]); + execute("gh", ["auth", "status", "--hostname", "github.com"], { capture: true }); + const githubLogin = output(execute, "gh", ["api", "user", "--jq", ".login"]); + const pnpmVersion = output(execute, "pnpm", ["--version"]); + if (!VERSION_PATTERN.test(pnpmVersion)) fail(`could not parse pnpm version ${JSON.stringify(pnpmVersion)}`); + + const userName = output(execute, "git", ["-C", repositoryDirectory, "config", "--get", "user.name"]); + const userEmail = output(execute, "git", ["-C", repositoryDirectory, "config", "--get", "user.email"]); + if (!userName || !userEmail) fail("git user.name and user.email must both be configured"); + + const upstreamUrl = output( + execute, + "git", + ["-C", repositoryDirectory, "remote", "get-url", "upstream"], + ); + const originUrl = output( + execute, + "git", + ["-C", repositoryDirectory, "remote", "get-url", "origin"], + ); + const originPushUrls = parseLines(output( + execute, + "git", + ["-C", repositoryDirectory, "remote", "get-url", "--push", "--all", "origin"], + )); + const upstream = githubRepository(upstreamUrl); + const origin = githubRepository(originUrl); + if (`${upstream.owner}/${upstream.repository}`.toLowerCase() !== UPSTREAM_REPOSITORY.toLowerCase()) { + fail(`upstream must point to ${UPSTREAM_REPOSITORY}, found ${upstream.owner}/${upstream.repository}`); + } + if (origin.repository.toLowerCase() !== "definitelytyped") { + fail(`origin must point to a DefinitelyTyped fork, found ${origin.owner}/${origin.repository}`); + } + if (origin.owner.toLowerCase() !== githubLogin.toLowerCase()) { + fail(`origin fork owner ${origin.owner} does not match authenticated GitHub user ${githubLogin}`); + } + if ( + !originPushUrls.length || originPushUrls.some((url) => { + const push = githubRepository(url); + return push.owner.toLowerCase() !== origin.owner.toLowerCase() + || push.repository.toLowerCase() !== origin.repository.toLowerCase(); + }) + ) { + fail("every origin push URL must point to the authenticated DefinitelyTyped fork"); + } + + return { originOwner: origin.owner, repositoryDirectory }; +} + +function regularFile(path, label, rejectSymlink = false) { + let stats; + try { + stats = rejectSymlink ? lstatSync(path) : statSync(path); + } catch (error) { + fail(`${label} is not readable at ${path}: ${error.message}`); + } + if (!stats.isFile()) fail(`${label} must be a regular file: ${path}`); + return path; +} + +function acquireArtifact(options, plan, temporaryDirectory, execute) { + if (options.artifact !== undefined) { + return regularFile(options.artifact, "provider types artifact"); + } + + const downloadDirectory = join(temporaryDirectory, "artifact"); + mkdirSync(downloadDirectory); + execute("gh", [ + "run", + "download", + options.runId, + "--repo", + PROVIDER_REPOSITORY, + "--name", + plan.artifactName, + "--dir", + downloadDirectory, + ], { capture: false }); + + const entries = readdirSync(downloadDirectory); + if (entries.length !== 1 || entries[0] !== "oidc-provider-types.json") { + fail( + `${plan.artifactName} must contain exactly one top-level file named oidc-provider-types.json`, + ); + } + return regularFile( + join(downloadDirectory, "oidc-provider-types.json"), + "downloaded provider types artifact", + true, + ); +} + +function exactObjectKeys(value, required, optional, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) fail(`${label} must be an object`); + const allowed = new Set([...required, ...optional]); + const keys = Object.keys(value); + const missing = required.filter((key) => !Object.hasOwn(value, key)); + const extra = keys.filter((key) => !allowed.has(key)); + if (missing.length || extra.length) { + fail( + `${label} has invalid keys${missing.length ? `; missing ${missing.join(", ")}` : ""}${ + extra.length ? `; unexpected ${extra.join(", ")}` : "" + }`, + ); + } +} + +function safePackagePath(path) { + return typeof path === "string" + && path.length > 0 + && path !== "." + && !path.includes("\\") + && path === posix.normalize(path) + && !posix.isAbsolute(path) + && path !== ".." + && !path.startsWith("../"); +} + +export function validateStatus(stdout, expectedVersion) { + let status; + try { + status = typeof stdout === "string" ? JSON.parse(stdout) : stdout; + } catch (error) { + fail(`update-types --status returned invalid JSON: ${error.message}`); + } + exactObjectKeys( + status, + [ + "schemaVersion", + "updateRequired", + "reasons", + "providerVersion", + "typesVersion", + "currentHash", + "candidateHash", + "changedFiles", + ], + ["warning"], + "update-types status", + ); + if (status.schemaVersion !== STATUS_SCHEMA_VERSION) { + fail(`unsupported update-types status schema ${JSON.stringify(status.schemaVersion)}`); + } + if (typeof status.updateRequired !== "boolean") fail("update-types status updateRequired must be a Boolean"); + if (!Array.isArray(status.reasons) || status.reasons.some((reason) => !STATUS_REASONS.has(reason))) { + fail("update-types status reasons contains an unsupported value"); + } + if (new Set(status.reasons).size !== status.reasons.length) { + fail("update-types status reasons must not contain duplicates"); + } + if (status.updateRequired !== (status.reasons.length > 0)) { + fail("update-types status updateRequired must agree with reasons"); + } + if (status.providerVersion !== expectedVersion) { + fail( + `provider artifact version ${ + JSON.stringify(status.providerVersion) + } does not match release ${expectedVersion}`, + ); + } + if (typeof status.typesVersion !== "string" || !VERSION_PATTERN.test(status.typesVersion)) { + fail("update-types status typesVersion must be an X.Y.Z version"); + } + if (!HASH_PATTERN.test(status.currentHash) || !HASH_PATTERN.test(status.candidateHash)) { + fail("update-types status hashes must be lowercase hexadecimal SHA-256 digests"); + } + if (!Array.isArray(status.changedFiles) || status.changedFiles.some((path) => !safePackagePath(path))) { + fail("update-types status changedFiles must contain safe package-relative paths"); + } + if (new Set(status.changedFiles).size !== status.changedFiles.length) { + fail("update-types status changedFiles must not contain duplicates"); + } + if (status.updateRequired !== (status.changedFiles.length > 0)) { + fail("update-types status updateRequired must agree with changedFiles"); + } + if (status.warning !== undefined && (typeof status.warning !== "string" || !status.warning)) { + fail("update-types status warning must be a non-empty string"); + } + return status; +} + +function parseLines(value) { + return value.split("\n").map((line) => line.trim()).filter(Boolean); +} + +export function assertPackageChanges(repositoryPaths, expectedPackagePaths) { + const actual = [...new Set(repositoryPaths)].sort(); + for (const path of actual) { + if (path !== PACKAGE_PATH && !path.startsWith(`${PACKAGE_PATH}/`)) { + fail(`unexpected change outside ${PACKAGE_PATH}: ${path}`); + } + } + if (expectedPackagePaths !== undefined) { + const expected = expectedPackagePaths.map((path) => `${PACKAGE_PATH}/${path}`).sort(); + if (actual.length !== expected.length || actual.some((path, index) => path !== expected[index])) { + fail( + `updated files do not match update-types status; expected ${expected.join(", ")}, found ${ + actual.join(", ") || "none" + }`, + ); + } + } + return actual; +} + +function workingChanges(repositoryDirectory, execute) { + const commands = [ + ["diff", "--name-only", "--no-ext-diff"], + ["diff", "--cached", "--name-only", "--no-ext-diff"], + ["ls-files", "--others", "--exclude-standard"], + ]; + return commands.flatMap((args) => + parseLines(output( + execute, + "git", + ["-C", repositoryDirectory, ...args], + )) + ); +} + +function installDependencies(worktreeDirectory, execute) { + execute( + "pnpm", + [ + "install", + "--no-frozen-lockfile", + "--ignore-scripts", + "--filter", + ".", + "--filter", + "@types/oidc-provider...", + ], + { capture: false, cwd: worktreeDirectory }, + ); +} + +function updaterStatus(packageDirectory, artifactPath, expectedVersion, execute) { + const result = execute( + "node", + ["scripts/update-types.mjs", "--status", "--artifact", artifactPath], + { capture: true, cwd: packageDirectory }, + ); + if (result.stderr) process.stderr.write(result.stderr); + return validateStatus(result.stdout, expectedVersion); +} + +function runChecks(worktreeDirectory, artifactPath, execute) { + const packageDirectory = join(worktreeDirectory, PACKAGE_PATH); + execute( + "node", + ["scripts/update-types.mjs", "--check", "--artifact", artifactPath], + { capture: false, cwd: packageDirectory }, + ); + execute( + "node", + ["scripts/update-types.test.mjs", "--artifact", artifactPath], + { capture: false, cwd: packageDirectory }, + ); + execute( + "pnpm", + ["exec", "dprint", "check", PACKAGE_PATH], + { capture: false, cwd: worktreeDirectory }, + ); + execute( + "pnpm", + ["test", "oidc-provider"], + { capture: false, cwd: worktreeDirectory }, + ); +} + +function matchingPullRequest(plan, originOwner, execute) { + const result = execute("gh", [ + "pr", + "list", + "--repo", + UPSTREAM_REPOSITORY, + "--state", + "all", + "--head", + plan.branch, + "--json", + "number,url,state,mergedAt,headRefName,headRepositoryOwner", + "--limit", + "100", + ], { capture: true }); + let pullRequests; + try { + pullRequests = JSON.parse(result.stdout); + } catch (error) { + fail(`gh pr list returned invalid JSON: ${error.message}`); + } + if (!Array.isArray(pullRequests)) fail("gh pr list did not return an array"); + const matching = pullRequests.filter(({ headRefName, headRepositoryOwner }) => + headRefName === plan.branch + && headRepositoryOwner?.login?.toLowerCase() === originOwner.toLowerCase() + ); + if (matching.length > 1) fail(`multiple pull requests use ${originOwner}:${plan.branch}`); + if (!matching.length) return undefined; + const [{ mergedAt, number, state, url }] = matching; + if ( + !Number.isSafeInteger(number) + || number <= 0 + || typeof url !== "string" + || !url + || !["OPEN", "CLOSED", "MERGED"].includes(state) + || (mergedAt !== null && typeof mergedAt !== "string") + ) { + fail("gh pr list returned invalid pull request metadata"); + } + return { merged: state === "MERGED" || mergedAt !== null, number, state, url }; +} + +function successfulPullRequest(pullRequest, plan) { + if (!pullRequest) return undefined; + if (pullRequest.state === "OPEN" || pullRequest.merged) return pullRequest; + fail( + `${pullRequest.url} for ${plan.branch} was closed without merging; recover or remove the branch before retrying`, + ); +} + +function remoteBranchHead(repositoryDirectory, branch, execute) { + const remote = output( + execute, + "git", + ["-C", repositoryDirectory, "ls-remote", "--heads", "origin", `refs/heads/${branch}`], + ); + if (!remote) return undefined; + const rows = parseLines(remote); + if (rows.length !== 1 || !/^[a-f0-9]{40,64}\s+refs\/heads\//.test(rows[0])) { + fail(`could not determine the state of origin/${branch}`); + } + return rows[0].split(/\s+/)[0]; +} + +export function pullRequestBody(plan, status) { + return [ + `Synchronizes the generated oidc-provider declaration mirrors with oidc-provider ${plan.tag}.`, + "", + `Rendered declaration hash: \`${status.candidateHash}\`.`, + ].join("\n"); +} + +export function submitPreparedBranch({ + execute = runCommand, + originOwner, + plan, + repositoryDirectory, + status, + worktreeDirectory, +}) { + const existing = successfulPullRequest(matchingPullRequest(plan, originOwner, execute), plan); + if (existing) return { created: false, ...existing }; + + if (remoteBranchHead(repositoryDirectory, plan.branch, execute)) { + fail( + `origin/${plan.branch} exists without a pull request; refusing to overwrite or adopt the orphaned branch`, + ); + } + execute( + "git", + ["-C", worktreeDirectory, "push", "--set-upstream", "origin", plan.branch], + { capture: false }, + ); + + const raced = successfulPullRequest(matchingPullRequest(plan, originOwner, execute), plan); + if (raced) return { created: false, ...raced }; + + const result = execute("gh", [ + "pr", + "create", + "--repo", + UPSTREAM_REPOSITORY, + "--base", + "master", + "--head", + `${originOwner}:${plan.branch}`, + "--title", + plan.commitTitle, + "--body", + pullRequestBody(plan, status), + ], { capture: true, cwd: worktreeDirectory }); + const url = result.stdout.trim(); + if (!url) fail("gh pr create did not return a pull request URL"); + return { created: true, url }; +} + +function localBranchExists(repositoryDirectory, branch, execute) { + const result = execute( + "git", + ["-C", repositoryDirectory, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`], + { allowStatuses: [0, 1], capture: true }, + ); + return result.status === 0; +} + +function verifyPreparedBranch(worktreeDirectory, plan, expectedPackagePaths, execute) { + const ancestry = execute( + "git", + ["-C", worktreeDirectory, "merge-base", "--is-ancestor", "upstream/master", plan.branch], + { allowStatuses: [0, 1], capture: true }, + ); + if (ancestry.status !== 0) { + fail( + `${plan.branch} is not based on the fetched upstream/master; inspect and delete or rename it before retrying`, + ); + } + const commitCount = output( + execute, + "git", + ["-C", worktreeDirectory, "rev-list", "--count", `upstream/master..${plan.branch}`], + ); + const subject = output( + execute, + "git", + ["-C", worktreeDirectory, "log", "-1", "--format=%s", plan.branch], + ); + if (commitCount !== "1" || subject !== plan.commitTitle) { + fail( + `${plan.branch} exists but is not the expected single prepared commit; inspect and delete or rename it before retrying`, + ); + } + const changes = parseLines(output( + execute, + "git", + ["-C", worktreeDirectory, "diff", "--name-only", `upstream/master...${plan.branch}`], + )); + if (!changes.length) fail(`${plan.branch} does not contain any package changes`); + assertPackageChanges(changes, expectedPackagePaths); + const comparison = execute( + "git", + ["-C", worktreeDirectory, "diff", "--quiet", plan.branch, "--", PACKAGE_PATH], + { allowStatuses: [0, 1], capture: true }, + ); + if (comparison.status !== 0) { + fail(`${plan.branch} does not contain the exact declaration update for ${plan.tag}`); + } +} + +function cleanupTemporaryWorktree(repositoryDirectory, worktreeDirectory, temporaryDirectory, execute) { + if (worktreeDirectory !== undefined) { + execute( + "git", + ["-C", repositoryDirectory, "worktree", "remove", worktreeDirectory], + { capture: true }, + ); + } + rmSync(temporaryDirectory, { force: true, recursive: true }); +} + +export function synchronize(options, execute = runCommand) { + const packageDirectory = resolve(fileURLToPath(new URL("..", import.meta.url))); + const plan = releasePlan(options.tag); + const preflightResult = preflight(packageDirectory, execute); + const temporaryDirectory = mkdtempSync(join(tmpdir(), "oidc-provider-sync-")); + let worktreeDirectory; + let succeeded = false; + + try { + const artifactPath = acquireArtifact(options, plan, temporaryDirectory, execute); + execute( + "git", + [ + "-C", + preflightResult.repositoryDirectory, + "fetch", + "--no-tags", + "upstream", + "refs/heads/master:refs/remotes/upstream/master", + ], + { capture: false }, + ); + + const branchExists = localBranchExists( + preflightResult.repositoryDirectory, + plan.branch, + execute, + ); + worktreeDirectory = join(temporaryDirectory, "worktree"); + execute( + "git", + [ + "-C", + preflightResult.repositoryDirectory, + "worktree", + "add", + "--detach", + worktreeDirectory, + "upstream/master", + ], + { capture: false }, + ); + installDependencies(worktreeDirectory, execute); + + const worktreePackage = join(worktreeDirectory, PACKAGE_PATH); + const status = updaterStatus(worktreePackage, artifactPath, plan.version, execute); + if (status.warning) console.warn(`sync-release: ${status.warning}`); + + const existing = successfulPullRequest( + matchingPullRequest(plan, preflightResult.originOwner, execute), + plan, + ); + if (existing) { + console.log(`${existing.merged ? "Merged" : "Open"} pull request already exists: ${existing.url}`); + succeeded = true; + return { branch: plan.branch, pullRequest: existing.url, reused: true }; + } + if (remoteBranchHead(preflightResult.repositoryDirectory, plan.branch, execute)) { + fail( + `origin/${plan.branch} exists without a pull request; recover or remove the orphaned branch before retrying`, + ); + } + + if (!status.updateRequired) { + if (branchExists) { + fail( + `${plan.branch} exists even though ${plan.tag} requires no update; inspect and delete or rename it before retrying`, + ); + } + console.log(`No declaration update is required for oidc-provider ${plan.tag}.`); + succeeded = true; + return { branch: undefined, updated: false }; + } + + execute( + "node", + ["scripts/update-types.mjs", "--artifact", artifactPath], + { capture: false, cwd: worktreePackage }, + ); + assertPackageChanges( + workingChanges(worktreeDirectory, execute), + status.changedFiles, + ); + runChecks(worktreeDirectory, artifactPath, execute); + assertPackageChanges( + workingChanges(worktreeDirectory, execute), + status.changedFiles, + ); + + if (branchExists) { + verifyPreparedBranch(worktreeDirectory, plan, status.changedFiles, execute); + execute( + "git", + ["-C", worktreeDirectory, "restore", "--source=HEAD", "--staged", "--worktree", "--", PACKAGE_PATH], + { capture: true }, + ); + if (workingChanges(worktreeDirectory, execute).length) { + fail("the temporary validation worktree could not be restored"); + } + } else { + execute( + "git", + ["-C", worktreeDirectory, "switch", "-c", plan.branch], + { capture: false }, + ); + execute( + "git", + ["-C", worktreeDirectory, "add", "--", PACKAGE_PATH], + { capture: true }, + ); + const staged = parseLines(output( + execute, + "git", + ["-C", worktreeDirectory, "diff", "--cached", "--name-only"], + )); + assertPackageChanges(staged, status.changedFiles); + const preparedTree = output( + execute, + "git", + ["-C", worktreeDirectory, "write-tree"], + ); + execute( + "git", + ["-C", worktreeDirectory, "commit", "-m", plan.commitTitle], + { capture: false }, + ); + const committedTree = output( + execute, + "git", + ["-C", worktreeDirectory, "rev-parse", "HEAD^{tree}"], + ); + if (committedTree !== preparedTree) { + fail("commit hooks changed the prepared declaration update"); + } + execute( + "node", + ["scripts/update-types.mjs", "--check", "--artifact", artifactPath], + { capture: false, cwd: worktreePackage }, + ); + const committed = parseLines(output( + execute, + "git", + ["-C", worktreeDirectory, "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD"], + )); + assertPackageChanges(committed, status.changedFiles); + if (workingChanges(worktreeDirectory, execute).length) { + fail("the prepared branch is not clean after committing"); + } + } + + let pullRequest; + if (options.submit) { + const submitted = submitPreparedBranch({ + execute, + originOwner: preflightResult.originOwner, + plan, + repositoryDirectory: preflightResult.repositoryDirectory, + status, + worktreeDirectory, + }); + pullRequest = submitted.url; + console.log(`${submitted.created ? "Created" : "Reused"} pull request: ${submitted.url}`); + } else { + console.log(`Prepared local branch ${plan.branch}.`); + console.log(`Inspect it with: git show --stat ${plan.branch}`); + const source = options.artifact === undefined + ? ["--run-id", options.runId] + : ["--artifact", options.artifact]; + console.log( + `Submit it with: ${ + shellCommand("node", [ + fileURLToPath(import.meta.url), + "--tag", + plan.tag, + ...source, + "--submit", + ]) + }`, + ); + } + + succeeded = true; + return { branch: plan.branch, pullRequest, updated: true }; + } catch (error) { + error.temporaryDirectory = temporaryDirectory; + throw error; + } finally { + if (succeeded && !options.keepTemp) { + cleanupTemporaryWorktree( + preflightResult.repositoryDirectory, + worktreeDirectory, + temporaryDirectory, + execute, + ); + } else if (temporaryDirectory) { + console.error(`sync-release: preserved temporary state at ${temporaryDirectory}`); + } + } +} + +const isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url); +if (isMain) { + try { + synchronize(parseArguments(process.argv.slice(2))); + } catch (error) { + console.error(`sync-release: ${error.message}`); + process.exitCode = 1; + } +} diff --git a/types/oidc-provider/scripts/sync-release.test.mjs b/types/oidc-provider/scripts/sync-release.test.mjs new file mode 100644 index 00000000000000..36169b6c9fd534 --- /dev/null +++ b/types/oidc-provider/scripts/sync-release.test.mjs @@ -0,0 +1,609 @@ +import assert from "node:assert/strict"; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { describe, it } from "node:test"; +import { fileURLToPath } from "node:url"; + +import { + assertPackageChanges, + githubRepository, + parseArguments, + pullRequestBody, + releasePlan, + shellCommand, + submitPreparedBranch, + synchronize, + validateStatus, +} from "./sync-release.mjs"; + +const hash = "a".repeat(64); +const candidateHash = "b".repeat(64); +const packageDirectory = resolve(fileURLToPath(new URL("..", import.meta.url))); +const repositoryDirectory = resolve(packageDirectory, "..", ".."); + +function status(overrides = {}) { + return { + schemaVersion: 1, + updateRequired: true, + reasons: ["declarations"], + providerVersion: "9.12.3", + typesVersion: "9.11.9999", + currentHash: hash, + candidateHash, + changedFiles: ["index.d.ts"], + ...overrides, + }; +} + +describe("sync-release argument and status validation", () => { + it("accepts exactly one artifact source and resolves local paths", () => { + assert.deepEqual( + parseArguments(["--tag", "v9.12.3", "--artifact", "artifact.json"], "/fixture"), + { + artifact: "/fixture/artifact.json", + keepTemp: false, + runId: undefined, + submit: false, + tag: "v9.12.3", + }, + ); + assert.deepEqual( + parseArguments([ + "--run-id", + "12345", + "--keep-temp", + "--submit", + "--tag", + "v9.12.3", + ]), + { + artifact: undefined, + keepTemp: true, + runId: "12345", + submit: true, + tag: "v9.12.3", + }, + ); + }); + + for ( + const [label, argv, pattern] of [ + ["missing tag", ["--artifact", "a.json"], /--tag is required/], + ["invalid tag", ["--tag", "9.12.3", "--artifact", "a.json"], /form vX\.Y\.Z/], + ["missing source", ["--tag", "v9.12.3"], /exactly one/], + [ + "both sources", + ["--tag", "v9.12.3", "--artifact", "a.json", "--run-id", "123"], + /exactly one/, + ], + ["invalid run", ["--tag", "v9.12.3", "--run-id", "latest"], /positive GitHub Actions/], + ["unknown option", ["--tag", "v9.12.3", "--artifact", "a.json", "--force"], /unknown/], + [ + "duplicate option", + ["--tag", "v9.12.3", "--tag", "v9.12.4", "--artifact", "a.json"], + /only be specified once/, + ], + ] + ) { + it(`rejects ${label}`, () => { + assert.throws(() => parseArguments(argv), pattern); + }); + } + + it("derives stable artifact, branch, commit, and pull request metadata", () => { + const plan = releasePlan("v9.12.3"); + assert.deepEqual(plan, { + artifactName: "oidc-provider-types-v9.12.3", + branch: "oidc-provider-v9.12.3", + commitTitle: "[oidc-provider] sync declarations for v9.12.3", + tag: "v9.12.3", + version: "9.12.3", + }); + assert.equal( + pullRequestBody(plan, status()), + [ + "Synchronizes the generated oidc-provider declaration mirrors with oidc-provider v9.12.3.", + "", + `Rendered declaration hash: \`${candidateHash}\`.`, + ].join("\n"), + ); + }); + + it("prints copyable shell commands without evaluating artifact paths", () => { + assert.equal( + shellCommand("node", ["script.mjs", "--artifact", "/tmp/a$(touch pwned)'types.json"]), + `node script.mjs --artifact '/tmp/a$(touch pwned)'"'"'types.json'`, + ); + }); + + it("accepts the updater status contract and enforces the release tag", () => { + assert.deepEqual(validateStatus(`${JSON.stringify(status())}\n`, "9.12.3"), status()); + assert.throws(() => validateStatus(status(), "9.12.4"), /does not match release/); + }); + + for ( + const [label, value, pattern] of [ + ["invalid JSON", "not-json", /invalid JSON/], + ["schema", status({ schemaVersion: 2 }), /unsupported.*schema/], + ["reason", status({ reasons: ["unknown"] }), /unsupported value/], + ["duplicate reason", status({ reasons: ["declarations", "declarations"] }), /duplicates/], + ["reason invariant", status({ reasons: [] }), /agree with reasons/], + ["hash", status({ candidateHash: "bad" }), /SHA-256/], + ["unsafe path", status({ changedFiles: ["../package.json"] }), /safe package-relative/], + ["duplicate path", status({ changedFiles: ["index.d.ts", "index.d.ts"] }), /duplicates/], + [ + "file invariant", + status({ updateRequired: false, reasons: [], changedFiles: ["index.d.ts"] }), + /agree with changedFiles/, + ], + ["empty warning", status({ warning: "" }), /non-empty string/], + ["extra property", { ...status(), extra: true }, /unexpected extra/], + ] + ) { + it(`rejects ${label} status`, () => { + assert.throws(() => validateStatus(value, "9.12.3"), pattern); + }); + } + + it("allows a warning-bearing no-update status", () => { + const value = status({ + changedFiles: [], + reasons: [], + updateRequired: false, + warning: "older release", + }); + assert.deepEqual(validateStatus(value, "9.12.3"), value); + }); + + it("allows only the exact package-relative changes reported by the updater", () => { + assert.deepEqual( + assertPackageChanges( + ["types/oidc-provider/lib/helpers/grants.d.ts", "types/oidc-provider/index.d.ts"], + ["index.d.ts", "lib/helpers/grants.d.ts"], + ), + ["types/oidc-provider/index.d.ts", "types/oidc-provider/lib/helpers/grants.d.ts"], + ); + assert.throws( + () => assertPackageChanges(["pnpm-lock.yaml"], ["index.d.ts"]), + /outside types\/oidc-provider/, + ); + assert.throws( + () => assertPackageChanges(["types/oidc-provider/index.d.ts"], ["package.json"]), + /do not match/, + ); + }); + + it("parses SSH and HTTPS GitHub remotes", () => { + assert.deepEqual(githubRepository("git@github.com:panva/DefinitelyTyped.git"), { + owner: "panva", + repository: "DefinitelyTyped", + }); + assert.deepEqual(githubRepository("ssh://git@github.com/panva/DefinitelyTyped.git"), { + owner: "panva", + repository: "DefinitelyTyped", + }); + assert.deepEqual(githubRepository("https://github.com/DefinitelyTyped/DefinitelyTyped.git"), { + owner: "DefinitelyTyped", + repository: "DefinitelyTyped", + }); + }); + + for ( + const remote of [ + "https://evilgithub.com/DefinitelyTyped/DefinitelyTyped.git", + "git@evilgithub.com:DefinitelyTyped/DefinitelyTyped.git", + "https://github.com.evil.example/DefinitelyTyped/DefinitelyTyped.git", + "https://token@github.com/DefinitelyTyped/DefinitelyTyped.git", + ] + ) { + it(`rejects the lookalike or credential-bearing remote ${remote}`, () => { + assert.throws(() => githubRepository(remote), /exact github\.com/); + }); + } +}); + +function submissionFixture(responses) { + const calls = []; + const execute = (command, args, options = {}) => { + calls.push({ args, command, options }); + const response = responses.shift(); + assert.ok(response, `unexpected command: ${command} ${args.join(" ")}`); + if (response.error) throw response.error; + return { status: 0, stderr: "", stdout: response.stdout || "" }; + }; + const plan = releasePlan("v9.12.3"); + const invoke = () => + submitPreparedBranch({ + execute, + originOwner: "panva", + plan, + repositoryDirectory: "/repo", + status: status(), + worktreeDirectory: "/worktree", + }); + return { calls, invoke, plan }; +} + +describe("sync-release submission safety", () => { + it("reuses an existing open pull request without pushing or creating another", () => { + const fixture = submissionFixture([ + { + stdout: + "[{\"headRefName\":\"oidc-provider-v9.12.3\",\"headRepositoryOwner\":{\"login\":\"panva\"},\"mergedAt\":null,\"number\":123,\"state\":\"OPEN\",\"url\":\"https://github.com/DefinitelyTyped/DefinitelyTyped/pull/123\"}]\n", + }, + ]); + assert.deepEqual(fixture.invoke(), { + created: false, + merged: false, + number: 123, + state: "OPEN", + url: "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/123", + }); + assert.equal(fixture.calls.length, 1); + assert.deepEqual(fixture.calls[0].args.slice(0, 2), ["pr", "list"]); + }); + + it("pushes a new branch without force and creates the deterministic pull request", () => { + const fixture = submissionFixture([ + { stdout: "[]\n" }, + { stdout: "" }, + { stdout: "" }, + { stdout: "[]\n" }, + { stdout: "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/456\n" }, + ]); + assert.deepEqual(fixture.invoke(), { + created: true, + url: "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/456", + }); + const push = fixture.calls.find(({ args }) => args.includes("push")); + assert.ok(push); + assert.deepEqual(push.args, [ + "-C", + "/worktree", + "push", + "--set-upstream", + "origin", + fixture.plan.branch, + ]); + assert.equal(push.args.some((argument) => argument === "-f" || argument.startsWith("--force")), false); + const create = fixture.calls.at(-1); + assert.deepEqual(create.args.slice(0, 2), ["pr", "create"]); + assert.ok(create.args.includes(fixture.plan.commitTitle)); + }); + + it("refuses to overwrite or adopt an orphaned remote branch", () => { + const fixture = submissionFixture([ + { stdout: "[]\n" }, + { stdout: `${"d".repeat(40)}\trefs/heads/oidc-provider-v9.12.3\n` }, + ]); + assert.throws(fixture.invoke, /orphaned branch/); + assert.equal(fixture.calls.some(({ args }) => args.includes("push")), false); + assert.equal(fixture.calls.some(({ args }) => args.includes("create")), false); + }); + + it("treats a merged pull request as a successful no-op", () => { + const fixture = submissionFixture([ + { + stdout: + "[{\"headRefName\":\"oidc-provider-v9.12.3\",\"headRepositoryOwner\":{\"login\":\"panva\"},\"mergedAt\":\"2026-08-26T10:00:00Z\",\"number\":789,\"state\":\"MERGED\",\"url\":\"https://github.com/DefinitelyTyped/DefinitelyTyped/pull/789\"}]\n", + }, + ]); + assert.deepEqual(fixture.invoke(), { + created: false, + merged: true, + number: 789, + state: "MERGED", + url: "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/789", + }); + assert.equal(fixture.calls.some(({ args }) => args.includes("push")), false); + }); + + it("requires recovery for a pull request closed without merging", () => { + const fixture = submissionFixture([ + { + stdout: + "[{\"headRefName\":\"oidc-provider-v9.12.3\",\"headRepositoryOwner\":{\"login\":\"panva\"},\"mergedAt\":null,\"number\":987,\"state\":\"CLOSED\",\"url\":\"https://github.com/DefinitelyTyped/DefinitelyTyped/pull/987\"}]\n", + }, + ]); + assert.throws(fixture.invoke, /closed without merging/); + assert.equal(fixture.calls.length, 1); + }); +}); + +function orchestrationFixture({ + branchExists = false, + downloadLayout = "valid", + failApply = false, + failStatus = false, + keepTemp = false, + mutateCommit = false, + pullRequest, + runId = false, +} = {}) { + const fixtureDirectory = mkdtempSync(join(tmpdir(), "sync-release-test-")); + const artifactPath = join(fixtureDirectory, "oidc-provider-types.json"); + writeFileSync(artifactPath, "{}\n"); + + const calls = []; + let applied = false; + let committed = false; + let staged = false; + let downloadDirectory; + let worktreeDirectory; + const updatedStatus = status(); + + const result = (stdout = "", statusCode = 0) => ({ status: statusCode, stderr: "", stdout }); + const execute = (command, args, options = {}) => { + calls.push({ args, command, options }); + if (command === "gh") { + if (args[0] === "--version") return result("gh version 2.98.0\n"); + if (args[0] === "auth") return result(); + if (args[0] === "api") return result("panva\n"); + if (args[0] === "pr" && args[1] === "list") { + return result(`${JSON.stringify(pullRequest ? [pullRequest] : [])}\n`); + } + if (args[0] === "run" && args[1] === "download") { + downloadDirectory = args[args.indexOf("--dir") + 1]; + if (downloadLayout !== "missing") { + writeFileSync(join(downloadDirectory, "oidc-provider-types.json"), "{}\n"); + } + if (downloadLayout === "extra") { + writeFileSync(join(downloadDirectory, "unexpected.json"), "{}\n"); + } + return result(); + } + } + if (command === "pnpm") { + if (args[0] === "--version") return result("10.30.1\n"); + return result(); + } + if (command === "node") { + if (args.includes("--status")) { + if (failStatus) throw new Error("injected artifact validation failure"); + return result(`${JSON.stringify(updatedStatus)}\n`); + } + if (args[0] === "scripts/update-types.mjs" && args.includes("--artifact") && !args.includes("--check")) { + if (failApply) throw new Error("injected updater failure"); + applied = true; + } + return result(); + } + if (command !== "git") throw new Error(`unexpected command ${command}`); + + if (args[0] === "--version") return result("git version 2.51.0\n"); + if (args.includes("rev-parse") && args.includes("--show-toplevel")) { + return result(`${repositoryDirectory}\n`); + } + if (args.includes("rev-parse") && args.includes("HEAD^{tree}")) { + return result(`${mutateCommit ? "changed-tree" : "prepared-tree"}\n`); + } + if (args.includes("config")) { + return result(`${args.at(-1) === "user.name" ? "Test User" : "test@example.com"}\n`); + } + if (args.includes("remote") && args.includes("get-url")) { + return result( + args.at(-1) === "upstream" + ? "git@github.com:DefinitelyTyped/DefinitelyTyped.git\n" + : "git@github.com:panva/DefinitelyTyped.git\n", + ); + } + if (args.includes("fetch") || args.includes("switch")) return result(); + if (args.includes("ls-remote")) return result(); + if (args.includes("show-ref")) return result("", branchExists ? 0 : 1); + if (args.includes("worktree") && args.includes("add")) { + const addIndex = args.indexOf("add"); + worktreeDirectory = args[addIndex + (args[addIndex + 1] === "--detach" ? 2 : 1)]; + mkdirSync(join(worktreeDirectory, "types", "oidc-provider"), { recursive: true }); + return result(); + } + if (args.includes("worktree") && args.includes("remove")) return result(); + if (args.includes("merge-base")) return result(); + if (args.includes("rev-list")) return result("1\n"); + if (args.includes("log")) return result("[oidc-provider] sync declarations for v9.12.3\n"); + if (args.includes("diff-tree")) return result("types/oidc-provider/index.d.ts\n"); + if (args.includes("diff") && args.some((argument) => argument.startsWith("upstream/master..."))) { + return result("types/oidc-provider/index.d.ts\n"); + } + if (args.includes("diff") && args.includes("--quiet")) return result(); + if (args.includes("diff") && args.includes("--cached")) { + return result(staged && !committed ? "types/oidc-provider/index.d.ts\n" : ""); + } + if (args.includes("write-tree")) return result("prepared-tree\n"); + if (args.includes("diff")) { + return result(applied && !staged && !committed ? "types/oidc-provider/index.d.ts\n" : ""); + } + if (args.includes("ls-files")) return result(); + if (args.includes("add")) { + staged = true; + return result(); + } + if (args.includes("commit")) { + committed = true; + return result(); + } + if (args.includes("restore")) { + applied = false; + staged = false; + return result(); + } + throw new Error(`unexpected git command: ${args.join(" ")}`); + }; + + const options = parseArguments([ + "--tag", + "v9.12.3", + ...(runId ? ["--run-id", "123456789"] : ["--artifact", artifactPath]), + ...(keepTemp ? ["--keep-temp"] : []), + ]); + return { + calls, + cleanup() { + rmSync(fixtureDirectory, { force: true, recursive: true }); + if (worktreeDirectory) rmSync(dirname(worktreeDirectory), { force: true, recursive: true }); + }, + execute, + get temporaryDirectory() { + return worktreeDirectory + ? dirname(worktreeDirectory) + : downloadDirectory + ? dirname(downloadDirectory) + : undefined; + }, + options, + }; +} + +describe("sync-release worktree lifecycle", () => { + it("removes the temporary worktree after preparing a local branch", (t) => { + t.mock.method(console, "log", () => {}); + const fixture = orchestrationFixture(); + try { + assert.deepEqual(synchronize(fixture.options, fixture.execute), { + branch: "oidc-provider-v9.12.3", + pullRequest: undefined, + updated: true, + }); + assert.equal(existsSync(fixture.temporaryDirectory), false); + assert.ok(fixture.calls.some(({ args }) => args.includes("commit"))); + assert.ok(fixture.calls.some(({ args }) => args.includes("remove"))); + const install = fixture.calls.find(({ args, command }) => command === "pnpm" && args[0] === "install"); + assert.ok(install.args.includes("--no-frozen-lockfile")); + assert.ok(install.args.includes("--ignore-scripts")); + } finally { + fixture.cleanup(); + } + }); + + it("preserves the temporary worktree after success with --keep-temp", (t) => { + t.mock.method(console, "log", () => {}); + t.mock.method(console, "error", () => {}); + const fixture = orchestrationFixture({ keepTemp: true }); + try { + synchronize(fixture.options, fixture.execute); + assert.equal(existsSync(fixture.temporaryDirectory), true); + assert.equal(fixture.calls.some(({ args }) => args.includes("remove")), false); + } finally { + fixture.cleanup(); + } + }); + + it("preserves the temporary worktree after a failure", (t) => { + t.mock.method(console, "error", () => {}); + const fixture = orchestrationFixture({ failApply: true }); + try { + assert.throws( + () => synchronize(fixture.options, fixture.execute), + /injected updater failure/, + ); + assert.equal(existsSync(fixture.temporaryDirectory), true); + assert.equal(fixture.calls.some(({ args }) => args.includes("remove")), false); + } finally { + fixture.cleanup(); + } + }); + + it("validates the artifact before reusing an existing pull request", (t) => { + t.mock.method(console, "error", () => {}); + const fixture = orchestrationFixture({ + failStatus: true, + pullRequest: { + headRefName: "oidc-provider-v9.12.3", + headRepositoryOwner: { login: "panva" }, + mergedAt: null, + number: 123, + state: "OPEN", + url: "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/123", + }, + }); + try { + assert.throws( + () => synchronize(fixture.options, fixture.execute), + /injected artifact validation failure/, + ); + assert.equal( + fixture.calls.some(({ args, command }) => command === "gh" && args[0] === "pr" && args[1] === "list"), + false, + ); + } finally { + fixture.cleanup(); + } + }); + + it("revalidates the committed files after commit hooks run", (t) => { + t.mock.method(console, "error", () => {}); + const fixture = orchestrationFixture({ mutateCommit: true }); + try { + assert.throws( + () => synchronize(fixture.options, fixture.execute), + /commit hooks changed the prepared declaration update/, + ); + const exactChecks = fixture.calls.filter(({ args, command }) => + command === "node" && args.includes("--check") + ); + assert.equal(exactChecks.length, 1); + assert.equal(fixture.calls.some(({ args }) => args.includes("push")), false); + } finally { + fixture.cleanup(); + } + }); + + it("reuses an existing matching prepared local branch", (t) => { + t.mock.method(console, "log", () => {}); + const fixture = orchestrationFixture({ branchExists: true }); + try { + const result = synchronize(fixture.options, fixture.execute); + assert.equal(result.branch, "oidc-provider-v9.12.3"); + assert.equal(fixture.calls.some(({ args }) => args.includes("switch")), false); + assert.equal(fixture.calls.some(({ args }) => args.includes("commit")), false); + assert.ok( + fixture.calls.some(({ args }) => + args.some((argument) => argument === "upstream/master...oidc-provider-v9.12.3") + ), + ); + assert.equal(existsSync(fixture.temporaryDirectory), false); + } finally { + fixture.cleanup(); + } + }); + + it("downloads the exact release artifact and consumes its sole JSON file", (t) => { + t.mock.method(console, "log", () => {}); + const fixture = orchestrationFixture({ runId: true }); + try { + synchronize(fixture.options, fixture.execute); + const download = fixture.calls.find(({ args, command }) => command === "gh" && args[0] === "run"); + assert.deepEqual(download.args, [ + "run", + "download", + "123456789", + "--repo", + "panva/node-oidc-provider", + "--name", + "oidc-provider-types-v9.12.3", + "--dir", + download.args.at(-1), + ]); + assert.equal(existsSync(fixture.temporaryDirectory), false); + } finally { + fixture.cleanup(); + } + }); + + for (const downloadLayout of ["missing", "extra"]) { + it(`rejects a downloaded artifact with a ${downloadLayout} top-level entry`, (t) => { + t.mock.method(console, "error", () => {}); + const fixture = orchestrationFixture({ downloadLayout, runId: true }); + try { + assert.throws( + () => synchronize(fixture.options, fixture.execute), + /exactly one top-level file named oidc-provider-types\.json/, + ); + assert.equal(existsSync(fixture.temporaryDirectory), true); + assert.equal(fixture.calls.some(({ args }) => args.includes("fetch")), false); + } finally { + fixture.cleanup(); + } + }); + } +}); diff --git a/types/oidc-provider/scripts/update-types.mjs b/types/oidc-provider/scripts/update-types.mjs new file mode 100644 index 00000000000000..7891a9a357f58d --- /dev/null +++ b/types/oidc-provider/scripts/update-types.mjs @@ -0,0 +1,388 @@ +#!/usr/bin/env node + +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { isAbsolute, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; + +const SCHEMA_VERSION = 1; +const STATUS_SCHEMA_VERSION = 1; +const GRANTS_PATH = "lib/helpers/grants.d.ts"; +const FRAGMENT_NAMES = ["contracts", "providerMembers", "relatedContracts"]; +const REGIONS = { + contracts: { + start: "// BEGIN GENERATED OIDC-PROVIDER CONTRACTS", + end: "// END GENERATED OIDC-PROVIDER CONTRACTS", + }, + providerMembers: { + start: " // BEGIN GENERATED OIDC-PROVIDER MEMBERS", + end: " // END GENERATED OIDC-PROVIDER MEMBERS", + }, + relatedContracts: { + start: "// BEGIN GENERATED OIDC-PROVIDER RELATED CONTRACTS", + end: "// END GENERATED OIDC-PROVIDER RELATED CONTRACTS", + }, +}; +const PROVENANCE_PATTERN = /^\/\/ oidc-provider types artifact .*; schema \d+; sha256 [a-f0-9]{64}\n?/m; + +function fail(message) { + console.error(`update-types: ${message}`); + process.exit(1); +} + +function usage() { + return "usage: node scripts/update-types.mjs [--check | --status] (--artifact | )"; +} + +function parseArguments(argv) { + let mode = "update"; + let artifactPath; + const positional = []; + + for (let index = 0; index < argv.length; index += 1) { + const argument = argv[index]; + if (argument === "--check" || argument === "--status") { + if (mode !== "update") fail("--check and --status cannot be combined"); + mode = argument.slice(2); + } else if (argument === "--artifact") { + if (artifactPath !== undefined) fail("--artifact may only be specified once"); + artifactPath = argv[index + 1]; + if (!artifactPath || artifactPath.startsWith("-")) fail("--artifact requires a path"); + index += 1; + } else if (argument.startsWith("-")) { + fail(`unknown option ${JSON.stringify(argument)}`); + } else { + positional.push(argument); + } + } + + if ((artifactPath === undefined && positional.length !== 1) || (artifactPath !== undefined && positional.length)) { + fail(usage()); + } + + return { + artifactPath: artifactPath === undefined ? undefined : resolve(artifactPath), + mode, + providerDirectory: artifactPath === undefined ? resolve(positional[0]) : undefined, + }; +} + +function readJson(path, label) { + try { + return JSON.parse(readFileSync(path, "utf8")); + } catch (error) { + fail(`could not read ${label} at ${path}: ${error.message}`); + } +} + +function versionLine(version, label) { + if (typeof version !== "string") { + fail(`${label} has an invalid version: ${JSON.stringify(version)}`); + } + const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(version); + if (!match) { + fail(`${label} has an invalid version: ${JSON.stringify(version)}`); + } + return { major: Number(match[1]), minor: Number(match[2]) }; +} + +function compareVersionLines(left, right) { + return left.major - right.major || left.minor - right.minor; +} + +function targetTypesVersion(providerLine) { + return `${providerLine.major}.${providerLine.minor}.9999`; +} + +function exactKeys(value, expected, label) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + fail(`${label} must be an object`); + } + + const actual = Object.keys(value).sort(); + const sortedExpected = [...expected].sort(); + if (actual.length !== sortedExpected.length || actual.some((key, index) => key !== sortedExpected[index])) { + fail(`${label} must have exactly these keys: ${sortedExpected.join(", ")}`); + } +} + +function declarationText(value, label) { + if (typeof value !== "string" || !value.trim()) { + fail(`${label} must be a non-empty string`); + } + if (value.includes("\r")) { + fail(`${label} must use LF line endings`); + } + return value; +} + +function dprintExecutable(repositoryDirectory) { + const directory = resolve(repositoryDirectory, "node_modules", "dprint"); + const packageMetadata = readJson(resolve(directory, "package.json"), "dprint package.json"); + const declared = typeof packageMetadata.bin === "string" ? packageMetadata.bin : packageMetadata.bin?.dprint; + if (typeof declared !== "string" || !declared) { + fail("dprint package.json must declare a dprint executable"); + } + + const executable = resolve(directory, declared); + const relativeExecutable = relative(directory, executable); + if ( + !relativeExecutable + || isAbsolute(relativeExecutable) + || relativeExecutable === ".." + || relativeExecutable.startsWith(`..${sep}`) + ) { + fail("dprint package.json declares an invalid executable path"); + } + return executable; +} + +function canonicalContent(indexFragments, files) { + return JSON.stringify({ + indexFragments: { + contracts: indexFragments.contracts, + providerMembers: indexFragments.providerMembers, + relatedContracts: indexFragments.relatedContracts, + }, + files: { + [GRANTS_PATH]: files[GRANTS_PATH], + }, + }); +} + +function validateArtifact(payload, expectedProviderVersion) { + exactKeys( + payload, + ["schemaVersion", "providerVersion", "hash", "indexFragments", "files"], + "provider types artifact", + ); + if (payload.schemaVersion !== SCHEMA_VERSION) { + fail(`unsupported provider types schema version ${JSON.stringify(payload.schemaVersion)}`); + } + versionLine(payload.providerVersion, "provider types artifact"); + if (expectedProviderVersion !== undefined && payload.providerVersion !== expectedProviderVersion) { + fail( + `provider types artifact is for ${ + JSON.stringify(payload.providerVersion) + }, expected ${expectedProviderVersion}`, + ); + } + if (typeof payload.hash !== "string" || !/^[a-f0-9]{64}$/.test(payload.hash)) { + fail("provider types artifact hash must be a lowercase hexadecimal SHA-256 digest"); + } + + exactKeys(payload.indexFragments, FRAGMENT_NAMES, "provider types indexFragments"); + exactKeys(payload.files, [GRANTS_PATH], "provider types files"); + for (const name of FRAGMENT_NAMES) { + declarationText(payload.indexFragments[name], `provider types indexFragments.${name}`); + } + declarationText(payload.files[GRANTS_PATH], `provider types files.${GRANTS_PATH}`); + + const actualHash = createHash("sha256") + .update(canonicalContent(payload.indexFragments, payload.files)) + .digest("hex"); + if (actualHash !== payload.hash) { + fail(`provider types artifact hash mismatch: expected ${payload.hash}, calculated ${actualHash}`); + } + + return payload; +} + +function artifactMetadata(payload) { + return `// oidc-provider types artifact ${ + JSON.stringify(payload.providerVersion) + }; schema ${payload.schemaVersion}; sha256 ${payload.hash}`; +} + +function replaceRegion(source, fragment, { start, end }, label) { + const startIndex = source.indexOf(start); + const endIndex = source.indexOf(end); + if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) { + fail(`index.d.ts does not contain the generated ${label} region`); + } + if ( + source.indexOf(start, startIndex + start.length) !== -1 + || source.indexOf(end, endIndex + end.length) !== -1 + ) { + fail(`index.d.ts must contain exactly one generated ${label} region`); + } + if (fragment.includes(start) || fragment.includes(end)) { + fail(`provider ${label} fragment contains a generated-region marker`); + } + + const normalized = fragment.replace(/^\n+|\n+$/g, ""); + return `${source.slice(0, startIndex + start.length)}\n${normalized}\n${source.slice(endIndex)}`; +} + +function formatDeclaration(source, packageDirectory, declarationPath) { + const repositoryDirectory = resolve(packageDirectory, "..", ".."); + const executable = dprintExecutable(repositoryDirectory); + const result = spawnSync(process.execPath, [executable, "fmt", "--stdin", declarationPath], { + cwd: repositoryDirectory, + encoding: "utf8", + input: source, + maxBuffer: 16 * 1024 * 1024, + }); + if (result.error) { + fail( + `could not run DefinitelyTyped's dprint executable at ${executable}: ${result.error.message}. Install the repository dependencies first`, + ); + } + if (result.status !== 0) { + if (result.stderr) process.stderr.write(result.stderr); + fail(`dprint exited with status ${result.status}`); + } + return result.stdout; +} + +function generateArtifact(providerDirectory) { + const result = spawnSync(process.execPath, ["docs/update-configuration.js", "--types-json"], { + cwd: providerDirectory, + encoding: "utf8", + maxBuffer: 16 * 1024 * 1024, + }); + if (result.error) { + fail(`could not run the provider types generator: ${result.error.message}`); + } + if (result.status !== 0) { + if (result.stderr) process.stderr.write(result.stderr); + fail(`provider types generator exited with status ${result.status}`); + } + + try { + return JSON.parse(result.stdout); + } catch (error) { + fail(`provider types generator returned invalid JSON: ${error.message}`); + } +} + +function renderedHash(outputs) { + return createHash("sha256").update(JSON.stringify({ + "index.d.ts": outputs.get("index.d.ts"), + [GRANTS_PATH]: outputs.get(GRANTS_PATH), + })).digest("hex"); +} + +function renderedOutputs(index, grants, packageDirectory, indexPath, grantsPath) { + return new Map([ + ["index.d.ts", formatDeclaration(index.replace(PROVENANCE_PATTERN, ""), packageDirectory, indexPath)], + [GRANTS_PATH, formatDeclaration(grants, packageDirectory, grantsPath)], + ]); +} + +const { artifactPath, mode, providerDirectory } = parseArguments(process.argv.slice(2)); +const packageDirectory = resolve(fileURLToPath(new URL("..", import.meta.url))); +const indexPath = resolve(packageDirectory, "index.d.ts"); +const grantsPath = resolve(packageDirectory, GRANTS_PATH); +const packagePath = resolve(packageDirectory, "package.json"); +const typesPackage = readJson(packagePath, "@types package.json"); + +if (typesPackage.name !== "@types/oidc-provider") { + fail(`expected @types/oidc-provider, found ${JSON.stringify(typesPackage.name)}`); +} + +let artifactPayload; +let expectedProviderVersion; +if (artifactPath === undefined) { + const providerPackage = readJson(resolve(providerDirectory, "package.json"), "provider package.json"); + if (providerPackage.name !== "oidc-provider") { + fail(`expected oidc-provider, found ${JSON.stringify(providerPackage.name)}`); + } + expectedProviderVersion = providerPackage.version; + artifactPayload = generateArtifact(providerDirectory); +} else { + artifactPayload = readJson(artifactPath, "provider types artifact"); +} +const artifact = validateArtifact(artifactPayload, expectedProviderVersion); + +const typesLine = versionLine(typesPackage.version, "@types/oidc-provider"); +const providerLine = versionLine(artifact.providerVersion, "oidc-provider"); +const lineComparison = compareVersionLines(providerLine, typesLine); +if (mode === "check" && lineComparison !== 0) { + fail( + `version mismatch: @types/oidc-provider is ${typesLine.major}.${typesLine.minor}.x but oidc-provider is ${providerLine.major}.${providerLine.minor}.x`, + ); +} + +const currentIndex = readFileSync(indexPath, "utf8"); +const currentGrants = readFileSync(grantsPath, "utf8"); +let generatedIndex = currentIndex; +for (const name of FRAGMENT_NAMES) { + const fragment = name === "contracts" + ? `${artifactMetadata(artifact)}\n${artifact.indexFragments[name]}` + : artifact.indexFragments[name]; + generatedIndex = replaceRegion(generatedIndex, fragment, REGIONS[name], name); +} +const generatedGrants = artifact.files[GRANTS_PATH]; +const exactOutputs = new Map([ + [indexPath, formatDeclaration(generatedIndex, packageDirectory, indexPath)], + [grantsPath, generatedGrants], +]); + +const currentRendered = renderedOutputs(currentIndex, currentGrants, packageDirectory, indexPath, grantsPath); +const candidateRendered = renderedOutputs(generatedIndex, generatedGrants, packageDirectory, indexPath, grantsPath); +const declarationChanges = [...candidateRendered] + .filter(([path, contents]) => currentRendered.get(path) !== contents) + .map(([path]) => path); +const reasons = []; +if (declarationChanges.length) reasons.push("declarations"); +if (providerLine.major > typesLine.major) reasons.push("major-version"); + +const olderTrackedLine = lineComparison < 0; +const updateRequired = !olderTrackedLine && reasons.length > 0; +const warning = olderTrackedLine + ? `oidc-provider ${artifact.providerVersion} is older than the tracked @types/oidc-provider ${typesLine.major}.${typesLine.minor}.x line; skipping` + : undefined; + +if (updateRequired) { + const candidateVersion = targetTypesVersion(providerLine); + if (typesPackage.version !== candidateVersion) { + exactOutputs.set(packagePath, `${JSON.stringify({ ...typesPackage, version: candidateVersion }, null, 4)}\n`); + } +} +const changedFiles = updateRequired + ? [...exactOutputs] + .filter(([path, contents]) => readFileSync(path, "utf8") !== contents) + .map(([path]) => path.slice(packageDirectory.length + 1)) + : []; + +if (mode === "status") { + const status = { + schemaVersion: STATUS_SCHEMA_VERSION, + updateRequired, + reasons: updateRequired ? reasons : [], + providerVersion: artifact.providerVersion, + typesVersion: typesPackage.version, + currentHash: renderedHash(currentRendered), + candidateHash: renderedHash(candidateRendered), + changedFiles, + }; + if (warning !== undefined) status.warning = warning; + process.stdout.write(`${JSON.stringify(status)}\n`); +} else if (olderTrackedLine) { + console.warn(`update-types: ${warning}`); +} else { + if (mode === "update" && !updateRequired) { + console.log("No declaration update is required for this oidc-provider release."); + process.exit(0); + } + + const changed = [...exactOutputs].filter(([path, contents]) => readFileSync(path, "utf8") !== contents); + if (!changed.length) { + console.log("oidc-provider declaration mirrors are up to date."); + } else if (mode === "check") { + fail( + `declaration mirrors are not up to date: ${ + changed.map(([path]) => path.slice(packageDirectory.length + 1)).join(", ") + }`, + ); + } else { + for (const [path, contents] of changed) writeFileSync(path, contents); + console.log( + `Updated oidc-provider declaration mirrors: ${ + changed.map(([path]) => path.slice(packageDirectory.length + 1)).join(", ") + }.`, + ); + } +} diff --git a/types/oidc-provider/scripts/update-types.test.mjs b/types/oidc-provider/scripts/update-types.test.mjs new file mode 100644 index 00000000000000..d6d4d71aa4fdda --- /dev/null +++ b/types/oidc-provider/scripts/update-types.test.mjs @@ -0,0 +1,371 @@ +#!/usr/bin/env node + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const arguments_ = process.argv.slice(2); +let providerDirectory; +let baselineArtifact; +if (arguments_.length === 1 && arguments_[0] !== "--artifact") { + providerDirectory = resolve(arguments_[0]); +} else if (arguments_.length === 2 && arguments_[0] === "--artifact") { + baselineArtifact = JSON.parse(readFileSync(resolve(arguments_[1]), "utf8")); +} else { + throw new TypeError( + "usage: node scripts/update-types.test.mjs ( | --artifact )", + ); +} + +const packageDirectory = resolve(fileURLToPath(new URL("..", import.meta.url))); +const repositoryDirectory = resolve(packageDirectory, "..", ".."); + +function run(command, args, cwd) { + return spawnSync(command, args, { + cwd, + encoding: "utf8", + maxBuffer: 16 * 1024 * 1024, + }); +} + +if (providerDirectory !== undefined) { + const generated = run( + process.execPath, + ["docs/update-configuration.js", "--types-json"], + providerDirectory, + ); + assert.equal(generated.status, 0, generated.stderr); + baselineArtifact = JSON.parse(generated.stdout); +} + +function canonicalContent(artifact) { + return JSON.stringify({ + indexFragments: { + contracts: artifact.indexFragments.contracts, + providerMembers: artifact.indexFragments.providerMembers, + relatedContracts: artifact.indexFragments.relatedContracts, + }, + files: { + "lib/helpers/grants.d.ts": artifact.files["lib/helpers/grants.d.ts"], + }, + }); +} + +function updateHash(artifact) { + artifact.hash = createHash("sha256").update(canonicalContent(artifact)).digest("hex"); + return artifact; +} + +function parseVersion(version) { + const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version); + assert.ok(match, `expected a stable provider version, received ${version}`); + return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) }; +} + +function version(major, minor, patch = 0) { + return `${major}.${minor}.${patch}`; +} + +const baselineVersion = parseVersion(baselineArtifact.providerVersion); +const patchVersion = version(baselineVersion.major, baselineVersion.minor, baselineVersion.patch + 1); +const minorVersion = version(baselineVersion.major, baselineVersion.minor + 1); +const majorVersion = version(baselineVersion.major + 1, 0); +const olderVersion = baselineVersion.minor > 0 + ? version(baselineVersion.major, baselineVersion.minor - 1, 999) + : version(baselineVersion.major - 1, 999, 999); + +const temporaryDirectory = mkdtempSync(join(tmpdir(), "oidc-provider-types-")); + +try { + const temporaryRepository = join(temporaryDirectory, "DefinitelyTyped"); + const temporaryPackage = join(temporaryRepository, "types", "oidc-provider"); + const temporaryScripts = join(temporaryPackage, "scripts"); + const temporaryGrants = join(temporaryPackage, "lib", "helpers"); + const fakeProvider = join(temporaryDirectory, "provider"); + const fakeProviderDocs = join(fakeProvider, "docs"); + const artifactPath = join(temporaryDirectory, "types.json"); + const indexPath = join(temporaryPackage, "index.d.ts"); + const packagePath = join(temporaryPackage, "package.json"); + const grantsPath = join(temporaryGrants, "grants.d.ts"); + + mkdirSync(temporaryScripts, { recursive: true }); + mkdirSync(temporaryGrants, { recursive: true }); + mkdirSync(fakeProviderDocs, { recursive: true }); + mkdirSync(join(temporaryRepository, "node_modules"), { recursive: true }); + + for (const path of ["index.d.ts", "package.json"]) { + copyFileSync(join(packageDirectory, path), join(temporaryPackage, path)); + } + copyFileSync(join(repositoryDirectory, ".dprint.jsonc"), join(temporaryRepository, ".dprint.jsonc")); + copyFileSync(join(packageDirectory, "lib", "helpers", "grants.d.ts"), grantsPath); + copyFileSync(join(packageDirectory, "scripts", "update-types.mjs"), join(temporaryScripts, "update-types.mjs")); + symlinkSync( + join(repositoryDirectory, "node_modules", "dprint"), + join(temporaryRepository, "node_modules", "dprint"), + "dir", + ); + + function restorePackage() { + copyFileSync(join(packageDirectory, "index.d.ts"), indexPath); + copyFileSync(join(packageDirectory, "package.json"), packagePath); + copyFileSync(join(packageDirectory, "lib", "helpers", "grants.d.ts"), grantsPath); + } + + function writeProvider(artifact, providerVersion = artifact.providerVersion) { + writeFileSync( + join(fakeProvider, "package.json"), + `${JSON.stringify({ name: "oidc-provider", version: providerVersion })}\n`, + ); + writeFileSync( + join(fakeProviderDocs, "update-configuration.js"), + `process.stdout.write(${JSON.stringify(`${JSON.stringify(artifact)}\n`)});\n`, + ); + } + + function writeArtifact(artifact) { + writeFileSync(artifactPath, `${JSON.stringify(artifact)}\n`); + } + + function updater(args) { + return run(process.execPath, ["scripts/update-types.mjs", ...args], temporaryPackage); + } + + function check(args = [fakeProvider]) { + return updater(["--check", ...args]); + } + + function status(artifact) { + writeArtifact(artifact); + const result = updater(["--status", "--artifact", artifactPath]); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stderr, "", "status diagnostics must not pollute stderr on success"); + assert.doesNotThrow(() => JSON.parse(result.stdout), "status stdout must be JSON only"); + return JSON.parse(result.stdout); + } + + function expectFailure(label, args, pattern) { + const result = updater(args); + assert.notEqual(result.status, 0, `${label} unexpectedly passed`); + assert.match(`${result.stdout}\n${result.stderr}`, pattern, label); + } + + function withProviderVersion(artifact, providerVersion) { + const result = structuredClone(artifact); + result.providerVersion = providerVersion; + return result; + } + + function contractsChange(artifact, text = "export interface StatusOnlyContract { value: string; }") { + const result = structuredClone(artifact); + result.indexFragments.contracts += `\n${text}\n`; + return updateHash(result); + } + + writeProvider(baselineArtifact); + writeArtifact(baselineArtifact); + const baselineCheckoutCheck = check(); + assert.equal( + baselineCheckoutCheck.status, + 0, + `the baseline checkout must pass --check\n${baselineCheckoutCheck.stdout}\n${baselineCheckoutCheck.stderr}`, + ); + const baselineArtifactCheck = check(["--artifact", artifactPath]); + assert.equal(baselineArtifactCheck.status, 0, baselineArtifactCheck.stderr); + const baselineStatus = status(baselineArtifact); + const baselineCheckoutStatus = updater(["--status", fakeProvider]); + assert.equal(baselineCheckoutStatus.status, 0, baselineCheckoutStatus.stderr); + assert.deepEqual(JSON.parse(baselineCheckoutStatus.stdout), baselineStatus); + assert.deepEqual(baselineStatus, { + schemaVersion: 1, + updateRequired: false, + reasons: [], + providerVersion: baselineArtifact.providerVersion, + typesVersion: JSON.parse(readFileSync(packagePath, "utf8")).version, + currentHash: baselineStatus.currentHash, + candidateHash: baselineStatus.currentHash, + changedFiles: [], + }); + + const schemaMismatch = structuredClone(baselineArtifact); + schemaMismatch.schemaVersion += 1; + writeArtifact(schemaMismatch); + expectFailure( + "schema mismatch", + ["--status", "--artifact", artifactPath], + /unsupported provider types schema version/, + ); + + const versionMismatch = withProviderVersion(baselineArtifact, patchVersion); + writeProvider(versionMismatch, baselineArtifact.providerVersion); + expectFailure("artifact/package version mismatch", ["--check", fakeProvider], /provider types artifact is for/); + + const hashMismatch = structuredClone(baselineArtifact); + hashMismatch.hash = `${hashMismatch.hash.slice(0, -1)}${hashMismatch.hash.endsWith("0") ? "1" : "0"}`; + writeArtifact(hashMismatch); + expectFailure( + "content hash mismatch", + ["--status", "--artifact", artifactPath], + /provider types artifact hash mismatch/, + ); + + const missingFragment = structuredClone(baselineArtifact); + delete missingFragment.indexFragments.providerMembers; + writeArtifact(missingFragment); + expectFailure( + "missing fragment", + ["--status", "--artifact", artifactPath], + /indexFragments must have exactly these keys/, + ); + + writeFileSync(artifactPath, "not json\n"); + expectFailure( + "malformed artifact", + ["--status", "--artifact", artifactPath], + /could not read provider types artifact/, + ); + + const patchDrift = withProviderVersion(baselineArtifact, patchVersion); + const patchStatus = status(patchDrift); + assert.equal(patchStatus.updateRequired, false); + assert.deepEqual(patchStatus.reasons, []); + assert.equal(patchStatus.currentHash, patchStatus.candidateHash); + assert.deepEqual(patchStatus.changedFiles, []); + writeArtifact(patchDrift); + expectFailure( + "provider patch provenance drift", + ["--check", "--artifact", artifactPath], + /declaration mirrors are not up to date/, + ); + const originalIndex = readFileSync(indexPath, "utf8"); + const patchOnlyUpdate = updater(["--artifact", artifactPath]); + assert.equal(patchOnlyUpdate.status, 0, patchOnlyUpdate.stderr); + assert.match(patchOnlyUpdate.stdout, /No declaration update is required/); + assert.equal(readFileSync(indexPath, "utf8"), originalIndex); + + const minorStatus = status(withProviderVersion(baselineArtifact, minorVersion)); + assert.equal(minorStatus.updateRequired, false); + assert.deepEqual(minorStatus.reasons, []); + assert.equal(minorStatus.currentHash, minorStatus.candidateHash); + + const majorStatus = status(withProviderVersion(baselineArtifact, majorVersion)); + assert.equal(majorStatus.updateRequired, true); + assert.deepEqual(majorStatus.reasons, ["major-version"]); + assert.equal(majorStatus.currentHash, majorStatus.candidateHash); + assert.deepEqual(majorStatus.changedFiles, ["index.d.ts", "package.json"]); + + const olderArtifact = withProviderVersion(contractsChange(baselineArtifact), olderVersion); + const olderStatus = status(olderArtifact); + assert.equal(olderStatus.updateRequired, false); + assert.deepEqual(olderStatus.reasons, []); + assert.deepEqual(olderStatus.changedFiles, []); + assert.match(olderStatus.warning, /older than the tracked/); + + const changedContracts = contractsChange(baselineArtifact); + const declarationStatus = status(changedContracts); + assert.equal(declarationStatus.updateRequired, true); + assert.deepEqual(declarationStatus.reasons, ["declarations"]); + assert.notEqual(declarationStatus.currentHash, declarationStatus.candidateHash); + assert.deepEqual(declarationStatus.changedFiles, ["index.d.ts"]); + + const documentationChange = contractsChange( + baselineArtifact, + "/** Status-only generated documentation. */\nexport interface DocumentedStatusContract {}", + ); + const documentationStatus = status(documentationChange); + assert.equal(documentationStatus.updateRequired, true); + assert.deepEqual(documentationStatus.reasons, ["declarations"]); + + const grantsChange = structuredClone(baselineArtifact); + grantsChange.files["lib/helpers/grants.d.ts"] += "\nexport interface StatusOnlyGrantHelper {}\n"; + updateHash(grantsChange); + const grantsStatus = status(grantsChange); + assert.equal(grantsStatus.updateRequired, true); + assert.deepEqual(grantsStatus.reasons, ["declarations"]); + assert.deepEqual(grantsStatus.changedFiles, ["index.d.ts", "lib/helpers/grants.d.ts"]); + + const formattingOnly = structuredClone(baselineArtifact); + formattingOnly.indexFragments.contracts += "\n\n"; + formattingOnly.files["lib/helpers/grants.d.ts"] += "\n\n"; + updateHash(formattingOnly); + const formattingStatus = status(formattingOnly); + assert.equal(formattingStatus.updateRequired, false); + assert.deepEqual(formattingStatus.reasons, []); + assert.equal(formattingStatus.currentHash, formattingStatus.candidateHash); + + writeArtifact(formattingOnly); + expectFailure( + "exact check retains normalized content drift", + ["--check", "--artifact", artifactPath], + /declaration mirrors are not up to date/, + ); + + restorePackage(); + const nextMinorChange = withProviderVersion(changedContracts, minorVersion); + writeArtifact(nextMinorChange); + const updateMinor = updater(["--artifact", artifactPath]); + assert.equal(updateMinor.status, 0, updateMinor.stderr); + assert.equal( + JSON.parse(readFileSync(packagePath, "utf8")).version, + `${baselineVersion.major}.${baselineVersion.minor + 1}.9999`, + ); + const updatedMinorCheck = check(["--artifact", artifactPath]); + assert.equal(updatedMinorCheck.status, 0, updatedMinorCheck.stderr); + + restorePackage(); + const majorOnly = withProviderVersion(baselineArtifact, majorVersion); + writeArtifact(majorOnly); + const updateMajor = updater(["--artifact", artifactPath]); + assert.equal(updateMajor.status, 0, updateMajor.stderr); + assert.equal(JSON.parse(readFileSync(packagePath, "utf8")).version, `${baselineVersion.major + 1}.0.9999`); + const updatedMajorCheck = check(["--artifact", artifactPath]); + assert.equal(updatedMajorCheck.status, 0, updatedMajorCheck.stderr); + + restorePackage(); + writeArtifact(withProviderVersion(baselineArtifact, minorVersion)); + const minorOnlyUpdate = updater(["--artifact", artifactPath]); + assert.equal(minorOnlyUpdate.status, 0, minorOnlyUpdate.stderr); + assert.match(minorOnlyUpdate.stdout, /No declaration update is required/); + assert.equal(readFileSync(packagePath, "utf8"), readFileSync(join(packageDirectory, "package.json"), "utf8")); + expectFailure( + "exact check retains major/minor compatibility check", + ["--check", "--artifact", artifactPath], + /version mismatch/, + ); + + restorePackage(); + writeArtifact(olderArtifact); + const olderUpdate = updater(["--artifact", artifactPath]); + assert.equal(olderUpdate.status, 0, olderUpdate.stderr); + assert.match(olderUpdate.stderr, /older than the tracked/); + assert.equal(readFileSync(indexPath, "utf8"), readFileSync(join(packageDirectory, "index.d.ts"), "utf8")); + + restorePackage(); + const index = readFileSync(indexPath, "utf8"); + const altered = index.replace("export type TokenFormat =", "export type AlteredTokenFormat ="); + assert.notEqual(altered, index, "expected to alter the generated contracts region"); + writeFileSync(indexPath, altered); + writeArtifact(baselineArtifact); + expectFailure( + "altered generated region", + ["--check", "--artifact", artifactPath], + /declaration mirrors are not up to date/, + ); + + if (providerDirectory !== undefined) { + const artifactOnlyRun = run( + process.execPath, + [fileURLToPath(import.meta.url), "--artifact", artifactPath], + packageDirectory, + ); + assert.equal(artifactOnlyRun.status, 0, artifactOnlyRun.stderr); + assert.match(artifactOnlyRun.stdout, /update-types artifact and status checks passed/); + } + + process.stdout.write("update-types artifact and status checks passed.\n"); +} finally { + rmSync(temporaryDirectory, { recursive: true, force: true }); +} diff --git a/types/vscode/index.d.ts b/types/vscode/index.d.ts index c8a5d8c803843d..6959ae30bc31ef 100644 --- a/types/vscode/index.d.ts +++ b/types/vscode/index.d.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ /** - * Type Definition for Visual Studio Code 1.134 Extension API + * Type Definition for Visual Studio Code 1.136 Extension API * See https://code.visualstudio.com/api for more information */ @@ -5639,6 +5639,9 @@ declare module 'vscode' { /** * The position of this hint. + * + * If multiple hints have the same position, they will be shown in the order + * they appear in the results. */ position: Position; diff --git a/types/vscode/package.json b/types/vscode/package.json index 2b2323d112a91e..d0d60522f4759b 100644 --- a/types/vscode/package.json +++ b/types/vscode/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@types/vscode", - "version": "1.134.9999", + "version": "1.136.9999", "nonNpm": "conflict", "nonNpmDescription": "TypeScript definitions for the Visual Studio Code Extension API", "projects": [ diff --git a/types/zoomist/.npmignore b/types/zoomist/.npmignore deleted file mode 100644 index 93e307400a5456..00000000000000 --- a/types/zoomist/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -* -!**/*.d.ts -!**/*.d.cts -!**/*.d.mts -!**/*.d.*.ts diff --git a/types/zoomist/index.d.ts b/types/zoomist/index.d.ts deleted file mode 100644 index 4c1601ba4d0c82..00000000000000 --- a/types/zoomist/index.d.ts +++ /dev/null @@ -1,178 +0,0 @@ -// Minimum TypeScript Version: 4.1 - -export as namespace Zoomist; - -export = Zoomist; -declare class Zoomist { - constructor(element: Element, options?: ZoomistOptions); - - __events__: { - drag: Array<(transform?: { x: number; y: number }, event?: Event) => void>; - dragEnd: Array<(transform?: { x: number; y: number }, event?: Event) => void>; - dragStart: Array<(transform?: { x: number; y: number }, event?: Event) => void>; - pinch: Array<(event?: Event) => void>; - pinchEnd: Array<(event?: Event) => void>; - pinchStart: Array<(event?: Event) => void>; - slide: Array<(value?: number, event?: Event) => void>; - slideEnd: Array<(value?: number, event?: Event) => void>; - slideStart: Array<(value?: number, event?: Event) => void>; - wheel: Array<(event?: WheelEvent) => void>; - zoom: Array<(ratio?: number) => void>; - - ready: Array<() => void>; - reset: Array<() => void>; - resize: Array<() => void>; - destroy: Array<() => void>; - update: Array<() => void>; - }; - __modules__: { - slider: { - direction: string; - el: string; - isCustomEl: boolean; - maxRatio: number; - mounted: boolean; - sldierBar: HTMLElement; - sliderEl: HTMLElement; - sliderButton: HTMLElement; - sliderMain: HTMLElement; - sliding: boolean; - value: number; - }; - zoomer: { - disableOnBounds: boolean; - inEl: string; - isCustomInEl: boolean; - isCustomOutEl: boolean; - mounted: boolean; - outEl: string; - zoomerEl: HTMLElement; - zoomerInEl: HTMLElement; - zoomerOutEl: HTMLElement; - }; - }; - data: { - containerData: { - aspectRatio: number; - height: number; - width: number; - }; - dragData: { - startX: number; - startY: number; - transX: number; - transY: number; - }; - imageData: { - aspectRatio: number; - height: number; - width: number; - left: number; - top: number; - naturalWidth: number; - naturalHeight: number; - }; - originalImageData: { - aspectRatio: number; - height: number; - width: number; - left: number; - top: number; - naturalWidth: number; - naturalHeight: number; - }; - pinchData: { - dist: number; - startX: number; - startY: number; - }; - }; - - init(): void; - create(url: string): void; - mount(): void; - render(): void; - - element: HTMLElement; - options: ZoomistOptions; - wrapper: HTMLDivElement; - image: HTMLImageElement; - mounted: boolean; - dragging: boolean; - pinching: boolean; - ratio: number; - url: string; - wheeling: boolean; - - /* Methods */ - getContainerData(): { width: number; height: number; aspectRatio: number }; - getImageData(): { - width: number; - height: number; - aspectRatio: number; - top: number; - left: number; - naturalWidth: number; - naturalHeight: number; - }; - getSliderValue(): number; - getZoomRation(): number; - zoom(ratio: number): void; - zoomTo(ratio: number): void; - move(x: number, y: number): void; - moveTo(x: number, y: number): void; - slideTo(value: number, isOnlySlide: boolean): void; - on(event: "ready" | "update" | "destroy" | "resize" | "reset", handler: () => void): void; - on(event: "zoom", handler: (ratio: number) => void): void; - on(event: "wheel", handler: (event: WheelEvent) => void): void; - on( - event: "drag" | "dragStart" | "dragEnd", - handler: (transform: { x: number; y: number }, event: Event) => void, - ): void; - on(event: "slide" | "slideStart" | "slideEnd", handler: (value: number, event: Event) => void): void; - on(event: "pinch" | "pinchStart" | "pinchEnd", handler: (event: Event) => void): void; - - reset(): void; - update(): void; - destroy(): void; -} - -interface ZoomistOptions { - src?: string | HTMLImageElement; - fill?: "cover" | "contain" | "none"; - draggable?: boolean; - wheelable?: boolean; - pinchable?: boolean; - bounds?: boolean; - zoomRatio?: number; - maxRatio?: number | false; - height?: "auto" | `${number}%` | number | false; - slider?: { - el?: string | HTMLElement | false; - direction?: "horizontal" | "vertical"; - maxRatio?: number; - }; - zoomer?: { - inEl?: string | HTMLElement | false; - outEl?: string | HTMLElement | false; - disableOnBounds?: boolean; - }; - on?: { - ready?(): void; - zoom?(ratio: number): void; - wheel?(event: WheelEvent): void; - dragStart?(transform: { x: number; y: number }, event: Event): void; - drag?(transform: { x: number; y: number }, event: Event): void; - dragEnd?(transform: { x: number; y: number }, event: Event): void; - slideStart?(value: number, event: Event): void; - slide?(value: number, event: Event): void; - slideEnd?(value: number, event: Event): void; - pinchStart?(event: Event): void; - pinch?(event: Event): void; - pinchEnd?(event: Event): void; - resize?(): void; - reset?(): void; - destroy?(): void; - update?(): void; - }; -} diff --git a/types/zoomist/package.json b/types/zoomist/package.json deleted file mode 100644 index 5bda4ef944cf4c..00000000000000 --- a/types/zoomist/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "private": true, - "name": "@types/zoomist", - "version": "1.1.9999", - "projects": [ - "https://github.com/cotton123236/zoomist" - ], - "devDependencies": { - "@types/zoomist": "workspace:." - }, - "owners": [ - { - "name": "scriptSQD", - "githubUsername": "scriptSQD" - }, - { - "name": "Wilson Wu", - "githubUsername": "cotton123236" - } - ] -} diff --git a/types/zoomist/tsconfig.json b/types/zoomist/tsconfig.json deleted file mode 100644 index a9e3ace35687b8..00000000000000 --- a/types/zoomist/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "module": "node16", - "lib": [ - "es6", - "dom" - ], - "noImplicitAny": true, - "noImplicitThis": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "types": [], - "noEmit": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.d.ts", - "zoomist-tests.ts" - ] -} diff --git a/types/zoomist/zoomist-tests.ts b/types/zoomist/zoomist-tests.ts deleted file mode 100644 index 12aeaac88d3991..00000000000000 --- a/types/zoomist/zoomist-tests.ts +++ /dev/null @@ -1,202 +0,0 @@ -// need to provide tests? 👀 - -import Zoomist = require("zoomist"); - -function checkCreateZoomist() { - const container = document.createElement("div"); - container.dataset["zoomist-custom-src"] = "https://avatars.githubusercontent.com/u/73490413?v=4"; - container.id = "zoomist-container"; - - document.body.appendChild(container); - - // $ExpectType Zoomist - const zoomist = new Zoomist(container, { - src: "zoomist-custom-src", - fill: "cover", - draggable: false, - wheelable: false, - pinchable: false, - bounds: false, - zoomRatio: 0.25, - maxRatio: 2.5, - height: "auto", - slider: { - el: false, - direction: "vertical", - maxRatio: 5, - }, - zoomer: { - inEl: false, - outEl: false, - disableOnBounds: true, - }, - }); -} - -function checkEventsFromZoomistConfig() { - const container = document.createElement("div"); - container.dataset["zoomist-custom-src"] = "https://avatars.githubusercontent.com/u/73490413?v=4"; - container.id = "zoomist-container"; - - document.body.appendChild(container); - - const zoomist = new Zoomist(container, { - on: { - // $ExpectType () => void - ready: () => console.log("ready"), - // $ExpectType (r: number) => void - zoom: (r: number) => { - console.log("zoom val:", r); - }, - // $ExpectType (e: Event) => void - wheel: (e: Event) => { - console.log("event type:", e.type); - }, - // $ExpectType (t: {}, e: Event) => void - dragStart: (t: {}, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }, - // $ExpectType (t: {}, e: Event) => void - drag: (t: {}, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }, - // $ExpectType (t: {}, e: Event) => void - dragEnd: (t: {}, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }, - // $ExpectType (v: number, e: Event) => void - slideStart: (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }, - // $ExpectType (v: number, e: Event) => void - slide: (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }, - // $ExpectType (v: number, e: Event) => void - slideEnd: (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }, - // $ExpectType (e: Event) => void - pinchStart: (e: Event) => { - console.log("event type:", e.type); - }, - // $ExpectType (e: Event) => void - pinch: (e: Event) => { - console.log("event type:", e.type); - }, - // $ExpectType (e: Event) => void - pinchEnd: (e: Event) => { - console.log("event type:", e.type); - }, - // $ExpectType () => void - resize: () => { - console.log("resize!"); - }, - // $ExpectType () => void - destroy: () => { - console.log("destroy!"); - }, - // $ExpectType () => void - update: () => { - console.log("update!"); - }, - }, - }); -} - -function checkEventsFromZoomistOn() { - const container = document.createElement("div"); - container.dataset["zoomist-custom-src"] = "https://avatars.githubusercontent.com/u/73490413?v=4"; - container.id = "zoomist-container"; - - document.body.appendChild(container); - - const zoomist = new Zoomist(container, {}); - - // $ExpectType void - zoomist.on("ready", () => { - console.log("ready!"); - }); - - // $ExpectType void - zoomist.on("zoom", (ratio: number) => { - console.log("ratio:", ratio); - }); - - // $ExpectType void - zoomist.on("wheel", (event: Event) => { - console.log("event type:", event.type); - }); - - // $ExpectType void - zoomist.on("dragStart", (t: { x: number; y: number }, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("drag", (t: { x: number; y: number }, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("dragEnd", (t: { x: number; y: number }, e: Event) => { - console.log("transform:", t); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("slideStart", (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("slide", (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("slideEnd", (v: number, e: Event) => { - console.log("value:", v); - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("pinchStart", (e: Event) => { - console.log("event type:", e.type); - }); - // $ExpectType void - zoomist.on("pinch", (e: Event) => { - console.log("event type:", e.type); - }); - // $ExpectType void - zoomist.on("pinchEnd", (e: Event) => { - console.log("event type:", e.type); - }); - - // $ExpectType void - zoomist.on("resize", () => { - console.log("resize!"); - }); - // $ExpectType void - zoomist.on("reset", () => { - console.log("reset!"); - }); - // $ExpectType void - zoomist.on("destroy", () => { - console.log("destroy!"); - }); - // $ExpectType void - zoomist.on("update", () => { - console.log("update!"); - }); -}