From 5d0d02379b70e8a27bfb7b38c34a2964151ee8e7 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Tue, 17 Dec 2024 08:57:45 +0100 Subject: [PATCH] Move documentation for Node.js adapter into own repo Closes #35539 Signed-off-by: rmartinc --- .gitignore | 1 + guides/attributes.adoc | 9 + guides/guides.mjs | 146 +++++++++ guides/images/logo.svg | 1 + guides/securing-apps/nodejs-adapter.adoc | 392 +++++++++++++++++++++++ guides/templates/guide.adoc | 33 ++ guides/templates/links.adoc | 1 + guides/templates/options.adoc | 51 +++ package-lock.json | 42 +-- package.json | 6 +- 10 files changed, 659 insertions(+), 23 deletions(-) create mode 100644 guides/attributes.adoc create mode 100755 guides/guides.mjs create mode 100644 guides/images/logo.svg create mode 100644 guides/securing-apps/nodejs-adapter.adoc create mode 100644 guides/templates/guide.adoc create mode 100644 guides/templates/links.adoc create mode 100644 guides/templates/options.adoc diff --git a/.gitignore b/.gitignore index c77468b5..f3a9e632 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ pid.txt tmp/ .vscode/ server/ +guides/target diff --git a/guides/attributes.adoc b/guides/attributes.adoc new file mode 100644 index 00000000..b2355075 --- /dev/null +++ b/guides/attributes.adoc @@ -0,0 +1,9 @@ +:project_name: Keycloak +:project_doc_base_url: https://www.keycloak.org/docs/latest +:authorizationguide_name: Authorization Services Guide +:authorizationguide_name_short: Authorization Services +:authorizationguide_link: {project_doc_base_url}/authorization_services/ +:project_product: false +:project_community: true +:section: guide +:quickstartRepo_link: https://github.com/keycloak/keycloak-quickstarts diff --git a/guides/guides.mjs b/guides/guides.mjs new file mode 100755 index 00000000..20476203 --- /dev/null +++ b/guides/guides.mjs @@ -0,0 +1,146 @@ +#!/usr/bin/env node + +/* + * Copyright 2024 Red Hat Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +import childProcess from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { Readable } from 'node:stream' +import url from 'node:url' +import JSZip from 'jszip' + +const donwloadFMPP = async (url, file) => { + if (!fs.existsSync(file)) { + console.log('Downloading fmpp from %s', url) + const response = await fetch(url) + if (!response.ok) { + throw new Error('Error downloading fmpp.') + } + await fs.promises.writeFile(file, Readable.fromWeb(response.body)) + } +} + +const unzipFMPP = async (targetDir, file) => { + console.log('Unzipping %s', file) + const zip = new JSZip() + const data = fs.readFileSync(file) + const contents = await zip.loadAsync(data) + for (const key of Object.keys(contents.files)) { + const name = path.join(targetDir, key) + const entry = zip.file(key) + if (entry) { + const content = await entry.async('nodebuffer') + fs.writeFileSync(name, content, name.endsWith('/bin/fmpp') ? { mode: 0o755 } : {}) + } else if (!fs.existsSync(name)) { + fs.mkdirSync(name) + } + } +} + +const executeFMPP = async (version, exe, sourceDir, outputDir) => { + const files = fs.readdirSync(sourceDir) + for (const file of files) { + const statFile = path.join(sourceDir, file) + const stat = fs.statSync(statFile) + + if (stat.isDirectory() && file !== 'templates' && file !== 'target' && file !== 'images') { + console.log('Processing folder %s', statFile) + const outputGuideDir = path.join(outputDir, file) + fs.mkdirSync(outputGuideDir) + + const files = fs.readdirSync(statFile) + const adocFiles = files.filter(el => path.extname(el) === '.adoc') + for (const adocFile of adocFiles) { + console.log('Processing file %s', adocFile) + const result = childProcess.spawnSync(exe, ['-S', sourceDir, '-O', outputDir, + '-D', 'id:' + path.parse(adocFile).name + ',version:' + version, + path.join(file, adocFile)]) + if (result.error) { + throw result.error + } + } + } + } +} + +const generateZipFromDirectory = async (zip, dir, root) => { + const files = fs.readdirSync(dir) + files.forEach(function (file) { + file = path.resolve(dir, file) + const stat = fs.statSync(file) + if (stat && stat.isDirectory()) { + generateZipFromDirectory(zip, file, root) + } else { + const filedata = fs.readFileSync(file) + zip.file(path.relative(root, file), filedata) + } + }) +} + +const generateZip = async (zip, zipFile, dir, root) => { + generateZipFromDirectory(zip, dir, root) + const data = await zip.generateAsync({ + type: 'nodebuffer', + compression: 'DEFLATE', + compressionOptions: { + level: 9 + } + }) + console.log('Generating zip file %s', zipFile) + fs.writeFileSync(zipFile, data) +} + +const main = async (version) => { + console.log('Generating guides for version %s', version) + + const fmppUrl = 'https://sourceforge.net/projects/fmpp/files/latest/download' + const dir = path.dirname(url.fileURLToPath(import.meta.url)) + const targetDir = path.join(dir, 'target') + const fmppZip = path.join(targetDir, 'fmpp.zip') + const fmppExe = path.join(targetDir, 'fmpp', 'bin', 'fmpp') + + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir) + } + + let outputDir = path.join(targetDir, 'keycloak-nodejs-connect-guides-' + version) + if (fs.existsSync(outputDir)) { + fs.rmSync(outputDir, { recursive: true }) + } + fs.mkdirSync(outputDir) + + outputDir = path.join(outputDir, 'generated-guides') + fs.mkdirSync(outputDir) + + await donwloadFMPP(fmppUrl, fmppZip) + await unzipFMPP(targetDir, fmppZip) + await executeFMPP(version, fmppExe, dir, outputDir) + + fs.cpSync(path.join(dir, 'images'), path.join(outputDir, 'images'), { recursive: true }) + fs.cpSync(path.join(dir, 'attributes.adoc'), path.join(outputDir, 'attributes.adoc')) + + const zipFile = path.join(targetDir, 'keycloak-nodejs-connect-guides-' + version + '.zip') + generateZip(new JSZip(), zipFile, outputDir, targetDir) +} + +if (process.argv.length !== 3) { + console.log('Usage: node generate.js ') + process.exit(1) +} + +const version = process.argv[2] +main(version) diff --git a/guides/images/logo.svg b/guides/images/logo.svg new file mode 100644 index 00000000..74f50ab4 --- /dev/null +++ b/guides/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/guides/securing-apps/nodejs-adapter.adoc b/guides/securing-apps/nodejs-adapter.adoc new file mode 100644 index 00000000..3d62c8e8 --- /dev/null +++ b/guides/securing-apps/nodejs-adapter.adoc @@ -0,0 +1,392 @@ +<#import "/templates/guide.adoc" as tmpl> +<#import "/templates/links.adoc" as links> + +<@tmpl.guide +title="{project_name} Node.js adapter" +priority=40 +summary="Node.js adapter to protect server-side JavaScript apps"> + +{project_name} provides a Node.js adapter built on top of https://github.com/senchalabs/connect[Connect] to protect server-side JavaScript apps - the goal was to be flexible enough to integrate with frameworks like https://expressjs.com/[Express.js]. +The adapter uses OpenID Connect protocol under the covers. You can take a look at the <@links.securingapps id="oidc-layers" anchor="_oidc_available_endpoints"/> {section} for the more generic information about OpenID Connect endpoints and capabilities. + +ifeval::[{project_community}==true] +The library can be downloaded directly from https://www.npmjs.com/package/keycloak-connect[ {project_name} organization] and the source is available at +https://github.com/keycloak/keycloak-nodejs-connect[GitHub]. +endif::[] + +To use the Node.js adapter, first you must create a client for your application in the {project_name} Admin Console. The adapter supports public, confidential, and bearer-only access type. Which one to choose depends on the use-case scenario. + +Once the client is created, click *Action* at the top right and choose *Download adapter config*. For *Format, choose *Keycloak OIDC JSON* and click *Download*. The downloaded `keycloak.json` file is at the root folder of your project. + +== Installation + +Assuming you have already installed https://nodejs.org[Node.js], create a folder for your application: + + mkdir myapp && cd myapp + +Use `npm init` command to create a `package.json` for your application. Now add the {project_name} connect adapter in the dependencies list: + +ifeval::[{project_community}==true] + +[source,json,subs="attributes"] +---- + "dependencies": { + "keycloak-connect": "{project_versionNpm}" + } +---- + +endif::[] + +ifeval::[{project_product}==true] + +[source,json,subs="attributes"] +---- + "dependencies": { + "keycloak-connect": "file:keycloak-connect-{project_versionNpm}.tgz" + } +---- + +endif::[] + +== Usage +Instantiate a Keycloak class:: + +The `Keycloak` class provides a central point for configuration +and integration with your application. The simplest creation +involves no arguments. + +In the root directory of your project create a file called `server.js` and add the following code: + +[source,javascript] +---- + const session = require('express-session'); + const Keycloak = require('keycloak-connect'); + + const memoryStore = new session.MemoryStore(); + const keycloak = new Keycloak({ store: memoryStore }); +---- + +Install the `express-session` dependency: + +---- + npm install express-session +---- + +To start the `server.js` script, add the following command in the 'scripts' section of the `package.json`: + +---- + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, +---- + +Now we have the ability to run our server with following command: + +---- + npm run start +---- + +By default, this will locate a file named `keycloak.json` alongside +the main executable of your application, in our case on the root folder, to initialize {project_name} specific +settings such as public key, realm name, various URLs. + +In that case a {project_name} deployment is necessary to access {project_name} admin console. + +Please visit links on how to deploy a {project_name} admin console with +https://www.keycloak.org/getting-started/getting-started-podman[Podman] or https://www.keycloak.org/getting-started/getting-started-docker[Docker] + +Now we are ready to obtain the `keycloak.json` file by visiting the {project_name} Admin Console -> clients (left sidebar) -> choose your client -> Installation -> Format Option -> Keycloak OIDC JSON -> Download + +Paste the downloaded file on the root folder of our project. + +Instantiation with this method results in all the reasonable defaults +being used. As alternative, it's also possible to provide a configuration +object, rather than the `keycloak.json` file: + +[source,javascript,subs="attributes+"] +---- + const kcConfig = { + clientId: 'myclient', + bearerOnly: true, + serverUrl: 'http://localhost:8080{kc_base_path}', + realm: 'myrealm', + realmPublicKey: 'MIIBIjANB...' + }; + + const keycloak = new Keycloak({ store: memoryStore }, kcConfig); +---- + +Applications can also redirect users to their preferred identity provider by using: +[source,javascript] +---- + const keycloak = new Keycloak({ store: memoryStore, idpHint: myIdP }, kcConfig); +---- + +Configuring a web session store:: + +If you want to use web sessions to manage +server-side state for authentication, you need to initialize the +`Keycloak(...)` with at least a `store` parameter, passing in the actual +session store that `express-session` is using. +[source,javascript] +---- + const session = require('express-session'); + const memoryStore = new session.MemoryStore(); + + // Configure session + app.use( + session({ + secret: 'mySecret', + resave: false, + saveUninitialized: true, + store: memoryStore, + }) + ); + + const keycloak = new Keycloak({ store: memoryStore }); +---- +Passing a custom scope value:: + +By default, the scope value `openid` is passed as a query parameter to {project_name}'s login URL, but you can add an additional custom value: +[source,javascript] + const keycloak = new Keycloak({ scope: 'offline_access' }); + +== Installing middleware + +Once instantiated, install the middleware into your connect-capable app: + +In order to do so, first we have to install Express: +---- + npm install express +---- + +then require Express in our project as outlined below: + +[source,javascript] +---- + const express = require('express'); + const app = express(); +---- + + +and configure Keycloak middleware in Express, by adding at the code below: + +[source,javascript] +---- + app.use( keycloak.middleware() ); +---- + +Last but not least, let's set up our server to listen for HTTP requests on port 3000 by adding the following code to `main.js`: + +[source,javascript] +---- + app.listen(3000, function () { + console.log('App listening on port 3000'); + }); +---- + +== Configuration for proxies + +If the application is running behind a proxy that terminates an SSL connection +Express must be configured per the link:https://expressjs.com/en/guide/behind-proxies.html[express behind proxies] guide. +Using an incorrect proxy configuration can result in invalid redirect URIs +being generated. + +Example configuration: + +[source,javascript] +---- + const app = express(); + + app.set( 'trust proxy', true ); + + app.use( keycloak.middleware() ); +---- + +== Protecting resources + +Simple authentication:: + +To enforce that a user must be authenticated before accessing a resource, +simply use a no-argument version of `keycloak.protect()`: + +[source,javascript] +---- + app.get( '/complain', keycloak.protect(), complaintHandler ); +---- + +Role-based authorization:: + +To secure a resource with an application role for the current app: + +[source,javascript] +---- + app.get( '/special', keycloak.protect('special'), specialHandler ); +---- + +To secure a resource with an application role for a *different* app: + +[source,javascript] + app.get( '/extra-special', keycloak.protect('other-app:special'), extraSpecialHandler ); + +To secure a resource with a realm role: + +[source,javascript] + app.get( '/admin', keycloak.protect( 'realm:admin' ), adminHandler ); + +Resource-Based Authorization:: + +Resource-Based Authorization allows you to protect resources, and their specific methods/actions,**** based on a set of policies defined in Keycloak, thus externalizing authorization from your application. This is achieved by exposing a `keycloak.enforcer` method which you can use to protect resources.* + +[source,javascript] +---- + app.get('/apis/me', keycloak.enforcer('user:profile'), userProfileHandler); +---- + +The `keycloak-enforcer` method operates in two modes, depending on the value of the `response_mode` configuration option. + +[source,javascript] +---- + app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), userProfileHandler); +---- + +If `response_mode` is set to `token`, permissions are obtained from the server on behalf of the subject represented by the bearer token that was sent to your application. In this case, a new access token is issued by Keycloak with the permissions granted by the server. If the server did not respond with a token with the expected permissions, the request is denied. When using this mode, you should be able to obtain the token from the request as follows: + +[source,javascript] +---- + app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), function (req, res) { + const token = req.kauth.grant.access_token.content; + const permissions = token.authorization ? token.authorization.permissions : undefined; + + // show user profile + }); +---- + +Prefer this mode when your application is using sessions and you want to cache previous decisions from the server, as well automatically handle refresh tokens. This mode is especially useful for applications acting as a client and resource server. + +If `response_mode` is set to `permissions` (default mode), the server only returns the list of granted permissions, without issuing a new access token. In addition to not issuing a new token, this method exposes the permissions granted by the server through the `request` as follows: + +[source,javascript] +---- + app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'permissions'}), function (req, res) { + const permissions = req.permissions; + + // show user profile + }); +---- + +Regardless of the `response_mode` in use, the `keycloak.enforcer` method will first try to check the permissions within the bearer token that was sent to your application. If the bearer token already carries the expected permissions, there is no need +to interact with the server to obtain a decision. This is specially useful when your clients are capable of obtaining access tokens from the server with the expected permissions before accessing a protected resource, so they can use some capabilities provided by Keycloak Authorization Services such as incremental authorization and avoid additional requests to the server when `keycloak.enforcer` is enforcing access to the resource. + +By default, the policy enforcer will use the `client_id` defined to the application (for instance, via `keycloak.json`) to + reference a client in Keycloak that supports Keycloak Authorization Services. In this case, the client can not be public given + that it is actually a resource server. + +If your application is acting as both a public client(frontend) and resource server(backend), you can use the following configuration to reference a different +client in Keycloak with the policies that you want to enforce: + +[source,javascript] +---- + keycloak.enforcer('user:profile', {resource_server_id: 'my-apiserver'}) +---- + +It is recommended to use distinct clients in Keycloak to represent your frontend and backend. + +If the application you are protecting is enabled with Keycloak authorization services and you have defined client credentials + in `keycloak.json`, you can push additional claims to the server and make them available to your policies in order to make decisions. +For that, you can define a `claims` configuration option which expects a `function` that returns a JSON with the claims you want to push: + +[source,javascript] +---- + app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], { + claims: function(request) { + return { + "http.uri": ["/protected/resource"], + "user.agent": // get user agent from request + } + } + }), function (req, res) { + // access granted +---- + +For more details about how to configure Keycloak to protected your application resources, please take a look at the link:{authorizationguide_link}[{authorizationguide_name}]. + +Advanced authorization:: + +To secure resources based on parts of the URL itself, assuming a role exists +for each section: + +[source,javascript] +---- + function protectBySection(token, request) { + return token.hasRole( request.params.section ); + } + + app.get( '/:section/:page', keycloak.protect( protectBySection ), sectionHandler ); +---- + +Advanced Login Configuration: + +By default, all unauthorized requests will be redirected to the {project_name} login page unless your client is bearer-only. +However, a confidential or public client may host both browsable and API endpoints. To prevent redirects on unauthenticated +API requests and instead return an HTTP 401, you can override the redirectToLogin function. + +For example, this override checks if the URL contains /api/ and disables login redirects: + +[source,javascript] +---- + Keycloak.prototype.redirectToLogin = function(req) { + const apiReqMatcher = /\/api\//i; + return !apiReqMatcher.test(req.originalUrl || req.url); + }; +---- + +== Additional URLs + +Explicit user-triggered logout:: + +By default, the middleware catches calls to `/logout` to send the user through a +{project_name}-centric logout workflow. This can be changed by specifying a `logout` +configuration parameter to the `middleware()` call: + +[source,javascript] +---- + app.use( keycloak.middleware( { logout: '/logoff' } )); +---- + +When the user-triggered logout is invoked a query parameter `redirect_url` can be passed: + +[source] +---- +https://example.com/logoff?redirect_url=https%3A%2F%2Fexample.com%3A3000%2Flogged%2Fout +---- + +This parameter is then used as the redirect url of the OIDC logout endpoint and the user will be redirected to +`\https://example.com/logged/out`. + +{project_name} Admin Callbacks:: + +Also, the middleware supports callbacks from the {project_name} console to log out a single +session or all sessions. By default, these type of admin callbacks occur relative +to the root URL of `/` but can be changed by providing an `admin` parameter +to the `middleware()` call: +[source,javascript] + app.use( keycloak.middleware( { admin: '/callbacks' } ); + +== Complete example + +A complete example using the Node.js adapter usage can be found in {quickstartRepo_link}/tree/main/nodejs/resource-server[Keycloak quickstarts for Node.js] + +[[_upgrade_nodejs_adapter]] +== Upgrading the `Node.js` adapter + +To upgrade a `Node.js` adapter that has been copied to your web application, perform the following procedure. + +.Procedure +. Download the new adapter archive. +. Remove the existing `Node.js` adapter directory +. Unzip the updated file into its place +. Change the dependency for keycloak-connect in the `package.json` of your application + + diff --git a/guides/templates/guide.adoc b/guides/templates/guide.adoc new file mode 100644 index 00000000..3ca7c545 --- /dev/null +++ b/guides/templates/guide.adoc @@ -0,0 +1,33 @@ +<#import "/templates/options.adoc" as opts> + +<#macro guide title summary priority=999 deniedCategories="" includedOptions="" preview="" tileVisible="true" previewDiscussionLink=""> +:guide-id: ${id} +:guide-title: ${title} +:guide-summary: ${summary} +:guide-priority: ${priority} +:guide-tile-visible: ${tileVisible} +:version: ${version} + +include::../attributes.adoc[] + +[[${id}]] += ${title} + +ifeval::["${preview}" == "true"] +WARNING: This {section} is describing a feature which is currently in preview. +ifeval::["${previewDiscussionLink}" == ""] +Please provide your feedback while we’re continuing to work on this. +endif::[] +ifeval::["${previewDiscussionLink}" != ""] +Please provide your feedback by link:${previewDiscussionLink}[joining this discussion] while we’re continuing to work on this. +endif::[] +endif::[] + +<#nested> + +<#if includedOptions?has_content> +== Relevant options + +<@opts.list options=ctx.options.getOptions(includedOptions, deniedCategories) anchor=false> + + diff --git a/guides/templates/links.adoc b/guides/templates/links.adoc new file mode 100644 index 00000000..b2f12d9e --- /dev/null +++ b/guides/templates/links.adoc @@ -0,0 +1 @@ +<#macro securingapps id anchor="">link:{links_securing-apps_${id}_url}<#if anchor != "">#${anchor}[{links_securing-apps_${id}_name}] diff --git a/guides/templates/options.adoc b/guides/templates/options.adoc new file mode 100644 index 00000000..43d5bf1d --- /dev/null +++ b/guides/templates/options.adoc @@ -0,0 +1,51 @@ +<#macro expectedValues option> + <#assign optionObj = ctx.options.getOption(option) /> + <#list optionObj.expectedValues as expectedValue> + * ${expectedValue} <#if optionObj.defaultValue?has_content && expectedValue == optionObj.defaultValue> (default) + + + +<#macro list options buildIcon=true anchor=true> +[cols="12a,4",role="options"] +|=== +| |Value + +<#list options as option> +| +[.options-key]#``${option.key}``# <#if buildIcon><#if option.build>[.none]#icon:tools[role=options-build]# + +[.options-description]#${option.description}# + +[<#if anchor>#option-extended-${option.key},role="options-extended"] +-- +<#if option.descriptionExtended?has_content>[.options-description-extended]#${option.descriptionExtended!}# + +*CLI:* `${option.keyCli}` + +*Env:* `${option.keyEnv}` +-- + +<#if option.enabledWhen?has_content> +${option.enabledWhen!} + + +<#if option.deprecated?has_content> +<#-- Either mark the whole option as deprecated, or just selected values --> +<#if !option.deprecated.deprecatedValues?has_content> +*DEPRECATED.* + +${option.deprecated.note!}<#if option.deprecated.newOptionsKeys?has_content><#if option.deprecated.note?has_content> Use: <#list option.deprecated.newOptionsKeys as key>`+${key}+`<#if key?has_next>, . +<#if option.deprecated.deprecatedValues?has_content> +*Deprecated values: <#list option.deprecated.deprecatedValues as value>`+${value}+`<#if value?has_next>, * + + + +|<#if option.expectedValues?has_content> +<#list option.expectedValues as value>`+${value!}+`<#if option.defaultValue?has_content && value = option.defaultValue> (default)<#if value?has_next>, <#if !option.strictExpectedValues>, or any +<#else> +<#if option.defaultValue?has_content>[.options-default]#`+${option.defaultValue!}+`# (default)<#if option.type?has_content && option.defaultValue?has_content> or <#if option.type?has_content && !option.expectedValues?has_content>any `+${option.type!}+` + + + + +|=== + diff --git a/package-lock.json b/package-lock.json index eb81f477..2d59bde6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "keycloak-connect", - "version": "17.0.0-dev", + "version": "999.0.0-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "keycloak-connect", - "version": "17.0.0-dev", + "version": "999.0.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "jwk-to-pem": "^2.0.0" @@ -25,6 +25,7 @@ "hogan-express": "^0.5.2", "ink-docstrap": "^1.1.4", "jsdoc": "^4.0.4", + "jszip": "^3.10.1", "keycloak-request-token": "^0.1.0", "nock": "^13.2.2", "node-fetch": "^3.2.3", @@ -5501,15 +5502,16 @@ } }, "node_modules/jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" } }, "node_modules/jszip/node_modules/isarray": { @@ -7588,14 +7590,12 @@ "node": ">= 0.4" } }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -13218,15 +13218,15 @@ } }, "jszip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", - "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, "requires": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "setimmediate": "^1.0.5" }, "dependencies": { "isarray": { @@ -14814,10 +14814,10 @@ "has-property-descriptors": "^1.0.2" } }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true }, "setprototypeof": { diff --git a/package.json b/package.json index aa3ba499..10bf83d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keycloak-connect", - "version": "17.0.0-dev", + "version": "999.0.0-SNAPSHOT", "description": "Keycloak Connect Middleware", "homepage": "http://keycloak.org", "main": "keycloak.js", @@ -9,7 +9,8 @@ "test": "./run-tests.sh", "docs": "jsdoc --verbose -d docs -t ./node_modules/ink-docstrap/template -R README.md index.js ./middleware/*.js stores/*.js ./middleware/auth-utils/*.js", "coverage": "nyc cover tape test/unit/*.js tape test/*.js", - "server:start": "./scripts/start-server.mjs -Dkeycloak.profile.feature.account_api=disabled -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=test/fixtures/auth-utils/nodejs-test-realm.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING" + "server:start": "./scripts/start-server.mjs -Dkeycloak.profile.feature.account_api=disabled -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=singleFile -Dkeycloak.migration.file=test/fixtures/auth-utils/nodejs-test-realm.json -Dkeycloak.migration.strategy=OVERWRITE_EXISTING", + "guides": "node guides/guides.mjs $npm_package_version" }, "keywords": [ "sso", @@ -51,6 +52,7 @@ "hogan-express": "^0.5.2", "ink-docstrap": "^1.1.4", "jsdoc": "^4.0.4", + "jszip": "^3.10.1", "keycloak-request-token": "^0.1.0", "nock": "^13.2.2", "node-fetch": "^3.2.3",