Skip to content

Commit

Permalink
Custom Storage
Browse files Browse the repository at this point in the history
  • Loading branch information
vzakharchenko committed Jul 7, 2021
1 parent 0a6a163 commit c867cb0
Show file tree
Hide file tree
Showing 38 changed files with 3,292 additions and 40 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ jobs:
- run: cd examples/crossTenantReactJSExample/production && npm run build
- run: cd examples/crossTenantReactJSExample/tenantSelectorApp && npm i
- run: cd examples/crossTenantReactJSExample/tenantSelectorApp && npm run lint
- run: cd examples/customStorageExample/customStorage && npm i
- run: cd examples/customStorageExample/customStorage && npm run lint
- run: cd examples/customStorageExample/customStorage && npm run build
- run: cd examples/customStorageExample/development && npm i
- run: cd examples/customStorageExample/development && npm run lint
- run: cd examples/customStorageExample/development && npm run build
- run: cd examples/customStorageExample/production && npm i
- run: cd examples/customStorageExample/production && npm run lint
- run: cd examples/customStorageExample/production && npm run build
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Login flow gateway through [Keycloak](https://www.keycloak.org/) for static Web
- [Single Tenant ReactJS Application](./examples/reactJSExample)
- [Multi-tenant ReactJS Application with Tenant selector](./examples/multiTenantReactJSExample)
- [Cross-tenant ReactJS Application with Tenant selector and approval proccess](./examples/crossTenantReactJSExample)
- [Custom Storage example](./examples/customStorageExample)

# Installation

Expand Down Expand Up @@ -204,6 +205,7 @@ where
- **storageType** place where store session data(user access and refresh tokens)
- DynamoDB store in AWS DynamoDB
- InMemoryDB store in file
- own implementation of StorageDB ([example](./examples/customStorageExample))
- **identityProviders** Identity Provider Alias name.
- multiTenant - Identity Provider for Multitenant application. need use the same alias name between tenants. can be overridden by request parameter kc_idp_hint
- singleTenant - Identity Provider for application.
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
- [Single Tenant ReactJS Application](./reactJSExample)
- [Multi-tenant ReactJS Application with Tenant selector](./multiTenantReactJSExample)
- [Cross-tenant ReactJS Application with Tenant selector and approval proccess](./crossTenantReactJSExample)
- [Custom Storage example](./customStorageExample)
84 changes: 84 additions & 0 deletions examples/customStorageExample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Example of custom storage

## Development

1. **Run Docker**
Using the image from https://hub.docker.com/r/jboss/keycloak/
```
docker run -p 8090:8080 -e JAVA_OPTS="-Dkeycloak.profile.feature.scripts=enabled -Dkeycloak.profile.feature.upload_scripts=enabled -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -v `pwd`:/express -e KEYCLOAK_IMPORT=/express/example-realm-export.json jboss/keycloak
```

2. **Run localhost development server**
```bash
cd development
npm i
npm run start
```
users:

| User | Password |
|:----------|:-----------|
| user | user |

## Deploy production package to Lambda@Edge

1. **build custom storage**
```
cd customStorage
npm i
npm run build
```
2. **Prepare frontend static resources**
```bash
cd development
npm i
npm run build
```
3. **Build Lambda@Edge package**
```
cd production
npm i
npm run build
```
4. **Run Keycloak docker image accessible from the Internet**
```
docker run -p 8090:8080 -e JAVA_OPTS="-Dkeycloak.profile.feature.scripts=enabled -Dkeycloak.profile.feature.upload_scripts=enabled -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -v `pwd`:/express -e KEYCLOAK_IMPORT=/express/example-realm-export.json jboss/keycloak
ngrok http 8090
```

5. **Run CDK Deploy Script**
```
cd production/keycloak-lambda-cdk
npm i
./deploy.sh -n <BucketName> -r <ARN ROle> --keycloakUrl https://834d39e42544.ngrok.io --profile <AWS PROFILE>
```

users:

| User | Password |
|:----------|:-----------|
| user | user |

# Run as ExpressJS Server:

```bash
cd customStorage
npm i
npm run build

cd ../development
npm i
npm run build

cd ../production
npm i
npm run build
cd dist/server
node server.js
```

users:

| User | Password |
|:----------|:-----------|
| user | user |
39 changes: 39 additions & 0 deletions examples/customStorageExample/customStorage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {StrorageDB, StrorageDBType} from 'keycloak-api-gateway/dist/src/session/storage/Strorage';

type InMemoryType = {
[key: string]: StrorageDBType
}


export class CustomStorageDB implements StrorageDB {

private inMemory: InMemoryType = {};

async deleteSession(sessionId: string): Promise<void> {
delete this.inMemory[sessionId];
}

async getSessionIfExists(sessionId: string): Promise<StrorageDBType | null> {
return this.inMemory[sessionId];
}

async saveSession(sessionId: string, keycloakSession: string, exp: number, email: string, externalToken: any): Promise<void> {
this.inMemory[sessionId] = {
session: sessionId,
exp,
keycloakSession,
email,
externalToken,
};
}

async updateSession(sessionId: string, email: string, externalToken: any): Promise<void> {
const sessionObject = this.inMemory[sessionId];
if (sessionObject) {
sessionObject.externalToken = externalToken;
} else {
throw new Error("Session Does not Exists");
}
}

}
30 changes: 30 additions & 0 deletions examples/customStorageExample/customStorage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "customstorage",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"lint": "eslint --ext .ts index.ts",
"lint:fix": "eslint --fix index.ts --ext .ts",
"build": "tsc --build"
},
"dependencies": {
"keycloak-api-gateway": "../../.."
},
"devDependencies": {
"@shopify/eslint-plugin": "^40.3.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",
"coveralls": "^3.1.1",
"eslint": "^7.30.0",
"eslint-plugin-no-loops": "^0.3.0",
"typescript": "^4.3.5"
},
"author": "",
"license": "ISC"
}
74 changes: 74 additions & 0 deletions examples/customStorageExample/customStorage/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["jest", "node"], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"exclude": [
"examples",
]
}
2 changes: 2 additions & 0 deletions examples/customStorageExample/development/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
build
36 changes: 36 additions & 0 deletions examples/customStorageExample/development/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"no-loops"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@shopify/esnext"
],
"settings": {
"import/resolver": {
"node": {
"extensions": [
".js",
".jsx",
".ts",
".tsx"
]
}
}
},
"rules": {
"no-undef": 0,
"no-process-env": 0,
"no-unused-vars": 0,
"require-await": 0,
"@typescript-eslint/no-explicit-any": 0,
"id-length": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-var-requires": 0
}
}
23 changes: 23 additions & 0 deletions examples/customStorageExample/development/ApiConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"defaultAdapterOptions": {
"keycloakJson": {
"realm": "express-example",
"auth-server-url": "http://localhost:8090/auth/",
"ssl-required": "external",
"resource": "express-example",
"credentials": {
"secret": "express-example"
},
"use-resource-role-mappings": true,
"confidential-port": 0
}
},
"keys": {
"privateKey": {
"key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD40tysViQSnd3E\nIe5+6hDM/7ixHND8UoxYAKWwnA2/PdH2lq/pzjOo1t1Jt6ZbZx2l3cNUDt7FQXHL\nvZeEn0w75/LVe/gIeoKJIUTWrXyVOrrPn50oWiaKX5pnMCLWUwk1usRwnP7o26SH\nURTebSfBI7kQfh22aiv68qgGvo4lMWISVrWNCNej4oItLafRzvgBBD7GvJhqvPIW\nTMFyqDzGRtVk8nYi9x3Wwp72eUW9aY/j/akPTLdU5a+uAjlQYDrPa0wkg+/2KIhx\nGD/ffyggjvUaopzOEbnNGyBVXiOS3rQwwQnXNq+ip0xVecYVDJBlpOdQAxE77fUl\nRrw5DzKtAgMBAAECggEASLuyf7nKX5q/2XYltfmLobDadwM6X5dtqMe/pylmp1FV\nz6PqlgiNdzwfgU3qletFclepoieanMRtlCW+Zaj+6r/5bsgHD8tn3tfXvH0H3sNF\nGi3JDaOUgnxBsQoUFNw+4/LNOzHZHY4ewONFm2MC7OUZUqXa35iXdIp77UTEXkBG\nn4QdMraDW1DJUCx8nlUXHPntFN1AJANnx92Nsg6ZbhQrRRH4Lw8ydnUa3bN+Cy12\n9secVwo2RVS8slJgW21UpkVKEdUxe0VIL2++0trMokGK219AwlQV86hzEDmVUum2\nRIR3S0eknzvkJKspYc0tVvy/1uWnZggeJ+mNo1w4DQKBgQD/jpEpcdYJ9tHtBI3L\ne8s2Q4QLqdVPScS5dMDCw0aE6+CQoDSr0l37tHy5hxPJT+WalhyLCvPVtj0H97NP\nZLAoF/pgARpd3qhPM90R7/h7HgqxW/y+n1Qt/bAG+sR6n8LCcriYU+/PeUp1ugSW\nAYipqpexeRHhbwAI6pAWBj9ZXwKBgQD5QU5q6gnzdM20WVzp3K4eevpaOt9w/OUW\neI6o9wgVkvAo0p9irq8OM1SQlL3w3cX/YHktG9iF5oNRW6M2p7aKN1d5ZlUDhr1k\n/ogbtqg2CTWUikac4cUlZcour589DExlpvVL3zQda5/L7Cr0RrBmKRjMb1fyPXsy\nWJIllAgTcwKBgQDta7AlBuNJQotpXe+1+f6jHTqR82h/TxN7EKL8zpq3ZsSs2InW\nj4xNCjNN0dZqEtZHNeqyqqw6AiLVQiTOP8cAmLY9dwjd6LwJSS+7OGxrRU+90q4P\nEssMJ0HgWh0rpz0zlY01x9VltVOd6AHWsvoaVqizcr1P6OXpYrIWJBu6lQKBgQDS\n5isP048v67jRzHsNdafuKmgCSKYe2ByOcttipAK3HmkOYYhy2xNLlKsM2o4Ma9nI\nRzzAqjr+sRiTklH7QNT3BfSBx9BO94bxGVzY9ihF8Gzhjk5JF87T4di8v+SgpvNN\nX4NV+zoBWrsOtHlzzwwapNNSxzNGyDahVsfx+9sJeQKBgFuvm70VulN5Rd4TMcF2\nWixQNHEDStWBWPTa15ehDRIvxqfGZCkuY5o9tGY1vHxnpiHhqVheyRtLuHI6j5b3\nil3T5+cXdt1MnmkXUksqwgwcJdMqI5fmcuO9vdeYuGV4MoXysBdKMhqPybcVIonT\n5coMCbW92hodfPZ3F93PQpJU\n-----END PRIVATE KEY-----\n"
},
"publicKey": {
"key": "-----BEGIN CERTIFICATE-----\nMIIDjzCCAnegAwIBAgIUNC48rSIoaMJC9YAcJ/MnfQcBmDgwDQYJKoZIhvcNAQEL\nBQAwVjELMAkGA1UEBhMCVVMxDzANBgNVBAgMBkRlbmlhbDEUMBIGA1UEBwwLU3By\naW5nZmllbGQxDDAKBgNVBAoMA0RpczESMBAGA1UEAwwJZGV2c2VydmVyMCAXDTIx\nMDYwNzIwMTQzOVoYDzIxMjEwNTE0MjAxNDM5WjBWMQswCQYDVQQGEwJVUzEPMA0G\nA1UECAwGRGVuaWFsMRQwEgYDVQQHDAtTcHJpbmdmaWVsZDEMMAoGA1UECgwDRGlz\nMRIwEAYDVQQDDAlkZXZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQD40tysViQSnd3EIe5+6hDM/7ixHND8UoxYAKWwnA2/PdH2lq/pzjOo1t1J\nt6ZbZx2l3cNUDt7FQXHLvZeEn0w75/LVe/gIeoKJIUTWrXyVOrrPn50oWiaKX5pn\nMCLWUwk1usRwnP7o26SHURTebSfBI7kQfh22aiv68qgGvo4lMWISVrWNCNej4oIt\nLafRzvgBBD7GvJhqvPIWTMFyqDzGRtVk8nYi9x3Wwp72eUW9aY/j/akPTLdU5a+u\nAjlQYDrPa0wkg+/2KIhxGD/ffyggjvUaopzOEbnNGyBVXiOS3rQwwQnXNq+ip0xV\necYVDJBlpOdQAxE77fUlRrw5DzKtAgMBAAGjUzBRMB0GA1UdDgQWBBRJRP2WG0uR\nvDPnSRmV6Y8Rxu6ErDAfBgNVHSMEGDAWgBRJRP2WG0uRvDPnSRmV6Y8Rxu6ErDAP\nBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDhKnZDt5VwTroWcTtX\nLSqIDtLLHiZxk6PIE8X9DG+rU//4Rfd+MFHClcKWiyLgYZPdgPaXSDXPiyfxlb7v\njOA0F0PXbEpR/RmjM5A+x3gljSufrWgedEC6rFFEg5Ju1IY+/7nJYkvd3ICMiLB3\ngOczMEp/tI7m89DS+bJAGG8AIYeBjj+3OjuGdEFtXpkt1ri33LYC4wK+rjqkBMyi\njqwex5bEkloSuyWP/IIDa8OpBWUM17H9ZswG74kQr5/wsvvTxc/JvRmMtNrbUyKa\n2JKXA1IJgNPP4/v2FxiGTibidZVf0fyXVqarU5Ngj/fVQyn7EBg+VGqPintiL5xU\ngUsi\n-----END CERTIFICATE-----\n"
}
}
}
24 changes: 24 additions & 0 deletions examples/customStorageExample/development/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ReactJs FrontEnd Environment for development

