@sbc-connect/nuxt-auth 0.4.1 → 0.6.0
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 +20 -0
- package/app/components/Connect/Account/Create/index.vue +9 -2
- package/app/composables/useAuthApi.ts +10 -8
- package/app/composables/useConnectAccountFlowRedirect.ts +27 -11
- package/app/middleware/04.idp-enforcement.global.ts +1 -1
- package/app/middleware/connect-auth.ts +17 -11
- package/app/pages/auth/login.vue +2 -1
- package/app/pages/auth/terms-of-use.vue +1 -1
- package/app/stores/connect-account.ts +19 -10
- package/app/utils/schemas/account.ts +1 -1
- package/package.json +3 -3
- package/testMocks/auth/index.ts +1 -0
- package/testMocks/auth/profile/index.ts +9 -0
- package/testMocks/auth/profile/json/PUBLIC_USER.json +29 -0
- package/testMocks/mock-helpers.ts +4 -1
- package/app/middleware/connect-login-page.ts +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-auth
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#127](https://github.com/bcgov/connect-nuxt/pull/127) [`63409ea`](https://github.com/bcgov/connect-nuxt/commit/63409eab3ef9072fe8e05662b44f61bb8be6cff4) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Improved name lookup validations
|
|
8
|
+
|
|
9
|
+
- [#125](https://github.com/bcgov/connect-nuxt/pull/125) [`fda1d48`](https://github.com/bcgov/connect-nuxt/commit/fda1d48ed55838aeece70aa04a82440153a3db24) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Includes contact POST and minor redirect refactors
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`8cc8cc0`](https://github.com/bcgov/connect-nuxt/commit/8cc8cc09aad741dc841a864998129c6ac5a6af2f)]:
|
|
14
|
+
- @sbc-connect/nuxt-base@0.6.1
|
|
15
|
+
- @sbc-connect/nuxt-forms@0.4.1
|
|
16
|
+
|
|
17
|
+
## 0.5.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- [#123](https://github.com/bcgov/connect-nuxt/pull/123) [`3c33132`](https://github.com/bcgov/connect-nuxt/commit/3c331327a27cfbd0613c268abf97842288d10f8c) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Auth Redirect Enhancements
|
|
22
|
+
|
|
3
23
|
## 0.4.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -24,9 +24,16 @@ const formErrors = computed<{
|
|
|
24
24
|
}
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
async function validate() {
|
|
28
|
-
return formRef.value?.validate({ silent: true })
|
|
27
|
+
async function validate(fieldName?: keyof AccountProfileSchema) {
|
|
28
|
+
return formRef.value?.validate({ name: fieldName, silent: true })
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
watch(() => statusCode.value, async () => {
|
|
32
|
+
if (statusCode.value !== undefined) {
|
|
33
|
+
await nextTick()
|
|
34
|
+
await validate('accountName')
|
|
35
|
+
}
|
|
36
|
+
})
|
|
30
37
|
</script>
|
|
31
38
|
|
|
32
39
|
<template>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { ConnectCreateAccount } from '#auth/app/interfaces/connect-account'
|
|
2
|
-
|
|
3
1
|
export const useAuthApi = () => {
|
|
4
2
|
const { $authApi } = useNuxtApp()
|
|
5
3
|
const queryCache = useQueryCache()
|
|
@@ -34,7 +32,10 @@ export const useAuthApi = () => {
|
|
|
34
32
|
*/
|
|
35
33
|
const useCreateAccount = defineMutation(() => {
|
|
36
34
|
const { mutateAsync, ...mutation } = useMutation({
|
|
37
|
-
mutation: (vars: {
|
|
35
|
+
mutation: (vars: {
|
|
36
|
+
payload: ConnectCreateAccount
|
|
37
|
+
successCb?: (createRes: ConnectAuthProfile) => Promise<unknown>
|
|
38
|
+
}) => {
|
|
38
39
|
return $authApi<ConnectAuthProfile>('/orgs', {
|
|
39
40
|
method: 'POST',
|
|
40
41
|
body: vars.payload
|
|
@@ -48,7 +49,7 @@ export const useAuthApi = () => {
|
|
|
48
49
|
onSuccess: async (_, _vars) => {
|
|
49
50
|
await queryCache.invalidateQueries({ key: ['auth-user-profile'], exact: true })
|
|
50
51
|
if (_vars.successCb) {
|
|
51
|
-
await _vars.successCb()
|
|
52
|
+
await _vars.successCb(_)
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
})
|
|
@@ -63,17 +64,18 @@ export const useAuthApi = () => {
|
|
|
63
64
|
* Updates a users contact by PUTing the given payload to `/users/contacts`.
|
|
64
65
|
* @returns Object containing mutation state and `updateUserContact` function.
|
|
65
66
|
*/
|
|
66
|
-
const
|
|
67
|
+
const useUpdateOrCreateUserContact = defineMutation(() => {
|
|
67
68
|
const { mutateAsync, ...mutation } = useMutation({
|
|
68
69
|
mutation: (vars: {
|
|
69
70
|
email: string
|
|
70
71
|
phone: string
|
|
71
72
|
phoneExtension: string | undefined
|
|
73
|
+
method?: 'POST' | 'PUT'
|
|
72
74
|
successCb?: () => Promise<unknown>
|
|
73
75
|
errorCb?: (error: unknown) => Promise<unknown>
|
|
74
76
|
}) => {
|
|
75
77
|
return $authApi<ConnectAuthProfile>('/users/contacts', {
|
|
76
|
-
method: 'PUT',
|
|
78
|
+
method: vars.method || 'PUT',
|
|
77
79
|
body: {
|
|
78
80
|
email: vars.email,
|
|
79
81
|
phone: vars.phone,
|
|
@@ -100,7 +102,7 @@ export const useAuthApi = () => {
|
|
|
100
102
|
|
|
101
103
|
return {
|
|
102
104
|
...mutation,
|
|
103
|
-
|
|
105
|
+
updateOrCreateUserContact: mutateAsync
|
|
104
106
|
}
|
|
105
107
|
})
|
|
106
108
|
|
|
@@ -148,7 +150,7 @@ export const useAuthApi = () => {
|
|
|
148
150
|
getTermsOfUse,
|
|
149
151
|
useCreateAccount,
|
|
150
152
|
usePatchTermsOfUse,
|
|
151
|
-
|
|
153
|
+
useUpdateOrCreateUserContact,
|
|
152
154
|
verifyAccountName
|
|
153
155
|
}
|
|
154
156
|
}
|
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
import type { RouteLocationNormalizedGeneric } from '#vue-router'
|
|
2
2
|
|
|
3
3
|
export const useConnectAccountFlowRedirect = () => {
|
|
4
|
-
function finalRedirect(route: RouteLocationNormalizedGeneric) {
|
|
4
|
+
function finalRedirect(route: RouteLocationNormalizedGeneric, manageAccount = false) {
|
|
5
|
+
const { authUser } = useConnectAuth()
|
|
6
|
+
const { currentAccount, userAccounts } = useConnectAccountStore()
|
|
5
7
|
const localePath = useLocalePath()
|
|
6
8
|
const ac = useAppConfig().connect.login
|
|
7
9
|
const externalRedirectUrl = route.query.return as string | undefined
|
|
8
10
|
const internalRedirectUrl = ac.redirect
|
|
9
|
-
|
|
10
11
|
const query = { ...route.query }
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const bypassAccounts = userAccounts.length === 1 && !query.populate
|
|
14
|
+
const isNonStaffAccount = ![AccountType.STAFF, AccountType.SBC_STAFF].includes(currentAccount.accountType)
|
|
15
|
+
const createOrSelectAccount = manageAccount && isNonStaffAccount && !bypassAccounts
|
|
16
|
+
|
|
17
|
+
if (createOrSelectAccount) {
|
|
18
|
+
const isBcscUserWithNoAccounts = (!userAccounts.length || userAccounts.length === 0)
|
|
19
|
+
&& authUser.value.loginSource === ConnectLoginSource.BCSC
|
|
20
|
+
const redirectPath = isBcscUserWithNoAccounts ? '/auth/account/create' : '/auth/account/select'
|
|
21
|
+
|
|
22
|
+
return navigateTo({ path: localePath(redirectPath), query })
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
if (externalRedirectUrl) {
|
|
26
|
+
const cleanQuery = { ...query }
|
|
27
|
+
delete cleanQuery.return
|
|
28
|
+
|
|
17
29
|
return navigateTo(
|
|
18
30
|
{
|
|
19
|
-
path: appendUrlParam(
|
|
20
|
-
|
|
31
|
+
path: appendUrlParam(
|
|
32
|
+
externalRedirectUrl,
|
|
33
|
+
'accountid',
|
|
34
|
+
currentAccount.id
|
|
35
|
+
),
|
|
36
|
+
query: cleanQuery
|
|
21
37
|
},
|
|
22
38
|
{ external: true }
|
|
23
39
|
)
|
|
24
|
-
} else {
|
|
25
|
-
return navigateTo({
|
|
26
|
-
path: localePath(internalRedirectUrl),
|
|
27
|
-
query
|
|
28
|
-
})
|
|
29
40
|
}
|
|
41
|
+
|
|
42
|
+
return navigateTo({
|
|
43
|
+
path: localePath(internalRedirectUrl),
|
|
44
|
+
query
|
|
45
|
+
})
|
|
30
46
|
}
|
|
31
47
|
|
|
32
48
|
return {
|
|
@@ -33,7 +33,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
33
33
|
if (idpEnforcement && authUser.value?.loginSource) {
|
|
34
34
|
// User's IDP is not allowed, log them out and redirect to login page
|
|
35
35
|
if (!allowedIdps?.includes(authUser.value?.loginSource.toLowerCase() as unknown as ConnectIdpHint)) {
|
|
36
|
-
showInvalidIdpModal()
|
|
36
|
+
await showInvalidIdpModal()
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
})
|
|
@@ -5,27 +5,33 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
5
5
|
const authApi = useAuthApi()
|
|
6
6
|
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
const isLoginPage = to.meta.connectLogin === true
|
|
9
|
+
const isTosPage = to.meta.connectTosPage === true
|
|
10
|
+
|
|
11
|
+
if (!isAuthenticated.value && !isLoginPage && !rtc.playwright) {
|
|
12
|
+
const defaultReturn = `${rtc.baseUrl}${to.fullPath.slice(1)}`
|
|
13
|
+
const returnUrl = (to.query.return && String(to.query.return)) || defaultReturn
|
|
14
|
+
|
|
15
|
+
return navigateTo(
|
|
16
|
+
{
|
|
17
|
+
path: localePath('/auth/login'),
|
|
18
|
+
query: {
|
|
19
|
+
...to.query,
|
|
20
|
+
return: returnUrl
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
|
-
|
|
23
|
+
)
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
if (isAuthenticated.value) {
|
|
20
27
|
const { data, refresh } = await authApi.getAuthUserProfile()
|
|
21
28
|
await refresh()
|
|
22
29
|
const hasAccepted = data.value?.userTerms.isTermsOfUseAccepted
|
|
23
|
-
const isTosPage = to.meta.connectTosPage === true
|
|
24
30
|
if (!hasAccepted && !isTosPage) {
|
|
25
31
|
const query = { ...to.query }
|
|
26
32
|
return navigateTo({ path: localePath('/auth/terms-of-use'), query })
|
|
27
|
-
} else if (hasAccepted && isTosPage) {
|
|
28
|
-
return finalRedirect(to)
|
|
33
|
+
} else if (hasAccepted && (isTosPage || isLoginPage)) {
|
|
34
|
+
return finalRedirect(to, true)
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
|
package/app/pages/auth/login.vue
CHANGED
|
@@ -12,7 +12,8 @@ useHead({
|
|
|
12
12
|
definePageMeta({
|
|
13
13
|
layout: 'connect-auth',
|
|
14
14
|
hideBreadcrumbs: true,
|
|
15
|
-
middleware: 'connect-
|
|
15
|
+
middleware: 'connect-auth',
|
|
16
|
+
connectLogin: true
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
const isSessionExpired = sessionStorage.getItem(ConnectAuthStorageKey.CONNECT_SESSION_EXPIRED)
|
|
@@ -70,7 +70,7 @@ const disableButtons = computed<boolean>(() => {
|
|
|
70
70
|
@submit="patchTermsOfUse({
|
|
71
71
|
accepted: true,
|
|
72
72
|
version: data!.versionId,
|
|
73
|
-
successCb: async () => await finalRedirect(useRoute()),
|
|
73
|
+
successCb: async () => await finalRedirect(useRoute(), true),
|
|
74
74
|
})"
|
|
75
75
|
/>
|
|
76
76
|
</UContainer>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { getAccountCreateSchema } from '#auth/app/utils/schemas/account'
|
|
2
2
|
import type { AccountProfileSchema } from '#auth/app/utils/schemas/account'
|
|
3
|
-
import type { ConnectCreateAccount } from '#auth/app/interfaces/connect-account'
|
|
4
3
|
|
|
5
4
|
export const useConnectAccountStore = defineStore('connect-auth-account-store', () => {
|
|
6
5
|
const { $authApi } = useNuxtApp()
|
|
7
6
|
const rtc = useRuntimeConfig().public
|
|
8
7
|
const { authUser } = useConnectAuth()
|
|
9
|
-
const { useCreateAccount,
|
|
8
|
+
const { useCreateAccount, useUpdateOrCreateUserContact, getAuthUserProfile } = useAuthApi()
|
|
10
9
|
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
11
10
|
|
|
12
11
|
// selected user account
|
|
@@ -22,7 +21,7 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
22
21
|
// Create account
|
|
23
22
|
const isLoading = ref<boolean>(false)
|
|
24
23
|
const { createAccount } = useCreateAccount()
|
|
25
|
-
const {
|
|
24
|
+
const { updateOrCreateUserContact } = useUpdateOrCreateUserContact()
|
|
26
25
|
const createAccountProfileSchema = getAccountCreateSchema()
|
|
27
26
|
const accountFormState = reactive<AccountProfileSchema>(createAccountProfileSchema.parse({}))
|
|
28
27
|
/**
|
|
@@ -88,13 +87,23 @@ export const useConnectAccountStore = defineStore('connect-auth-account-store',
|
|
|
88
87
|
await createAccount({
|
|
89
88
|
payload,
|
|
90
89
|
// Update User Contact Info on create account success
|
|
91
|
-
successCb: async () =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
successCb: async (createResponse: ConnectAuthProfile) => {
|
|
91
|
+
// Refresh and switch to new account prior to redirect
|
|
92
|
+
if (createResponse?.id) {
|
|
93
|
+
await setAccountInfo()
|
|
94
|
+
switchCurrentAccount(createResponse.id)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Update or create user contact and then redirect regardless of success or failure
|
|
98
|
+
await updateOrCreateUserContact({
|
|
99
|
+
email: accountFormState.emailAddress,
|
|
100
|
+
phone: accountFormState.phone.phoneNumber,
|
|
101
|
+
phoneExtension: accountFormState.phone.ext,
|
|
102
|
+
method: userAccounts.value.length === 1 ? 'POST' : 'PUT', // if only 1 account exists then contact is new
|
|
103
|
+
successCb: async () => await finalRedirect(useRoute()),
|
|
104
|
+
errorCb: async () => await finalRedirect(useRoute())
|
|
105
|
+
})
|
|
106
|
+
}
|
|
98
107
|
})
|
|
99
108
|
} catch (error) {
|
|
100
109
|
// Error handled in useAuthApi
|
|
@@ -6,7 +6,7 @@ export function getAccountNameSchema(status?: number | undefined) {
|
|
|
6
6
|
|
|
7
7
|
return z.string().min(1, t('connect.validation.requiredAccountName'))
|
|
8
8
|
.superRefine((val, ctx) => {
|
|
9
|
-
//
|
|
9
|
+
// Validate account name uniqueness based on API response status code
|
|
10
10
|
if (val && status !== undefined) {
|
|
11
11
|
if (status === 200) {
|
|
12
12
|
ctx.addIssue({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sbc-connect/nuxt-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"keycloak-js": "26.2.2",
|
|
23
23
|
"pinia": "3.0.4",
|
|
24
24
|
"pinia-plugin-persistedstate": "4.7.1",
|
|
25
|
-
"@sbc-connect/nuxt-base": "0.6.
|
|
26
|
-
"@sbc-connect/nuxt-forms": "0.4.
|
|
25
|
+
"@sbc-connect/nuxt-base": "0.6.1",
|
|
26
|
+
"@sbc-connect/nuxt-forms": "0.4.1"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"preinstall": "npx only-allow pnpm",
|
package/testMocks/auth/index.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { createResolver } from 'nuxt/kit'
|
|
3
|
+
|
|
4
|
+
const { resolve } = createResolver(import.meta.url)
|
|
5
|
+
|
|
6
|
+
export const getUserProfileMock = () => {
|
|
7
|
+
const json: ConnectAuthProfile = JSON.parse(fs.readFileSync(resolve('./json/PUBLIC_USER.json'), 'utf8'))
|
|
8
|
+
return json
|
|
9
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contacts": [
|
|
3
|
+
{
|
|
4
|
+
"email": "v9x3z!q__mock@fuzzmail.zzz",
|
|
5
|
+
"phone": "(902) 555-xyzz",
|
|
6
|
+
"phoneExtension": "7q1"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"created": "2024-03-19T04:92:17+00:00",
|
|
10
|
+
"firstname": "Nxlqrp3##",
|
|
11
|
+
"id": 88422,
|
|
12
|
+
"idpUserid": "ZZ91XQWPMR7T4!$CBA9900PLMNXXQTT",
|
|
13
|
+
"keycloakGuid": "xxxxxxxx-bb77-49c1-f01a-xxxxxxxxx",
|
|
14
|
+
"lastname": "T'W&&two--",
|
|
15
|
+
"loginSource": "BCSC",
|
|
16
|
+
"loginTime": "2026-01-23T28:77:59+00:00",
|
|
17
|
+
"modified": "2026-01-23T28:77:59+00:00",
|
|
18
|
+
"modifiedBy": "Nxlqrp3## T'W&&two--",
|
|
19
|
+
"type": "PUBLIC_USER",
|
|
20
|
+
"userStatus": 1,
|
|
21
|
+
"userTerms": {
|
|
22
|
+
"isTermsOfUseAccepted": true,
|
|
23
|
+
"termsOfUseAcceptedVersion": "999x"
|
|
24
|
+
},
|
|
25
|
+
"username": "bcsc/zz91xqwpmr7t4!$cba9900plmnxxqtt",
|
|
26
|
+
"verified": true,
|
|
27
|
+
"version": 9999
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { Page } from '@playwright/test'
|
|
2
2
|
import { AccountType } from '#auth/app/enums/account-type'
|
|
3
|
-
import { getUserSettingsMock } from '#auth/testMocks/auth'
|
|
3
|
+
import { getUserProfileMock, getUserSettingsMock } from '#auth/testMocks/auth'
|
|
4
4
|
|
|
5
5
|
export const mockApiCallsForSetAccount = async (
|
|
6
6
|
page: Page,
|
|
7
7
|
accountType: AccountType = AccountType.PREMIUM
|
|
8
8
|
) => {
|
|
9
|
+
page.route('**/users/@me', async (route) => {
|
|
10
|
+
await route.fulfill({ json: getUserProfileMock() })
|
|
11
|
+
})
|
|
9
12
|
page.route('**/users/**/settings', async (route) => {
|
|
10
13
|
await route.fulfill({ json: getUserSettingsMock(accountType) })
|
|
11
14
|
})
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export default defineNuxtRouteMiddleware((to) => {
|
|
2
|
-
const { isAuthenticated, authUser } = useConnectAuth()
|
|
3
|
-
const accountStore = useConnectAccountStore()
|
|
4
|
-
const localePath = useLocalePath()
|
|
5
|
-
const { finalRedirect } = useConnectAccountFlowRedirect()
|
|
6
|
-
|
|
7
|
-
const numAccounts = accountStore.userAccounts.length
|
|
8
|
-
|
|
9
|
-
if (isAuthenticated.value) {
|
|
10
|
-
if (numAccounts === 1) {
|
|
11
|
-
return finalRedirect(to)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (numAccounts === 0 && authUser.value.loginSource === ConnectLoginSource.BCSC) {
|
|
15
|
-
return navigateTo({
|
|
16
|
-
path: localePath('/auth/account/create'),
|
|
17
|
-
query: to.query
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return navigateTo({
|
|
22
|
-
path: localePath('/auth/account/select'),
|
|
23
|
-
query: to.query
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
})
|