Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

fix: use grantManager to parse token in onSubscriptionConnect #56

Merged
merged 2 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/KeycloakSubscriptionHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Keycloak from './KeycloakTypings'
import { Token } from './KeycloakToken'
import { KeycloakSubscriptionHandlerOptions } from './api'

/**
Expand Down Expand Up @@ -55,7 +54,7 @@ export class KeycloakSubscriptionHandler {
* @param webSocket
* @param context
*/
public async onSubscriptionConnect(connectionParams: any, webSocket: any, context: any): Promise<Keycloak.Token | undefined | Error> {
public async onSubscriptionConnect(connectionParams: any, webSocket: any, context: any): Promise<Keycloak.Token | undefined> {
if (!connectionParams || typeof connectionParams !== 'object') {
if (this.protect === true) {
throw new Error('Access Denied - missing connection parameters for Authentication')
Expand All @@ -72,20 +71,23 @@ export class KeycloakSubscriptionHandler {
}
return
}
const token = this.getBearerTokenFromHeader(header, this.keycloak.config.clientId)
try {
await this.keycloak.grantManager.validateToken(token, 'Bearer')
//@ts-ignore
return token
const accessToken = this.getAccessTokenFromHeader(header)
const grant = await this.keycloak.grantManager.createGrant(accessToken)
return grant.access_token as unknown as Keycloak.Token
} catch (e) {
throw new Error(`Access Denied - ${e}`)
}
}

private getBearerTokenFromHeader(header: any, clientId?: string) {
private getAccessTokenFromHeader(header: any) {
if (header && typeof header === 'string' && (header.indexOf('bearer ') === 0 || header.indexOf('Bearer ') === 0)) {
const token = header.substring(7)
return new Token(token, clientId)
const tokenString = header.substring(7)
return {
access_token: tokenString
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Trivial] FieldName is not conforming to standards.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah so this is the format required by the keycloak.grantmanager.createGrant perhaps it would be better to return just the tokenString value here and then explicitly pass {access_token: tokenString} into that method call to make things clearer.

};
} else {
throw new Error('Invalid Authorization field in connection params. Must be in the format "Authorization": "Bearer <token string>"')
}
}
}
126 changes: 52 additions & 74 deletions test/KeycloakSubscriptionHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import test from 'ava'
import Keycloak from '../src/KeycloakTypings'

import { KeycloakSubscriptionHandler } from '../src/KeycloakSubscriptionHandler'
import { Token } from '../src/KeycloakToken';
import { Token } from './utils/KeycloakToken';

const TEST_CLIENT_ID = 'voyager-testing'

test('onSubscriptionConnect throws if no keycloak provided', async t => {
t.throws(() => {
Expand All @@ -13,14 +15,9 @@ test('onSubscriptionConnect throws if no keycloak provided', async t => {

test('onSubscriptionConnect throws if no connectionParams Provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -34,14 +31,9 @@ test('onSubscriptionConnect throws if no connectionParams Provided', async t =>

test('onSubscriptionConnect throws if no connectionParams is not an object', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -56,14 +48,9 @@ test('onSubscriptionConnect throws if no connectionParams is not an object', asy

test('onSubscriptionConnect throws if no Auth provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -76,16 +63,28 @@ test('onSubscriptionConnect throws if no Auth provided', async t => {
}, 'Access Denied - missing Authorization field in connection parameters')
})

test('onSubscriptionConnect throws if "Authorization" field is not formed correctly', async t => {
const stubKeycloak = {
grantManager: {
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak

const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: '1234' }

await t.throwsAsync(async () => {
await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
}, 'Access Denied - Error: Invalid Authorization field in connection params. Must be in the format "Authorization": "Bearer <token string>"')
})

test('onSubscriptionConnect returns a token Object if the keycloak library considers it valid', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -96,19 +95,16 @@ test('onSubscriptionConnect returns a token Object if the keycloak library consi
const connectionParams = { Authorization: tokenString }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(token instanceof Token)
t.truthy(token)
//@ts-ignore
t.truthy(token.content)
})

test('onSubscriptionConnect can also parse the token with lowercase \'bearer\'', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -119,19 +115,18 @@ test('onSubscriptionConnect can also parse the token with lowercase \'bearer\'',
const connectionParams = { Authorization: tokenString }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(token instanceof Token)

t.truthy(token)
//@ts-ignore
t.truthy(token.content)
})

test('the token object will have hasRole, hasRealmRole and hasPermissions if the', async t => {
test('the token object will have hasRole function if grant is successfully created', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
console.log(`createGant called with token String ${tokenString}`)
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -143,19 +138,17 @@ test('the token object will have hasRole, hasRealmRole and hasPermissions if the
const subscriptionHandler = new KeycloakSubscriptionHandler({ keycloak: stubKeycloak })
const connectionParams = { Authorization: tokenString, clientId: 'voyager-testing' }

const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {}) as Token
t.truthy(token instanceof Token)
const token = await subscriptionHandler.onSubscriptionConnect(connectionParams, {}, {})
t.truthy(token)
//@ts-ignore
t.truthy(token.hasRole('tester'))
})

test('If the keycloak token validation fails, then onSubscriptionConnect will throw', async t => {
test('If grant creation fails then onSubscriptionConnect will throw', async t => {
const errorMsg = 'token is invalid'
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
createGrant: (token: any) => {
return new Promise((resolve, reject) => {
reject(new Error(errorMsg))
})
Expand All @@ -177,14 +170,9 @@ test('If the keycloak token validation fails, then onSubscriptionConnect will th

test('onSubscriptionConnect with {protect: false} does not throw if no connectionParams Provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -198,14 +186,9 @@ test('onSubscriptionConnect with {protect: false} does not throw if no connectio

test('onSubscriptionConnect with {protect: false} does not throw if connectionParams is not an object', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand All @@ -220,14 +203,9 @@ test('onSubscriptionConnect with {protect: false} does not throw if connectionPa

test('onSubscriptionConnect with {protect: false} does not throw if no Auth provided', async t => {
const stubKeycloak = {
config: {
clientId: 'voyager-testing',
},
grantManager: {
validateToken: (token: string, type: 'string') => {
return new Promise((resolve, reject) => {
resolve(true)
})
createGrant: (token: any) => {
return { access_token: new Token(token.access_token, TEST_CLIENT_ID)}
}
}
} as unknown as Keycloak.Keycloak
Expand Down
File renamed without changes.