@sbc-connect/nuxt-auth 0.1.8 → 0.1.9
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/CHANGELOG.md +8 -0
- package/README.md +2 -9
- package/app/app.config.ts +4 -1
- package/app/composables/useConnectAuth.ts +2 -24
- package/app/composables/useConnectHeaderOptions.ts +5 -4
- package/app/composables/useConnectLaunchDarkly.ts +245 -0
- package/app/enums/connect-auth-storage-key.ts +0 -2
- package/app/middleware/connect-auth.ts +9 -0
- package/app/pages/auth/login.vue +99 -0
- package/app/pages/auth/logout.vue +23 -0
- package/app/types/auth-app-config.d.ts +4 -1
- package/i18n/locales/en-CA.ts +14 -0
- package/package.json +4 -3
- package/public/img/BCReg_Generic_Login_image.jpg +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-auth
|
|
2
2
|
|
|
3
|
+
## 0.1.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#40](https://github.com/bcgov/connect-nuxt/pull/40) [`f4b62e1`](https://github.com/bcgov/connect-nuxt/commit/f4b62e19570ed062399ce7d23ce07abcf682285f) Thanks [@deetz99](https://github.com/deetz99)! - Add login page and auth middleware issue: bcgov/entity#29335
|
|
8
|
+
|
|
9
|
+
- [#38](https://github.com/bcgov/connect-nuxt/pull/38) [`8b8f067`](https://github.com/bcgov/connect-nuxt/commit/8b8f067aba4cda2cd2cd8de5c6f74ccc24eaf822) Thanks [@deetz99](https://github.com/deetz99)! - Add LaunchDarkly composable with user context. issue: bcgov/entity#29335
|
|
10
|
+
|
|
3
11
|
## 0.1.8
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ This package provides the necessary composables, plugins, and logic to integrate
|
|
|
16
16
|
- login() and logout() helper functions.
|
|
17
17
|
- Automatic token refreshing and session validation.
|
|
18
18
|
|
|
19
|
-
For detailed usage and documentation, please see the [Auth Layer Docs](../../../docs/packages/layers/auth/
|
|
19
|
+
For detailed usage and documentation, please see the [Auth Layer Docs](../../../docs/packages/layers/auth/overview.md).
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
@@ -46,14 +46,7 @@ Create a file named .env in the root of the project.
|
|
|
46
46
|
Copy the contents of the .env.example file into your new .env file.
|
|
47
47
|
|
|
48
48
|
### Local Development
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
# .env
|
|
53
|
-
NUXT_PUBLIC_AUTH_PROVIDER_URL="https://dev.oidc.gov.bc.ca/auth"
|
|
54
|
-
NUXT_PUBLIC_AUTH_PROVIDER_REALM="your-realm-name"
|
|
55
|
-
NUXT_PUBLIC_AUTH_PROVIDER_CLIENT_ID="your-client-id"
|
|
56
|
-
```
|
|
49
|
+
Copy the contents of the **.env.example** file into your new .env file.
|
|
57
50
|
|
|
58
51
|
### Production Environments
|
|
59
52
|
> [!IMPORTANT]
|
package/app/app.config.ts
CHANGED
|
@@ -9,8 +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
|
|
13
|
-
const redirectUri = redirect ?? loginRedirectUrl ?? window.location.href
|
|
12
|
+
const redirectUri = redirect ?? window.location.href
|
|
14
13
|
|
|
15
14
|
return $connectAuth.login(
|
|
16
15
|
{
|
|
@@ -27,8 +26,7 @@ export const useConnectAuth = () => {
|
|
|
27
26
|
*/
|
|
28
27
|
function logout(redirect?: string): Promise<void> {
|
|
29
28
|
const siteminderUrl = rtc.siteminderLogoutUrl
|
|
30
|
-
|
|
31
|
-
let redirectUri = redirect ?? logoutRedirectUrl ?? window.location.href
|
|
29
|
+
let redirectUri = redirect ?? window.location.href
|
|
32
30
|
|
|
33
31
|
if (siteminderUrl) {
|
|
34
32
|
redirectUri = `${siteminderUrl}?returl=${redirectUri.replace(/(https?:\/\/)|(\/)+/g, '$1$2')}&retnow=1`
|
|
@@ -81,30 +79,10 @@ export const useConnectAuth = () => {
|
|
|
81
79
|
})
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
function setLoginRedirectUrl(url: string) {
|
|
85
|
-
sessionStorage.setItem(ConnectAuthStorageKey.LOGIN_REDIRECT_URL, url)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function setLogoutRedirectUrl(url: string) {
|
|
89
|
-
sessionStorage.setItem(ConnectAuthStorageKey.LOGOUT_REDIRECT_URL, url)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function clearLoginRedirectUrl() {
|
|
93
|
-
sessionStorage.removeItem(ConnectAuthStorageKey.LOGIN_REDIRECT_URL)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function clearLogoutRedirectUrl() {
|
|
97
|
-
sessionStorage.removeItem(ConnectAuthStorageKey.LOGOUT_REDIRECT_URL)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
82
|
return {
|
|
101
83
|
login,
|
|
102
84
|
logout,
|
|
103
85
|
getToken,
|
|
104
|
-
clearLoginRedirectUrl,
|
|
105
|
-
clearLogoutRedirectUrl,
|
|
106
|
-
setLoginRedirectUrl,
|
|
107
|
-
setLogoutRedirectUrl,
|
|
108
86
|
isAuthenticated,
|
|
109
87
|
authUser
|
|
110
88
|
}
|
|
@@ -11,8 +11,9 @@ export function useConnectHeaderOptions() {
|
|
|
11
11
|
const route = useRoute()
|
|
12
12
|
const overlay = useOverlay()
|
|
13
13
|
const { t, locale: { value: locale } } = useNuxtApp().$i18n
|
|
14
|
-
const { login,
|
|
14
|
+
const { login, isAuthenticated, authUser } = useConnectAuth()
|
|
15
15
|
const accountStore = useConnectAccountStore()
|
|
16
|
+
const localePath = useLocalePath()
|
|
16
17
|
|
|
17
18
|
const whatsNew = useStorage<ConnectWhatsNewState>('connect-whats-new', { viewed: false, items: [] })
|
|
18
19
|
const slideover = overlay.create(ConnectSlideoverWhatsNew)
|
|
@@ -38,7 +39,7 @@ export function useConnectHeaderOptions() {
|
|
|
38
39
|
options.push({
|
|
39
40
|
label: t('connect.label.logout'),
|
|
40
41
|
icon: 'i-mdi-logout-variant',
|
|
41
|
-
onSelect: () => logout
|
|
42
|
+
onSelect: () => navigateTo(localePath('/auth/logout'))
|
|
42
43
|
})
|
|
43
44
|
return options
|
|
44
45
|
})
|
|
@@ -134,8 +135,8 @@ export function useConnectHeaderOptions() {
|
|
|
134
135
|
return options
|
|
135
136
|
})
|
|
136
137
|
|
|
137
|
-
const loginRedirectUrl = ac.login.
|
|
138
|
-
? appBaseUrl + locale + ac.login.
|
|
138
|
+
const loginRedirectUrl = ac.login.redirect
|
|
139
|
+
? appBaseUrl + locale + ac.login.redirect
|
|
139
140
|
: undefined
|
|
140
141
|
|
|
141
142
|
const loginOptionsMap: Record<'bcsc' | 'bceid' | 'idir',
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { initialize } from 'launchdarkly-js-client-sdk'
|
|
2
|
+
import type { LDClient, LDFlagSet, LDOptions, LDMultiKindContext } from 'launchdarkly-js-client-sdk'
|
|
3
|
+
import { isEqual } from 'es-toolkit'
|
|
4
|
+
|
|
5
|
+
// default anon context
|
|
6
|
+
const anonymousContext: LDMultiKindContext = {
|
|
7
|
+
kind: 'multi',
|
|
8
|
+
org: { key: 'anonymous' },
|
|
9
|
+
user: { key: 'anonymous' }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// state
|
|
13
|
+
// Defined outside of composable, so they are created only once and shared
|
|
14
|
+
const ldClient = shallowRef<LDClient | null>(null)
|
|
15
|
+
const ldFlagSet = shallowRef<LDFlagSet>({})
|
|
16
|
+
const ldContext = shallowRef<LDMultiKindContext>(anonymousContext)
|
|
17
|
+
const ldInitialized = ref(false)
|
|
18
|
+
const isInitializing = ref(false)
|
|
19
|
+
|
|
20
|
+
function _createLdContext(): LDMultiKindContext {
|
|
21
|
+
const appName = useRuntimeConfig().public.appName
|
|
22
|
+
const { authUser, isAuthenticated } = useConnectAuth()
|
|
23
|
+
const account = useConnectAccountStore().currentAccount
|
|
24
|
+
|
|
25
|
+
if (!isAuthenticated.value) {
|
|
26
|
+
return anonymousContext
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create user context
|
|
30
|
+
const user = {
|
|
31
|
+
key: authUser.value.keycloakGuid,
|
|
32
|
+
firstName: authUser.value.firstName,
|
|
33
|
+
lastName: authUser.value.lastName,
|
|
34
|
+
email: authUser.value.email,
|
|
35
|
+
roles: authUser.value.roles,
|
|
36
|
+
loginSource: authUser.value.loginSource,
|
|
37
|
+
appSource: appName
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Default org to user key if no account
|
|
41
|
+
let org: Partial<ConnectAccount & { key: string, appSource: string }> = { key: user.key, appSource: appName }
|
|
42
|
+
|
|
43
|
+
// Use account info if available
|
|
44
|
+
if (account.id) {
|
|
45
|
+
org = {
|
|
46
|
+
key: String(account.id),
|
|
47
|
+
accountType: account.accountType,
|
|
48
|
+
accountStatus: account.accountStatus,
|
|
49
|
+
type: account.type,
|
|
50
|
+
label: account.label,
|
|
51
|
+
appSource: appName
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { kind: 'multi', org, user }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _updateLdContext() {
|
|
59
|
+
if (!ldClient.value) {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newContext = _createLdContext()
|
|
64
|
+
if (isEqual(ldContext.value, newContext)) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ldContext.value = newContext
|
|
69
|
+
ldClient.value.identify(newContext).then(() => {
|
|
70
|
+
ldFlagSet.value = ldClient.value?.allFlags() || {}
|
|
71
|
+
}).catch((error) => {
|
|
72
|
+
console.error('LaunchDarkly: Failed to update context.', error)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initializes the LaunchDarkly client.
|
|
78
|
+
*/
|
|
79
|
+
function _init(): void {
|
|
80
|
+
const rtc = useRuntimeConfig().public
|
|
81
|
+
|
|
82
|
+
// Prevent re-initialization
|
|
83
|
+
if (ldInitialized.value || isInitializing.value) {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
// Prevent initialization if missing client ID
|
|
87
|
+
if (!rtc.ldClientId) {
|
|
88
|
+
console.error('LaunchDarkly: ldClientId is not configured.')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isInitializing.value = true
|
|
93
|
+
|
|
94
|
+
ldContext.value = _createLdContext()
|
|
95
|
+
|
|
96
|
+
const options: LDOptions = {
|
|
97
|
+
streaming: false,
|
|
98
|
+
useReport: false,
|
|
99
|
+
diagnosticOptOut: true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
ldClient.value = initialize(rtc.ldClientId, ldContext.value, options)
|
|
104
|
+
|
|
105
|
+
ldClient.value.on('initialized', () => {
|
|
106
|
+
ldInitialized.value = true
|
|
107
|
+
isInitializing.value = false
|
|
108
|
+
ldFlagSet.value = ldClient.value?.allFlags() || {}
|
|
109
|
+
console.info('LaunchDarkly: Anonymous initialization complete.')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
ldClient.value.on('error', (error) => {
|
|
113
|
+
console.error('LaunchDarkly: Initialization error.', error)
|
|
114
|
+
isInitializing.value = false
|
|
115
|
+
})
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('LaunchDarkly: Failed to initialize.', error)
|
|
118
|
+
isInitializing.value = false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Composable for the LaunchDarkly service.
|
|
124
|
+
*/
|
|
125
|
+
export const useConnectLaunchDarkly = () => {
|
|
126
|
+
// initialize only once
|
|
127
|
+
if (!ldInitialized.value && !isInitializing.value && import.meta.client) {
|
|
128
|
+
_init()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { isAuthenticated } = useConnectAuth()
|
|
132
|
+
const accountStore = useConnectAccountStore()
|
|
133
|
+
|
|
134
|
+
watch(
|
|
135
|
+
[isAuthenticated, () => accountStore.currentAccount],
|
|
136
|
+
() => {
|
|
137
|
+
_updateLdContext()
|
|
138
|
+
},
|
|
139
|
+
{ immediate: true }
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns a flag's value. Can operate in two modes.
|
|
144
|
+
* @param name The name of the feature flag.
|
|
145
|
+
* @param defaultValue The value to use until the flag is loaded.
|
|
146
|
+
* @param mode - 'reactive' (default) returns a ref that updates automatically.
|
|
147
|
+
* 'await' returns a promise that resolves when the client is ready.
|
|
148
|
+
* @returns A readonly ref or a promise resolving to the flag's value.
|
|
149
|
+
*/
|
|
150
|
+
function getFeatureFlag<T>(
|
|
151
|
+
name: string,
|
|
152
|
+
defaultValue: T,
|
|
153
|
+
mode: 'await'
|
|
154
|
+
): Promise<T>
|
|
155
|
+
function getFeatureFlag<T>(
|
|
156
|
+
name: string,
|
|
157
|
+
defaultValue: T,
|
|
158
|
+
mode?: 'reactive'
|
|
159
|
+
): Readonly<Ref<T>>
|
|
160
|
+
function getFeatureFlag<T>(
|
|
161
|
+
name: string,
|
|
162
|
+
defaultValue?: T,
|
|
163
|
+
mode?: 'reactive'
|
|
164
|
+
): Readonly<Ref<T | undefined>>
|
|
165
|
+
function getFeatureFlag<T>(
|
|
166
|
+
name: string,
|
|
167
|
+
defaultValue?: T,
|
|
168
|
+
mode: 'reactive' | 'await' = 'reactive'
|
|
169
|
+
): Readonly<Ref<T | undefined>> | Promise<T | undefined> {
|
|
170
|
+
if (mode === 'await') {
|
|
171
|
+
if (!ldClient.value) {
|
|
172
|
+
return Promise.resolve(defaultValue)
|
|
173
|
+
}
|
|
174
|
+
return ldClient.value.waitUntilReady()
|
|
175
|
+
.then(() => ldClient.value ? ldClient.value.variation(name, defaultValue) : defaultValue)
|
|
176
|
+
.catch((error) => {
|
|
177
|
+
console.error(`LaunchDarkly: Error waiting for client while getting flag "${name}".`, error)
|
|
178
|
+
return defaultValue
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return readonly(computed(() => {
|
|
183
|
+
if (!ldClient.value || !ldInitialized.value || !ldFlagSet.value) {
|
|
184
|
+
return defaultValue
|
|
185
|
+
}
|
|
186
|
+
return ldClient.value.variation(name, defaultValue)
|
|
187
|
+
}))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Returns a flag's value from the locally stored flag set. Can operate in two modes.
|
|
192
|
+
* @param name The name of the feature flag.
|
|
193
|
+
* @param defaultValue The value to use until the flag is loaded.
|
|
194
|
+
* @param mode - 'reactive' (default) returns a ref that updates automatically.
|
|
195
|
+
* 'await' returns a promise that resolves when the client is ready.
|
|
196
|
+
* @returns A readonly ref or a promise resolving to the flag's value.
|
|
197
|
+
*/
|
|
198
|
+
async function getStoredFlag<T>(name: string, defaultValue: T, mode: 'await'): Promise<T>
|
|
199
|
+
function getStoredFlag<T>(name: string, defaultValue?: T, mode?: 'reactive'): Readonly<Ref<T>>
|
|
200
|
+
function getStoredFlag<T>(
|
|
201
|
+
name: string,
|
|
202
|
+
defaultValue: T,
|
|
203
|
+
mode: 'reactive' | 'await' = 'reactive'
|
|
204
|
+
): Readonly<Ref<T>> | Promise<T> {
|
|
205
|
+
if (mode === 'await') {
|
|
206
|
+
if (!ldClient.value) {
|
|
207
|
+
return Promise.resolve(defaultValue)
|
|
208
|
+
}
|
|
209
|
+
return ldClient.value.waitUntilReady()
|
|
210
|
+
.then(() => ldFlagSet.value[name] ?? defaultValue)
|
|
211
|
+
.catch((error) => {
|
|
212
|
+
console.error(`LaunchDarkly: Error waiting for client while getting stored flag "${name}".`, error)
|
|
213
|
+
return defaultValue
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// reactive mode
|
|
218
|
+
return readonly(computed(() => {
|
|
219
|
+
if (!ldInitialized.value || !ldFlagSet.value) {
|
|
220
|
+
return defaultValue
|
|
221
|
+
}
|
|
222
|
+
return ldFlagSet.value[name] ?? defaultValue
|
|
223
|
+
}))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resets the LaunchDarkly state and closes the client connection.
|
|
228
|
+
*/
|
|
229
|
+
const $reset = () => {
|
|
230
|
+
ldInitialized.value = false
|
|
231
|
+
isInitializing.value = false
|
|
232
|
+
ldClient.value?.close()
|
|
233
|
+
ldClient.value = null
|
|
234
|
+
ldFlagSet.value = {}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
getFeatureFlag,
|
|
239
|
+
getStoredFlag,
|
|
240
|
+
ldInitialized: readonly(ldInitialized),
|
|
241
|
+
ldFlagSet: readonly(ldFlagSet),
|
|
242
|
+
ldClient: readonly(ldClient),
|
|
243
|
+
$reset
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
2
|
+
const { isAuthenticated } = useConnectAuth()
|
|
3
|
+
const rtc = useRuntimeConfig().public
|
|
4
|
+
const localePath = useLocalePath()
|
|
5
|
+
|
|
6
|
+
if (!isAuthenticated.value) {
|
|
7
|
+
return navigateTo(localePath(`/auth/login?return=${rtc.baseUrl}${to.fullPath.slice(1)}`))
|
|
8
|
+
}
|
|
9
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import loginImage from '#auth/public/img/BCReg_Generic_Login_image.jpg'
|
|
3
|
+
|
|
4
|
+
const { t, locale } = useI18n()
|
|
5
|
+
const { login } = useConnectAuth()
|
|
6
|
+
const rtc = useRuntimeConfig().public
|
|
7
|
+
const ac = useAppConfig().connect
|
|
8
|
+
const route = useRoute()
|
|
9
|
+
|
|
10
|
+
useHead({
|
|
11
|
+
title: t('connect.page.login.title')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
definePageMeta({
|
|
15
|
+
layout: 'connect-auth',
|
|
16
|
+
hideBreadcrumbs: true,
|
|
17
|
+
middleware: async (to) => {
|
|
18
|
+
const { $connectAuth, $router, _appConfig } = useNuxtApp()
|
|
19
|
+
if ($connectAuth.authenticated) {
|
|
20
|
+
if (to.query.return) {
|
|
21
|
+
window.location.replace(to.query.return as string)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
await $router.push(_appConfig.connect.login.redirect || '/')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const isSessionExpired = sessionStorage.getItem(ConnectAuthStorageKey.CONNECT_SESSION_EXPIRED)
|
|
30
|
+
|
|
31
|
+
const loginOptions = computed(() => {
|
|
32
|
+
const urlReturn = route.query.return
|
|
33
|
+
const redirectUrl = urlReturn !== undefined
|
|
34
|
+
? urlReturn as string
|
|
35
|
+
: `${rtc.baseUrl}${locale.value}${ac.login.redirect}`
|
|
36
|
+
|
|
37
|
+
const loginOptionsMap: Record<
|
|
38
|
+
'bcsc' | 'bceid' | 'idir',
|
|
39
|
+
{ label: string, icon: string, onClick: () => Promise<void> }
|
|
40
|
+
> = {
|
|
41
|
+
bcsc: {
|
|
42
|
+
label: t('connect.page.login.loginBCSC'),
|
|
43
|
+
icon: 'i-mdi-account-card-details-outline',
|
|
44
|
+
onClick: () => login(ConnectIdpHint.BCSC, redirectUrl)
|
|
45
|
+
},
|
|
46
|
+
bceid: {
|
|
47
|
+
label: t('connect.page.login.loginBCEID'),
|
|
48
|
+
icon: 'i-mdi-two-factor-authentication',
|
|
49
|
+
onClick: () => login(ConnectIdpHint.BCEID, redirectUrl)
|
|
50
|
+
},
|
|
51
|
+
idir: {
|
|
52
|
+
label: t('connect.page.login.loginIDIR'),
|
|
53
|
+
icon: 'i-mdi-account-group-outline',
|
|
54
|
+
onClick: () => login(ConnectIdpHint.IDIR, redirectUrl)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return ac.login.idps.map(key => loginOptionsMap[key as keyof typeof loginOptionsMap])
|
|
59
|
+
})
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<div class="flex grow flex-col items-center justify-center py-10">
|
|
64
|
+
<div class="flex flex-col items-center gap-10">
|
|
65
|
+
<h1>
|
|
66
|
+
{{ $t('connect.page.login.h1') }}
|
|
67
|
+
</h1>
|
|
68
|
+
<UAlert
|
|
69
|
+
v-if="isSessionExpired"
|
|
70
|
+
color="warning"
|
|
71
|
+
variant="subtle"
|
|
72
|
+
:title="$t('connect.page.login.sessionExpiredAlert.title')"
|
|
73
|
+
:description="$t('connect.page.login.sessionExpiredAlert.description')"
|
|
74
|
+
icon="i-mdi-alert"
|
|
75
|
+
/>
|
|
76
|
+
<UCard class="my-auto max-w-md">
|
|
77
|
+
<img
|
|
78
|
+
:src="loginImage"
|
|
79
|
+
class="pb-4"
|
|
80
|
+
:alt="$t('connect.text.imageAltGenericLogin')"
|
|
81
|
+
>
|
|
82
|
+
<div class="space-y-4 pt-2.5">
|
|
83
|
+
<div
|
|
84
|
+
v-for="(option, i) in loginOptions"
|
|
85
|
+
:key="option.label"
|
|
86
|
+
class="flex flex-col items-center gap-1"
|
|
87
|
+
>
|
|
88
|
+
<UButton
|
|
89
|
+
:variant="i === 0 ? 'solid' : 'outline'"
|
|
90
|
+
block
|
|
91
|
+
class="py-2.5"
|
|
92
|
+
v-bind="option"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</UCard>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
const rtc = useRuntimeConfig().public
|
|
4
|
+
const ac = useAppConfig().connect
|
|
5
|
+
const { locale } = useI18n()
|
|
6
|
+
const { logout } = useConnectAuth()
|
|
7
|
+
|
|
8
|
+
const redirectUrl = computed(() => {
|
|
9
|
+
const urlReturn = route.query.return
|
|
10
|
+
const url = urlReturn !== undefined
|
|
11
|
+
? urlReturn as string
|
|
12
|
+
: `${rtc.baseUrl}${locale.value}${ac.login.redirect}`
|
|
13
|
+
return url
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
onMounted(async () => {
|
|
17
|
+
await logout(redirectUrl.value)
|
|
18
|
+
})
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<ConnectSpinner fullscreen />
|
|
23
|
+
</template>
|
|
@@ -2,9 +2,12 @@ declare module '@nuxt/schema' {
|
|
|
2
2
|
interface AppConfigInput {
|
|
3
3
|
connect?: {
|
|
4
4
|
login?: {
|
|
5
|
-
|
|
5
|
+
redirect?: string
|
|
6
6
|
idps?: Array<'bcsc' | 'bceid' | 'idir'>
|
|
7
7
|
}
|
|
8
|
+
logout?: {
|
|
9
|
+
redirect?: string
|
|
10
|
+
}
|
|
8
11
|
header?: {
|
|
9
12
|
loginMenu?: boolean
|
|
10
13
|
createAccount?: boolean
|
package/i18n/locales/en-CA.ts
CHANGED
|
@@ -21,6 +21,19 @@ export default {
|
|
|
21
21
|
teamMembers: 'Team Members',
|
|
22
22
|
transactions: 'Transactions'
|
|
23
23
|
},
|
|
24
|
+
page: {
|
|
25
|
+
login: {
|
|
26
|
+
h1: 'SBC Connect Account Login',
|
|
27
|
+
title: 'Log in - SBC Connect',
|
|
28
|
+
loginBCSC: 'Login with BC Services Card',
|
|
29
|
+
loginBCEID: 'Login with BCeID',
|
|
30
|
+
loginIDIR: 'Login with IDIR',
|
|
31
|
+
sessionExpiredAlert: {
|
|
32
|
+
title: 'Session Expired',
|
|
33
|
+
description: 'Your session has expired. Please log in again to continue.'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
24
37
|
sessionExpiry: {
|
|
25
38
|
title: 'Session Expiring Soon',
|
|
26
39
|
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.',
|
|
@@ -31,6 +44,7 @@ export default {
|
|
|
31
44
|
}
|
|
32
45
|
},
|
|
33
46
|
text: {
|
|
47
|
+
imageAltGenericLogin: 'Generic Login Image',
|
|
34
48
|
notifications: {
|
|
35
49
|
none: 'No Notifications',
|
|
36
50
|
teamMemberApproval: '{count} team member requires approval to access this account. | {count} team members require approval to access this account.'
|
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.9",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"nuxt": "^4.0.2",
|
|
13
13
|
"typescript": "^5.8.3",
|
|
14
14
|
"vue-tsc": "^3.0.4",
|
|
15
|
-
"@sbc-connect/eslint-config": "0.0.6",
|
|
16
15
|
"@sbc-connect/playwright-config": "0.0.6",
|
|
16
|
+
"@sbc-connect/eslint-config": "0.0.6",
|
|
17
17
|
"@sbc-connect/vitest-config": "0.0.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"lint": "eslint .",
|
|
34
34
|
"lint:fix": "eslint --fix .",
|
|
35
35
|
"test": "pnpm run test:unit; pnpm run test:e2e",
|
|
36
|
-
"test:unit": "vitest run",
|
|
36
|
+
"test:unit": "vitest run --passWithNoTests",
|
|
37
|
+
"test:unit:watch": "vitest",
|
|
37
38
|
"test:e2e": "npx playwright test",
|
|
38
39
|
"typecheck": "npx nuxt typecheck"
|
|
39
40
|
}
|
|
Binary file
|