Skip to content

Commit 92b4d2b

Browse files
author
hirsch
committed
feat(): add load json config and add keycloak instance to the composable
1 parent 94fbcc7 commit 92b4d2b

File tree

9 files changed

+86
-45
lines changed

9 files changed

+86
-45
lines changed

README.md

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ import { vueKeycloak } from '@baloise/vue-keycloak'
3737
Apply the library to the vue app instance.
3838

3939
```typescript
40-
createApp(App).use(vueKeycloak, {
40+
const app = createApp(App)
41+
42+
app.use(vueKeycloak, {
4143
initOptions: {
4244
flow: 'standard', // default
4345
checkLoginIframe: false, // default
@@ -48,15 +50,21 @@ createApp(App).use(vueKeycloak, {
4850
realm: 'myrealm',
4951
clientId: 'myapp'
5052
}
51-
}).mount('#app')
53+
})
54+
```
55+
56+
Or use a JSON file with the configs.
57+
58+
```typescript
59+
app.use(vueKeycloak, '/keycloak.json')
5260
```
5361

5462
### Configuration
5563

56-
| Config | Type | Description |
57-
| ----------- | ----------------------------------- | ---------------------------------------- |
58-
| initOptions | `Keycloak.KeycloakInitOptions` | `initOptions` is Keycloak init options. |
59-
| config | `string \| Keycloak.KeycloakConfig` | `config` are the Keycloak configuration. |
64+
| Config | Type | Description |
65+
| ----------- | ------------------------------ | ---------------------------------------- |
66+
| initOptions | `Keycloak.KeycloakInitOptions` | `initOptions` is Keycloak init options. |
67+
| config | `Keycloak.KeycloakConfig` | `config` are the Keycloak configuration. |
6068

6169
Use the example below to generate dynamic Keycloak conifiguration.
6270

@@ -76,29 +84,15 @@ app.use(vueKeycloak, async () => {
7684
})
7785
```
7886

79-
Or load the Keycloak configuration from a json file.
80-
81-
```typescript
82-
app.use(vueKeycloak, async () => {
83-
return {
84-
config: 'http://localhost:8080/myapp/keycloak.json',
85-
}
86-
})
87-
```
88-
8987
> It is also possible to access the keycloak instance with `getKeycloak()`
9088
9189
## Use Token
9290

9391
We export two helper functions for the token.
9492

95-
### isTokenReady
96-
97-
This functions returs a promise and only gets resolved if we have received a token.
98-
9993
### getToken
10094

101-
This promise returns a token. `isTokenReady` gets called inside this function.
95+
This function checks if the token is still valid and will update it if it is expired.
10296

10397
> Have a look at our [vueAxios](https://github.com/baloise/vue-axios) plugin.
10498
@@ -157,21 +151,23 @@ const {
157151
decodedToken,
158152
username,
159153
roles,
154+
keycloak,
160155

161156
// Functions
162157
hasRoles,
163158
} = useKeycloak()
164159
```
165160

166-
| State | Type | Description |
167-
| --------------- | --------------- | ------------------------------------------------------ |
168-
| isAuthenticated | `Ref<boolean>` | If `true` the user is authenticated. |
169-
| isPending | `Ref<boolean>` | If `true` the authentication request is still pending. |
170-
| hasFailed | `Ref<boolean>` | If `true` authentication request has failed. |
171-
| token | `Ref<string>` | `token` is the raw value of the JWT token. |
172-
| decodedToken | `Ref<T>` | `decodedToken` is the decoded value of the JWT token. |
173-
| username | `Ref<string>` | `username` the name of our user. |
174-
| roles | `Ref<string[]>` | `roles` is a list of the users roles. |
161+
| State | Type | Description |
162+
| --------------- | --------------------------- | ------------------------------------------------------ |
163+
| isAuthenticated | `Ref<boolean>` | If `true` the user is authenticated. |
164+
| isPending | `Ref<boolean>` | If `true` the authentication request is still pending. |
165+
| hasFailed | `Ref<boolean>` | If `true` authentication request has failed. |
166+
| token | `Ref<string>` | `token` is the raw value of the JWT token. |
167+
| decodedToken | `Ref<T>` | `decodedToken` is the decoded value of the JWT token. |
168+
| username | `Ref<string>` | `username` the name of our user. |
169+
| roles | `Ref<string[]>` | `roles` is a list of the users roles. |
170+
| keycloak | `Keycloak.KeycloakInstance` | `keycloak` is the instance of the keycloak-js adapter. |
175171

