diff --git a/examples/crossTenantReactJSExample/development/package.json b/examples/crossTenantReactJSExample/development/package.json index 48646ee..c0f62b9 100644 --- a/examples/crossTenantReactJSExample/development/package.json +++ b/examples/crossTenantReactJSExample/development/package.json @@ -12,7 +12,7 @@ "author": "", "license": "ISC", "dependencies": { - "antd": "^4.16.3", + "antd": "^4.16.8", "axios": "^0.21.1", "jsonwebtoken": "^8.5.1", "jwa": "^2.0.0", @@ -20,22 +20,22 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@craco/craco": "^6.1.2", - "@shopify/eslint-plugin": "^40.2.3", - "@types/jsonwebtoken": "^8.5.2", - "@types/react": "^17.0.11", - "@types/react-dom": "^17.0.8", - "@typescript-eslint/parser": "^4.27.0", + "@craco/craco": "^6.2.0", + "@shopify/eslint-plugin": "^40.4.0", + "@types/jsonwebtoken": "^8.5.4", + "@types/react": "^17.0.14", + "@types/react-dom": "^17.0.9", + "@typescript-eslint/parser": "^4.28.4", "body-parser": "^1.19.0", "cookie": "^0.4.1", "cookie-parser": "^1.4.5", - "eslint": "^7.29.0", + "eslint": "^7.31.0", "eslint-plugin-no-loops": "^0.3.0", "express": "^4.17.1", "express-session": "^1.17.2", "keycloak-api-gateway": "../../..", "parcel-bundler": "^1.12.5", - "typescript": "^4.3.4", + "typescript": "^4.3.5", "webpack-manifest-plugin": "^3.1.1" }, "browserslist": { diff --git a/examples/crossTenantReactJSExample/tenantSelectorApp/index.js b/examples/crossTenantReactJSExample/tenantSelectorApp/index.js index e04f9ae..4b4a923 100644 --- a/examples/crossTenantReactJSExample/tenantSelectorApp/index.js +++ b/examples/crossTenantReactJSExample/tenantSelectorApp/index.js @@ -5,9 +5,9 @@ const session = require('express-session'); const Keycloak = require('keycloak-connect'); const express = require('express'); const exphbs = require('express-handlebars'); -const {serviceAccountJWT} = require('keycloak-lambda-authorizer/src/serviceAccount'); -const {getKeycloakUrl, getUrl} = require('keycloak-lambda-authorizer/src/utils/restCalls'); const bodyParser = require('body-parser'); +const KeycloakAdapter = require('keycloak-lambda-authorizer/dist/Adapter'); +const {getKeycloakUrl,getUrl} = require('keycloak-lambda-authorizer/dist/src/utils/KeycloakUtils'); const {fetchData, sendData} = require('./restCalls'); @@ -17,6 +17,8 @@ function getMaterKeycloakJSON() { return JSON.parse(fs.readFileSync(`${__dirname}/master-keycloak.json`, 'utf8')); } +const serviceAccount = new KeycloakAdapter.default({keycloakJson:getMaterKeycloakJSON}).getServiceAccount(); + const memoryStore = new session.MemoryStore(); app.use(session({ @@ -62,7 +64,7 @@ app.post('/requestAccess', keycloak.protect(), keycloak.enforcer(['Request-acces const userName = request.kauth.grant.access_token.content.preferred_username; const userId = request.kauth.grant.access_token.content.sub; const keycloakJSon = getMaterKeycloakJSON(); - const token = await serviceAccountJWT(keycloakJSon, {}); + const token = await serviceAccount.getServiceAccountToken({request:request}); let res = await sendData(`${getKeycloakUrl(keycloakJSon)}/admin/realms/${request.query.tenant}/users`, 'POST', JSON.stringify({ enabled: false, attributes: {}, @@ -92,7 +94,7 @@ app.get('/', keycloak.protect(), keycloak.enforcer(['Tenant-List']), async (requ const userName = request.kauth.grant.access_token.content.preferred_username; const keycloakJSon = getMaterKeycloakJSON(); try { - const token = await serviceAccountJWT(keycloakJSon, {}); + const token = await serviceAccount.getServiceAccountToken({request:request}); let res = await fetchData(`${getKeycloakUrl(keycloakJSon)}/admin/realms`, 'GET', { Authorization: `Bearer ${token}`, }); diff --git a/examples/multiTenantReactJSExample/development/package.json b/examples/multiTenantReactJSExample/development/package.json index 48646ee..c0f62b9 100644 --- a/examples/multiTenantReactJSExample/development/package.json +++ b/examples/multiTenantReactJSExample/development/package.json @@ -12,7 +12,7 @@ "author": "", "license": "ISC", "dependencies": { - "antd": "^4.16.3", + "antd": "^4.16.8", "axios": "^0.21.1", "jsonwebtoken": "^8.5.1", "jwa": "^2.0.0", @@ -20,22 +20,22 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@craco/craco": "^6.1.2", - "@shopify/eslint-plugin": "^40.2.3", - "@types/jsonwebtoken": "^8.5.2", - "@types/react": "^17.0.11", - "@types/react-dom": "^17.0.8", - "@typescript-eslint/parser": "^4.27.0", + "@craco/craco": "^6.2.0", + "@shopify/eslint-plugin": "^40.4.0", + "@types/jsonwebtoken": "^8.5.4", + "@types/react": "^17.0.14", + "@types/react-dom": "^17.0.9", + "@typescript-eslint/parser": "^4.28.4", "body-parser": "^1.19.0", "cookie": "^0.4.1", "cookie-parser": "^1.4.5", - "eslint": "^7.29.0", + "eslint": "^7.31.0", "eslint-plugin-no-loops": "^0.3.0", "express": "^4.17.1", "express-session": "^1.17.2", "keycloak-api-gateway": "../../..", "parcel-bundler": "^1.12.5", - "typescript": "^4.3.4", + "typescript": "^4.3.5", "webpack-manifest-plugin": "^3.1.1" }, "browserslist": { diff --git a/examples/multiTenantReactJSExample/tenantSelectorApp/index.js b/examples/multiTenantReactJSExample/tenantSelectorApp/index.js index 38c50a4..8afcefb 100644 --- a/examples/multiTenantReactJSExample/tenantSelectorApp/index.js +++ b/examples/multiTenantReactJSExample/tenantSelectorApp/index.js @@ -3,8 +3,8 @@ const path = require('path'); const express = require('express'); const exphbs = require('express-handlebars'); -const {serviceAccountJWT} = require('keycloak-lambda-authorizer/src/serviceAccount'); -const {getKeycloakUrl, getUrl} = require('keycloak-lambda-authorizer/src/utils/restCalls'); +const KeycloakAdapter = require('keycloak-lambda-authorizer/dist/Adapter'); +const {getKeycloakUrl, getUrl} = require('keycloak-lambda-authorizer/dist/src/utils/KeycloakUtils'); const bodyParser = require('body-parser'); const {fetchData, sendData} = require('./restCalls'); @@ -15,6 +15,7 @@ function getKeycloakJSON() { return JSON.parse(fs.readFileSync(`${__dirname}/keycloak.json`, 'utf8')); } +const serviceAccount = new KeycloakAdapter.default({keycloakJson:getKeycloakJSON,}).getServiceAccount(); app.use(bodyParser.urlencoded({extended: true})); app.engine('.hbs', exphbs({ @@ -43,7 +44,7 @@ app.get('/', async (request, response) => { const keycloakJSon = getKeycloakJSON(); try { - const token = await serviceAccountJWT(keycloakJSon, {}); + const token = await serviceAccount.getServiceAccountToken({request}); let res = await fetchData(`${getKeycloakUrl(keycloakJSon)}/admin/realms`, 'GET', { Authorization: `Bearer ${token}`, }); diff --git a/examples/reactJSExample/development/package.json b/examples/reactJSExample/development/package.json index c11cef5..f3ed2d6 100644 --- a/examples/reactJSExample/development/package.json +++ b/examples/reactJSExample/development/package.json @@ -20,22 +20,22 @@ }, "devDependencies": { "@craco/craco": "^6.1.2", + "@shopify/eslint-plugin": "^40.2.3", "@types/jsonwebtoken": "^8.5.2", "@types/react": "^17.0.11", "@types/react-dom": "^17.0.8", + "@typescript-eslint/parser": "^4.27.0", "body-parser": "^1.19.0", - "keycloak-api-gateway": "../../..", "cookie": "^0.4.1", "cookie-parser": "^1.4.5", + "eslint": "^7.29.0", + "eslint-plugin-no-loops": "^0.3.0", "express": "^4.17.1", "express-session": "^1.17.2", + "keycloak-api-gateway": "../../..", "parcel-bundler": "^1.12.5", "typescript": "^4.3.4", - "webpack-manifest-plugin": "^3.1.1", - "@shopify/eslint-plugin": "^40.2.3", - "@typescript-eslint/parser": "^4.27.0", - "eslint": "^7.29.0", - "eslint-plugin-no-loops": "^0.3.0" + "webpack-manifest-plugin": "^3.1.1" }, "browserslist": { "production": [ diff --git a/package.json b/package.json index e5cc847..a696f45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "keycloak-api-gateway", - "version": "0.2.1", + "version": "1.0.1", "description": "", "main": "index.js", "scripts": { @@ -17,7 +17,12 @@ "keycloak", "api", "gateway", - "api-gateway" + "cloudfront", + "lambda:edge", + "lambda@edge", + "api-gateway", + "multi-tenant", + "multitenant" ], "author": "vzakharchenko", "license": "Apache-2.0", @@ -26,25 +31,26 @@ }, "homepage": "https://github.com/vzakharchenko/keycloak-api-gateway#readme", "devDependencies": { - "@shopify/eslint-plugin": "^40.3.0", + "@shopify/eslint-plugin": "^40.4.0", "@types/cookie": "^0.4.1", "@types/cookie-parser": "^1.4.2", "@types/jest": "^26.0.24", "@types/jsonwebtoken": "^8.5.4", "@types/uuid": "^8.3.1", - "@typescript-eslint/eslint-plugin": "^4.28.2", - "@typescript-eslint/parser": "^4.28.2", + "@typescript-eslint/eslint-plugin": "^4.28.4", + "@typescript-eslint/parser": "^4.28.4", "coveralls": "^3.1.1", - "eslint": "^7.30.0", + "eslint": "^7.31.0", "eslint-plugin-no-loops": "^0.3.0", "jest": "^27.0.6", - "ts-jest": "^27.0.3", + "ts-jest": "^27.0.4", "typescript": "^4.3.5" }, "dependencies": { - "aws-sdk": "^2.940.0", + "aws-sdk": "^2.952.0", "jsonwebtoken": "^8.5.1", - "keycloak-lambda-authorizer": "^0.5.2", + "jws": "^4.0.0", + "keycloak-lambda-authorizer": "1.0.1", "uuid": "^8.3.2" }, "optionalDependencies": { diff --git a/src/apigateway/ApiGateway.test.ts b/src/apigateway/ApiGateway.test.ts index ca90c6a..1a46348 100644 --- a/src/apigateway/ApiGateway.test.ts +++ b/src/apigateway/ApiGateway.test.ts @@ -5,7 +5,7 @@ import {AccessLevel, Options, RequestObject, ResponseObject} from "../index"; import {initOptions} from "../utils/DefaultPageHandlers"; import {Logout} from "../logout/Logout"; import {Callback} from "../callback/Callback"; -import {JWKS} from "../jwks/JWKS"; +import {UrlJWKS} from "../jwks/UrlJWKS"; import {TenantAdapter} from "../tenant/TenantAdapter"; import {MultiTenantAdapter} from "../multitenants/Multi-tenant-adapter"; import {getCustomPageHandler} from "../utils/KeycloakUtils"; @@ -61,7 +61,7 @@ export class DummyLogout implements Logout { } } -export class DummyJWKS implements JWKS { +export class DummyJWKS implements UrlJWKS { private isRequest: boolean; constructor(isRequest: boolean) { @@ -72,7 +72,7 @@ export class DummyJWKS implements JWKS { return this.isRequest; } - async jwks(req: RequestObject, res: ResponseObject): Promise { + async getPublicKey(req: RequestObject, res: ResponseObject): Promise { throw new Error('jwks'); } @@ -245,6 +245,7 @@ describe('ApiGateway tests', () => { }); test('test singleTenant error2', async () => { + // @ts-ignore const apiGateway = new DefaultApiGateway({ ...options, ...{ @@ -268,6 +269,7 @@ describe('ApiGateway tests', () => { }); test('test singleTenant', async () => { + // @ts-ignore const apiGateway = new DefaultApiGateway({ ...options, ...{ @@ -303,6 +305,7 @@ describe('ApiGateway tests', () => { }, }; }); + // @ts-ignore const apiGateway = new DefaultApiGateway({ ...options, ...{ @@ -338,6 +341,7 @@ describe('ApiGateway tests', () => { } return null; }); + // @ts-ignore const apiGateway = new DefaultApiGateway({ ...options, ...{ diff --git a/src/apigateway/ApiGateway.ts b/src/apigateway/ApiGateway.ts index 5b4d38a..0c0aeef 100644 --- a/src/apigateway/ApiGateway.ts +++ b/src/apigateway/ApiGateway.ts @@ -1,5 +1,7 @@ +import {AdapterContent, KeycloakJsonStructure, AdapterDependencies} from "keycloak-lambda-authorizer/dist/src/Options"; + import {getSessionToken, SessionTokenKeys} from "../session/SessionManager"; -import {IdentityProviders, Options, RequestObject, ResponseObject} from "../index"; +import {IdentityProviders, MultitenantAdapterDependencies, Options, RequestObject, ResponseObject} from "../index"; import {DynamoDbSettings} from "../session/storage/DynamoDB"; import {getCustomPageHandler, getSessionName} from "../utils/KeycloakUtils"; import {initOptions} from "../utils/DefaultPageHandlers"; @@ -8,11 +10,9 @@ import {StrorageDB} from "../session/storage/Strorage"; export type APIGateWayOptions = { - multiTenantJson?: (tenant: string) => Promise | any; - // eslint-disable-next-line no-warning-comments, line-comment-position - multiTenantAdapterOptions?: any; // todo - // eslint-disable-next-line no-warning-comments, line-comment-position - defaultAdapterOptions?: any; // todo + multiTenantJson?: (tenant: string) => Promise | KeycloakJsonStructure; + multiTenantAdapterOptions?: MultitenantAdapterDependencies; + defaultAdapterOptions?: AdapterDependencies; identityProviders?: IdentityProviders; pageHandlers?: PageHandlers; storageType: 'InMemoryDB'|'DynamoDB' | StrorageDB, @@ -62,7 +62,7 @@ export class DefaultApiGateway implements ApiGateway { return; } if (this.options.jwks.isJwksRoute(request)) { - await this.options.jwks.jwks(request, response); + await this.options.jwks.getPublicKey(request, response); return; } if (this.options.callback.isCallBack(request)) { diff --git a/src/callback/Callback.test.ts b/src/callback/Callback.test.ts index e1980b6..d7566f0 100644 --- a/src/callback/Callback.test.ts +++ b/src/callback/Callback.test.ts @@ -1,30 +1,32 @@ /* eslint-disable no-empty-function, no-shadow, @typescript-eslint/no-empty-function, @typescript-eslint/ban-ts-comment */ import 'jest'; +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; + import {Options, RequestObject, ResponseObject} from "../index"; -import {SessionManager, SessionToken, TokenType} from "../session/SessionManager"; +import {SessionManager, SessionToken} from "../session/SessionManager"; import {initOptions} from "../utils/DefaultPageHandlers"; import * as t from "../utils/KeycloakUtils"; import {KeycloakState} from "../utils/KeycloakUtils"; +import {DummyClientAuthorization} from "../utils/DummyImplementations.test"; import {DefaultCallback} from "./Callback"; // import mock = jest.mock; -jest.mock('keycloak-lambda-authorizer/src/utils/optionsUtils'); jest.mock('../utils/KeycloakUtils'); let options: Options; class DummySessionManager implements SessionManager { - async createSession(req: RequestObject, state: KeycloakState, token: TokenType): Promise { + async createSession(req: RequestObject, state: KeycloakState, token: TokenJson): Promise { return "sessionId"; } - getSessionAccessToken(session: SessionToken): Promise { + async getSessionAccessToken(session: SessionToken): Promise { return Promise.resolve(undefined); } - updateSession(sessionId: string, email: string, externalToken: any): Promise { + updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { return Promise.resolve(undefined); } @@ -48,14 +50,14 @@ describe('Callback tests', () => { }, }, multiTenantOptions: { + // @ts-ignore multiTenantJson: () => { return {}; }, multiTenantAdapterOptions: { + clientAuthorization: new DummyClientAuthorization(), }, }, }; options = initOptions(options); - // @ts-ignore - t.getTokenByCode.mockImplementation(async () => { return {}; }); }); test('test isCallBack false', async () => { @@ -180,8 +182,8 @@ describe('Callback tests', () => { redirectedUrl = url; }, }; - // @ts-ignore - options.singleTenantOptions.defaultAdapterOptions = null; + // @ts-ignore + options.singleTenantOptions = {}; const defaultCallback = new DefaultCallback(options); await defaultCallback.callback(request, response); expect(redirectedUrl).toEqual('/error?message=Default Adapter Options does not defined'); @@ -212,8 +214,14 @@ describe('Callback tests', () => { redirectedUrl = url; }, }; - // @ts-ignore - options.singleTenantOptions.defaultAdapterOptions = {}; + // @ts-ignore + options.singleTenantOptions = {defaultAdapterOptions: { + keycloakJson: () => ({realm: 'realm', + "auth-server-url": 'url', + "ssl-required": 'false', + resource: 'test'}), + clientAuthorization: new DummyClientAuthorization(), + }}; const defaultCallback = new DefaultCallback(options); await defaultCallback.callback(request, response); expect(redirectedUrl).toEqual('/'); diff --git a/src/callback/Callback.ts b/src/callback/Callback.ts index 5f64fdf..d3c9eba 100644 --- a/src/callback/Callback.ts +++ b/src/callback/Callback.ts @@ -1,8 +1,8 @@ -import {DefaultSessionManager} from "../session/SessionManager"; +import {AdapterContent, updateOptions} from "keycloak-lambda-authorizer/dist/src/Options"; + import {Options, RequestObject, ResponseObject} from "../index"; -import {getCurrentHost, getSessionName, getTokenByCode, KeycloakState} from "../utils/KeycloakUtils"; +import {getCurrentHost, getSessionName, KeycloakState} from "../utils/KeycloakUtils"; -const {commonOptions} = require('keycloak-lambda-authorizer/src/utils/optionsUtils'); export interface Callback { @@ -24,6 +24,7 @@ export interface Callback { export class DefaultCallback implements Callback { private options: Options; + private singleOptions:AdapterContent|null = null; constructor(options: Options) { this.options = options; @@ -50,9 +51,15 @@ export class DefaultCallback implements Callback { throw new Error('Multi-tenant Options does not defined'); } const keycloakJson = await this.options.multiTenantOptions.multiTenantJson(state.tenant); - token = await getTokenByCode(code, currentHost, { - ...commonOptions(this.options.multiTenantOptions.multiTenantAdapterOptions, keycloakJson), - }, keycloakJson); + const multitenantOptions = updateOptions( + {...this.options.multiTenantOptions.multiTenantAdapterOptions, ...{keycloakJson}}, + ); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + token = await multitenantOptions.clientAuthorization.getTokenByCode({ + request: req, + realm: state.tenant, + }, code, `${currentHost}/callbacks/${keycloakJson.realm}/${keycloakJson.resource}/callback`); sessionId = await this.options.session.sessionManager.createSession(req, state, token); } else { if (!this.options.singleTenantOptions) { @@ -61,12 +68,20 @@ export class DefaultCallback implements Callback { if (!this.options.singleTenantOptions.defaultAdapterOptions) { throw new Error('Default Adapter Options does not defined'); } - token = await getTokenByCode(code, currentHost, - { - ...commonOptions(this.options.singleTenantOptions.defaultAdapterOptions, - this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson), - }, - this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson); + if (!this.singleOptions) { + this.singleOptions = updateOptions(this.options.singleTenantOptions.defaultAdapterOptions); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const keycloakJson = await this.singleOptions.keycloakJson(this.singleOptions, { + request: req, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + token = await this.singleOptions.clientAuthorization.getTokenByCode({ + request: req, + realm: state.tenant, + }, code, `${currentHost}/callbacks/${keycloakJson.realm}/${keycloakJson.resource}/callback`); sessionId = await this.options.session.sessionManager.createSession(req, state, token); } res.cookie(getSessionName(this.options), sessionId); diff --git a/src/handlers/MultiTenantUrlPageHandler.test.ts b/src/handlers/MultiTenantUrlPageHandler.test.ts index d99d09c..b6886e1 100644 --- a/src/handlers/MultiTenantUrlPageHandler.test.ts +++ b/src/handlers/MultiTenantUrlPageHandler.test.ts @@ -34,6 +34,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/handlers/PublicUrlPageHandler.test.ts b/src/handlers/PublicUrlPageHandler.test.ts index ab40224..b39c0fd 100644 --- a/src/handlers/PublicUrlPageHandler.test.ts +++ b/src/handlers/PublicUrlPageHandler.test.ts @@ -33,6 +33,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/handlers/SingleTenantUrlPageHandler.test.ts b/src/handlers/SingleTenantUrlPageHandler.test.ts index cd577c5..fb68960 100644 --- a/src/handlers/SingleTenantUrlPageHandler.test.ts +++ b/src/handlers/SingleTenantUrlPageHandler.test.ts @@ -33,6 +33,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', + // @ts-ignore defaultAdapterOptions: {}, keys: { privateKey: { diff --git a/src/handlers/TenantExternalPage.test.ts b/src/handlers/TenantExternalPage.test.ts index 5caca18..3768cea 100644 --- a/src/handlers/TenantExternalPage.test.ts +++ b/src/handlers/TenantExternalPage.test.ts @@ -37,6 +37,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/handlers/TenantInternalPage.test.ts b/src/handlers/TenantInternalPage.test.ts index 617de0f..e575bae 100644 --- a/src/handlers/TenantInternalPage.test.ts +++ b/src/handlers/TenantInternalPage.test.ts @@ -38,6 +38,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/handlers/TokenPageHandler.test.ts b/src/handlers/TokenPageHandler.test.ts index d54cec1..8c968aa 100644 --- a/src/handlers/TokenPageHandler.test.ts +++ b/src/handlers/TokenPageHandler.test.ts @@ -34,6 +34,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/index.ts b/src/index.ts index 1273cf1..81ec982 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,25 @@ -import {SessionConfiguration, SessionManager, SessionToken} from "./session/SessionManager"; -import {MultiTenantAdapter} from "./multitenants/Multi-tenant-adapter"; -import {TenantAdapter} from "./tenant/TenantAdapter"; -import {Logout} from "./logout/Logout"; -import {JWKS} from "./jwks/JWKS"; -import {Callback} from "./callback/Callback"; +import {AdapterDependencies, + ClientJwtKeys, + KeycloakJsonStructure, + LoggerType, +} from "keycloak-lambda-authorizer/dist/src/Options"; +import {AdapterCache} from "keycloak-lambda-authorizer/dist/src/cache/AdapterCache"; +import {RestCalls} from "keycloak-lambda-authorizer/dist/src/utils/restCalls"; +import {EnforcerAction} from "keycloak-lambda-authorizer/dist/src/enforcer/Enforcer"; +import {UmaConfiguration} from "keycloak-lambda-authorizer/dist/src/uma/UmaConfiguration"; +import {ClientAuthorization} from "keycloak-lambda-authorizer/dist/src/clients/ClientAuthorization"; +import {ServiceAccount} from "keycloak-lambda-authorizer/dist/src/serviceaccount/ServiceAccount"; +import {SecurityAdapter} from "keycloak-lambda-authorizer/dist/src/adapters/SecurityAdapter"; +import {ResourceChecker} from "keycloak-lambda-authorizer/dist/src/enforcer/resource/Resource"; + + import {PageHandlers} from "./handlers/PageHandler"; +import {Callback} from "./callback/Callback"; +import {Logout} from "./logout/Logout"; +import {TenantAdapter} from "./tenant/TenantAdapter"; +import {MultiTenantAdapter} from "./multitenants/Multi-tenant-adapter"; +import {SessionConfiguration, SessionManager, SessionToken} from "./session/SessionManager"; +import {UrlJWKS} from "./jwks/UrlJWKS"; export type AccessLevel = 'public' | 'single' | 'multi-tenant'; export type ExecuteType = (tenant: string) => Promise | any; @@ -28,17 +43,28 @@ export type IdentityProviders = { singleTenant?: string; } +export type MultitenantAdapterDependencies = { + keys?: ClientJwtKeys, + cache?: AdapterCache, + logger?: LoggerType, + restClient?: RestCalls, + enforcer?: EnforcerAction, + umaConfiguration?: UmaConfiguration, + clientAuthorization?: ClientAuthorization, + serviceAccount?: ServiceAccount, + securityAdapter?: SecurityAdapter, + resourceChecker?: ResourceChecker, +} + export type MultiTenantOptions = { - multiTenantJson: (tenant: string) => Promise | any; - // eslint-disable-next-line no-warning-comments, line-comment-position - multiTenantAdapterOptions: any; // todo + multiTenantJson: (tenant: string) => Promise | KeycloakJsonStructure; + multiTenantAdapterOptions: MultitenantAdapterDependencies; multiTenantAdapter?: MultiTenantAdapter; multiTenantSelectorOptions?: MultiTenantSelectorOptions; idp?:string; } export type SingleTenantOptions = { - // eslint-disable-next-line no-warning-comments, line-comment-position - defaultAdapterOptions?: any; // todo + defaultAdapterOptions: AdapterDependencies; singleTenantAdapter?: TenantAdapter; idp?:string; } @@ -81,7 +107,7 @@ export type Options = { pageHandlers?: PageHandlers; session: SessionOptions; logout?: Logout; - jwks?: JWKS; + jwks?: UrlJWKS; callback?: Callback; } diff --git a/src/jwks/JWKS.test.ts b/src/jwks/JWKS.test.ts index 2c575d5..1911f8c 100644 --- a/src/jwks/JWKS.test.ts +++ b/src/jwks/JWKS.test.ts @@ -7,11 +7,12 @@ import {RequestObject, ResponseObject} from "../index"; import {APIGateWayOptions} from "../apigateway/ApiGateway"; import {initOptions} from "../utils/DefaultPageHandlers"; -import {DefaultJWKS} from "./JWKS"; +import {DefaultUrlJWKS} from "./UrlJWKS"; const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { @@ -106,33 +107,42 @@ describe('JWKS tests', () => { }); test('test JWKS success 1', async () => { - const jwks = new DefaultJWKS(handlerOptions); + const jwks = new DefaultUrlJWKS(handlerOptions); expect(jwks.isJwksRoute({...request, ...{ baseUrl: '/keycloak/jwks', }})).toEqual(true); }); test('test JWKS success 2', async () => { - const jwks = new DefaultJWKS(handlerOptions); + const jwks = new DefaultUrlJWKS(handlerOptions); expect(jwks.isJwksRoute({...request, ...{ originalUrl: '/keycloak/jwks', }})).toEqual(true); }); test('test JWKS failure', async () => { - const jwks = new DefaultJWKS(handlerOptions); + const jwks = new DefaultUrlJWKS(handlerOptions); expect(jwks.isJwksRoute({...request, ...{ originalUrl: '/keycloak/jwk1s', }})).toEqual(false); }); test('test JWKS failure 2', async () => { - const jwks = new DefaultJWKS(handlerOptions); + const jwks = new DefaultUrlJWKS(handlerOptions); let json = false; - expect(await jwks.jwks({...request, ...{ - originalUrl: '/keycloak/jwk1s', + expect(await jwks.getPublicKey({...request, ...{ + originalUrl: '/keycloak/jwks', }}, {...response, ...{ json: (object: any) => { - expect(object).toEqual(JSON.stringify({keys: [{kty: "RSA", use: "sig", n: "APjS3KxWJBKd3cQh7n7qEMz_uLEc0PxSjFgApbCcDb890faWr-nOM6jW3Um3pltnHaXdw1QO3sVBccu9l4SfTDvn8tV7-Ah6gokhRNatfJU6us-fnShaJopfmmcwItZTCTW6xHCc_ujbpIdRFN5tJ8EjuRB-HbZqK_ryqAa-jiUxYhJWtY0I16Pigi0tp9HO-AEEPsa8mGq88hZMwXKoPMZG1WTydiL3HdbCnvZ5Rb1pj-P9qQ9Mt1Tlr64COVBgOs9rTCSD7_YoiHEYP99_KCCO9RqinM4Ruc0bIFVeI5LetDDBCdc2r6KnTFV5xhUMkGWk51ADETvt9SVGvDkPMq0", e: "AQAB"}]})); + expect(object).toEqual({ + keys: [ + { + e: "AQAB", + kty: "RSA", + n: "APjS3KxWJBKd3cQh7n7qEMz_uLEc0PxSjFgApbCcDb890faWr-nOM6jW3Um3pltnHaXdw1QO3sVBccu9l4SfTDvn8tV7-Ah6gokhRNatfJU6us-fnShaJopfmmcwItZTCTW6xHCc_ujbpIdRFN5tJ8EjuRB-HbZqK_ryqAa-jiUxYhJWtY0I16Pigi0tp9HO-AEEPsa8mGq88hZMwXKoPMZG1WTydiL3HdbCnvZ5Rb1pj-P9qQ9Mt1Tlr64COVBgOs9rTCSD7_YoiHEYP99_KCCO9RqinM4Ruc0bIFVeI5LetDDBCdc2r6KnTFV5xhUMkGWk51ADETvt9SVGvDkPMq0", + use: "sig", + }, + ], + }); json = true; }, }})).toEqual(undefined); diff --git a/src/jwks/JWKS.ts b/src/jwks/JWKS.ts deleted file mode 100644 index 99e77cf..0000000 --- a/src/jwks/JWKS.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Options, RequestObject, ResponseObject} from "../index"; - -const {adapter} = require('keycloak-lambda-authorizer'); - -/** - * JWKS endpoint for signed jwt request - */ -export interface JWKS { - isJwksRoute(request: RequestObject): boolean; - - /** - * return public key to verify SJWT - * @param req - * @param res - */ - jwks(req: RequestObject, res: ResponseObject): Promise; -} - -export class DefaultJWKS implements JWKS { - private jwksRouteString = new RegExp('(^)(\\/|)(/keycloak/jwks)(/$|(\\?|$))', 'g'); - - private options: Options; - - constructor(options: Options) { - this.options = options; - } - - isJwksRoute(request: RequestObject): boolean { - return Boolean((request.baseUrl || request.originalUrl).match(this.jwksRouteString)); - } - - async jwks(req: RequestObject, res: ResponseObject): Promise { - return res.json(adapter.jwksUrl(this.options.session.sessionConfiguration.keys.publicKey.key)); - } - -} diff --git a/src/jwks/UrlJWKS.ts b/src/jwks/UrlJWKS.ts new file mode 100644 index 0000000..016f7b4 --- /dev/null +++ b/src/jwks/UrlJWKS.ts @@ -0,0 +1,71 @@ +import {DefaultJWKS, JWKS} from "keycloak-lambda-authorizer/dist/src/jwks/JWKS"; + +import {Options, RequestObject, ResponseObject} from "../index"; + +const KeycloakAdapter = require('keycloak-lambda-authorizer'); + +export type TypeJWKS ='single'|'multi-tenant'|'session'; + +/** + * JWKS endpoint for signed jwt request + */ +export interface UrlJWKS { + isJwksRoute(request: RequestObject): boolean; + + /** + * return public key to verify SJWT + * @param req + * @param res + */ + getPublicKey(req: RequestObject, res: ResponseObject): Promise; +} + +export class DefaultUrlJWKS implements UrlJWKS { + private jwksRouteString = new RegExp('(^)(\\/|)(/keycloak/jwks)(/$|(\\?|$))', 'g'); + + private options: Options; + private jwks:JWKS; + + + constructor(options: Options) { + this.options = options; + this.jwks = new DefaultJWKS(); + } + + isJwksRoute(request: RequestObject): boolean { + return Boolean((request.baseUrl || request.originalUrl).match(this.jwksRouteString)); + } + + async getPublicKey(req: RequestObject, res: ResponseObject): Promise { + const type:TypeJWKS = req.query.type || 'session'; + switch (type) { + case "single": { + if (!this.options.singleTenantOptions || !this.options.singleTenantOptions.defaultAdapterOptions.keys) { + res.json({message: `unsupported type ${type}`}); + return; + } + res.json(await this.jwks.json(this.options.singleTenantOptions.defaultAdapterOptions.keys.publicKey)); + return; + } + case "multi-tenant": { + if (!this.options.multiTenantOptions || !this.options.multiTenantOptions.multiTenantAdapterOptions.keys) { + res.json({message: `unsupported type ${type}`}); + return; + } + res.json(await this.jwks.json(this.options.multiTenantOptions.multiTenantAdapterOptions.keys.publicKey)); + return; + } + case 'session': { + res.json(await this.jwks.json(this.options.session.sessionConfiguration.keys.publicKey)); + return; + } + default: { + res.json({message: `unsupported type ${type}`}); + + } + + } + + } + +} diff --git a/src/lambdaedge/LambdaEdgeAdapter.test.ts b/src/lambdaedge/LambdaEdgeAdapter.test.ts index 8e0bad0..9091e7d 100644 --- a/src/lambdaedge/LambdaEdgeAdapter.test.ts +++ b/src/lambdaedge/LambdaEdgeAdapter.test.ts @@ -30,6 +30,7 @@ class DummyApiGateway implements ApiGateway { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { diff --git a/src/logout/Logout.test.ts b/src/logout/Logout.test.ts index 0448b64..67b29dd 100644 --- a/src/logout/Logout.test.ts +++ b/src/logout/Logout.test.ts @@ -7,6 +7,7 @@ import {RequestObject, ResponseObject} from "../index"; import {initOptions} from "../utils/DefaultPageHandlers"; import {APIGateWayOptions} from "../apigateway/ApiGateway"; import {getSessionToken, SessionToken} from "../session/SessionManager"; +import {DummyClientAuthorization} from "../utils/DummyImplementations.test"; import {DefaultLogout} from "./Logout"; @@ -46,7 +47,10 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', - multiTenantAdapterOptions: {}, + multiTenantAdapterOptions: { + clientAuthorization: new DummyClientAuthorization(), + }, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { @@ -94,8 +98,10 @@ describe('Logout tests', () => { }); test('test Logout redirectDefaultLogout', async () => { + // @ts-ignore + handlerOptions.singleTenantOptions = {defaultAdapterOptions: {}}; // @ts-ignore - handlerOptions.singleTenantOptions.defaultAdapterOptions = {keycloakJson: {"auth-server-url": "http://localhost:8090/auth/"}}; + handlerOptions.singleTenantOptions.defaultAdapterOptions.keycloakJson = () => ({"auth-server-url": "http://localhost:8090/auth/"}); const logout = new DefaultLogout(handlerOptions); let redirect = false; await logout.redirectDefaultLogout(request, {...response, ...{redirect: (code: number, url: string) => { @@ -137,8 +143,10 @@ describe('Logout tests', () => { test('test Logout single tenant logout', async () => { + // @ts-ignore + handlerOptions.singleTenantOptions = {defaultAdapterOptions: {}}; // @ts-ignore - handlerOptions.singleTenantOptions.defaultAdapterOptions = {keycloakJson: {"auth-server-url": "http://localhost:8090/auth/"}}; + handlerOptions.singleTenantOptions.defaultAdapterOptions.keycloakJson = () => ({"auth-server-url": "http://localhost:8090/auth/"}); const logout = new DefaultLogout(handlerOptions); let redirect = false; await logout.logout(request, {...response, ...{redirect: (code: number, url: string) => { diff --git a/src/logout/Logout.ts b/src/logout/Logout.ts index f403fce..232900d 100644 --- a/src/logout/Logout.ts +++ b/src/logout/Logout.ts @@ -1,8 +1,8 @@ -import {getCurrentHost, getKeycloakJsonFunction, getSessionName} from "../utils/KeycloakUtils"; +import {getCurrentHost, getSessionName} from "../utils/KeycloakUtils"; import {Options, RequestObject, ResponseObject} from "../index"; import {getSessionToken} from "../session/SessionManager"; -const {getKeycloakUrl} = require('keycloak-lambda-authorizer/src/utils/restCalls'); +const {getKeycloakUrl} = require('keycloak-lambda-authorizer/dist/src/utils/KeycloakUtils'); export interface Logout { isLogout(request: RequestObject):boolean; @@ -27,7 +27,12 @@ export class DefaultLogout implements Logout { if (!this.options.singleTenantOptions) { throw new Error('singleTenantOptions does not defined'); } - const keycloakJson = await getKeycloakJsonFunction(this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const keycloakJson = await this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson(this.options.singleTenantOptions.defaultAdapterOptions, { + request: req, + token: null, + }); res.redirect(302, `${getKeycloakUrl(keycloakJson)}/realms/${keycloakJson.realm}/protocol/openid-connect/logout?redirect_uri=${getCurrentHost(req)}/`); } diff --git a/src/multitenants/Multi-tenant-adapter.ts b/src/multitenants/Multi-tenant-adapter.ts index 910142e..5b4978c 100644 --- a/src/multitenants/Multi-tenant-adapter.ts +++ b/src/multitenants/Multi-tenant-adapter.ts @@ -1,17 +1,18 @@ import {v4} from 'uuid'; +import {getKeycloakUrl} from "keycloak-lambda-authorizer/dist/src/utils/KeycloakUtils"; +import {SecurityAdapter} from "keycloak-lambda-authorizer/dist/src/adapters/SecurityAdapter"; +import KeycloakAdapter from "keycloak-lambda-authorizer"; +import {AdapterContent, EnforcerFunction, RequestContent, TokenJson, AdapterDependencies} from "keycloak-lambda-authorizer/dist/src/Options"; +import {decodeToken} from "keycloak-lambda-authorizer/dist/src/utils/TokenUtils"; -import {Options, RequestObject, ResponseObject} from "../index"; -import {getSessionToken, SessionToken} from "../session/SessionManager"; import { getCurrentHost, getSessionName, KeycloakState, } from "../utils/KeycloakUtils"; +import {getSessionToken, SessionToken} from "../session/SessionManager"; +import {Options, RequestObject, ResponseObject} from "../index"; -const {getKeycloakUrl} = require('keycloak-lambda-authorizer/src/utils/restCalls'); -const {adapter} = require('keycloak-lambda-authorizer/src/keycloakAuthorizer'); -const {keycloakRefreshToken} = require('keycloak-lambda-authorizer/src/clientAuthorization'); -const {commonOptions} = require('keycloak-lambda-authorizer/src/utils/optionsUtils'); /** * Multi-Tenant Adapter @@ -45,12 +46,22 @@ export interface MultiTenantAdapter { export class DefaultMultiTenantAdapter implements MultiTenantAdapter { readonly options: Options; + securityAdapter:SecurityAdapter; constructor(options: Options) { this.options = options; if (!options.multiTenantOptions) { throw new Error('multiTenantOptions is not defined'); } + const options1 = { + ...options.multiTenantOptions.multiTenantAdapterOptions, + ...{keycloakJson: async (opt: AdapterContent, requestContent: RequestContent) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line no-return-await + return await this.options.multiTenantOptions.multiTenantJson(requestContent.realm); + }}}; + this.securityAdapter = new KeycloakAdapter( options1).getDefaultAdapter(); } async isMultiTenant(req: RequestObject): Promise { @@ -78,7 +89,7 @@ export class DefaultMultiTenantAdapter implements MultiTenantAdapter { res.redirect(302, `${getKeycloakUrl(tenantRealmJson)}/realms/${tenantRealmJson.realm}/protocol/openid-connect/auth?client_id=${tenantRealmJson.resource}&redirect_uri=${getCurrentHost(req)}/callbacks/${tenantRealmJson.realm}/${tenantRealmJson.resource}/callback&&state=${encodeURIComponent(JSON.stringify(keycloakState))}&response_type=code&nonce=${v4()}${tenantHint}`); } - async tenantCheckToken(res: ResponseObject, sessionToken: SessionToken, tok: any): Promise { + async tenantCheckToken(req: RequestObject, res: ResponseObject, sessionToken: SessionToken, tok: TokenJson): Promise { if (!this.options.multiTenantOptions || !sessionToken.tenant) { throw new Error('multiTenantOptions or tenant does not defined'); @@ -86,24 +97,29 @@ export class DefaultMultiTenantAdapter implements MultiTenantAdapter { if (!this.options.session.sessionManager) { throw new Error('sessionManager does not defined'); } - const tenantRealmJson = await this.options.multiTenantOptions.multiTenantJson(sessionToken.tenant); const token = await this.options.session.sessionManager.getSessionAccessToken(sessionToken); if (token) { let returnToken; - const tenantOptions = commonOptions( - this.options.multiTenantOptions.multiTenantAdapterOptions, - tenantRealmJson, - ); try { - - await adapter(tok.token, tenantOptions.keycloakJson, - tenantOptions); + const jwtToken = decodeToken(token.access_token); + await this.securityAdapter.validate({ + request: req, + realm: sessionToken.tenant, + token: jwtToken, + tokenString: token.access_token, + }); return token; } catch (e) { - returnToken = await keycloakRefreshToken(token, tenantOptions); - await this.options.session.sessionManager.updateSession( - sessionToken.jti, sessionToken.email, returnToken, - ); + returnToken = await this.securityAdapter.refreshToken({ + realm: sessionToken.tenant, + token, + request: req, + }); + if (returnToken) { + await this.options.session.sessionManager.updateSession( + sessionToken.jti, sessionToken.email, returnToken.token, + ); + } } return returnToken; } @@ -118,9 +134,11 @@ export class DefaultMultiTenantAdapter implements MultiTenantAdapter { if (!this.options.session.sessionManager) { throw new Error('sessionManager is not defined'); } - const accessToken = await this.options.session.sessionManager.getSessionAccessToken(sessionToken); - const tok = getSessionToken(accessToken.access_token, true); - const token = await this.tenantCheckToken(res, sessionToken, tok); + const tokens = await this.options.session.sessionManager.getSessionAccessToken(sessionToken); + if (!tokens) { + throw new Error('tokens are empty'); + } + const token = await this.tenantCheckToken(req, res, sessionToken, tokens); return token; } catch (e) { // eslint-disable-next-line no-console diff --git a/src/session/SessionManager.test.ts b/src/session/SessionManager.test.ts index abe76f0..e3a095c 100644 --- a/src/session/SessionManager.test.ts +++ b/src/session/SessionManager.test.ts @@ -2,6 +2,7 @@ */ import 'jest'; import {decode} from "jsonwebtoken"; +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; import {RequestObject, ResponseObject} from "../index"; import {APIGateWayOptions} from "../apigateway/ApiGateway"; @@ -30,9 +31,11 @@ const sessionToken:SessionToken = { }; + const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { @@ -77,14 +80,15 @@ class DummyStorage implements StrorageDB { } async getSessionIfExists(sessionId: string): Promise { + // @ts-ignore return {session: '1', exp: 1000, keycloakSession: 'sessionState', email: '1@1.tt', externalToken: {}}; } - async saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, externalToken: any): Promise { + async saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, externalToken: TokenJson): Promise { } - async updateSession(sessionId: string, email: string, externalToken: any): Promise { + async updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { } @@ -142,10 +146,12 @@ describe('SessionManager tests', () => { test('test SessionManager createSession update', async () => { const sessionManager = new DefaultSessionManager(handlerOptions); + // @ts-ignore await sessionManager.updateSession('sessionId', 'email', {access_token: 'a', refresh_token: 'r'}); }); test('test SessionManager createSession update', async () => { const sessionManager = new DefaultSessionManager(handlerOptions); + // @ts-ignore await sessionManager.updateSession('sessionId', 'email', {access_token: 'a', refresh_token: 'r'}); }); @@ -158,10 +164,11 @@ describe('SessionManager tests', () => { test('test SessionManager createSession getSessionAccessToken null', async () => { const sessionManager = new DefaultSessionManager(handlerOptions); const session = await sessionManager.getSessionAccessToken({...sessionToken, sessionState: '1'}); - expect(session).toEqual(null); + expect(session).toEqual(undefined); }); test('test SessionManager createSession success', async () => { const sessionManager = new DefaultSessionManager(handlerOptions); + // @ts-ignore const sessionId = await sessionManager.createSession(request, keycloakState, {access_token: 'a', refresh_token: 'r'}); expect(sessionId).toHaveLength(sessionId.length); }); diff --git a/src/session/SessionManager.ts b/src/session/SessionManager.ts index 3a90bb3..8079902 100644 --- a/src/session/SessionManager.ts +++ b/src/session/SessionManager.ts @@ -1,5 +1,6 @@ import {decode} from 'jsonwebtoken'; import {v4} from 'uuid'; +import {RSAKey, TokenJson, updateOptions} from "keycloak-lambda-authorizer/dist/src/Options"; import {Options, RequestObject} from "../index"; import {getCurrentStorage, getSessionName, KeycloakState} from "../utils/KeycloakUtils"; @@ -8,8 +9,6 @@ import {StrorageDB} from "./storage/Strorage"; import {InMemoryDB} from './storage/InMemoryDB'; import {DynamoDB} from './storage/DynamoDB'; -const {clientJWT} = require('keycloak-lambda-authorizer/src/clientAuthorization'); - export type SessionToken = { jti: string, @@ -23,13 +22,9 @@ export type SessionToken = { token?: string, } -export type SessionPrivateKey = { - key: string, - passphrase?: string; -} export type SessionTokenKeys = { - privateKey: SessionPrivateKey, - publicKey: SessionPrivateKey, + privateKey: RSAKey, + publicKey: RSAKey, } export function getSessionToken(sessionTokenString: string, @@ -54,13 +49,6 @@ export type SessionConfiguration = { keys: SessionTokenKeys, } -export type TokenType = { - // eslint-disable-next-line babel/camelcase - access_token: string, - // eslint-disable-next-line babel/camelcase - refresh_token: string, -} - /** * Session Manager */ @@ -70,7 +58,7 @@ export interface SessionManager { * exchange session Token to Access Token * @param session session token */ - getSessionAccessToken(session: SessionToken): Promise + getSessionAccessToken(session: SessionToken): Promise /** * update access and refresh tokens inside storage @@ -78,7 +66,7 @@ export interface SessionManager { * @param email user email * @param externalToken access and refresh tokens */ - updateSession(sessionId: string, email: string, externalToken: any): Promise; + updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise; /** * Create storage record with access_token,refresh_token and return signed SessionToken @@ -86,7 +74,7 @@ export interface SessionManager { * @param state - keycloak session id * @param token access_token and refresh_token */ - createSession(req: RequestObject, state: KeycloakState, token: TokenType): Promise + createSession(req: RequestObject, state: KeycloakState, token: TokenJson): Promise } export class DefaultSessionManager implements SessionManager { @@ -102,7 +90,7 @@ export class DefaultSessionManager implements SessionManager { this.options = options; } - async updateSession(sessionId: string, email: string, externalToken: any): Promise { + async updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { await (await getCurrentStorage(this.options)).updateSession(sessionId, email, externalToken); } @@ -116,7 +104,7 @@ export class DefaultSessionManager implements SessionManager { }; } - async createSession(req: RequestObject, state: KeycloakState, token: TokenType): Promise { + async createSession(req: RequestObject, state: KeycloakState, token: TokenJson): Promise { const sessionToken = getSessionToken(req.cookies[getSessionName(this.options)], false); const accessToken = getSessionToken(token.access_token); const refreshToken = getSessionToken(token.refresh_token); @@ -124,13 +112,17 @@ export class DefaultSessionManager implements SessionManager { throw new Error('accessToken or refreshToken does not exists'); } const sessionId = v4(); - const ret = await clientJWT({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const adapterOptions = updateOptions({keycloakJson: {}}); + const ret = await adapterOptions.clientAuthorization.clientJWT({ ...(sessionToken || {}), ...this.createJWS(sessionId), ...state, email: accessToken.email, sessionState: accessToken.session_state, - }, {keys: this.defaultSessionType.keys}); + }, this.defaultSessionType.keys.privateKey); + await (await getCurrentStorage(this.options)) .saveSession(sessionId, accessToken.session_state, @@ -140,7 +132,7 @@ export class DefaultSessionManager implements SessionManager { return ret; } - async getSessionAccessToken(session: SessionToken): Promise { + async getSessionAccessToken(session: SessionToken): Promise { const token = session; const sessionId = token.jti; const {sessionState} = token; @@ -152,7 +144,7 @@ export class DefaultSessionManager implements SessionManager { // eslint-disable-next-line no-console console.log(`keycloak session is not the same. Expected ${sessionValue.keycloakSession} but found ${sessionState}`); } - return null; + return undefined; } } diff --git a/src/session/storage/DynamoDB.ts b/src/session/storage/DynamoDB.ts index e31d479..8731638 100644 --- a/src/session/storage/DynamoDB.ts +++ b/src/session/storage/DynamoDB.ts @@ -1,4 +1,5 @@ import AWS from 'aws-sdk'; +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; import {StrorageDB, StrorageDBType} from "./Strorage"; @@ -67,7 +68,7 @@ export class DynamoDB implements StrorageDB { async saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, - externalToken: any): Promise { + externalToken: TokenJson): Promise { await this.dynamodb.putItem({ TableName: this.dynamoDbSettings.tableName, Item: { @@ -91,7 +92,7 @@ export class DynamoDB implements StrorageDB { logger.debug(`session: ${sessionId} (${email}) saved`); } - async updateSession(sessionId: string, email: string, externalToken: any): Promise { + async updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { logger.debug(`begin update session: ${sessionId} (${email}) ${JSON.stringify(externalToken)}`); await this.dynamodb.updateItem({ TableName: this.dynamoDbSettings.tableName, diff --git a/src/session/storage/InMemoryDB.test.ts b/src/session/storage/InMemoryDB.test.ts index ab8d874..5b53411 100644 --- a/src/session/storage/InMemoryDB.test.ts +++ b/src/session/storage/InMemoryDB.test.ts @@ -31,6 +31,7 @@ const response: ResponseObject = { const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { @@ -53,6 +54,7 @@ describe('InMemoryDB tests', () => { const storage = new InMemoryDB(); storage.readStorage(); storage.updateStorage(); + // @ts-ignore await storage.saveSession("1", "1", 1, "email", {}); storage.updateStorage(); const session = await storage.getSessionIfExists("1"); @@ -63,6 +65,7 @@ describe('InMemoryDB tests', () => { keycloakSession: "1", session: "1", }); + // @ts-ignore await storage.updateSession("1", "email", {}); await storage.deleteSession("1"); }); diff --git a/src/session/storage/InMemoryDB.ts b/src/session/storage/InMemoryDB.ts index 66cc9e4..9437f5b 100644 --- a/src/session/storage/InMemoryDB.ts +++ b/src/session/storage/InMemoryDB.ts @@ -1,5 +1,7 @@ import fs from 'fs'; +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; + import {pathUtils} from './PathUtils'; import {StrorageDB, StrorageDBType} from "./Strorage"; @@ -66,7 +68,7 @@ export class InMemoryDB implements StrorageDB { keycloakSession: string, exp: number, email: string, - externalToken: any): + externalToken: TokenJson): Promise { this.inMemory[sessionId] = { session: sessionId, @@ -78,7 +80,7 @@ export class InMemoryDB implements StrorageDB { this.saveStorage(this.inMemory); } - async updateSession(sessionId: string, email: string, externalToken: any): Promise { + async updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { const sessionObject = this.inMemory[sessionId]; if (sessionObject) { sessionObject.externalToken = externalToken; diff --git a/src/session/storage/Strorage.ts b/src/session/storage/Strorage.ts index c304e03..698cf03 100644 --- a/src/session/storage/Strorage.ts +++ b/src/session/storage/Strorage.ts @@ -1,10 +1,11 @@ +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; export type StrorageDBType = { session: string, exp:number, keycloakSession:string, email:string, - externalToken:any, + externalToken: TokenJson, } export interface StrorageDB { @@ -21,7 +22,7 @@ export interface StrorageDB { keycloakSession: string, exp: number, email: string, - externalToken: any):Promise; + externalToken: TokenJson):Promise; /** * update session with new access and refresh token @@ -29,7 +30,7 @@ export interface StrorageDB { * @param email user email * @param externalToken new access and refresh token */ - updateSession(sessionId:string, email:string, externalToken:any):Promise; + updateSession(sessionId:string, email:string, externalToken:TokenJson):Promise; /** * get access and refresh token by sessionId diff --git a/src/tenant/TenantAdapter.ts b/src/tenant/TenantAdapter.ts index 2e33b43..7f9041d 100644 --- a/src/tenant/TenantAdapter.ts +++ b/src/tenant/TenantAdapter.ts @@ -1,10 +1,11 @@ -import {Options, RequestObject, ResponseObject} from "../index"; -import {getSessionToken, SessionToken} from "../session/SessionManager"; -import {getCurrentHost, getKeycloakJsonFunction, getSessionName, KeycloakState} from "../utils/KeycloakUtils"; +import KeycloakAdapter from "keycloak-lambda-authorizer"; +import {SecurityAdapter} from "keycloak-lambda-authorizer/dist/src/adapters/SecurityAdapter"; +import {RefreshContext, TokenJson, updateOptions} from "keycloak-lambda-authorizer/dist/src/Options"; +import {getKeycloakUrl} from "keycloak-lambda-authorizer/dist/src/utils/KeycloakUtils"; -const {getKeycloakUrl} = require('keycloak-lambda-authorizer/src/utils/restCalls'); -const {awsAdapter} = require('keycloak-lambda-authorizer/src/apigateway/apigateway'); -const {keycloakRefreshToken} = require('keycloak-lambda-authorizer/src/clientAuthorization'); +import {getCurrentHost, getSessionName, KeycloakState} from "../utils/KeycloakUtils"; +import {getSessionToken, SessionToken} from "../session/SessionManager"; +import {Options, RequestObject, ResponseObject} from "../index"; /** * single tenant adapter @@ -31,38 +32,39 @@ export interface TenantAdapter { export class DefaultTenantAdapter implements TenantAdapter { options: Options; + securityAdapter:SecurityAdapter|null=null; constructor(options: Options) { this.options = options; } - async tenantCheckToken(res: ResponseObject, sessionToken: SessionToken, tok: any): Promise { + async tenantCheckToken(req:RequestObject, res: ResponseObject, sessionToken: SessionToken, tok: any): Promise { if (!this.options.singleTenantOptions) { throw new Error('singleTenantOptions is not defined'); } - if (!this.options.singleTenantOptions.singleTenantAdapter) { - throw new Error('singleTenantAdapter does not defined'); - } + if (!this.options.session.sessionManager) { throw new Error('sessionManager does not defined'); } - const tenantRealmJson = await getKeycloakJsonFunction(this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson); const token = await this.options.session.sessionManager.getSessionAccessToken(sessionToken); if (token) { - let returnToken; - const tenantOptions = { - ...this.options.singleTenantOptions.defaultAdapterOptions, - ...{keycloakJson: () => tenantRealmJson}, - }; + let returnToken:TokenJson|null = null; + if (!this.securityAdapter) { + this.securityAdapter = new KeycloakAdapter(this.options.singleTenantOptions.defaultAdapterOptions) + .getDefaultAdapter(); + } try { - await awsAdapter.adapter(tok.token, - tenantOptions); + await this.securityAdapter.validate(tok.token); return token; } catch (e) { - returnToken = await keycloakRefreshToken(token, tenantOptions); - await this.options.session.sessionManager.updateSession( - sessionToken.jti, sessionToken.email, returnToken, - ); + const refreshContext:RefreshContext|null = await this.securityAdapter.refreshToken({request: req, token}); + if (refreshContext) { + returnToken = refreshContext.token; + await this.options.session.sessionManager.updateSession( + sessionToken.jti, sessionToken.email, returnToken, + ); + } + } return returnToken; } @@ -82,7 +84,7 @@ export class DefaultTenantAdapter implements TenantAdapter { } try { const tok = await sessionManager.getSessionAccessToken(sessionToken); - const token = await this.tenantCheckToken(res, sessionToken, tok); + const token = await this.tenantCheckToken(req, res, sessionToken, tok); return token; } catch (e) { // eslint-disable-next-line no-console @@ -96,7 +98,12 @@ export class DefaultTenantAdapter implements TenantAdapter { if (!this.options.singleTenantOptions) { throw new Error('singleTenantOptions is not defined'); } - const keycloakJson = await getKeycloakJsonFunction(this.options.singleTenantOptions.defaultAdapterOptions.keycloakJson); + const adapterContent = updateOptions(this.options.singleTenantOptions.defaultAdapterOptions); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const keycloakJson = await adapterContent.keycloakJson(adapterContent, { + request: req, + }); const keycloakState: KeycloakState = { multiFlag: false, url: '/', diff --git a/src/utils/DefaultPageHandlers.ts b/src/utils/DefaultPageHandlers.ts index 52b5b69..03bb937 100644 --- a/src/utils/DefaultPageHandlers.ts +++ b/src/utils/DefaultPageHandlers.ts @@ -4,7 +4,7 @@ import { import {DefaultSessionManager} from "../session/SessionManager"; import {APIGateWayOptions} from "../apigateway/ApiGateway"; import {DefaultLogout} from "../logout/Logout"; -import {DefaultJWKS} from "../jwks/JWKS"; +import {DefaultUrlJWKS} from "../jwks/UrlJWKS"; import {DefaultCallback} from "../callback/Callback"; import {DefaultTenantAdapter} from "../tenant/TenantAdapter"; import {DefaultMultiTenantAdapter} from "../multitenants/Multi-tenant-adapter"; @@ -28,6 +28,7 @@ export const defaultPageHandlers = [ ]; function transform(opts: APIGateWayOptions): Options { + const options: Options = { session: { sessionConfiguration: { @@ -37,6 +38,8 @@ function transform(opts: APIGateWayOptions): Options { }, }, singleTenantOptions: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore defaultAdapterOptions: opts.defaultAdapterOptions, idp: opts.identityProviders?.singleTenant, }, @@ -58,7 +61,7 @@ export function initOptions(opts: APIGateWayOptions | Options): Options { options.logout = new DefaultLogout(options); } if (!options.jwks) { - options.jwks = new DefaultJWKS(options); + options.jwks = new DefaultUrlJWKS(options); } if (!options.session.sessionManager) { options.session.sessionManager = new DefaultSessionManager(options); @@ -66,13 +69,12 @@ export function initOptions(opts: APIGateWayOptions | Options): Options { if (!options.callback) { options.callback = new DefaultCallback(options); } - if (!options.singleTenantOptions) { - options.singleTenantOptions = { - }; - } - if (!options.singleTenantOptions.singleTenantAdapter) { - options.singleTenantOptions.singleTenantAdapter = new DefaultTenantAdapter(options); + if (options.singleTenantOptions) { + if (!options.singleTenantOptions.singleTenantAdapter) { + options.singleTenantOptions.singleTenantAdapter = new DefaultTenantAdapter(options); + } } + if (options.multiTenantOptions) { if (!options.multiTenantOptions.multiTenantAdapter) { options.multiTenantOptions.multiTenantAdapter = new DefaultMultiTenantAdapter(options); diff --git a/src/utils/DummyImplementations.test.ts b/src/utils/DummyImplementations.test.ts new file mode 100644 index 0000000..17911f1 --- /dev/null +++ b/src/utils/DummyImplementations.test.ts @@ -0,0 +1,206 @@ +/* eslint-disable no-negated-condition, no-empty-function, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-empty-function + */ + +import {AdapterCache} from "keycloak-lambda-authorizer/dist/src/cache/AdapterCache"; +import {RestCalls, HTTPMethod} from "keycloak-lambda-authorizer/dist/src/utils/restCalls"; +import {ClientAuthorization, JWSPayload} from "keycloak-lambda-authorizer/dist/src/clients/ClientAuthorization"; +import { + Enforcer, + EnforcerFunc, EnforcerFunction, + RefreshContext, + RequestContent, + RSAKey, SecurityResource, + TokenJson, +} from "keycloak-lambda-authorizer/dist/src/Options"; +import {ResourceChecker} from "keycloak-lambda-authorizer/dist/src/enforcer/resource/Resource"; +import {UmaConfiguration, UMAResponse} from "keycloak-lambda-authorizer/dist/src/uma/UmaConfiguration"; +import {ServiceAccount} from "keycloak-lambda-authorizer/dist/src/serviceaccount/ServiceAccount"; +import {EnforcerAction} from "keycloak-lambda-authorizer/dist/src/enforcer/Enforcer"; +import {SecurityAdapter} from "keycloak-lambda-authorizer/dist/src/adapters/SecurityAdapter"; +import {JWKS, JWKSType} from "keycloak-lambda-authorizer/dist/src/jwks/JWKS"; + +export type GetFunc =(region: string, key: string)=>string|undefined + +export class DummyCache implements AdapterCache { + value:GetFunc; + + constructor(value?: string| GetFunc) { + if (!value) { + // @ts-ignore + this.value = (region, key) => undefined; + } else { + this.value = typeof value === 'string' ? (region: string, key: string) => value + : value; + } + } + + get(region: string, key: string): string | undefined { + return this.value(region, key); + } + + put(region: string, key: string, value: any, ttl?: number): void { + + } + +} + +export class DummyRestCalls implements RestCalls { + + resp?:string; + + + constructor(resp?: string) { + this.resp = resp; + } + + async fetchData(url: string, method?: HTTPMethod, headers?: any): Promise { + return this.resp || '{}'; + } + + async sendData(url: string, method: HTTPMethod, data: string, headers?: any): Promise { + return this.resp || '{}'; + } + +} + +export class DummyClientAuthorization implements ClientAuthorization { + token?:any; + stringValue?:string; + + constructor(token?: any, stringValue?:string) { + this.token = token; + this.stringValue = stringValue; + } + + async clientAuthentication(requestContent: RequestContent): Promise { + return this.token; + } + + clientIdAuthorization(requestContent: RequestContent): Promise { + return this.token; + } + + async clientJWT(payload: any, privateKey: RSAKey): Promise { + // @ts-ignore + return this.stringValue; + } + + async createJWS(requestContent: RequestContent): Promise { + // @ts-ignore + return {jti: this.stringValue}; + } + + async exchangeRPT(requestContent: RequestContent, accessToken: string, clientId: string): Promise { + return this.token; + } + + async getRPT(requestContent: RequestContent, enforcerFunc:EnforcerFunc): Promise { + return this.token; + } + + async getTokenByCode(requestContent: RequestContent, code: string, host: string): Promise { + // @ts-ignore + // eslint-disable-next-line babel/camelcase + return {access_token: this.token, refresh_token: this.token}; + } + + async keycloakRefreshToken(refreshContext: RefreshContext, enforcerFunc?:EnforcerFunc): Promise { + return this.token; + } + + async logout(requestContent: RequestContent, refreshToken: any): Promise { + + } + +} + +export class DummyResourceChecker implements ResourceChecker { + async getResource(requestContent: RequestContent, permission: SecurityResource): Promise { + return {}; + } + + async matchResource(requestContent: RequestContent, enforcer?: Enforcer): Promise { + + } + +} + +export class DummyUmaConfiguration implements UmaConfiguration { + umaResponse?:UMAResponse; + + constructor(umaResponse?: UMAResponse) { + this.umaResponse = umaResponse; + } + + async getUma2Configuration(requestContent: RequestContent): Promise { + // @ts-ignore + return this.umaResponse || {}; + } + +} + +export class DummyServiceAccount implements ServiceAccount { + accessToken:string; + + constructor(accessToken: string) { + this.accessToken = accessToken; + } + + async getServiceAccountToken(requestContent: RequestContent): Promise { + return this.accessToken; + } + +} + +export class DummyEnforcerAction implements EnforcerAction { + async enforce(requestContent: RequestContent, enforcer: EnforcerFunc): Promise { + + } + +} + +export class DummySecurityAdapter implements SecurityAdapter { + requestContent?:RequestContent; + + constructor(requestContent?: RequestContent) { + this.requestContent = requestContent; + } + + async validate(request: string | RequestContent, enforcer?: EnforcerFunction): Promise { + return this.requestContent || {token: {payload: {}, header: {alg: 'alg', kid: '1'}, tokenString: 'JWT'}, tokenString: 'JWT'}; + } + + async refreshToken(tokenJson: TokenJson|RefreshContext, enforcer?: EnforcerFunction): Promise { + let refreshContext:RefreshContext; + // @ts-ignore + if (tokenJson.access_token) { + refreshContext = { + token: tokenJson, + }; + } else { + refreshContext = tokenJson; + } + return refreshContext; + } + +} + +export class DummyJWKS implements JWKS { + + jWKSType?:JWKSType; + + constructor(jWKSType?: JWKSType) { + this.jWKSType = jWKSType; + } + + json(publicKey: RSAKey): JWKSType { + return this.jWKSType || {keys: [{test: 'test'}]}; + } + +} + +describe('Dummy tests', () => { + test('Dummy test', () => { + expect(1).toEqual(1); + }); +}); diff --git a/src/utils/KeycloakUtils.test.ts b/src/utils/KeycloakUtils.test.ts index 320de8a..af9157b 100644 --- a/src/utils/KeycloakUtils.test.ts +++ b/src/utils/KeycloakUtils.test.ts @@ -3,6 +3,8 @@ import 'jest'; +import {TokenJson} from "keycloak-lambda-authorizer/dist/src/Options"; + import {RequestObject, ResponseObject} from "../index"; import {APIGateWayOptions} from "../apigateway/ApiGateway"; import {StrorageDB, StrorageDBType} from "../session/storage/Strorage"; @@ -15,7 +17,6 @@ import { getCurrentStorage, getCustomPageHandler, getHostName, - getKeycloakJsonFunction, getSessionName, isCustomBehavior, } from "./KeycloakUtils"; @@ -48,18 +49,20 @@ class DummyStorageDB implements StrorageDB { return null; } - saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, externalToken: any): Promise { + saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, externalToken: TokenJson): Promise { return Promise.resolve(undefined); } - updateSession(sessionId: string, email: string, externalToken: any): Promise { + updateSession(sessionId: string, email: string, externalToken: TokenJson): Promise { return Promise.resolve(undefined); } } + const defOptions: APIGateWayOptions = { storageType: 'InMemoryDB', multiTenantAdapterOptions: {}, + // @ts-ignore multiTenantJson: () => "test", keys: { privateKey: { @@ -131,10 +134,6 @@ describe('KeycloakUtils tests', () => { await getCurrentStorage(handlerOptions); }); - test('test PublicUrlPageHandler getCurrentStorage getKeycloakJsonFunction', async () => { - expect(await getKeycloakJsonFunction({})).toEqual({}); - }); - test('test PublicUrlPageHandler getCurrentStorage isCustomBehavior 1', async () => { expect(await isCustomBehavior('public', {request, options: handlerOptions})).toEqual(null); }); diff --git a/src/utils/KeycloakUtils.ts b/src/utils/KeycloakUtils.ts index 2c06266..4eb8290 100644 --- a/src/utils/KeycloakUtils.ts +++ b/src/utils/KeycloakUtils.ts @@ -10,10 +10,6 @@ import {StrorageDB} from "../session/storage/Strorage"; import {InMemoryDB} from "../session/storage/InMemoryDB"; import {DynamoDB} from "../session/storage/DynamoDB"; -const {clientJWT} = require('keycloak-lambda-authorizer/src/clientAuthorization'); -const {commonOptions} = require('keycloak-lambda-authorizer/src/utils/optionsUtils'); -const {getKeycloakUrl, sendData} = require('keycloak-lambda-authorizer/src/utils/restCalls'); - export type KeycloakState = { multiFlag: boolean, url: string, @@ -28,53 +24,6 @@ export function getCurrentHost(req: RequestObject) { return `http${req.secure ? 's' : ''}://${getHostName(req)}`; } -async function createJWS(options: any) { - const timeLocal = new Date().getTime(); - const timeSec = Math.floor(timeLocal / 1000); - const keycloakJson = await options.keycloakJson(options); - return { - jti: v4(), - iss: keycloakJson.resource, - sub: keycloakJson.resource, - aud: `${getKeycloakUrl(keycloakJson)}/realms/${keycloakJson.realm}`, - exp: timeSec + 30, - iat: timeSec, - }; -} - -async function clientIdAuthorization(options: any) { - const keycloakJson = await options.keycloakJson(options); - let authorization = `client_id=${keycloakJson.resource}`; - if (keycloakJson.credentials && keycloakJson.credentials.secret) { - const {secret} = keycloakJson.credentials; - if (secret) { - authorization += `&client_secret=${secret}`; - } - } else if (options.keys && options.keys.privateKey) { - authorization += `&client_assertion=${await clientJWT(await createJWS(options), options)}`; - } else { - throw new Error('Unsupported Credential Type'); - } - return authorization; -} - -export async function getTokenByCode(code: string, - host: string, - adapterOptions: any, - keycloakJson: any) { - const options = commonOptions(adapterOptions, keycloakJson); - const keycloakJson0 = options.keycloakJson(options); - const url = `${getKeycloakUrl(keycloakJson0)}/realms/${keycloakJson0.realm}/protocol/openid-connect/token`; - const authorization = await clientIdAuthorization(options); - const data = `code=${code}&grant_type=authorization_code&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&${authorization}&redirect_uri=${encodeURIComponent(`${host}/callbacks/${keycloakJson0.realm}/${keycloakJson0.resource}/callback`)}`; - const tokenResponse = await sendData(url, - 'POST', - data, - {'Content-Type': 'application/x-www-form-urlencoded'}); - const tokenJson = tokenResponse; - return JSON.parse(tokenJson); -} - export function getSessionName(options: Options) { if (!options.session.sessionConfiguration.sessionCookieName) { options.session.sessionConfiguration.sessionCookieName = "KAP"; @@ -133,11 +82,6 @@ export async function getCustomPageHandler(accessLevel: AccessLevel, request: Re return null; } -export async function getKeycloakJsonFunction(keycloakJson: any): Promise { - const ret = await commonOptions({}, keycloakJson).keycloakJson(); - return ret; -} - export async function getCurrentStorage(options: Options): Promise { if (typeof options.session.sessionConfiguration.storageType === 'string') { switch (options.session.sessionConfiguration.storageType) { diff --git a/tsconfig.json b/tsconfig.json index b217ea4..a8261d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -70,5 +70,6 @@ }, "exclude": [ "examples", + "**/*.test.ts" ] }