Skip to content

Commit

Permalink
- regexp
Browse files Browse the repository at this point in the history
- get keykloak json from function
  • Loading branch information
vzakharchenko committed May 19, 2020
1 parent 7e6e86d commit 0272cd3
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 21 deletions.
98 changes: 92 additions & 6 deletions README.md
Expand Up @@ -13,6 +13,7 @@ Implementation [Keycloak](https://www.keycloak.org/) adapter for aws Lambda
- supports "clientId/secret" and "client-jwt" credential types
- Role based authorization
- support MultiTenant
- Regexp endpoints for Lambda@Edge
- Resource based authorization ( [Keycloak Authorization Services](https://www.keycloak.org/docs/latest/authorization_services/) )

# Installation
Expand Down Expand Up @@ -338,7 +339,42 @@ export async function authorization(event, context, callback) {
}), callback);
}
```
## 2. Create JWKS endpoint by Lambda:Edge
## 2. protect Url with Regexp

```javascript
import { lamdaEdge } from 'keycloak-lambda-authorizer';
import { SessionManager } from 'keycloak-lambda-authorizer/src/edge/storage/SessionManager';
import { LocalSessionStorage } from 'keycloak-lambda-authorizer/src/edge/storage/localSessionStorage';
import { DynamoDbSessionStorage } from 'keycloak-lambda-authorizer/src/edge/storage/DynamoDbSessionStorage';
import { isLocalhost } from 'keycloak-lambda-authorizer/src/edge/lambdaEdgeUtils';

const keycloakJson = ...;
const privateKey = ...;
const publicKey = ...;

lamdaEdge.routes.addProtected(
(^)(\/|)someUrl(|((\/)))$,
keycloakJson,
{
enforce: {
enabled: true,
resource: {
name: 'tenantResource',
},
},
}
);
// eslint-disable-next-line import/prefer-default-export
export async function authorization(event, context, callback) {
await lamdaEdge.lambdaEdgeRouter(event, context, new SessionManager(isLocalhost()? new LocalSessionStorage(): new DynamoDbSessionStorage({ region: 'us-east-1' },'teablename'), {
keys: {
privateKey,
publicKey,
},
}), callback);
}
```
## 3. Create JWKS endpoint by Lambda:Edge

```javascript
import { lamdaEdge } from 'keycloak-lambda-authorizer';
Expand All @@ -364,7 +400,7 @@ export async function authorization(event, context, callback) {
```


## 3. Public url
## 4. Public url

```javascript
import { lamdaEdge } from 'keycloak-lambda-authorizer';
Expand All @@ -389,7 +425,7 @@ export async function authorization(event, context, callback) {
}
```

## 4. Custom Url Handler
## 5. Custom Url Handler

```javascript
import { lamdaEdge } from 'keycloak-lambda-authorizer';
Expand Down Expand Up @@ -421,7 +457,7 @@ export async function authorization(event, context, callback) {
}
```

## 5. Custom Url Handler with Lambda:Edge [EventType](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html)
## 6. Custom Url Handler with Lambda:Edge [EventType](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html)


```javascript
Expand Down Expand Up @@ -458,7 +494,7 @@ export async function authorization(event, context, callback) {
}
```

# Implementation For Custom Service or non amazon cloud
# 7. Implementation For Custom Service or non amazon cloud

```javascript
import { adapter } from 'keycloak-lambda-authorizer';
Expand Down Expand Up @@ -495,4 +531,54 @@ async function handler(request,response) {
});
...
}
```
```

## 8. protect Url with keycloak function