176172
#### Functions
177173

src/composable.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { KeycloakInstance } from 'keycloak-js'
12
import { toRefs, Ref } from 'vue'
3+
import { getKeycloak } from './keycloak'
24
import { KeycloakState, state } from './state'
35
import { isNil } from './utils'
46

@@ -9,12 +11,14 @@ export interface KeycloakComposable {
911
token: Ref<string>
1012
username: Ref<string>
1113
roles: Ref<string[]>
14+
keycloak: KeycloakInstance
1215
hasRoles: (roles: string[]) => boolean
1316
}
1417

1518
export const useKeycloak = (): KeycloakComposable => {
1619
return {
1720
...toRefs<KeycloakState>(state),
21+
keycloak: getKeycloak(),
1822
hasRoles: (roles: string[]) =>
1923
!isNil(roles) && state.isAuthenticated && roles.every(role => state.roles.includes(role)),
2024
}

src/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export function loadJsonConfig<T>(url: string): Promise<T> {
2+
return new Promise((resolve, reject) => {
3+
const xhttp = new XMLHttpRequest()
4+
xhttp.overrideMimeType('application/json')
5+
xhttp.onreadystatechange = function () {
6+
if (this.readyState == 4 && this.status == 200) {
7+
const jsonResponse = this.responseText
8+
const response = JSON.parse(jsonResponse)
9+
resolve(response)
10+
} else {
11+
reject('Could not load ' + url + ' file')
12+
}
13+
}
14+
xhttp.open('GET', url, true)
15+
xhttp.send()
16+
})
17+
}

src/keycloak.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { createKeycloak, getToken, initKeycloak, isTokenReady } from './keycloak'
22
import Keycloak, { KeycloakConfig } from 'keycloak-js'
3-
import { hasFailed, isAuthenticated, isPending, updateToken } from './state'
3+
import { hasFailed, isAuthenticated, isPending, setToken } from './state'
44
import { defaultInitConfig } from './const'
55

66
jest.mock('keycloak-js', () => jest.fn())
77
jest.mock('./state', () => {
88
return {
9-
updateToken: jest.fn(),
9+
setToken: jest.fn(),
1010
hasFailed: jest.fn(),
1111
isPending: jest.fn(),
1212
isAuthenticated: jest.fn(),
@@ -28,7 +28,7 @@ describe('keyckoak', () => {
2828

2929
beforeEach(() => {
3030
;(Keycloak as jest.Mock).mockClear()
31-
;(updateToken as jest.Mock).mockClear()
31+
;(setToken as jest.Mock).mockClear()
3232
;(hasFailed as jest.Mock).mockClear()
3333
;(isAuthenticated as jest.Mock).mockClear()
3434
;(isPending as jest.Mock).mockClear()

src/keycloak.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Keycloak from 'keycloak-js'
2-
import { hasFailed, isAuthenticated, isPending, updateToken } from './state'
2+
import { hasFailed, isAuthenticated, isPending, setToken } from './state'
33
import { isNil } from './utils'
44

55
type KeycloakInstance = Keycloak.KeycloakInstance | undefined
@@ -23,11 +23,29 @@ export function getKeycloak(): Keycloak.KeycloakInstance {
2323
}
2424

2525
export async function getToken(): Promise<string> {
26-
await isTokenReady()
26+
return updateToken()
27+
}
28+
29+
export async function isLoggedIn(): Promise<boolean> {
30+
try {
31+
if (!$keycloak.authenticated) {
32+
return false
33+
}
34+
await this.updateToken()
35+
return true
36+
} catch (error) {
37+
return false
38+
}
39+
}
40+
41+
export async function updateToken(): Promise<string> {
42+
if (!$keycloak) {
43+
throw new Error('Keycloak is not initialized.')
44+
}
2745

2846
try {
2947
await $keycloak.updateToken(10)
30-
updateToken($keycloak.token as string)
48+
setToken($keycloak.token as string)
3149
} catch (error) {
3250
hasFailed(true)
3351
throw new Error('Failed to refresh the token, or the session has expired')
@@ -46,8 +64,11 @@ export async function initKeycloak(initConfig: Keycloak.KeycloakInitOptions): Pr
4664
const _isAuthenticated = await $keycloak.init(initConfig)
4765
isAuthenticated(_isAuthenticated)
4866
if (!isNil($keycloak.token)) {
49-
updateToken($keycloak.token as string)
67+
setToken($keycloak.token as string)
5068
}
69+
70+
$keycloak.onAuthRefreshSuccess = () => setToken($keycloak.token as string)
71+
$keycloak.onTokenExpired = () => updateToken()
5172
} catch (error) {
5273
hasFailed(true)
5374
isAuthenticated(false)

src/plugin.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import { Plugin } from 'vue'
22
import Keycloak from 'keycloak-js'
33
import { defaultInitConfig } from './const'
44
import { createKeycloak, initKeycloak } from './keycloak'
5-
import { isPromise, isFunction, isNil } from './utils'
5+
import { isPromise, isFunction, isNil, isString } from './utils'
6+
import { loadJsonConfig } from './config'
67

78
interface KeycloakPluginConfig {
8-
config: string | Keycloak.KeycloakConfig
9+
config: Keycloak.KeycloakConfig
910
initOptions?: Keycloak.KeycloakInitOptions
1011
}
1112

1213
type KeycloakConfigFactory = () => KeycloakPluginConfig
1314
type KeycloakConfigAsyncFactory = () => Promise<KeycloakPluginConfig>
1415

15-
type VueKeycloakPluginConfig = KeycloakPluginConfig | KeycloakConfigFactory | KeycloakConfigAsyncFactory
16+
type VueKeycloakPluginConfig = string | KeycloakPluginConfig | KeycloakConfigFactory | KeycloakConfigAsyncFactory
1617

1718
export const vueKeycloak: Plugin = {
1819
async install(app, options: VueKeycloakPluginConfig) {
@@ -21,7 +22,9 @@ export const vueKeycloak: Plugin = {
2122
}
2223

2324
let keycloakPluginConfig: KeycloakPluginConfig
24-
if (isPromise(options) || isFunction(options)) {
25+
if (isString(options)) {
26+
keycloakPluginConfig = await loadJsonConfig(options as string)
27+
} else if (isPromise(options) || isFunction(options)) {
2528
keycloakPluginConfig = await (options as KeycloakConfigAsyncFactory)()
2629
} else {
2730
keycloakPluginConfig = options as KeycloakPluginConfig

src/state.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { state, updateToken } from './state'
1+
import { state, setToken } from './state'
22

33
describe('state', () => {
44
const token =
@@ -14,7 +14,7 @@ describe('state', () => {
1414
})
1515

1616
test('should update the state', () => {
17-
updateToken(token)
17+
setToken(token)
1818

1919
expect(state.token).toBe(token)
2020
expect(state.username).toBe('my-name')

src/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface TokenContent {
2828
}
2929
}
3030

31-
export const updateToken = (token: string): void => {
31+
export const setToken = (token: string): void => {
3232
state.token = token
3333
const content = jwtDecode<TokenContent>(state.token)
3434
state.decodedToken = content

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function isFunction(fun: any): boolean {
1010

1111
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
1212
export function isString(text: any): boolean {
13-
return !isNil(text) && typeof text === 'string'
13+
return !isNil(text) && (typeof text === 'string' || text instanceof String)
1414
}
1515

1616
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any

0 commit comments

Comments
 (0)