1. **Run Docker**
Using the image from https://hub.docker.com/r/jboss/keycloak/
```
cd ..
docker run -p 8090:8080 -e JAVA_OPTS="-Dkeycloak.profile.feature.scripts=enabled -Dkeycloak.profile.feature.upload_scripts=enabled -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true" -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -v `pwd`:/express -e KEYCLOAK_IMPORT=/express/example-realm-export.json jboss/keycloak
```

2. **Run localhost development server**
```bash
npm i
npm run start
```

## 4. Open UI
[http://localhost:3000](http://localhost:3000)

users:

| User | Password |
|:----------|:-----------|
| user | user |

26 changes: 26 additions & 0 deletions examples/customStorageExample/development/craco.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const {CustomStorageDB} = require("customstorage/dist");

const adapter = require('keycloak-api-gateway/dist/index');
const fs = require('fs');

const options = JSON.parse(fs.readFileSync('./ApiConfig.json', 'utf-8'));
options.storageType=new CustomStorageDB();
const keycloakApiGateWayAdapter = new adapter.KeycloakApiGateWayAdapter(
options
);


module.exports = {
webpack: {
configure: (webpackConfig, {env, paths}) => {
webpackConfig.devServer = {};
keycloakApiGateWayAdapter.webPackDevServerMiddleWare(webpackConfig.devServer);
return webpackConfig;
}
},
devServer: (devServerConfig) => {
keycloakApiGateWayAdapter.webPackDevServerMiddleWare().applyMiddleWare(devServerConfig);
return devServerConfig;
},
keycloakApiGateWayAdapter
};
Loading

0 comments on commit c867cb0

Please sign in to comment.