```javascript
import { lamdaEdge } from 'keycloak-lambda-authorizer';
import { SessionManager } from 'keycloak-lambda-authorizer/src/edge/storage/SessionManager';
import { LocalSessionStorage } from 'keycloak-lambda-authorizer/src/edge/storage/localSessionStorage';
import { DynamoDbSessionStorage } from 'keycloak-lambda-authorizer/src/edge/storage/DynamoDbSessionStorage';
import { isLocalhost } from 'keycloak-lambda-authorizer/src/edge/lambdaEdgeUtils';

const privateKey = ...;
const publicKey = ...;

function getKeycloakJson(realm, clientId){
return {
"realm": realm,
"auth-server-url": "http://localhost:8090/auth",
"ssl-required": "external",
"resource": clientId,
"verify-token-audience": true,
"credentials": {
"secret": "772decbe-0151-4b08-8171-bec6d097293b"
},
"confidential-port": 0,
"policy-enforcer": {}
}
}

lamdaEdge.routes.addProtected(
'/',
getKeycloakJson("lambda-authorizer", "lambda"),
{
enforce: {
enabled: true,
resource: {
name: 'tenantResource',
},
},
}
);
// eslint-disable-next-line import/prefer-default-export
export async function authorization(event, context, callback) {
await lamdaEdge.lambdaEdgeRouter(event, context, new SessionManager(isLocalhost()? new LocalSessionStorage(): new DynamoDbSessionStorage({ region: 'us-east-1' },'teablename'), {
keys: {
privateKey,
publicKey,
},
}), callback);
}
```
59 changes: 59 additions & 0 deletions __tests__/src/edge/routers/routesTest.js
Expand Up @@ -58,6 +58,23 @@ describe('testing routers', () => {
});
});

test('test unProtected Regexp', async () => {
routers.unProtected = [];
registerRoute.mockImplementation((route) => {
routers.unProtected.push(route);
});
routes.addUnProtected(new RegExp('(^)(\\/)(test)(/$|(\\?|$))', 'g'));
const route = routers.unProtected[0];
expect(await route.isRoute({ uri: '/t' })).toEqual(false);
expect(await route.isRoute({ uri: '/test' })).toEqual(true);
expect(await route.isRoute({ uri: '/test/' })).toEqual(true);
expect(await route.isRoute({ uri: '/test/fdfd' })).toEqual(false);
expect(await route.isRoute({ uri: '/test?test=kk' })).toEqual(true);
await route.handle({}, {}, (error, response) => {
expect(response).toEqual({});
});
});

