@sbc-connect/nuxt-auth 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -1
- package/CHANGELOG.md +18 -0
- package/app/components/Connect/Header/AccountLabel.vue +1 -1
- package/app/components/Connect/Modal/SessionExpired.vue +93 -0
- package/app/composables/useConnectAuth.ts +7 -7
- package/app/composables/useConnectHeaderOptions.ts +1 -3
- package/app/enums/{connect-auth-storage-keys.ts → connect-auth-storage-key.ts} +1 -1
- package/app/layouts/ConnectAuth.vue +11 -0
- package/app/plugins/connect-auth.client.ts +15 -21
- package/app/stores/connect-account.ts +25 -73
- package/app/types/auth-page-meta.d.ts +8 -0
- package/app/utils/resetPiniaStores.ts +33 -0
- package/app/utils/setOnBeforeSessionExpired.ts +23 -0
- package/i18n/locales/en-CA.ts +9 -0
- package/nuxt.config.ts +5 -1
- package/package.json +3 -3
- package/app/enums/connect-error-category.ts +0 -6
- package/app/interfaces/connect-api-error.ts +0 -6
package/.env.example
CHANGED
|
@@ -22,4 +22,10 @@ NUXT_PUBLIC_LD_CLIENT_ID=""
|
|
|
22
22
|
NUXT_PUBLIC_IDP_URL="https://dev.loginproxy.gov.bc.ca/auth"
|
|
23
23
|
NUXT_PUBLIC_IDP_REALM="bcregistry"
|
|
24
24
|
NUXT_PUBLIC_IDP_CLIENTID="connect-web"
|
|
25
|
-
NUXT_PUBLIC_SITEMINDER_LOGOUT_URL="https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi"
|
|
25
|
+
NUXT_PUBLIC_SITEMINDER_LOGOUT_URL="https://logontest7.gov.bc.ca/clp-cgi/logoff.cgi"
|
|
26
|
+
|
|
27
|
+
# Session Timeout
|
|
28
|
+
NUXT_PUBLIC_TOKEN_REFRESH_INTERVAL=30000
|
|
29
|
+
NUXT_PUBLIC_TOKEN_MIN_VALIDITY=120000
|
|
30
|
+
NUXT_PUBLIC_SESSION_INACTIVITY_TIMEOUT=1800000
|
|
31
|
+
NUXT_PUBLIC_SESSION_MODAL_TIMEOUT=120000
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-auth
|
|
2
2
|
|
|
3
|
+
## 0.1.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#34](https://github.com/bcgov/connect-nuxt/pull/34) [`b2a0458`](https://github.com/bcgov/connect-nuxt/commit/b2a04587d5408d213d463ef6161b701ca597ef86) Thanks [@deetz99](https://github.com/deetz99)! - - handle user session expiry in auth plugin
|
|
8
|
+
|
|
9
|
+
- onBeforeSessionExpiry route meta
|
|
10
|
+
- onAccountChange route meta
|
|
11
|
+
- resetPiniaStores util and reset on logout
|
|
12
|
+
- add account id to layouts/ConnectAuth breadcrumb slot
|
|
13
|
+
|
|
14
|
+
issue: bcgov/entity#29335
|
|
15
|
+
|
|
16
|
+
- [#32](https://github.com/bcgov/connect-nuxt/pull/32) [`66d83d1`](https://github.com/bcgov/connect-nuxt/commit/66d83d14b2ec7950057dd39a4d876a8c4096923f) Thanks [@kialj876](https://github.com/kialj876)! - Pay layer cleanup bcgov/entity#29337
|
|
17
|
+
|
|
18
|
+
- Updated dependencies [[`66d83d1`](https://github.com/bcgov/connect-nuxt/commit/66d83d14b2ec7950057dd39a4d876a8c4096923f)]:
|
|
19
|
+
- @sbc-connect/nuxt-base@0.1.8
|
|
20
|
+
|
|
3
21
|
## 0.1.7
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -26,7 +26,7 @@ defineProps({
|
|
|
26
26
|
</span>
|
|
27
27
|
<span
|
|
28
28
|
class="line-clamp-1 overflow-hidden text-ellipsis text-xs opacity-75"
|
|
29
|
-
:class="{ 'text-line': theme === 'header', 'text-neutral': theme === 'dropdown' }"
|
|
29
|
+
:class="{ 'text-line-muted': theme === 'header', 'text-neutral': theme === 'dropdown' }"
|
|
30
30
|
>
|
|
31
31
|
{{ accountName }}
|
|
32
32
|
</span>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const isSmallScreen = useMediaQuery('(max-width: 640px)')
|
|
3
|
+
const rtc = useRuntimeConfig().public
|
|
4
|
+
const modalTimeout = rtc.sessionModalTimeout ? Number(rtc.sessionModalTimeout) : 120000
|
|
5
|
+
const { t } = useI18n()
|
|
6
|
+
const route = useRoute()
|
|
7
|
+
|
|
8
|
+
const emit = defineEmits<{ close: [] }>()
|
|
9
|
+
|
|
10
|
+
const timeRemaining = ref(toValue((modalTimeout) / 1000))
|
|
11
|
+
|
|
12
|
+
const intervalId = setInterval(async () => {
|
|
13
|
+
const value = timeRemaining.value - 1
|
|
14
|
+
timeRemaining.value = value < 0 ? 0 : value
|
|
15
|
+
|
|
16
|
+
if (value === 0) {
|
|
17
|
+
if (route.meta.onBeforeSessionExpired) {
|
|
18
|
+
await route.meta.onBeforeSessionExpired()
|
|
19
|
+
}
|
|
20
|
+
sessionStorage.setItem(ConnectAuthStorageKey.CONNECT_SESSION_EXPIRED, 'true')
|
|
21
|
+
await useConnectAuth().logout()
|
|
22
|
+
}
|
|
23
|
+
}, 1000)
|
|
24
|
+
|
|
25
|
+
const ariaCountdownText = computed(() => {
|
|
26
|
+
if (timeRemaining.value === 30) { // trigger aria alert when 30 seconds remain
|
|
27
|
+
return t('connect.sessionExpiry.content', { count: timeRemaining.value })
|
|
28
|
+
} else if (timeRemaining.value === 2) { // trigger aria alert when session expires
|
|
29
|
+
return t('connect.sessionExpiry.sessionExpired')
|
|
30
|
+
} else {
|
|
31
|
+
return ''
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
function closeModal() {
|
|
36
|
+
clearInterval(intervalId)
|
|
37
|
+
emit('close')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onMounted(() => {
|
|
41
|
+
// allow any keypress to close the modal
|
|
42
|
+
window.addEventListener('keydown', closeModal)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
onUnmounted(() => {
|
|
46
|
+
// cleanup
|
|
47
|
+
window.removeEventListener('keydown', closeModal)
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<UModal
|
|
53
|
+
id="session-expired-dialog"
|
|
54
|
+
overlay
|
|
55
|
+
:title="$t('connect.sessionExpiry.title')"
|
|
56
|
+
:description="$t('connect.sessionExpiry.content', { count: timeRemaining })"
|
|
57
|
+
@after:leave="closeModal"
|
|
58
|
+
>
|
|
59
|
+
<template #content>
|
|
60
|
+
<div class="p-10 flex flex-col gap-6">
|
|
61
|
+
<div role="alert">
|
|
62
|
+
<h2
|
|
63
|
+
id="session-expired-dialog-title"
|
|
64
|
+
class="text-xl font-bold text-neutral-highlighted"
|
|
65
|
+
>
|
|
66
|
+
{{ $t('connect.sessionExpiry.title') }}
|
|
67
|
+
</h2>
|
|
68
|
+
</div>
|
|
69
|
+
<div>
|
|
70
|
+
<ConnectI18nHelper
|
|
71
|
+
id="session-expired-dialog-description"
|
|
72
|
+
translation-path="connect.sessionExpiry.content"
|
|
73
|
+
:count="timeRemaining"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<div role="alert">
|
|
77
|
+
<span class="sr-only">{{ ariaCountdownText }}</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="flex flex-wrap items-center justify-center gap-4">
|
|
81
|
+
<UButton
|
|
82
|
+
:block="isSmallScreen"
|
|
83
|
+
:label="$t('connect.sessionExpiry.continueBtn.main')"
|
|
84
|
+
:aria-label="$t('connect.sessionExpiry.continueBtn.aria')"
|
|
85
|
+
size="xl"
|
|
86
|
+
class="font-bold"
|
|
87
|
+
@click="closeModal"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</template>
|
|
92
|
+
</UModal>
|
|
93
|
+
</template>
|
|
@@ -9,7 +9,7 @@ export const useConnectAuth = () => {
|
|
|
9
9
|
* @returns A promise that resolves when login is complete.
|
|
10
10
|
*/
|
|
11
11
|
function login(idpHint: ConnectIdpHint, redirect?: string): Promise<void> {
|
|
12
|
-
const loginRedirectUrl = sessionStorage.getItem(
|
|
12
|
+
const loginRedirectUrl = sessionStorage.getItem(ConnectAuthStorageKey.LOGIN_REDIRECT_URL)
|
|
13
13
|
const redirectUri = redirect ?? loginRedirectUrl ?? window.location.href
|
|
14
14
|
|
|
15
15
|
return $connectAuth.login(
|
|
@@ -27,14 +27,14 @@ export const useConnectAuth = () => {
|
|
|
27
27
|
*/
|
|
28
28
|
function logout(redirect?: string): Promise<void> {
|
|
29
29
|
const siteminderUrl = rtc.siteminderLogoutUrl
|
|
30
|
-
const logoutRedirectUrl = sessionStorage.getItem(
|
|
30
|
+
const logoutRedirectUrl = sessionStorage.getItem(ConnectAuthStorageKey.LOGOUT_REDIRECT_URL)
|
|
31
31
|
let redirectUri = redirect ?? logoutRedirectUrl ?? window.location.href
|
|
32
32
|
|
|
33
33
|
if (siteminderUrl) {
|
|
34
34
|
redirectUri = `${siteminderUrl}?returl=${redirectUri.replace(/(https?:\/\/)|(\/)+/g, '$1$2')}&retnow=1`
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
resetPiniaStores()
|
|
38
38
|
return $connectAuth.logout({
|
|
39
39
|
redirectUri
|
|
40
40
|
})
|
|
@@ -82,19 +82,19 @@ export const useConnectAuth = () => {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function setLoginRedirectUrl(url: string) {
|
|
85
|
-
sessionStorage.setItem(
|
|
85
|
+
sessionStorage.setItem(ConnectAuthStorageKey.LOGIN_REDIRECT_URL, url)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function setLogoutRedirectUrl(url: string) {
|
|
89
|
-
sessionStorage.setItem(
|
|
89
|
+
sessionStorage.setItem(ConnectAuthStorageKey.LOGOUT_REDIRECT_URL, url)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
function clearLoginRedirectUrl() {
|
|
93
|
-
sessionStorage.removeItem(
|
|
93
|
+
sessionStorage.removeItem(ConnectAuthStorageKey.LOGIN_REDIRECT_URL)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function clearLogoutRedirectUrl() {
|
|
97
|
-
sessionStorage.removeItem(
|
|
97
|
+
sessionStorage.removeItem(ConnectAuthStorageKey.LOGOUT_REDIRECT_URL)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
return {
|
|
@@ -87,9 +87,7 @@ export function useConnectHeaderOptions() {
|
|
|
87
87
|
onSelect: () => {
|
|
88
88
|
if (!isActive && account.id) {
|
|
89
89
|
if (route.meta.onAccountChange) {
|
|
90
|
-
|
|
91
|
-
const allowAccountChange = true
|
|
92
|
-
// const allowAccountChange = route.meta.onAccountChange(accountStore.currentAccount, account)
|
|
90
|
+
const allowAccountChange = route.meta.onAccountChange(accountStore.currentAccount, account)
|
|
93
91
|
if (allowAccountChange) {
|
|
94
92
|
accountStore.switchCurrentAccount(account.id)
|
|
95
93
|
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const accountStore = useConnectAccountStore()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<template>
|
|
2
6
|
<ConnectLayout>
|
|
3
7
|
<template #header>
|
|
4
8
|
<ConnectHeaderAuth />
|
|
5
9
|
</template>
|
|
10
|
+
<template #breadcrumb>
|
|
11
|
+
<ConnectBreadcrumb
|
|
12
|
+
:account-id="accountStore.currentAccount.id
|
|
13
|
+
? String(accountStore.currentAccount.id)
|
|
14
|
+
: undefined"
|
|
15
|
+
/>
|
|
16
|
+
</template>
|
|
6
17
|
<slot />
|
|
7
18
|
</ConnectLayout>
|
|
8
19
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Keycloak from 'keycloak-js'
|
|
2
|
+
import { ConnectModalSessionExpired } from '#components'
|
|
2
3
|
|
|
3
4
|
export default defineNuxtPlugin(async () => {
|
|
4
5
|
const rtc = useRuntimeConfig().public
|
|
@@ -10,6 +11,10 @@ export default defineNuxtPlugin(async () => {
|
|
|
10
11
|
clientId: rtc.idpClientid
|
|
11
12
|
})
|
|
12
13
|
|
|
14
|
+
const tokenRefreshInterval = rtc.tokenRefreshInterval ? Number(rtc.tokenRefreshInterval) : 30000
|
|
15
|
+
const tokenMinValidity = rtc.tokenMinValidity ? Number(rtc.tokenMinValidity) / 1000 : 120
|
|
16
|
+
const sessionInactivityTimeout = rtc.sessionInactivityTimeout ? Number(rtc.sessionInactivityTimeout) : 1800000
|
|
17
|
+
|
|
13
18
|
try {
|
|
14
19
|
// default behaviour when keycloak session expires
|
|
15
20
|
// try to update token - log out if token update fails
|
|
@@ -18,7 +23,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
18
23
|
keycloak.onTokenExpired = async () => {
|
|
19
24
|
try {
|
|
20
25
|
console.info('[Auth] Token expired, refreshing token...')
|
|
21
|
-
await keycloak.updateToken(
|
|
26
|
+
await keycloak.updateToken(tokenMinValidity)
|
|
22
27
|
console.info('[Auth] Token refreshed.')
|
|
23
28
|
} catch (error) {
|
|
24
29
|
console.error('[Auth] Failed to refresh token on expiration; logging out.', error)
|
|
@@ -36,34 +41,24 @@ export default defineNuxtPlugin(async () => {
|
|
|
36
41
|
console.error('[Auth] Failed to initialize Keycloak adapter: ', error)
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
const refreshIntervalTimeout = 30000 // rtc.tokenRefreshInterval as number
|
|
41
|
-
const minValidity = 120 // toValue((rtc.tokenMinValidity as number) / 1000) // convert to seconds
|
|
42
|
-
const idleTimeout = 1800000 // rtc.sessionIdleTimeout as number
|
|
43
|
-
|
|
44
|
-
// const route = useRoute()
|
|
45
|
-
const { idle } = useIdle(idleTimeout)
|
|
44
|
+
const { idle } = useIdle(sessionInactivityTimeout)
|
|
46
45
|
|
|
47
46
|
// executed when user is authenticated and idle = true
|
|
48
|
-
// TODO: manage session expiry
|
|
49
47
|
async function sessionExpired() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// await useConnectModals().openSessionExpiringModal()
|
|
54
|
-
// }
|
|
55
|
-
console.info('TODO - MANAGE SESSION EXPIRY')
|
|
48
|
+
const overlay = useOverlay()
|
|
49
|
+
const modal = overlay.create(ConnectModalSessionExpired)
|
|
50
|
+
modal.open()
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
// refresh token if expiring within <
|
|
53
|
+
// refresh token if expiring within <tokenMinValidity> - checks every <tokenRefreshInterval>
|
|
59
54
|
function scheduleRefreshToken() {
|
|
60
55
|
console.info('[Auth] Verifying token validity.')
|
|
61
56
|
|
|
62
57
|
setTimeout(async () => {
|
|
63
|
-
if (keycloak.isTokenExpired(
|
|
58
|
+
if (keycloak.isTokenExpired(tokenMinValidity)) {
|
|
64
59
|
console.info('[Auth] Token set to expire soon. Refreshing token...')
|
|
65
60
|
try {
|
|
66
|
-
await keycloak.updateToken(
|
|
61
|
+
await keycloak.updateToken(tokenMinValidity)
|
|
67
62
|
console.info('[Auth] Token refreshed.')
|
|
68
63
|
} catch (error) {
|
|
69
64
|
console.error('[Auth] Failed to refresh token; logging out.', error)
|
|
@@ -72,7 +67,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
scheduleRefreshToken()
|
|
75
|
-
},
|
|
70
|
+
}, tokenRefreshInterval)
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
// Watch for changes in authentication and idle state
|
|
@@ -82,8 +77,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
82
77
|
[() => keycloak.authenticated, () => idle.value],
|
|
83
78
|
async ([isAuth, isIdle]) => {
|
|
84
79
|
if (isAuth) {
|
|
85
|
-
|
|
86
|
-
// sessionStorage.removeItem(ConnectStorageKeys.CONNECT_SESSION_EXPIRED)
|
|
80
|
+
sessionStorage.removeItem(ConnectAuthStorageKey.CONNECT_SESSION_EXPIRED)
|
|
87
81
|
if (!isIdle) {
|
|
88
82
|
console.info('[Auth] Starting token refresh schedule.')
|
|
89
83
|
scheduleRefreshToken()
|
|
@@ -11,7 +11,6 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
11
11
|
const userFirstName = ref<string>(user.value?.firstName || '-')
|
|
12
12
|
const userLastName = ref<string>(user.value?.lastName || '')
|
|
13
13
|
const userFullName = computed(() => `${userFirstName.value} ${userLastName.value}`)
|
|
14
|
-
const errors = ref<ConnectApiError[]>([])
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Checks if the current account or the Keycloak user has any of the specified roles.
|
|
@@ -42,35 +41,17 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
42
41
|
|
|
43
42
|
/** Get user information from AUTH */
|
|
44
43
|
async function getAuthUserProfile(identifier: string): Promise<{ firstname: string, lastname: string } | undefined> {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
onResponseError({ response }) {
|
|
49
|
-
errors.value.push({
|
|
50
|
-
statusCode: response.status || 500,
|
|
51
|
-
message: response._data?.message || response._data?.description || 'Error fetching user info.',
|
|
52
|
-
detail: response._data.detail || '',
|
|
53
|
-
category: ConnectErrorCategory.USER_INFO
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
} catch (e) {
|
|
58
|
-
console.error('Error fetching user info.', e)
|
|
59
|
-
// logFetchError(e, 'Error fetching user info.')
|
|
60
|
-
}
|
|
44
|
+
return $authApi<{ firstname: string, lastname: string }>(`/users/${identifier}`, {
|
|
45
|
+
parseResponse: JSON.parse
|
|
46
|
+
})
|
|
61
47
|
}
|
|
62
48
|
|
|
63
49
|
/** Update user information in AUTH with current token info */
|
|
64
50
|
async function updateAuthUserInfo(): Promise<void> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
} catch (e) {
|
|
71
|
-
console.error('Error updating auth user info', e)
|
|
72
|
-
// logFetchError(e, 'Error updating auth user info')
|
|
73
|
-
}
|
|
51
|
+
await $authApi('/users', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
body: { isLogin: true }
|
|
54
|
+
})
|
|
74
55
|
}
|
|
75
56
|
|
|
76
57
|
/** Set user name information */
|
|
@@ -90,25 +71,9 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
90
71
|
if (!authUser.value?.keycloakGuid) {
|
|
91
72
|
return undefined
|
|
92
73
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
onResponseError({ response }) {
|
|
97
|
-
errors.value.push({
|
|
98
|
-
statusCode: response.status || 500,
|
|
99
|
-
message: response._data?.message || 'Error retrieving user accounts.',
|
|
100
|
-
detail: response._data.detail || '',
|
|
101
|
-
category: ConnectErrorCategory.ACCOUNT_LIST
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
return response?.filter(setting => setting.type === UserSettingsType.ACCOUNT) as ConnectAccount[]
|
|
107
|
-
} catch (e) {
|
|
108
|
-
console.error('Error retrieving user accounts', e)
|
|
109
|
-
// logFetchError(e, 'Error retrieving user accounts')
|
|
110
|
-
return undefined
|
|
111
|
-
}
|
|
74
|
+
// TODO: use orgs fetch instead to get branch name ? $authApi<UserSettings[]>('/users/orgs')
|
|
75
|
+
const response = await $authApi<ConnectUserSettings[]>(`/users/${authUser.value.keycloakGuid}/settings`)
|
|
76
|
+
return response?.filter(setting => setting.type === UserSettingsType.ACCOUNT) as ConnectAccount[]
|
|
112
77
|
}
|
|
113
78
|
|
|
114
79
|
/** Set the user account list and current account */
|
|
@@ -136,23 +101,8 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
136
101
|
if (!accountId || !keycloakGuid) {
|
|
137
102
|
return
|
|
138
103
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
onResponseError({ response }) {
|
|
142
|
-
errors.value.push({
|
|
143
|
-
statusCode: response.status || 500,
|
|
144
|
-
message: response._data.message || 'Error retrieving pending approvals.',
|
|
145
|
-
detail: response._data.detail || '',
|
|
146
|
-
category: ConnectErrorCategory.PENDING_APPROVAL_COUNT
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
pendingApprovalCount.value = response?.count || 0
|
|
152
|
-
} catch (e) {
|
|
153
|
-
console.error('Error retrieving pending approvals', e)
|
|
154
|
-
// logFetchError(e, 'Error retrieving pending approvals')
|
|
155
|
-
}
|
|
104
|
+
const response = await $authApi<{ count: number }>(`/users/${keycloakGuid}/org/${accountId}/notifications`)
|
|
105
|
+
pendingApprovalCount.value = response?.count || 0
|
|
156
106
|
}
|
|
157
107
|
|
|
158
108
|
async function checkAccountStatus() {
|
|
@@ -188,17 +138,21 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
188
138
|
}
|
|
189
139
|
|
|
190
140
|
async function initAccountStore(): Promise<void> {
|
|
191
|
-
|
|
192
|
-
setAccountInfo(),
|
|
193
|
-
updateAuthUserInfo(),
|
|
194
|
-
setUserName()
|
|
195
|
-
])
|
|
196
|
-
|
|
197
|
-
if (currentAccount.value.id) {
|
|
141
|
+
try {
|
|
198
142
|
await Promise.all([
|
|
199
|
-
|
|
200
|
-
|
|
143
|
+
setAccountInfo(),
|
|
144
|
+
updateAuthUserInfo(),
|
|
145
|
+
setUserName()
|
|
201
146
|
])
|
|
147
|
+
|
|
148
|
+
if (currentAccount.value.id) {
|
|
149
|
+
await Promise.all([
|
|
150
|
+
checkAccountStatus(),
|
|
151
|
+
getPendingApprovalCount()
|
|
152
|
+
])
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
logFetchError(e, '[Account Store] - Error during initialization')
|
|
202
156
|
}
|
|
203
157
|
}
|
|
204
158
|
|
|
@@ -207,7 +161,6 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
207
161
|
currentAccount.value = {} as ConnectAccount
|
|
208
162
|
userAccounts.value = []
|
|
209
163
|
pendingApprovalCount.value = 0
|
|
210
|
-
errors.value = []
|
|
211
164
|
userFirstName.value = user.value?.firstName || '-'
|
|
212
165
|
userLastName.value = user.value?.lastName || ''
|
|
213
166
|
}
|
|
@@ -217,7 +170,6 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
217
170
|
currentAccountName,
|
|
218
171
|
userAccounts,
|
|
219
172
|
pendingApprovalCount,
|
|
220
|
-
errors,
|
|
221
173
|
userFullName,
|
|
222
174
|
checkAccountStatus,
|
|
223
175
|
setUserName,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Pinia, Store } from 'pinia'
|
|
2
|
+
import { getActivePinia } from 'pinia'
|
|
3
|
+
|
|
4
|
+
interface ExtendedPinia extends Pinia {
|
|
5
|
+
_s: Map<string, Store>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resets specific Pinia stores if store IDs are specified.
|
|
10
|
+
* If no IDs are provided, resets all stores that have a `$reset` method.
|
|
11
|
+
* @param {string[]} storeIds - Array of store IDs to reset. If empty, all stores will be reset.
|
|
12
|
+
*/
|
|
13
|
+
export function resetPiniaStores(storeIds: string[] = []): void {
|
|
14
|
+
const pinia = getActivePinia() as ExtendedPinia
|
|
15
|
+
|
|
16
|
+
if (!pinia && import.meta.env.DEV === true) {
|
|
17
|
+
console.warn('No pinia stores found.')
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// null check still fails so must catch error instead
|
|
22
|
+
pinia._s.forEach((store) => {
|
|
23
|
+
if (storeIds.length === 0 || storeIds.includes(store.$id)) {
|
|
24
|
+
try {
|
|
25
|
+
store.$reset()
|
|
26
|
+
} catch {
|
|
27
|
+
if (import.meta.env.DEV === true) {
|
|
28
|
+
console.warn(`Store "${store.$id}" does not implement $reset. Skipping reset.`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// cant use await inside definePageMeta so must use separate util to set page meta if await required
|
|
2
|
+
/**
|
|
3
|
+
* Sets the `onBeforeSessionExpired` callback for the current route.
|
|
4
|
+
*
|
|
5
|
+
* This function allows you to provide a callback (`cb`) that will be called before the session expires.
|
|
6
|
+
* The callback can either be synchronous (returning `void`) or asynchronous (returning a `Promise`).
|
|
7
|
+
*
|
|
8
|
+
* @param {() => T | Promise<T>} cb - The callback function to execute before session expiry.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* setOnBeforeSessionExpired(() => {
|
|
12
|
+
* // Do something before session expiry
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* setOnBeforeSessionExpired(() => submitApplication());
|
|
17
|
+
*/
|
|
18
|
+
export function setOnBeforeSessionExpired<T>(cb: () => T | Promise<T>) {
|
|
19
|
+
const route = useRoute()
|
|
20
|
+
route.meta.onBeforeSessionExpired = async () => {
|
|
21
|
+
await cb()
|
|
22
|
+
}
|
|
23
|
+
}
|
package/i18n/locales/en-CA.ts
CHANGED
|
@@ -21,6 +21,15 @@ export default {
|
|
|
21
21
|
teamMembers: 'Team Members',
|
|
22
22
|
transactions: 'Transactions'
|
|
23
23
|
},
|
|
24
|
+
sessionExpiry: {
|
|
25
|
+
title: 'Session Expiring Soon',
|
|
26
|
+
content: 'Your session is about to expire due to inactivity. You will be logged out in {boldStart}0{boldEnd} seconds. Press any key to continue your session. | Your session is about to expire due to inactivity. You will be logged out in {boldStart}1{boldEnd} second. Press any key to continue your session. | Your session is about to expire due to inactivity. You will be logged out in {boldStart}{count}{boldEnd} seconds. Press any key to continue your session.',
|
|
27
|
+
sessionExpired: 'Session Expired',
|
|
28
|
+
continueBtn: {
|
|
29
|
+
main: 'Continue Session',
|
|
30
|
+
aria: 'Your session is about to expire, press any key to continue your session.'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
24
33
|
text: {
|
|
25
34
|
notifications: {
|
|
26
35
|
none: 'No Notifications',
|
package/nuxt.config.ts
CHANGED
|
@@ -71,7 +71,11 @@ export default defineNuxtConfig({
|
|
|
71
71
|
authApiUrl: '',
|
|
72
72
|
authApiVersion: '',
|
|
73
73
|
xApiKey: '',
|
|
74
|
-
authWebUrl: ''
|
|
74
|
+
authWebUrl: '',
|
|
75
|
+
tokenRefreshInterval: '',
|
|
76
|
+
tokenMinValidity: '',
|
|
77
|
+
sessionInactivityTimeout: '',
|
|
78
|
+
sessionModalTimeout: ''
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
})
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sbc-connect/nuxt-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.8",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"typescript": "^5.8.3",
|
|
14
14
|
"vue-tsc": "^3.0.4",
|
|
15
15
|
"@sbc-connect/eslint-config": "0.0.6",
|
|
16
|
-
"@sbc-connect/playwright-config": "0.0.
|
|
16
|
+
"@sbc-connect/playwright-config": "0.0.6",
|
|
17
17
|
"@sbc-connect/vitest-config": "0.0.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"keycloak-js": "^26.2.0",
|
|
22
22
|
"pinia": "^3.0.3",
|
|
23
23
|
"pinia-plugin-persistedstate": "^4.4.1",
|
|
24
|
-
"@sbc-connect/nuxt-base": "0.1.
|
|
24
|
+
"@sbc-connect/nuxt-base": "0.1.8"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"preinstall": "npx only-allow pnpm",
|