From e660aa55c067d6eec702ca7a7a9c183962977725 Mon Sep 17 00:00:00 2001 From: Johnathan Storey Date: Sun, 22 Oct 2023 22:10:36 -0700 Subject: [PATCH] Ofmcc 244 impersonate (#22) * Updating branch with implementation for impersonate functionality. * Fixed end of line semi-colons not caught by prettier. * Updates to conform with code review feedback. * Added unit tests. --------- Co-authored-by: weskubo-cgi --- backend/src/components/auth.js | 174 +++++------ backend/src/components/user.js | 49 ++- backend/src/components/utils.js | 13 +- backend/src/routes/user.js | 20 +- backend/src/util/mapping/Mappings.js | 2 +- frontend/package-lock.json | 125 +------- frontend/src/App.vue | 32 +- frontend/src/assets/css/main.css | 80 +++++ frontend/src/common/apiService.js | 26 +- frontend/src/common/authService.js | 11 +- frontend/src/components/TheHeader.vue | 36 ++- frontend/src/components/TheModalIdle.vue | 7 +- frontend/src/mixins/alertMixin.js | 7 +- frontend/src/router/index.js | 23 +- frontend/src/stores/app.js | 6 +- frontend/src/stores/auth.js | 111 ++++--- frontend/src/utils/constants.js | 18 +- frontend/src/utils/rules.js | 29 ++ .../src/views/BackendSessionExpiredView.vue | 4 +- frontend/src/views/HomeView.vue | 46 ++- frontend/src/views/ImpersonateView.vue | 75 +++++ frontend/src/views/LoginView.vue | 26 +- frontend/src/views/LogoutView.vue | 8 +- frontend/src/views/MinistryLoginView.vue | 88 ++++++ frontend/src/views/SessionExpiredView.vue | 16 +- frontend/test/views/HomeView.test.js | 21 +- frontend/test/views/ImpersonateView.test.js | 32 ++ frontend/test/views/MinistryLoginView.test.js | 26 ++ .../views/__snapshots__/HomeView.test.js.snap | 291 +++++++++++++++++- .../ImpersonateView.test.js.snap | 85 +++++ .../__snapshots__/LoginView.test.js.snap | 4 +- .../MinistryLoginView.test.js.snap | 63 ++++ frontend/yarn.lock | 4 +- 33 files changed, 1183 insertions(+), 375 deletions(-) create mode 100644 frontend/src/assets/css/main.css create mode 100644 frontend/src/utils/rules.js create mode 100644 frontend/src/views/ImpersonateView.vue create mode 100644 frontend/src/views/MinistryLoginView.vue create mode 100644 frontend/test/views/ImpersonateView.test.js create mode 100644 frontend/test/views/MinistryLoginView.test.js create mode 100644 frontend/test/views/__snapshots__/ImpersonateView.test.js.snap create mode 100644 frontend/test/views/__snapshots__/MinistryLoginView.test.js.snap diff --git a/backend/src/components/auth.js b/backend/src/components/auth.js index 4811b9b0..d4f66d55 100644 --- a/backend/src/components/auth.js +++ b/backend/src/components/auth.js @@ -1,171 +1,173 @@ -'use strict'; - -const axios = require('axios'); -const config = require('../config/index'); -const log = require('./logger'); -const jsonwebtoken = require('jsonwebtoken'); -const qs = require('querystring'); -const utils = require('./utils'); -const HttpStatus = require('http-status-codes'); -const safeStringify = require('fast-safe-stringify'); -const {ApiError} = require('./error'); -const {pick} = require('lodash'); +'use strict' + +const axios = require('axios') +const config = require('../config/index') +const log = require('./logger') +const jsonwebtoken = require('jsonwebtoken') +const qs = require('querystring') +const utils = require('./utils') +const HttpStatus = require('http-status-codes') +const safeStringify = require('fast-safe-stringify') +const { ApiError } = require('./error') +const { pick } = require('lodash') const auth = { // Check if JWT Access Token has expired isTokenExpired(token) { - const now = Date.now().valueOf() / 1000; - const payload = jsonwebtoken.decode(token); - return (!!payload['exp'] && payload['exp'] < (now + 30)); // Add 30 seconds to make sure , edge case is avoided and token is refreshed. + const now = Date.now().valueOf() / 1000 + const payload = jsonwebtoken.decode(token) + return !!payload['exp'] && payload['exp'] < now + 30 // Add 30 seconds to make sure , edge case is avoided and token is refreshed. }, // Check if JWT Refresh Token has expired isRenewable(token) { - const now = Date.now().valueOf() / 1000; - const payload = jsonwebtoken.decode(token); + const now = Date.now().valueOf() / 1000 + const payload = jsonwebtoken.decode(token) // Check if expiration exists, or lacks expiration - return (typeof (payload.exp) !== 'undefined' && payload.exp !== null && - payload.exp === 0 || payload.exp > now); + return (typeof payload.exp !== 'undefined' && payload.exp !== null && payload.exp === 0) || payload.exp > now }, // Get new JWT and Refresh tokens async renew(refreshToken, isIdirUser) { - let result = {}; - let clientId = isIdirUser? config.get('oidc:clientIdIDIR') : config.get('oidc:clientId'); - let clientSecret = isIdirUser? config.get('oidc:clientSecretIDIR') : config.get('oidc:clientSecret'); + let result = {} + let clientId = isIdirUser ? config.get('oidc:clientIdIDIR') : config.get('oidc:clientId') + let clientSecret = isIdirUser ? config.get('oidc:clientSecretIDIR') : config.get('oidc:clientSecret') try { - const discovery = await utils.getOidcDiscovery(); - const response = await axios.post(discovery.token_endpoint, + const discovery = await utils.getOidcDiscovery() + const response = await axios.post( + discovery.token_endpoint, qs.stringify({ client_id: clientId, client_secret: clientSecret, grant_type: 'refresh_token', refresh_token: refreshToken, - scope: 'offline_access' - }), { + scope: 'offline_access', + }), + { headers: { Accept: 'application/json', 'Cache-Control': 'no-cache', 'Content-Type': 'application/x-www-form-urlencoded', - } - } - ); + }, + }, + ) - log.verbose('renew', utils.prettyStringify(response.data)); + log.verbose('renew', utils.prettyStringify(response.data)) if (response && response.data && response.data.access_token && response.data.refresh_token) { - result.jwt = response.data.access_token; - result.refreshToken = response.data.refresh_token; + result.jwt = response.data.access_token + result.refreshToken = response.data.refresh_token } else { - log.error('Access token or refresh token not retreived properly'); + log.error('Access token or refresh token not retreived properly') } } catch (error) { - log.error('renew', error.message); - log.error('renew', error); - result = error.response && error.response.data; + log.error('renew', error.message) + log.error('renew', error) + result = error.response && error.response.data } - return result; + return result }, // Update or remove token based on JWT and user state async refreshJWT(req, _res, next) { try { if (!!req && !!req.user && !!req.user.jwt) { - log.verbose('refreshJWT', 'User & JWT exists'); + log.verbose('refreshJWT', 'User & JWT exists') if (auth.isTokenExpired(req.user.jwt)) { - log.verbose('refreshJWT', 'JWT has expired'); + log.verbose('refreshJWT', 'JWT has expired') if (!!req.user.refreshToken && auth.isRenewable(req.user.refreshToken)) { - log.verbose('refreshJWT', 'Can refresh JWT token'); + log.verbose('refreshJWT', 'Can refresh JWT token') // Get new JWT and Refresh Tokens and update the request - let isIdir = (req.session?.passport?.user?._json?.idir_user_guid) ? true : false; - const result = await auth.renew(req.user.refreshToken, isIdir); - req.user.jwt = result.jwt; // eslint-disable-line require-atomic-updates - req.user.refreshToken = result.refreshToken; // eslint-disable-line require-atomic-updates + let isIdir = req.session?.passport?.user?._json?.idir_user_guid ? true : false + const result = await auth.renew(req.user.refreshToken, isIdir) + req.user.jwt = result.jwt // eslint-disable-line require-atomic-updates + req.user.refreshToken = result.refreshToken // eslint-disable-line require-atomic-updates } else { - log.verbose('refreshJWT', 'Cannot refresh JWT token'); - delete req.user; + log.verbose('refreshJWT', 'Cannot refresh JWT token') + delete req.user } } } else { - log.verbose('refreshJWT', 'No existing User or JWT'); - delete req.user; + log.verbose('refreshJWT', 'No existing User or JWT') + delete req.user } } catch (error) { - log.error('refreshJWT', error.message); + log.error('refreshJWT', error.message) } - next(); + next() }, generateUiToken() { - const i = config.get('tokenGenerate:issuer'); - const s = 'user@penrequest.ca'; - const a = config.get('server:frontend'); + const i = config.get('tokenGenerate:issuer') + const s = 'user@penrequest.ca' + const a = config.get('server:frontend') const signOptions = { issuer: i, subject: s, audience: a, expiresIn: '30m', - algorithm: 'RS256' - }; + algorithm: 'RS256', + } - const privateKey = config.get('tokenGenerate:privateKey'); - const uiToken = jsonwebtoken.sign({}, privateKey, signOptions); - log.verbose('Generated JWT', uiToken); - return uiToken; + const privateKey = config.get('tokenGenerate:privateKey') + const uiToken = jsonwebtoken.sign({}, privateKey, signOptions) + log.verbose('Generated JWT', uiToken) + return uiToken }, async getApiCredentials() { try { - const discovery = await utils.getOidcDiscovery(); - const response = await axios.post(discovery.token_endpoint, + const discovery = await utils.getOidcDiscovery() + const response = await axios.post( + discovery.token_endpoint, qs.stringify({ client_id: config.get('oidc:clientId'), client_secret: config.get('oidc:clientSecret'), grant_type: 'client_credentials', - scope: discovery.scopes_supported - }), { + scope: discovery.scopes_supported, + }), + { headers: { Accept: 'application/json', 'Cache-Control': 'no-cache', 'Content-Type': 'application/x-www-form-urlencoded', - } - } - ); + }, + }, + ) - log.verbose('getApiCredentials Res', safeStringify(response.data)); + log.verbose('getApiCredentials Res', safeStringify(response.data)) - let result = {}; - result.accessToken = response.data.access_token; - result.refreshToken = response.data.refresh_token; - return result; + let result = {} + result.accessToken = response.data.access_token + result.refreshToken = response.data.refresh_token + return result } catch (error) { - log.error('getApiCredentials Error', error.response ? pick(error.response, ['status', 'statusText', 'data']) : error.message); - const status = error.response ? error.response.status : HttpStatus.INTERNAL_SERVER_ERROR; - throw new ApiError(status, {message: 'Get getApiCredentials error'}, error); + log.error('getApiCredentials Error', error.response ? pick(error.response, ['status', 'statusText', 'data']) : error.message) + const status = error.response ? error.response.status : HttpStatus.INTERNAL_SERVER_ERROR + throw new ApiError(status, { message: 'Get getApiCredentials error' }, error) } }, isValidBackendToken() { return function (req, res, next) { if (req?.session?.passport?.user?.jwt) { try { - jsonwebtoken.verify(req.session.passport.user.jwt, config.get('oidc:publicKey')); + jsonwebtoken.verify(req.session.passport.user.jwt, config.get('oidc:publicKey')) } catch (e) { - log.info('error is from verify', e); - return res.status(HttpStatus.UNAUTHORIZED).json(); + log.info('error is from verify', e) + return res.status(HttpStatus.UNAUTHORIZED).json() } - log.info('Backend token is valid moving to next'); - return next(); + log.info('Backend token is valid moving to next') + return next() } else { - log.info('no jwt responding back 401'); - return res.status(HttpStatus.UNAUTHORIZED).json(); + log.info('no jwt responding back 401') + return res.status(HttpStatus.UNAUTHORIZED).json() } - }; - } -}; - + } + }, +} -module.exports = auth; +module.exports = auth diff --git a/backend/src/components/user.js b/backend/src/components/user.js index a6439acf..d1fee51a 100644 --- a/backend/src/components/user.js +++ b/backend/src/components/user.js @@ -1,5 +1,5 @@ 'use strict' -const { getSessionUser, getUserName, getHttpHeader, minify, getUserGuid, isIdirUser } = require('./utils') +const { getSessionUser, getUserName, getBusinessName, getHttpHeader, minify, getUserGuid, isIdirUser, getOperation } = require('./utils') const config = require('../config/index') const ApiError = require('./error') const axios = require('axios') @@ -18,12 +18,13 @@ async function getUserInfo(req, res) { message: 'No session data', }) } + const userGuid = getUserGuid(req) const isIdir = isIdirUser(req) const queryUserName = req.params?.queryUserName const userName = getUserName(req) + const businessName = getBusinessName(req) // if is idir user (ministry user), make sure they are a user in dynamics - // TODO commented out until we focus on IDIR login and weather this code is relevant if (isIdir) { let response = await getDynamicsUserByEmail(req) if (response.value?.length > 0 && response.value[0].systemuserid) { @@ -35,12 +36,13 @@ async function getUserInfo(req, res) { }) } } + let resData = { - // TODO i thing this has to do with impersonate... displayName: (queryUserName)? userName + '-' + queryUserName : userName, + displayName: queryUserName ? userName + '-' + queryUserName : userName, userName: userName, + businessName: businessName, isMinistryUser: isIdir, serverTime: new Date(), - //TODO: unreadMessages is hardcoded. Remove this with API values when built out! unreadMessages: false, } let userResponse = undefined @@ -54,19 +56,25 @@ async function getUserInfo(req, res) { if (userResponse === null) { return res.status(HttpStatus.NOT_FOUND).json({ message: 'No user found with that BCeID UserName' }) } + userResponse.isImpersonated = true } catch (e) { log.error('getUserProfile Error', e.response ? e.response.status : e.message) - throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, { message: 'API Get error' }, e) + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json(resData) } } else { - //If not looking for a username, return from here since ministry staff should not have an account + // TODO: Get info for ministry user from session header for now... until we get an endpiont for ministry + // user from dynamics + resData.userId = userGuid + resData.firstName = req.session.passport.user._json.given_name + resData.lastName = req.session.passport.user._json.family_name + resData.email = req.session.passport.user._json.email + log.verbose('Ministry User response:', resData) return res.status(HttpStatus.OK).json(resData) } } else { //Not an idir user, so just get the guid from the header - const userGuid = getUserGuid(req) log.verbose('User Guid is: ', userGuid) - userResponse = await getUserProfile(userGuid) + userResponse = await getUserProfile(userGuid, null) } if (log.isVerboseEnabled) { @@ -91,12 +99,33 @@ async function getUserInfo(req, res) { return res.status(HttpStatus.OK).json(results) } -async function getUserProfile(userGuid) { +async function getDynamicsUserByEmail(req) { + let email = req.session.passport.user._json.email + if (!email) { + //If for some reason, an email is not associated with the IDIR, just use IDR@gov.bc.ca + email = `${req.session.passport.user._json.idir_username}@gov.bc.ca` + } + // eslint-disable-next-line quotes, + email.includes("'") ? (email = email.replace("'", "''")) : email + try { + let response = await getOperation(`systemusers?$select=firstname,domainname,lastname&$filter=internalemailaddress eq '${email}'`) + return response + } catch (e) { + log.error('getDynamicsUserByEmail Error', e.response ? e.response.status : e.message) + throw new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, { message: 'API Get error' }, e) + } +} + +async function getUserProfile(userGuid, userName) { try { let url = undefined + if (userGuid) { - url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userId=${userGuid}` + url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userId=${userGuid}&userName=${userName}` + } else { + url = config.get('dynamicsApi:apiEndpoint') + `/api/ProviderProfile?userName=${userName}` } + log.verbose('UserProfile Url is', url) let response = undefined response = await axios.get(url, getHttpHeader()) diff --git a/backend/src/components/utils.js b/backend/src/components/utils.js index f507f014..e9c519f2 100644 --- a/backend/src/components/utils.js +++ b/backend/src/components/utils.js @@ -80,6 +80,7 @@ function getUserGuid(req) { } return guid } + function isIdirUser(req) { const userInfo = req.session?.passport?.user if (!userInfo || !userInfo.jwt || !userInfo._json) { @@ -90,11 +91,12 @@ function isIdirUser(req) { //For local development only. //generally set isIdirUser to false, so that developers can log in using their //IDIRS as a normal, non-ministry user. - if ('local' === config.get('environment') && !config.get('server:useImpersonate')) { + /* if ('local' === config.get('environment') && !config.get('server:useImpersonate')) { return false - } + } */ return isIdir } + function getUserName(req) { let userName = req.session?.passport?.user?._json?.bceid_username if (!userName) { @@ -102,6 +104,12 @@ function getUserName(req) { } return userName } + +function getBusinessName(req) { + let businessName = req.session?.passport?.user?._json?.bceid_business_name + return businessName +} + function getAccessToken(req) { const user = getSessionUser(req) return user && user.jwt @@ -237,6 +245,7 @@ const utils = { getUserGuid, isIdirUser, getUserName, + getBusinessName, getOperationWithObjectId, getOperation, postOperation, diff --git a/backend/src/routes/user.js b/backend/src/routes/user.js index c7a7ea22..697c854a 100644 --- a/backend/src/routes/user.js +++ b/backend/src/routes/user.js @@ -1,15 +1,15 @@ -'use strict'; +'use strict' -const passport = require('passport'); -const express = require('express'); -const router = express.Router(); -const auth = require('../components/auth'); -const isValidBackendToken = auth.isValidBackendToken(); +const passport = require('passport') +const express = require('express') +const router = express.Router() +const auth = require('../components/auth') +const isValidBackendToken = auth.isValidBackendToken() -const { getUserInfo} = require('../components/user'); +const { getUserInfo } = require('../components/user') -router.get('/', passport.authenticate('jwt', {session: false}), isValidBackendToken, getUserInfo); +router.get('/', passport.authenticate('jwt', { session: false }), isValidBackendToken, getUserInfo) -router.get('/:queryUserName', passport.authenticate('jwt', {session: false}), isValidBackendToken, getUserInfo); +router.get('/:queryUserName', passport.authenticate('jwt', { session: false }), isValidBackendToken, getUserInfo) -module.exports = router; +module.exports = router diff --git a/backend/src/util/mapping/Mappings.js b/backend/src/util/mapping/Mappings.js index 9ad8ebe4..40d4f08b 100644 --- a/backend/src/util/mapping/Mappings.js +++ b/backend/src/util/mapping/Mappings.js @@ -1,7 +1,7 @@ const UserProfileMappings = [ { back: 'contactid', front: 'contactId' }, { back: 'ccof_userid', front: 'userId' }, - { back: 'ccof_username', front: 'username' }, + { back: 'ccof_username', front: 'userName' }, { back: 'emailaddress1', front: 'email' }, { back: 'ofm_first_name', front: 'firstName' }, { back: 'ofm_last_name', front: 'lastName' }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3d34d0c6..06586540 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "rfdc": "^1.3.0", "sass": "^1.66.1", "sass-loader": "^13.3.2", + "vite-plugin-vuetify": "^1.0.2", "vue": "^3.3.4", "vue-meta": "^3.0.0-alpha.7", "vue-router": "^4.2.4", @@ -71,7 +72,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -87,7 +87,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -103,7 +102,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -119,7 +117,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -135,7 +132,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -151,7 +147,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -167,7 +162,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -183,7 +177,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -199,7 +192,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -215,7 +207,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -231,7 +222,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -247,7 +237,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -263,7 +252,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -279,7 +267,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -295,7 +282,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -311,7 +297,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -327,7 +312,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -343,7 +327,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -359,7 +342,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -375,7 +357,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -391,7 +372,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -407,7 +387,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -969,8 +948,6 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.7.1.tgz", "integrity": "sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==", - "optional": true, - "peer": true, "dependencies": { "find-cache-dir": "^3.3.2", "upath": "^2.0.1" @@ -1519,9 +1496,7 @@ "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "optional": true, - "peer": true + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -1580,7 +1555,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -1732,7 +1706,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "devOptional": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2024,8 +1997,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "optional": true, - "peer": true, "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -2571,8 +2542,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -2587,8 +2556,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "optional": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -2653,8 +2620,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.6", @@ -2785,8 +2751,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true, - "peer": true, "engines": { "node": ">=6" } @@ -2807,7 +2771,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, "engines": { "node": ">=8" } @@ -2915,8 +2878,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "optional": true, - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -2928,8 +2889,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "optional": true, - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -2942,8 +2901,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "optional": true, - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -2955,8 +2912,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -2971,8 +2926,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "optional": true, - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -3194,7 +3147,6 @@ "version": "3.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "devOptional": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -3608,8 +3560,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "optional": true, - "peer": true, "engines": { "node": ">=4", "yarn": "*" @@ -3663,7 +3613,6 @@ "version": "4.4.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "devOptional": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -3741,8 +3690,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-1.0.2.tgz", "integrity": "sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==", - "optional": true, - "peer": true, "dependencies": { "@vuetify/loader-shared": "^1.7.1", "debug": "^4.3.3", @@ -4137,154 +4084,132 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, "optional": true }, "@esbuild/android-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, "optional": true }, "@esbuild/android-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, "optional": true }, "@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, "optional": true }, "@esbuild/darwin-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, "optional": true }, "@esbuild/freebsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, "optional": true }, "@esbuild/linux-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, "optional": true }, "@esbuild/linux-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, "optional": true }, "@esbuild/linux-ia32": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, "optional": true }, "@esbuild/linux-loong64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, "optional": true }, "@esbuild/linux-mips64el": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, "optional": true }, "@esbuild/linux-ppc64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, "optional": true }, "@esbuild/linux-riscv64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, "optional": true }, "@esbuild/linux-s390x": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, "optional": true }, "@esbuild/linux-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, "optional": true }, "@esbuild/netbsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, "optional": true }, "@esbuild/openbsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, "optional": true }, "@esbuild/sunos-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, "optional": true }, "@esbuild/win32-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, "optional": true }, "@esbuild/win32-ia32": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, "optional": true }, "@esbuild/win32-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, "optional": true }, "@eslint-community/eslint-utils": { @@ -4725,8 +4650,6 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.7.1.tgz", "integrity": "sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==", - "optional": true, - "peer": true, "requires": { "find-cache-dir": "^3.3.2", "upath": "^2.0.1" @@ -5158,9 +5081,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "optional": true, - "peer": true + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, "concat-map": { "version": "0.0.1", @@ -5210,7 +5131,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, "requires": { "ms": "2.1.2" } @@ -5320,7 +5240,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "devOptional": true, "requires": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", @@ -5537,8 +5456,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "optional": true, - "peer": true, "requires": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -5945,8 +5862,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, - "peer": true, "requires": { "semver": "^6.0.0" }, @@ -5954,9 +5869,7 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "optional": true, - "peer": true + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -6008,8 +5921,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nanoid": { "version": "3.3.6", @@ -6100,9 +6012,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true, - "peer": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "parent-module": { "version": "1.0.1", @@ -6116,8 +6026,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -6174,8 +6083,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "optional": true, - "peer": true, "requires": { "find-up": "^4.0.0" }, @@ -6184,8 +6091,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "optional": true, - "peer": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -6195,8 +6100,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "optional": true, - "peer": true, "requires": { "p-locate": "^4.1.0" } @@ -6205,8 +6108,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, - "peer": true, "requires": { "p-try": "^2.0.0" } @@ -6215,8 +6116,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "optional": true, - "peer": true, "requires": { "p-limit": "^2.2.0" } @@ -6365,7 +6264,6 @@ "version": "3.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "devOptional": true, "requires": { "fsevents": "~2.3.2" } @@ -6618,9 +6516,7 @@ "upath": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", - "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "optional": true, - "peer": true + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==" }, "update-browserslist-db": { "version": "1.0.11", @@ -6650,7 +6546,6 @@ "version": "4.4.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", - "devOptional": true, "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", @@ -6676,8 +6571,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-1.0.2.tgz", "integrity": "sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==", - "optional": true, - "peer": true, "requires": { "@vuetify/loader-shared": "^1.7.1", "debug": "^4.3.3", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index f3ff61ef..5dab932d 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -51,9 +51,11 @@ export default { ...mapState(useAppStore, ['pageTitle', 'showNavBar']), }, watch: { + /* isAuthenticated() { this.handleWebSocket() }, + */ isAuthorizedWebsocketUser() { this.handleWebSocket() }, @@ -64,20 +66,21 @@ export default { async created() { //this.setLoading(true); this.getJwtToken() - //TODO commented out during sprint 1, might need in later sprint...then(() => Promise.all([this.getConfig()])) - .catch((e) => { - if (!e.response || e.response.status !== HttpStatus.UNAUTHORIZED) { - this.logout() - this.$router.replace({ - name: 'error', - query: { message: `500_${e.data || 'ServerError'}` }, - }) - } - }) - .finally(() => { - //this.setLoading(false); - }) - //this.setLoading(false); + //TODO commented out during sprint 1, might need in later sprint if we need an endpoint for config info... + /*.then(() => Promise.all([this.getConfig()])) + .catch((e) => { + if (!e.response || e.response.status !== HttpStatus.UNAUTHORIZED) { + this.logout() + this.$router.replace({ + name: 'error', + query: { message: `500_${e.data || 'ServerError'}` }, + }) + } + }) + .finally(() => { + this.setLoading(false); + }) + this.setLoading(false); */ }, methods: { ...mapActions(useAppStore, ['getConfig']), @@ -116,6 +119,7 @@ export default { width: 100%; z-index: 1002; } + #toTopBtn { opacity: 0.5; } diff --git a/frontend/src/assets/css/main.css b/frontend/src/assets/css/main.css new file mode 100644 index 00000000..54f751e7 --- /dev/null +++ b/frontend/src/assets/css/main.css @@ -0,0 +1,80 @@ +.cc-top-level-card { + padding: 1em; + margin-bottom: 2em; +} + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type='number'] { + -moz-appearance: textfield; +} + +.noticeAlertIcon { + color: #d40d19 !important; +} +.noticeAlert { + font-size: medium; + color: #d40d19; + font-family: 'BCSans', Verdana, Arial, sans-serif; + padding-top: 8px; + padding-bottom: 8px; + background-color: #f2dede; + border: 1px solid #d40d19; +} + +.noticeWaringIcon { + color: #6c4a00 !important; +} +.noticeWarning { + font-size: medium; + color: #6c4a00; + font-family: 'BCSans', Verdana, Arial, sans-serif; + padding-top: 8px; + padding-bottom: 8px; + background-color: #f9f1c6; + border: 1px solid #6c4a00; +} + +.noticeInfoIcon { + color: #003366 !important; +} +.noticeInfo { + font-size: medium; + color: #313132; + font-family: 'BCSans', Verdana, Arial, sans-serif; + padding-top: 8px; + padding-bottom: 8px; + background-color: #c1dcf6; + border: 1px solid #313132; +} + +.disabledButton { + background-color: rgba(0, 0, 0, 0.12) !important; + color: rgba(0, 0, 0, 0.26) !important; + box-shadow: none !important; +} + +.blueButton { + color: white !important; + background-color: #003366 !important; +} + +.stickyNavButtons { + position: sticky; + bottom: 0; + padding: 10px 0px; + background-color: lightgray; + z-index: 2; +} + +@keyframes bone { + to { + background-position: 100% 0; + } +} diff --git a/frontend/src/common/apiService.js b/frontend/src/common/apiService.js index 45346ef3..da4a1624 100644 --- a/frontend/src/common/apiService.js +++ b/frontend/src/common/apiService.js @@ -1,5 +1,6 @@ +import { ApiRoutes, AuthRoutes } from '@/utils/constants' + import AuthService from '@/common/authService' -import { Routes } from '@/utils/constants' import axios from 'axios' // Buffer concurrent requests while refresh token is being acquired @@ -73,13 +74,32 @@ export default { delete apiAxios.defaults.headers.common['Authorization'] } }, - // TODO jstorey not sure if we are going to want to get codes this way... + + async getUserInfo() { + try { + return await apiAxios.get(ApiRoutes.USER) + } catch (e) { + console.log(`Failed to get from Nodejs getUserInfo API - ${e}`) + throw e + } + }, + + async getUserImpersonateInfo(userName) { + try { + return await apiAxios.get(`${ApiRoutes.USER}/${userName}`) + } catch (e) { + console.log(`Failed to get from Nodejs getUserImpersonateInfo API - ${e}`) + throw e + } + }, + + // TODO: consider weather we want to do codes this way when we address code list/description functionality... //getAllActiveInstituteProvinceCodes: getCodes(`${Routes.cache.PROVINCES_URL}?active=true`), //getAllActiveInstituteCountryCodes: getCodes(`${Routes.cache.COUNTRIES_URL}?active=true`), async getConfig() { try { - const response = await apiAxios.get(Routes.CONFIG) + const response = await apiAxios.get(AuthRoutes.CONFIG) return response } catch (e) { console.log(`Failed to do get from Nodejs getConfig API - ${e}`) diff --git a/frontend/src/common/authService.js b/frontend/src/common/authService.js index 4d85fa16..a6249f29 100644 --- a/frontend/src/common/authService.js +++ b/frontend/src/common/authService.js @@ -1,16 +1,15 @@ +import { AuthRoutes } from '@/utils/constants' import axios from 'axios' -import { Routes } from '@/utils/constants' - export default { //Retrieves an auth token from the API endpoint async getAuthToken() { try { - const response = await axios.get(Routes.TOKEN) + const response = await axios.get(AuthRoutes.TOKEN) return response.data } catch (e) { - console.log(`Failed to acquire JWT token - ${e}`) // eslint-disable-line no-console + console.log(`Failed to acquire JWT token - ${e}`) throw e } }, @@ -18,7 +17,7 @@ export default { //Refreshes the users auth token async refreshAuthToken(token) { try { - const response = await axios.post(Routes.REFRESH, { + const response = await axios.post(AuthRoutes.REFRESH, { refreshToken: token, }) @@ -28,7 +27,7 @@ export default { return response.data } catch (e) { - console.log(`Failed to refresh JWT token - ${e}`) // eslint-disable-line no-console + console.log(`Failed to refresh JWT token - ${e}`) throw e } }, diff --git a/frontend/src/components/TheHeader.vue b/frontend/src/components/TheHeader.vue index 2a563916..4c69f58c 100644 --- a/frontend/src/components/TheHeader.vue +++ b/frontend/src/components/TheHeader.vue @@ -15,27 +15,26 @@ - - - + + + - --> -
- Log out
@@ -44,17 +43,21 @@ diff --git a/frontend/src/components/TheModalIdle.vue b/frontend/src/components/TheModalIdle.vue index 4631bd2e..29640105 100644 --- a/frontend/src/components/TheModalIdle.vue +++ b/frontend/src/components/TheModalIdle.vue @@ -1,19 +1,18 @@ @@ -116,4 +110,4 @@ export default { div.v-card-title { padding-bottom: 0px; } - \ No newline at end of file + diff --git a/frontend/src/views/ImpersonateView.vue b/frontend/src/views/ImpersonateView.vue new file mode 100644 index 00000000..47854761 --- /dev/null +++ b/frontend/src/views/ImpersonateView.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index 3a973dec..7e01bbe7 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -6,7 +6,8 @@ - Early Childhood Care - Operating Funding Model + Early Childhood Care - Operating + Funding Model
Welcome
@@ -17,13 +18,17 @@

NOTE: - The information collected through Early Childhood Care - Operating Funding Model is collected under the authority of the + The information collected through Early Childhood Care - Operating Funding Model is collected under the authority + of the Freedom of Information and Protection of Privacy Act (FOIPPA) and the Child Care BC Act - (SBC 2001, c. 4) and will be used for the purpose of administering the Child Care Operating Funding Program, which includes the Child Care Fee Reduction Initiative and the Early Childhood - Educator (ECE) Wage Enhancement for successfully enrolled applicants. Personal information is protected from unauthorized use and disclosure in accordance with FOIPPA. Any questions or - concerns about the collection of this information can be directed to the Director, Child Care Operating Funding Program, PO Box 9965 Stn Prov Govt, Victoria BC V8W 9R4, Phone: in Greater + (SBC 2001, c. 4) and will be used for the purpose of administering the Child Care Operating Funding Program, which + includes the Child Care Fee Reduction Initiative and the Early Childhood + Educator (ECE) Wage Enhancement for successfully enrolled applicants. Personal information is protected from + unauthorized use and disclosure in accordance with FOIPPA. Any questions or + concerns about the collection of this information can be directed to the Director, Child Care Operating Funding + Program, PO Box 9965 Stn Prov Govt, Victoria BC V8W 9R4, Phone: in Greater Victoria: 250 356-6501, outside of Greater Victoria, Toll Free: 1 888 338-6622 (option 2).


@@ -38,7 +43,9 @@ Log in with your primary Business BCeID - BCeID Log In + + BCeID + Log In
@@ -59,7 +66,8 @@ You must register for a Business BCeID before you can log in - it only takes a few minutes. - Register for a BCeID + Register for a BCeID @@ -71,7 +79,7 @@ + + diff --git a/frontend/src/views/SessionExpiredView.vue b/frontend/src/views/SessionExpiredView.vue index bcad5162..254efc0f 100644 --- a/frontend/src/views/SessionExpiredView.vue +++ b/frontend/src/views/SessionExpiredView.vue @@ -12,15 +12,8 @@ Your secure session has ended as a result of inactivity. - Log Inagain to continue. + Log + Inagain to continue. @@ -31,7 +24,7 @@