test('test JWKS', async () => {
routers.jwks = [];
registerRoute.mockImplementation((route) => {
Expand All @@ -74,6 +91,48 @@ describe('testing routers', () => {
});
});

test('test Protected keycloak JSON', async () => {
routers.protected = [];
registerRoute.mockImplementation((route) => {
routers.protected.push(route);
});
routes.addProtected('/', () => ({ realm: 'realm', resource: 'resource' }));
const routeLogout = routers.protected[0];
expect(await routeLogout.isRoute({ uri: '/realm/resource/logout' })).toEqual(true);
await routeLogout.handle({}, {}, (error, response) => {
expect(response).toEqual({
status: '302',
statusDescription: 'Found',
});
});

const routeCallback = routers.protected[1];
expect(await routeCallback.isRoute({ uri: '/realm/resource/callback' })).toEqual(true);
await routeCallback.handle({}, {}, (error, response) => {
expect(response).toEqual({
status: '302',
statusDescription: 'Found',
});
});


const routeRefresh = routers.protected[2];
expect(await routeRefresh.isRoute({ uri: '/realm/resource/refresh' })).toEqual(true);
await routeRefresh.handle({}, {}, (error, response) => {
expect(response).toEqual({
status: '302',
statusDescription: 'Found',
});
});
const route = routers.protected[3];
expect(await route.isRoute({ uri: '/' })).toEqual(true);
expect(await route.isRoute({ uri: '/test' })).toEqual(true);
expect(await route.isRoute({ uri: '/test/333' })).toEqual(true);
await route.handle({ r: 'r' }, {}, (error, response) => {
expect(response).toEqual({ r: 'r' });
});
});

test('test Protected', async () => {
routers.protected = [];
registerRoute.mockImplementation((route) => {
Expand Down
14 changes: 7 additions & 7 deletions package.json
Expand Up @@ -53,27 +53,27 @@
"author": "vzakharchenko",
"license": "Apache-2.0",
"devDependencies": {
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.6",
"@babel/polyfill": "^7.8.7",
"@babel/runtime": "^7.9.2",
"@babel/runtime": "^7.9.6",
"coveralls": "^3.1.0",
"eslint": "^6.8.0",
"eslint": "^7.0.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-plugin-import": "^2.20.2",
"jest": "^25.4.0"
"jest": "^26.0.1"
},
"dependencies": {
"aws-arn-parser": "^1.0.1",
"axios": "^0.19.2",
"cookie": "^0.4.0",
"copy-webpack-plugin": "^5.1.1",
"cookie": "^0.4.1",
"copy-webpack-plugin": "^6.0.1",
"get-keycloak-public-key": "^1.0.3",
"jsonwebtoken": "^8.5.1",
"jws": "^4.0.0",
"node-cache": "^5.1.0",
"node-forge": "^0.9.1",
"querystring": "^0.2.0",
"rsa-pem-to-jwk": "^1.1.3",
"uuid": "^7.0.3"
"uuid": "^8.0.0"
}
}
27 changes: 19 additions & 8 deletions src/edge/routes/routes.js
Expand Up @@ -12,10 +12,15 @@ const {

async function isRequest(request, routePath) {
const { uri } = request;
return (uri.startsWith(`/${routePath}`)
return (routePath instanceof RegExp) ? !!uri.match(routePath)
: (uri.startsWith(`/${routePath}`)
|| uri.startsWith(routePath));
}

function defaultRegexRoute(url) {
return new RegExp(`(^)(\\/|)(${url})(/$|(\\?|$))`, 'g');
}

function addRoute(route) {
registerRoute(route);
console.log('added route');
Expand Down Expand Up @@ -45,15 +50,21 @@ function addJwksEndpoint(routePath, publicKey) {
}

function addProtected(routePath, keycloakJson, options = {}) {
let kjson = keycloakJson;
if (!keycloakJson) {
throw new Error('keycloak.json is empty');
}
if (typeof keycloakJson === 'function') {
kjson = keycloakJson(routePath, options);
}

const newOptions = lambdaEdgeRouteOptions(options, keycloakJson);

const newOptions = lambdaEdgeRouteOptions(options, kjson);
// tenant logout
console.log(`tenant logout route /${keycloakJson.realm}/${keycloakJson.resource}/logout`);
console.log(`tenant logout route /${kjson.realm}/${kjson.resource}/logout`);
addRoute({
isRoute: async (request) => await isRequest(request, `/${keycloakJson.realm}/${keycloakJson.resource}/logout`),

isRoute: async (request) => await isRequest(request, defaultRegexRoute(`/${kjson.realm}/${kjson.resource}/logout`)),
handle: async (request, config, callback, lambdaEdgeOptions) => {
const newOptions0 = { ...newOptions, ...lambdaEdgeOptions };
const response = await tenantLogout(request, newOptions0);
Expand All @@ -62,10 +73,10 @@ function addProtected(routePath, keycloakJson, options = {}) {
}
},
});
console.log(`tenant callback route /${keycloakJson.realm}/${keycloakJson.resource}/callback`);
console.log(`tenant callback route /${kjson.realm}/${kjson.resource}/callback`);
// tenant callback
addRoute({
isRoute: async (request) => await isRequest(request, `/${keycloakJson.realm}/${keycloakJson.resource}/callback`),
isRoute: async (request) => await isRequest(request, defaultRegexRoute(`/${kjson.realm}/${kjson.resource}/callback`)),
handle: async (request, config, callback, lambdaEdgeOptions) => {
const newOptions0 = { ...newOptions, ...lambdaEdgeOptions };
const response = await callbackHandler(request, newOptions0, callback);
Expand All @@ -75,10 +86,10 @@ function addProtected(routePath, keycloakJson, options = {}) {
},
});

console.log(`tenant refresh token route /${keycloakJson.realm}/${keycloakJson.resource}/refresh`);
console.log(`tenant refresh token route /${kjson.realm}/${kjson.resource}/refresh`);
// tenant refresh
addRoute({
isRoute: async (request) => await isRequest(request, `/${keycloakJson.realm}/${keycloakJson.resource}/refresh`),
isRoute: async (request) => await isRequest(request, defaultRegexRoute(`/${kjson.realm}/${kjson.resource}/refresh`)),
handle: async (request, config, callback, lambdaEdgeOptions) => {
const newOptions0 = { ...newOptions, ...lambdaEdgeOptions };
let refreshToken = null;
Expand Down

0 comments on commit 0272cd3

Please sign in to comment.