From 7deb8d577eb60c6a931edc4d52d0f158c43d29f7 Mon Sep 17 00:00:00 2001 From: Nithin Shekar Kuruba <81444731+NithinKuruba@users.noreply.github.com> Date: Fri, 8 Sep 2023 09:11:04 -0700 Subject: [PATCH 1/4] feat: refactor of my dashboard and use react-table (#108) --- app/components/SearchBar.tsx | 36 +++ app/components/Table.tsx | 165 ++++++++++ app/controllers/realm.ts | 21 +- app/html-components/Table.tsx | 4 +- app/package.json | 4 + app/page-partials/my-dashboard/RealmEdit.tsx | 60 ++-- .../my-dashboard/RealmLeftPanel.tsx | 6 +- app/page-partials/my-dashboard/RealmTable.tsx | 151 +++++---- app/pages/api/realms/one.ts | 95 +++--- app/pages/realm/[rid].tsx | 63 ++-- app/yarn.lock | 287 +++++++++++++++++- helm/webapp/migration.sql | 13 +- 12 files changed, 695 insertions(+), 210 deletions(-) create mode 100644 app/components/SearchBar.tsx create mode 100644 app/components/Table.tsx diff --git a/app/components/SearchBar.tsx b/app/components/SearchBar.tsx new file mode 100644 index 0000000..d25b065 --- /dev/null +++ b/app/components/SearchBar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import styled from 'styled-components'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'; + +const Wrapper = styled.div` + width: 100%; + position: relative; +`; + +const Icon = styled.i` + position: absolute; + right: 0.5em; + top: 0.5em; + color: grey; +`; + +const Input = styled.input` + width: 100%; + border: 2px solid #606060; + padding: 0.3em 0.5em; + border-radius: 0.25em; +`; + +function SearchBar(props: any) { + return ( + + + + + + + ); +} + +export default SearchBar; diff --git a/app/components/Table.tsx b/app/components/Table.tsx new file mode 100644 index 0000000..8198336 --- /dev/null +++ b/app/components/Table.tsx @@ -0,0 +1,165 @@ +import { + getCoreRowModel, + useReactTable, + flexRender, + getPaginationRowModel, + FilterFn, + getFilteredRowModel, +} from '@tanstack/react-table'; +import type { ColumnDef } from '@tanstack/react-table'; +import Pagination from 'react-bootstrap/Pagination'; +import styled from 'styled-components'; +import Select from 'react-select'; +import Grid from '@button-inc/bcgov-theme/Grid'; +import StyledTable from 'html-components/Table'; +import { useState } from 'react'; +import { rankItem } from '@tanstack/match-sorter-utils'; +import SearchBar from './SearchBar'; + +const StyledPagination = styled(Pagination)` + margin: 0 !important; + & li { + margin: 0 !important; + } +`; + +const PageInfo = styled.li` + padding-left: 5px; + line-height: 40px; +`; + +const StyledSelect = styled(Select)` + width: 150px; + display: inline-block; +`; + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value); + + // Store the itemRank info + addMeta({ + itemRank, + }); + + // Return if the item should be filtered in/out + return itemRank.passed; +}; + +interface ReactTableProps { + data: T[]; + columns: ColumnDef[]; + noDataFoundMessage: string; +} +const Table = ({ data, columns, noDataFoundMessage = 'no data found.' }: ReactTableProps) => { + const numOfItemsPerPage = () => { + const options = [5, 10, 15, 20, 25, 30].map((val) => { + return { value: val, label: `${val} per page` }; + }); + + return options; + }; + + const [globalFilter, setGlobalFilter] = useState(''); + + const table = useReactTable({ + data, + columns, + initialState: { + pagination: { + pageSize: 5, + }, + }, + state: { + globalFilter, + }, + onGlobalFilterChange: setGlobalFilter, + globalFilterFn: fuzzyFilter, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + }); + + return ( + <> + setGlobalFilter(String(v.target.value))} + placeholder="Search all columns..." + style={{ marginTop: '10px', maxWidth: '25%' }} + /> + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + {noDataFoundMessage}{' '} + + )} + + + + + + + { + table.previousPage(); + }} + > + Previous + + { + table.nextPage(); + }} + > + Next + + {`${table.getState().pagination.pageIndex + 1} of ${table.getPageCount()}`} + + + + + { + table.setPageSize(Number(e.value)); + }} + > + + + + + > + ); +}; + +export default Table; diff --git a/app/controllers/realm.ts b/app/controllers/realm.ts index 0d42c2b..5618a0f 100644 --- a/app/controllers/realm.ts +++ b/app/controllers/realm.ts @@ -16,7 +16,6 @@ export async function getAllowedRealms(session: any) { id, realm, product_name, - openshift_namespace, product_owner_email, product_owner_idir_userid, technical_contact_email, @@ -27,8 +26,10 @@ export async function getAllowedRealms(session: any) { division, branch, created_at, - updated_at - FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC + updated_at, + rc_channel, + rc_channel_owned_by + FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(second_technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC `, [username], ); @@ -41,18 +42,10 @@ export async function getAllowedRealms(session: any) { if (kcAdminClient) { for (let x = 0; x < result?.rows.length; x++) { const realm = result?.rows[x]; - const [realmData, poName, techName, secTechName] = await Promise.all([ - kcCore.getRealm(realm.realm), - kcCore.getIdirUserName(realm.product_owner_idir_userid), - kcCore.getIdirUserName(realm.technical_contact_idir_userid), - kcCore.getIdirUserName(realm.second_technical_contact_idir_userid), - ]); - - realm.product_owner_name = poName; - realm.technical_contact_name = techName; - realm.second_technical_contact_name = secTechName; - realm.displayName = realmData?.displayName || ''; + const [realmData] = await Promise.all([kcCore.getRealm(realm.realm)]); realm.idps = realmData?.identityProviders?.map((v) => v.displayName || v.alias) || []; + const distinctProviders = new Set(realmData?.identityProviders?.map((v) => v.providerId) || []); + realm.protocol = Array.from(distinctProviders); } } } diff --git a/app/html-components/Table.tsx b/app/html-components/Table.tsx index daa1935..4a5536b 100644 --- a/app/html-components/Table.tsx +++ b/app/html-components/Table.tsx @@ -7,7 +7,8 @@ const Table = styled.table` box-shadow: none; text-align: left; border-collapse: separate; - border-spacing: 0 5px; + border-spacing: 0; + table-layout: fixed; & thead { font-size: 12px; @@ -38,6 +39,7 @@ const Table = styled.table` & th, & td { border: none; + word-wrap: break-word; } `; diff --git a/app/package.json b/app/package.json index 575ea21..1ecbf7c 100644 --- a/app/package.json +++ b/app/package.json @@ -14,6 +14,8 @@ "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.1.18", + "@tanstack/match-sorter-utils": "^8.8.4", + "@tanstack/react-table": "^8.9.7", "axios": "^0.27.2", "bootstrap": "^5.1.3", "easy-soap-request": "^4.7.0", @@ -24,6 +26,7 @@ "lodash.get": "^4.4.2", "lodash.map": "^4.6.0", "next": "12.1.6", + "node-cron": "^3.0.2", "pg": "^8.7.3", "pg-format": "^1.0.4", "qs": "^6.10.3", @@ -32,6 +35,7 @@ "react-dom": "18.1.0", "react-hook-form": "^7.31.3", "react-loader-spinner": "^6.0.0-0", + "react-select": "^5.7.4", "react-typography": "^0.16.20", "semantic-ui-react": "^2.1.3", "store2": "^2.13.2", diff --git a/app/page-partials/my-dashboard/RealmEdit.tsx b/app/page-partials/my-dashboard/RealmEdit.tsx index e2a99f5..300913c 100644 --- a/app/page-partials/my-dashboard/RealmEdit.tsx +++ b/app/page-partials/my-dashboard/RealmEdit.tsx @@ -180,14 +180,14 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { Realm Name: {realm.realm} - Realm Descriptive Name - This name is the name you've configured in custom realm setting + Realm + Name of the realm you've configured in custom realm setting Product Name* @@ -196,19 +196,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { - - Openshift Namespace* - - If this realm is tied to OS, provide the license plate, if this realm is shared with multiple products type{' '} - Various. If OS is not applicable, please help type NA - - - {loading ? ( @@ -283,7 +271,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { type="text" placeholder="Product Owner Email" disabled={!isAdmin && !isPO} - {...register('product_owner_email', { required: false, pattern: /^\S+@\S+$/i })} + {...register('product_owner_email', { required: true, pattern: /^\S+@\S+$/i })} /> Product Owner Idir @@ -316,7 +304,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { Technical Contact Idir @@ -339,7 +327,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { Second Technical Contact Idir(optional) @@ -355,29 +343,21 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { /> {isAdmin && ( <> - {/* ADMIN NOTE 1 */} - Admin Note 1 - Rocket.Chat Channel + - {/* ADMIN NOTE 2 */} - Admin Note 2 - - {/* Next Step */} - Next Step - Rocket.Chat Channel Owner + {/* Material To Send */} Material To Send diff --git a/app/page-partials/my-dashboard/RealmLeftPanel.tsx b/app/page-partials/my-dashboard/RealmLeftPanel.tsx index aa42f9e..0ca93cc 100644 --- a/app/page-partials/my-dashboard/RealmLeftPanel.tsx +++ b/app/page-partials/my-dashboard/RealmLeftPanel.tsx @@ -11,12 +11,12 @@ interface Props { } function RealmLeftPanel({ realms, onEditClick, onCancel }: Props) { - const [tab, setTab] = useState('dashbaord'); + const [tab, setTab] = useState('dashboard'); return ( <> - setTab('dashbaord')}> + setTab('dashboard')}> My Dashboard - {tab === 'dashbaord' ? : } + {tab === 'dashboard' ? : } > ); } diff --git a/app/page-partials/my-dashboard/RealmTable.tsx b/app/page-partials/my-dashboard/RealmTable.tsx index bc130ab..26b79f4 100644 --- a/app/page-partials/my-dashboard/RealmTable.tsx +++ b/app/page-partials/my-dashboard/RealmTable.tsx @@ -1,16 +1,7 @@ -import React, { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; -import Loader from 'react-loader-spinner'; -import styled from 'styled-components'; -import StyledTable from 'html-components/Table'; -import ResponsiveContainer, { MediaRule } from 'components/ResponsiveContainer'; -import CenteredModal from 'components/CenteredModal'; +import React from 'react'; import Button from '@button-inc/bcgov-theme/Button'; -import Modal from '@button-inc/bcgov-theme/Modal'; -import Tabs from 'components/Tabs'; -import RadioGroup from 'components/RadioGroup'; -import { getRealmProfiles } from 'services/realm'; import { RealmProfile } from 'types/realm-profile'; +import Table from 'components/Table'; interface Props { realms: RealmProfile[]; @@ -19,46 +10,104 @@ interface Props { function RealmTable({ realms, onEditClick }: Props) { return ( - - - - Project Name - Openshift Namespace - Product Owner - Technical Owner - Second Technical Owner (Optional) - Keycloak Realm Name - IDPs Connected To - - - - - {realms.length > 0 ? ( - realms.map((realm) => { - return ( - - {realm.product_name} - {realm.openshift_namespace} - {realm.product_owner_name} - {realm.technical_contact_name} - {realm.second_technical_contact_name} - {realm.realm} - {realm.idps?.join('/')} - - onEditClick(realm.id)}> - Edit - - - - ); - }) - ) : ( - - No realms found. - - )} - - + + { + return { + realm: r.realm, + productName: r.product_name, + idps: r.idps.join(', '), + protocol: r.protocol.join(', '), + productOwnerEmail: r.product_owner_email, + productOwnerIdirUserId: r.product_owner_idir_userid, + technicalContactEmail: r.technical_contact_email, + technicalContactIdirUserId: r.technical_contact_idir_userid, + secondTechnicalContactEmail: r.second_technical_contact_email, + secondTechnicalContactIdirUserId: r.second_technical_contact_idir_userid, + rcChannel: r.rc_channel, + rcChannelOwnedBy: r.rc_channel_owned_by, + actions: ( + onEditClick(r.id)} + > + Edit{' '} + + ), + }; + })} + columns={[ + { + header: 'Keycloak Realm Name', + cell: (row) => row.renderValue(), + accessorKey: 'realm', + }, + { + header: 'Product Name', + cell: (row) => row.renderValue(), + accessorKey: 'productName', + }, + { + header: 'Identity Provider', + cell: (row) => row.renderValue(), + accessorKey: 'idps', + }, + { + header: 'Protocol', + cell: (row) => row.renderValue(), + accessorKey: 'protocol', + }, + { + header: 'Product Owner Email', + cell: (row) => row.renderValue(), + accessorKey: 'productOwnerEmail', + }, + { + header: 'Product Owner IDIR', + cell: (row) => row.renderValue(), + accessorKey: 'productOwnerIdirUserId', + }, + { + header: 'Technical Owner Email', + cell: (row) => row.renderValue(), + accessorKey: 'technicalContactEmail', + }, + { + header: 'Technical Owner IDIR', + cell: (row) => row.renderValue(), + accessorKey: 'technicalContactIdirUserId', + }, + { + header: 'Second Technical Owner Email', + cell: (row) => row.renderValue(), + accessorKey: 'secondTechnicalContactEmail', + }, + { + header: 'Second Technical Owner IDIR', + cell: (row) => row.renderValue(), + accessorKey: 'secondTechnicalContactIdirUserId', + }, + { + header: 'Rocket Chat Channel', + cell: (row) => row.renderValue(), + accessorKey: 'rcChannel', + }, + { + header: 'Rocket Chat Channel Owner', + cell: (row) => row.renderValue(), + accessorKey: 'rcChannelOwnedBy', + }, + { + header: 'Actions', + cell: (row) => row.renderValue(), + accessorKey: 'actions', + }, + ]} + noDataFoundMessage={'No realms found.'} + /> + ); } diff --git a/app/pages/api/realms/one.ts b/app/pages/api/realms/one.ts index 071224f..c5f977a 100644 --- a/app/pages/api/realms/one.ts +++ b/app/pages/api/realms/one.ts @@ -35,7 +35,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< id, realm, product_name, - openshift_namespace, product_owner_email, product_owner_idir_userid, technical_contact_email, @@ -46,8 +45,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< division, branch, created_at, - updated_at - FROM rosters WHERE id=$1 AND (LOWER(technical_contact_idir_userid)=LOWER($2) OR LOWER(product_owner_idir_userid)=LOWER($2)) + updated_at, + rc_channel, + rc_channel_owned_by + FROM rosters WHERE id=$1 AND (LOWER(technical_contact_idir_userid)=LOWER($2) OR LOWER(second_technical_contact_idir_userid)=LOWER($2) OR LOWER(product_owner_idir_userid)=LOWER($2)) `, [id, username], ); @@ -55,16 +56,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const realm = result?.rows.length > 0 ? result?.rows[0] : null; if (realm) { - const [realmData, poName, techName] = await Promise.all([ - kcCore.getRealm(realm.realm), - kcCore.getIdirUserName(realm.product_owner_idir_userid), - kcCore.getIdirUserName(realm.technical_contact_idir_userid), - ]); - - realm.product_owner_name = poName; - realm.technical_contact_name = techName; - realm.displayName = realmData?.displayName || ''; + const [realmData] = await Promise.all([kcCore.getRealm(realm.realm)]); realm.idps = realmData?.identityProviders?.map((v) => v.displayName || v.alias) || []; + const distinctProviders = new Set(realmData?.identityProviders?.map((v) => v.providerId) || []); + realm.protocol = Array.from(distinctProviders); } return res.send(realm); @@ -77,14 +72,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< branch, branch_other, product_name, - openshift_namespace, technical_contact_email, product_owner_email, technical_contact_idir_userid, product_owner_idir_userid, - admin_note_1, - admin_note_2, - next_steps, + rc_channel, + rc_channel_owned_by, material_to_send, second_technical_contact_email, second_technical_contact_idir_userid, @@ -103,27 +96,24 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< UPDATE rosters SET product_name=$2, - openshift_namespace=$3, - technical_contact_email=$4, - product_owner_email=$5, - technical_contact_idir_userid=$6, - product_owner_idir_userid=$7, - ministry=$8, - division=$9, - branch=$10, - admin_note_1=$11, - admin_note_2=$12, - next_steps=$13, - material_to_send=$14, - second_technical_contact_email=$15, - second_technical_contact_idir_userid=$16, + technical_contact_email=$3, + product_owner_email=$4, + technical_contact_idir_userid=$5, + product_owner_idir_userid=$6, + ministry=$7, + division=$8, + branch=$9, + rc_channel=$10, + rc_channel_owned_by=$11, + material_to_send=$12, + second_technical_contact_email=$13, + second_technical_contact_idir_userid=$14, updated_at=now() WHERE id=$1 RETURNING *`, [ id, product_name, - openshift_namespace, technical_contact_email, product_owner_email, technical_contact_idir_userid, @@ -131,9 +121,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< _ministry, _division, _branch, - admin_note_1, - admin_note_2, - next_steps, + rc_channel, + rc_channel_owned_by, material_to_send, second_technical_contact_email, second_technical_contact_idir_userid, @@ -145,15 +134,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< UPDATE rosters SET product_name=$3, - openshift_namespace=$4, - technical_contact_email=$5, - product_owner_email=$6, - technical_contact_idir_userid=$7, - ministry=$8, - division=$9, - branch=$10, - second_technical_contact_email=$11, - second_technical_contact_idir_userid=$12, + technical_contact_email=$4, + product_owner_email=$5, + technical_contact_idir_userid=$6, + ministry=$7, + division=$8, + branch=$9, + second_technical_contact_email=$10, + second_technical_contact_idir_userid=$11, updated_at=now() WHERE id=$1 AND LOWER(product_owner_idir_userid)=LOWER($2) RETURNING *`, @@ -161,7 +149,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< id, username, product_name, - openshift_namespace, technical_contact_email, product_owner_email, technical_contact_idir_userid, @@ -178,13 +165,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< UPDATE rosters SET product_name=$3, - openshift_namespace=$4, - technical_contact_email=$5, - ministry=$6, - division=$7, - branch=$8, - second_technical_contact_email=$9, - second_technical_contact_idir_userid=$10, + technical_contact_email=$4, + ministry=$5, + division=$6, + branch=$7, + second_technical_contact_email=$8, + second_technical_contact_idir_userid=$9, updated_at=now() WHERE id=$1 AND LOWER(technical_contact_idir_userid)=LOWER($2) RETURNING *`, @@ -192,7 +178,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< id, username, product_name, - openshift_namespace, technical_contact_email, _ministry, _division, @@ -207,9 +192,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< sendUpdateEmail(realm, session); if (!isAdmin && realm) { - delete realm.admin_note_1; - delete realm.admin_note_2; - delete realm.next_steps; + delete realm.protocol; + delete realm.rc_channel; + delete realm.rc_channel_owned_by; delete realm.material_to_send; } diff --git a/app/pages/realm/[rid].tsx b/app/pages/realm/[rid].tsx index c24a0a7..9250afb 100644 --- a/app/pages/realm/[rid].tsx +++ b/app/pages/realm/[rid].tsx @@ -114,24 +114,6 @@ function EditRealm({ alert, currentUser }: Props) { return ( - Realm Name - - - - Realm Descriptive Name* - - - Product Name* @@ -141,15 +123,6 @@ function EditRealm({ alert, currentUser }: Props) { {...register('product_name', { required: false, minLength: 2, maxLength: 1000 })} /> - - Openshift Namespace* - - - Product Owner Email* @@ -183,6 +156,42 @@ function EditRealm({ alert, currentUser }: Props) { disabled {...register('technical_contact_idir_userid', { required: false, minLength: 2, maxLength: 1000 })} /> + + + Second Technical Contact Email* + + + + Second Technical Contact Idir + + + + Rocket.Chat Channel* + + + + + Rocket.Chat Channel Owner* + + + {realm && Last Updated: {new Date(realm.updated_at).toLocaleString()}} diff --git a/app/yarn.lock b/app/yarn.lock index ef533d5..c3ab39a 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -55,6 +55,13 @@ dependencies: "@babel/types" "^7.15.4" +"@babel/helper-module-imports@^7.16.7": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + "@babel/helper-split-export-declaration@^7.15.4": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" @@ -62,11 +69,21 @@ dependencies: "@babel/types" "^7.15.4" +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz#601fa28e4cc06786c18912dca138cec73b882044" + integrity sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ== + "@babel/highlight@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" @@ -96,6 +113,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.13.16", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": version "7.16.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" @@ -142,6 +166,15 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@babel/types@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.15.tgz#266cb21d2c5fd0b3931e7a91b6dd72d2f617d282" + integrity sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.15" + to-fast-properties "^2.0.0" + "@bcgov/bc-sans@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@bcgov/bc-sans/-/bc-sans-1.0.1.tgz#8b622032465463934319771ea92fa3df74e6c9d3" @@ -183,6 +216,23 @@ html-react-parser "^1.2.6" react-syntax-highlighter "^15.4.3" +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -193,6 +243,17 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" +"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + "@emotion/core@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" @@ -219,6 +280,11 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + "@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6", "@emotion/is-prop-valid@^0.8.8": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -243,6 +309,25 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.8.1": + version "11.11.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" + integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": version "0.11.16" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" @@ -254,11 +339,27 @@ "@emotion/utils" "0.11.3" csstype "^2.5.7" +"@emotion/serialize@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" + integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + "@emotion/styled-base@^10.0.27": version "10.0.31" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" @@ -287,16 +388,36 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + "@emotion/utils@0.11.3": version "0.11.3" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + "@emotion/weak-memoize@0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -312,6 +433,26 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" + integrity sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ== + dependencies: + "@floating-ui/utils" "^0.1.1" + +"@floating-ui/dom@^1.0.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.1.tgz#88b70defd002fe851f17b4a25efb2d3c04d7a8d7" + integrity sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw== + dependencies: + "@floating-ui/core" "^1.4.1" + "@floating-ui/utils" "^0.1.1" + +"@floating-ui/utils@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83" + integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw== + "@fluentui/react-component-event-listener@~0.63.0": version "0.63.0" resolved "https://registry.yarnpkg.com/@fluentui/react-component-event-listener/-/react-component-event-listener-0.63.0.tgz#d595890f39a97939e20624778325a60196b2bbe4" @@ -750,6 +891,25 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@tanstack/match-sorter-utils@^8.8.4": + version "8.8.4" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" + integrity sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw== + dependencies: + remove-accents "0.4.2" + +"@tanstack/react-table@^8.9.7": + version "8.9.7" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.9.7.tgz#ae32e7c93b6392927208f63dbc89d409d19b4e4f" + integrity sha512-UKUekM8JNUyWbjT1q3s1GpH5OtBL9mJ4258Il23fsahvkh3ou9TuFVmqI0/UPiFROgHkRlCBDNPUhcsC9YPFgg== + dependencies: + "@tanstack/table-core" "8.9.7" + +"@tanstack/table-core@8.9.7": + version "8.9.7" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.9.7.tgz#54c08abf10b98070efb1cd760db3859669b802a3" + integrity sha512-lkhVcGDxa9GSoDFPkplPDvzsiUACPZrxT3U1edPs0DCMKFhBDgZ7d1DPd7cqHH0JoybfbQ/qiTQYOQBg8sinJg== + "@types/color-convert@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22" @@ -890,6 +1050,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.0": + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" + integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== + dependencies: + "@types/react" "*" + "@types/react-transition-group@^4.4.4": version "4.4.4" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e" @@ -1189,6 +1356,15 @@ babel-plugin-macros@^2.0.0: cosmiconfig "^6.0.0" resolve "^1.12.0" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + "babel-plugin-styled-components@>= 1.12.0": version "1.13.3" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz#1f1cb3927d4afa1e324695c78f690900e3d075bc" @@ -1409,6 +1585,17 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-react-context@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" @@ -2290,7 +2477,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -2415,6 +2602,13 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-core-module@^2.2.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" @@ -2818,6 +3012,11 @@ markdown-to-jsx@^7.1.3: resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.3.tgz#f00bae66c0abe7dd2d274123f84cb6bd2a2c7c6a" integrity sha512-jtQ6VyT7rMT5tPV0g2EJakEnXLiPksnvlYtwQsVVZ611JsWGN8bQ1tVSDX4s6JllfEH6wmsYxNjTUAMrPmNA8w== +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -2934,6 +3133,13 @@ next@12.1.6: "@next/swc-win32-ia32-msvc" "12.1.6" "@next/swc-win32-x64-msvc" "12.1.6" +node-cron@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.2.tgz#bb0681342bd2dfb568f28e464031280e7f06bd01" + integrity sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ== + dependencies: + uuid "8.3.2" + object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -3262,6 +3468,15 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" +prop-types@^15.6.0, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -3271,15 +3486,6 @@ prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" -prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - property-information@^5.0.0: version "5.6.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" @@ -3418,6 +3624,21 @@ react-property@2.0.0: resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136" integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw== +react-select@^5.7.4: + version "5.7.4" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.4.tgz#d8cad96e7bc9d6c8e2709bdda8f4363c5dd7ea7d" + integrity sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@floating-ui/dom" "^1.0.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^6.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + use-isomorphic-layout-effect "^1.1.2" + react-sizeme@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4" @@ -3459,6 +3680,16 @@ react-textarea-autosize@^8.3.0: use-composed-ref "^1.0.0" use-latest "^1.0.0" +react-transition-group@^4.3.0: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-transition-group@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" @@ -3504,6 +3735,11 @@ regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -3518,6 +3754,11 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -3536,6 +3777,15 @@ resolve@^1.12.0, resolve@^1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.19.0: + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -3841,6 +4091,11 @@ styled-tools@^1.7.2: resolved "https://registry.yarnpkg.com/styled-tools/-/styled-tools-1.7.2.tgz#a8f71198535cf785d7db0927cc1c1b88337c4440" integrity sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -4027,6 +4282,11 @@ use-isomorphic-layout-effect@^1.0.0: resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225" integrity sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ== +use-isomorphic-layout-effect@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + use-latest@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" @@ -4039,6 +4299,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -4107,7 +4372,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/helm/webapp/migration.sql b/helm/webapp/migration.sql index dbc3e6b..05ef168 100644 --- a/helm/webapp/migration.sql +++ b/helm/webapp/migration.sql @@ -2,26 +2,23 @@ create table if not exists public.rosters ( id serial not null, realm varchar(1000), product_name varchar(1000), - openshift_namespace varchar(1000), product_owner_email varchar(1000), product_owner_idir_userid varchar(1000), technical_contact_email varchar(1000), technical_contact_idir_userid varchar(1000), - admin_note_1 text, - admin_note_2 text, + second_technical_contact_email varchar(1000), + second_technical_contact_idir_userid varchar(1000), + rc_channel text, ministry varchar(1000), division varchar(1000), branch varchar(1000), created_at timestamp with time zone default current_timestamp, updated_at timestamp with time zone default current_timestamp, + material_to_send text, + rc_channel_owned_by varchar(1000), primary key(id) ); -alter table public.rosters add column if not exists next_steps text; -alter table public.rosters add column if not exists material_to_send text; -alter table public.rosters add column if not exists second_technical_contact_email varchar(1000); -alter table public.rosters add column if not exists second_technical_contact_idir_userid varchar(1000); - create table if not exists public.surveys_1 ( idir_userid varchar(1000), contact_email varchar(1000), From 87afec61cb826a0485717a00318f34129ea49c85 Mon Sep 17 00:00:00 2001 From: Nithin Shekar Kuruba <81444731+NithinKuruba@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:59:56 -0700 Subject: [PATCH 2/4] feat: refactor helm chart for applying in gold (#109) * feat: refactor helm chart for applying in gold * feat: use official spilo image * fix: pre-commit issue with end of line --- .github/workflows/publish-image.yml | 7 ---- .tool-versions | 2 +- app/.env.example | 8 ++-- app/next.config.js | 9 +++-- .../my-dashboard/DuplicateIDIR.tsx | 2 +- app/page-partials/my-dashboard/RealmIDIR.tsx | 2 +- app/page-partials/my-dashboard/RealmURIs.tsx | 12 +++--- app/utils/ches.ts | 9 +++++ helm/webapp/Chart.yaml | 2 +- helm/webapp/templates/deployment.yaml | 6 ++- helm/webapp/values-c6af30-dev.yaml | 33 +++++++++++++++++ helm/webapp/values-c6af30-prod.yaml | 37 +++++++++++++++++++ helm/webapp/values.yaml | 2 +- 13 files changed, 104 insertions(+), 27 deletions(-) create mode 100644 helm/webapp/values-c6af30-dev.yaml create mode 100644 helm/webapp/values-c6af30-prod.yaml diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml index 420562b..80b12bf 100644 --- a/.github/workflows/publish-image.yml +++ b/.github/workflows/publish-image.yml @@ -86,10 +86,3 @@ jobs: with: bump_version_scheme: patch tag_prefix: v - - - name: Check Output Parameters - if: github.ref == 'refs/heads/main' - run: | - echo "Got tag name ${{ steps.release.outputs.tag_name }}" - echo "Got release version ${{ steps.release.outputs.version }}" - echo "Upload release artifacts to ${{ steps.release.outputs.upload_url }}" \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index 497c659..5fd41e9 100644 --- a/.tool-versions +++ b/.tool-versions @@ -2,4 +2,4 @@ nodejs 16.14.0 yarn 1.22.4 python 3.11.0 postgres 14.1 -helm 3.2.4 +helm 3.10.2 diff --git a/app/.env.example b/app/.env.example index 24a1cf3..86b6c55 100644 --- a/app/.env.example +++ b/app/.env.example @@ -1,4 +1,4 @@ -SSO_URL=https://dev.oidc.gov.bc.ca/auth/realms/onestopauth +SSO_URL=https://dev.loginproxy.gov.bc.ca/auth/realms/standard SSO_CLIENT_ID=sso-requests SSO_CLIENT_SECRET=sso-requests-secret SSO_REDIRECT_URI=http://localhost:3000 @@ -6,13 +6,13 @@ SSO_LOGOUT_REDIRECT_URI=http://localhost:3000 SSO_AUTHORIZATION_RESPONSE_TYPE=code SSO_AUTHORIZATION_SCOPE=openid SSO_TOKEN_GRANT_TYPE=authorization_code -DEV_KC_URL=https://dev.oidc.gov.bc.ca +DEV_KC_URL=https://dev.loginproxy.gov.bc.ca DEV_KC_CLIENT_ID=script-cli DEV_KC_CLIENT_SECRET= -TEST_KC_URL=https://dev.oidc.gov.bc.ca +TEST_KC_URL=https://dev.loginproxy.gov.bc.ca TEST_KC_CLIENT_ID=script-cli TEST_KC_CLIENT_SECRET= -PROD_KC_URL=https://dev.oidc.gov.bc.ca +PROD_KC_URL=https://dev.loginproxy.gov.bc.ca PROD_KC_CLIENT_ID=script-cli PROD_KC_CLIENT_SECRET= JWT_SECRET=verysecuresecret diff --git a/app/next.config.js b/app/next.config.js index 3ada74c..d3eed09 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -24,21 +24,22 @@ module.exports = { pg_database: process.env.PGDATABASE || 'realm_profile', pg_ssl: process.env.PGSSL === 'true', - dev_kc_url: process.env.DEV_KC_URL || 'https://dev.oidc.gov.bc.ca', + dev_kc_url: process.env.DEV_KC_URL || 'https://dev.loginproxy.gov.bc.ca', dev_kc_client_id: process.env.DEV_KC_CLIENT_ID || 'script-cli', dev_kc_client_secret: process.env.DEV_KC_CLIENT_SECRET, - test_kc_url: process.env.TEST_KC_URL || 'https://dev.oidc.gov.bc.ca', + test_kc_url: process.env.TEST_KC_URL || 'https://dev.loginproxy.gov.bc.ca', test_kc_client_id: process.env.TEST_KC_CLIENT_ID || 'script-cli', test_kc_client_secret: process.env.TEST_KC_CLIENT_SECRET, - prod_kc_url: process.env.PROD_KC_URL || 'https://dev.oidc.gov.bc.ca', + prod_kc_url: process.env.PROD_KC_URL || 'https://dev.loginproxy.gov.bc.ca', prod_kc_client_id: process.env.PROD_KC_CLIENT_ID || 'script-cli', prod_kc_client_secret: process.env.PROD_KC_CLIENT_SECRET, ches_api_endpoint: process.env.CHES_API_ENDPOINT || 'https://ches.api.gov.bc.ca/api/v1/email', ches_token_endpoint: - process.env.CHES_TOKEN_ENDPOINT || 'https://dev.oidc.gov.bc.ca/auth/realms/xxxxxxx/protocol/openid-connect/token', + process.env.CHES_TOKEN_ENDPOINT || + 'https://dev.loginproxy.gov.bc.ca/auth/realms/xxxxxxx/protocol/openid-connect/token', ches_username: process.env.CHES_USERNAME, ches_password: process.env.CHES_PASSWORD, diff --git a/app/page-partials/my-dashboard/DuplicateIDIR.tsx b/app/page-partials/my-dashboard/DuplicateIDIR.tsx index 496b94c..98f2f25 100644 --- a/app/page-partials/my-dashboard/DuplicateIDIR.tsx +++ b/app/page-partials/my-dashboard/DuplicateIDIR.tsx @@ -91,7 +91,7 @@ const TableContent = ({ {info.affected.map((realm: string) => ( {realm} diff --git a/app/page-partials/my-dashboard/RealmIDIR.tsx b/app/page-partials/my-dashboard/RealmIDIR.tsx index 8a31a86..a1da35c 100644 --- a/app/page-partials/my-dashboard/RealmIDIR.tsx +++ b/app/page-partials/my-dashboard/RealmIDIR.tsx @@ -168,7 +168,7 @@ function RealmIDIR({ realm }: Props) { {result.affected.map((realmName) => ( {`Realm Link: ${realmName}`} diff --git a/app/page-partials/my-dashboard/RealmURIs.tsx b/app/page-partials/my-dashboard/RealmURIs.tsx index 7b3a41f..d0dac94 100644 --- a/app/page-partials/my-dashboard/RealmURIs.tsx +++ b/app/page-partials/my-dashboard/RealmURIs.tsx @@ -19,23 +19,23 @@ function RealmURIs({ realm }: Props) { <> Development {`https://dev.oidc.gov.bc.ca/auth/admin/${realm.realm}/console`} + >{`https://dev.loginproxy.gov.bc.ca/auth/admin/${realm.realm}/console`} Test {`https://test.oidc.gov.bc.ca/auth/admin/${realm.realm}/console`} + >{`https://test.loginproxy.gov.bc.ca/auth/admin/${realm.realm}/console`} Production {`https://oidc.gov.bc.ca/auth/admin/${realm.realm}/console`} + >{`https://loginproxy.gov.bc.ca/auth/admin/${realm.realm}/console`} > ); } diff --git a/app/utils/ches.ts b/app/utils/ches.ts index 3a5b3bf..7a990bb 100644 --- a/app/utils/ches.ts +++ b/app/utils/ches.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import url from 'url'; import getConfig from 'next/config'; +import https from 'https'; const { serverRuntimeConfig = {} } = getConfig() || {}; const { ches_api_endpoint, ches_token_endpoint, ches_username, ches_password } = serverRuntimeConfig; @@ -19,10 +20,18 @@ interface EmailOptions { tag?: string; } +const httpsAgent = new https.Agent({ + rejectUnauthorized: false, +}); + const fetchChesToken = async () => { const params = new url.URLSearchParams({ grant_type: 'client_credentials' }); try { const { data } = await axios.post(ches_token_endpoint, params.toString(), { + headers: { + 'Accept-Encoding': 'application/json', + }, + httpsAgent, auth: { username: ches_username, password: ches_password, diff --git a/helm/webapp/Chart.yaml b/helm/webapp/Chart.yaml index ed72709..4f09c8f 100644 --- a/helm/webapp/Chart.yaml +++ b/helm/webapp/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 name: realm-registry -version: 0.1.0 +version: 0.2.0 appVersion: 0.1.0 description: Nextjs application to manage SSO keycloak custom realm profiles dependencies: diff --git a/helm/webapp/templates/deployment.yaml b/helm/webapp/templates/deployment.yaml index 19f5005..50251dd 100644 --- a/helm/webapp/templates/deployment.yaml +++ b/helm/webapp/templates/deployment.yaml @@ -23,7 +23,7 @@ spec: vault.hashicorp.com/agent-inject-token: 'true' vault.hashicorp.com/agent-init-first: 'true' vault.hashicorp.com/agent-pre-populate: 'true' - vault.hashicorp.com/auth-path: auth/k8s-silver + vault.hashicorp.com/auth-path: auth/k8s-gold vault.hashicorp.com/namespace: platform-services vault.hashicorp.com/role: {{ .Values.vault.vaultSecretEngine }} vault.hashicorp.com/agent-inject-secret-postgres: {{ .Values.vault.vaultSecretEngine }}/{{ .Values.vault.postgresSecret }} @@ -49,6 +49,10 @@ spec: export TEST_KC_CLIENT_ID="{{ .Data.data.TEST_KC_CLIENT_ID}}" export TEST_KC_CLIENT_SECRET="{{ .Data.data.TEST_KC_CLIENT_SECRET}}" export TEST_KC_URL="{{ .Data.data.TEST_KC_URL}}" + export CHES_API_ENDPOINT="{{ .Data.data.CHES_API_ENDPOINT }}" + export CHES_PASSWORD="{{ .Data.data.CHES_PASSWORD }}" + export CHES_TOKEN_ENDPOINT="{{ .Data.data.CHES_TOKEN_ENDPOINT }}" + export CHES_USERNAME="{{ .Data.data.CHES_USERNAME }}" {{- end }}`}} spec: initContainers: diff --git a/helm/webapp/values-c6af30-dev.yaml b/helm/webapp/values-c6af30-dev.yaml new file mode 100644 index 0000000..9bb69da --- /dev/null +++ b/helm/webapp/values-c6af30-dev.yaml @@ -0,0 +1,33 @@ +image: + tag: dev + +route: + host: realm-registry-sandbox.apps.gold.devops.gov.bc.ca + +sso: + url: https://dev.loginproxy.gov.bc.ca/auth/realms/standard + redirectUri: http://realm-registry-sandbox.apps.gold.devops.gov.bc.ca/oidc/keycloak + logoutRedirectUri: http://realm-registry-sandbox.apps.gold.devops.gov.bc.ca + +env: + APP_ENV: 'development' + SECURE_HEADERS: 'true' + IDIR_JWKS_URI: https://dev.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs + IDIR_ISSUER: https://dev.loginproxy.gov.bc.ca/auth/realms/standard + IDIR_AUDIENCE: css-app-in-gold-4128 + CHES_API_ENDPOINT: https://ches.api.gov.bc.ca/api/v1/email + +vault: + vaultSecretEngine: c6af30-nonprod + postgresSecret: sandbox-realm-registry-patroni-appusers + realmRegistrySecret: sandbox-realm-registry + serviceAccountName: c6af30-vault + +patroni: + image: + repository: registry.opensource.zalan.do/acid/spilo-14 + pullPolicy: Always + tag: 2.1-p5 + + walG: + enabled: false diff --git a/helm/webapp/values-c6af30-prod.yaml b/helm/webapp/values-c6af30-prod.yaml new file mode 100644 index 0000000..abeb7b9 --- /dev/null +++ b/helm/webapp/values-c6af30-prod.yaml @@ -0,0 +1,37 @@ +image: + tag: main + +route: + host: realm-registry.apps.gold.devops.gov.bc.ca + +sso: + url: https://loginproxy.gov.bc.ca/auth/realms/standard + redirectUri: http://realm-registry.apps.gold.devops.gov.bc.ca/oidc/keycloak + logoutRedirectUri: http://realm-registry.apps.gold.devops.gov.bc.ca + +env: + APP_ENV: 'production' + SECURE_HEADERS: 'true' + IDIR_JWKS_URI: https://loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/certs + IDIR_ISSUER: https://loginproxy.gov.bc.ca/auth/realms/standard + IDIR_AUDIENCE: css-app-in-gold-4128 + CHES_API_ENDPOINT: https://ches.api.gov.bc.ca/api/v1/email + +vault: + vaultSecretEngine: c6af30-prod + postgresSecret: prod-realm-registry-patroni-appusers + realmRegistrySecret: prod-realm-registry + serviceAccountName: c6af30-vault + +patroni: + image: + repository: registry.opensource.zalan.do/acid/spilo-14 + pullPolicy: Always + tag: 2.1-p5 + + walG: + enabled: true + scheduleCronJob: 00 01 * * * + retainBackups: 7 + pvc: + size: 1Gi diff --git a/helm/webapp/values.yaml b/helm/webapp/values.yaml index 21dfe47..3d704d4 100644 --- a/helm/webapp/values.yaml +++ b/helm/webapp/values.yaml @@ -77,7 +77,7 @@ patroni: resources: {} persistentVolume: - size: 10Gi + size: 1Gi podDisruptionBudget: enabled: true From f0b903ded05f579fc785ec7930ad5dab9e035bb0 Mon Sep 17 00:00:00 2001 From: Nithin Shekar Kuruba <81444731+NithinKuruba@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:00:29 -0700 Subject: [PATCH 3/4] feat: utilize next auth to manage the auth and sessions (#110) * feat: utilize next auth to manage the auth and sessions * feat: update helm chart to include NEXTAUTH_URL * feat: disable duplicate idir feature * feat: use custom session and user types * feat: add NEXTAUTH_URL to env example --- app/.env.example | 1 + app/controllers/realm.ts | 8 +- app/layout/Layout.tsx | 23 +- app/package.json | 1 + .../my-dashboard/RealmLeftPanel.tsx | 5 +- .../my-dashboard/RealmRightPanel.tsx | 14 +- app/pages/_app.tsx | 51 ++- app/pages/api/auth/[...nextauth].ts | 96 ++++++ app/pages/api/realms/all.ts | 11 +- app/pages/api/realms/one.ts | 13 +- app/pages/api/surveys/1.ts | 4 +- app/pages/api/users/idir.ts | 10 +- app/pages/index.tsx | 25 +- app/pages/my-dashboard.tsx | 17 +- app/pages/realm/[rid].tsx | 3 +- app/services/axios.ts | 3 +- app/svg/IntroRealms.svg | 95 +++++ app/svg/IntroRealms.tsx | 324 ++++++++++++++++++ app/types/next-auth.d.ts | 24 ++ app/yarn.lock | 76 +++- helm/webapp/Chart.yaml | 2 +- helm/webapp/values-c6af30-dev.yaml | 1 + helm/webapp/values-c6af30-prod.yaml | 1 + 23 files changed, 705 insertions(+), 103 deletions(-) create mode 100644 app/pages/api/auth/[...nextauth].ts create mode 100644 app/svg/IntroRealms.svg create mode 100644 app/svg/IntroRealms.tsx create mode 100644 app/types/next-auth.d.ts diff --git a/app/.env.example b/app/.env.example index 86b6c55..44588bc 100644 --- a/app/.env.example +++ b/app/.env.example @@ -33,3 +33,4 @@ BCEID_SERVICE_ID= BCEID_SERVICE_BASIC_AUTH= IDIR_JWKS_URI= IDIR_ISSUER= +NEXTAUTH_URL=http://localhost:3000 diff --git a/app/controllers/realm.ts b/app/controllers/realm.ts index 5618a0f..bf01d21 100644 --- a/app/controllers/realm.ts +++ b/app/controllers/realm.ts @@ -2,8 +2,8 @@ import { runQuery } from 'utils/db'; import KeycloakCore from 'utils/keycloak-core'; export async function getAllowedRealms(session: any) { - const username = session?.idir_username || ''; - const roles = session?.client_roles || []; + const username = session?.user?.idir_username || ''; + const roles = session?.user?.client_roles || []; const isAdmin = roles.includes('sso-admin'); let result: any = null; @@ -54,8 +54,8 @@ export async function getAllowedRealms(session: any) { } export async function getAllowedRealmNames(session: any) { - const username = session?.idir_username || ''; - const roles = session?.client_roles || []; + const username = session?.user?.idir_username || ''; + const roles = session?.user?.client_roles || []; const isAdmin = roles.includes('sso-admin'); let result: any = null; diff --git a/app/layout/Layout.tsx b/app/layout/Layout.tsx index a413f2a..3206ea3 100644 --- a/app/layout/Layout.tsx +++ b/app/layout/Layout.tsx @@ -10,6 +10,9 @@ import { startCase } from 'lodash'; import BCSans from './BCSans'; import Navigation from './Navigation'; import BottomAlertProvider from './BottomAlert'; +import { useSession } from 'next-auth/react'; +import { useEffect } from 'react'; +import { User } from 'next-auth'; const headerPlusFooterHeight = '152px'; @@ -106,11 +109,11 @@ const routes: Route[] = [ { path: '/realm', label: 'Realm Profile', roles: ['user'], hide: true }, ]; -const LeftMenuItems = ({ currentUser, currentPath }: { currentUser: any; currentPath: string }) => { - let roles = ['guest']; +const LeftMenuItems = ({ currentUser, currentPath }: { currentUser: Partial; currentPath: string }) => { + let roles: string[] = ['guest']; if (currentUser) { - roles = currentUser?.client_roles?.length > 0 ? currentUser.client_roles : ['user']; + roles = currentUser?.client_roles?.length! > 0 ? currentUser.client_roles! : ['user']; } const isCurrent = (path: string) => currentPath === path || currentPath.startsWith(`${path}/`); @@ -136,7 +139,7 @@ const RightMenuItems = () => ( <> Need help? - + @@ -146,20 +149,22 @@ const RightMenuItems = () => ( - + > ); // identity_provider, idir_userid, client_roles, family_name, given_name -function Layout({ children, currentUser, onLoginClick, onLogoutClick }: any) { +function Layout({ children, onLoginClick, onLogoutClick }: any) { const router = useRouter(); + const { data } = useSession(); + const currentUser: Partial = data?.user!; const pathname = router.pathname; const rightSide = currentUser ? ( - Welcome {`${currentUser.given_name} ${currentUser.family_name}`} + Welcome {`${currentUser?.given_name} ${currentUser?.family_name}`} Log out @@ -177,7 +182,7 @@ function Layout({ children, currentUser, onLoginClick, onLogoutClick }: any) { Need help? - + @@ -185,7 +190,7 @@ function Layout({ children, currentUser, onLoginClick, onLogoutClick }: any) { - + diff --git a/app/package.json b/app/package.json index 1ecbf7c..b4f8a83 100644 --- a/app/package.json +++ b/app/package.json @@ -26,6 +26,7 @@ "lodash.get": "^4.4.2", "lodash.map": "^4.6.0", "next": "12.1.6", + "next-auth": "^4.23.1", "node-cron": "^3.0.2", "pg": "^8.7.3", "pg-format": "^1.0.4", diff --git a/app/page-partials/my-dashboard/RealmLeftPanel.tsx b/app/page-partials/my-dashboard/RealmLeftPanel.tsx index 0ca93cc..3a5a06b 100644 --- a/app/page-partials/my-dashboard/RealmLeftPanel.tsx +++ b/app/page-partials/my-dashboard/RealmLeftPanel.tsx @@ -19,7 +19,8 @@ function RealmLeftPanel({ realms, onEditClick, onCancel }: Props) { setTab('dashboard')}> My Dashboard - { setTab('duplicate'); @@ -27,7 +28,7 @@ function RealmLeftPanel({ realms, onEditClick, onCancel }: Props) { }} > Duplicate Users - + */} {tab === 'dashboard' ? : } > diff --git a/app/page-partials/my-dashboard/RealmRightPanel.tsx b/app/page-partials/my-dashboard/RealmRightPanel.tsx index 9f9ea97..7069924 100644 --- a/app/page-partials/my-dashboard/RealmRightPanel.tsx +++ b/app/page-partials/my-dashboard/RealmRightPanel.tsx @@ -1,18 +1,10 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { useRouter } from 'next/router'; -import { useForm } from 'react-hook-form'; -import Loader from 'react-loader-spinner'; -import ResponsiveContainer, { MediaRule } from 'components/ResponsiveContainer'; -import Button from '@button-inc/bcgov-theme/Button'; -import Checkbox from '@button-inc/bcgov-theme/Checkbox'; +import React, { useState } from 'react'; import Tabs from 'components/Tabs'; -import { withBottomAlert, BottomAlert } from 'layout/BottomAlert'; -import { UserSession } from 'types/user-session'; import styled from 'styled-components'; import { RealmProfile } from 'types/realm-profile'; import RealmEdit from './RealmEdit'; import RealmURIs from './RealmURIs'; -import RealmIDIR from './RealmIDIR'; +import { User } from 'next-auth'; const Container = styled.div` font-size: 1rem; @@ -50,7 +42,7 @@ const Container = styled.div` interface Props { realm: RealmProfile; - currentUser: UserSession; + currentUser: Partial; onUpdate: (realm: RealmProfile) => void; onCancel: () => void; } diff --git a/app/pages/_app.tsx b/app/pages/_app.tsx index adf1c2d..33d3e4a 100644 --- a/app/pages/_app.tsx +++ b/app/pages/_app.tsx @@ -1,48 +1,41 @@ import 'bootstrap/dist/css/bootstrap.min.css'; import '../styles/globals.css'; -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { useRouter } from 'next/router'; import type { AppProps } from 'next/app'; import Head from 'next/head'; -import store2 from 'store2'; import Layout from 'layout/Layout'; +import { SessionProvider, signOut, signIn } from 'next-auth/react'; -// store2('app-session', { name, preferred_username, email }); - -function MyApp({ Component, pageProps }: AppProps) { +function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) { const router = useRouter(); - const [currentUser, setCurrentUser] = useState(null); - - useEffect(() => setCurrentUser(store2.session.get('app-session')), []); - - useEffect(() => { - const redirect = async () => { - if (currentUser) await router.push('/my-dashboard'); - }; - - redirect(); - }, [currentUser]); const handleLogin = async () => { - window.location.href = '/api/oidc/keycloak/login'; + signIn('keycloak', { + callbackUrl: '/my-dashboard', + redirect: true, + }); }; const handleLogout = async () => { - store2.session.remove('app-token'); - store2.session.remove('app-session'); - window.location.href = '/api/oidc/keycloak/logout'; + signOut({ + redirect: true, + callbackUrl: '/api/oidc/keycloak/logout', + }); }; return ( - - - - Keycloak Realm Registry - - - - - + + + + + Keycloak Realm Registry + + + + + + ); } export default MyApp; diff --git a/app/pages/api/auth/[...nextauth].ts b/app/pages/api/auth/[...nextauth].ts new file mode 100644 index 0000000..576e1ea --- /dev/null +++ b/app/pages/api/auth/[...nextauth].ts @@ -0,0 +1,96 @@ +import NextAuth, { User, Account, NextAuthOptions } from 'next-auth'; +import KeycloakProvider from 'next-auth/providers/keycloak'; +import { JWT } from 'next-auth/jwt'; +import jwt from 'jsonwebtoken'; +import axios from 'axios'; + +async function refreshAccessToken(token: any) { + try { + const url = `${process.env.SSO_URL}/protocol/openid-connect/token?`; + const response = await axios.post( + url, + new URLSearchParams({ + client_id: process.env.SSO_CLIENT_ID || '', + client_secret: process.env.SSO_CLIENT_SECRET || '', + grant_type: 'refresh_token', + refresh_token: token.refreshToken, + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + ); + const refreshedTokens = await response.data; + return { + ...token, + accessToken: refreshedTokens.access_token, + accessTokenExpired: Date.now() + (refreshedTokens.expires_in - 15) * 1000, + refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, + refreshTokenExpired: Date.now() + (refreshedTokens.refresh_expires_in - 15) * 1000, + }; + } catch (error) { + console.error('refresh token error', error); + return { + ...token, + error: 'RefreshAccessTokenError', + }; + } +} + +export const authOptions: NextAuthOptions = { + providers: [ + KeycloakProvider({ + clientId: process.env.SSO_CLIENT_ID || '', + clientSecret: process.env.SSO_CLIENT_SECRET || '', + issuer: process.env.SSO_URL, + profile(profile) { + return { + ...profile, + id: profile.sub, + name: profile.name, + email: profile.email, + image: null, + }; + }, + }), + ], + secret: process.env.JWT_SECRET, + callbacks: { + async jwt({ token, account, user }: { token: any; account: any; user: any }) { + if (account) { + token.accessToken = account?.access_token; + token.refreshToken = account.refresh_token; + token.accessTokenExpired = Date.now() + (account?.expires_at - 15) * 1000; + token.refreshTokenExpired = Date.now() + (account?.refresh_expires_in - 15) * 1000; + token.user = user; + } + + const decodedToken = jwt.decode(token.accessToken || '') as any; + + token.client_roles = decodedToken?.client_roles; + token.given_name = decodedToken?.given_name; + token.family_name = decodedToken?.family_name; + token.preferred_username = decodedToken?.preferred_username; + token.email = decodedToken?.email; + token.idir_username = decodedToken?.idir_username; + + if (Date.now() < token.accessTokenExpired) { + return refreshAccessToken(token); + } + + return token; + }, + async session({ session, token }: { session: any; token: JWT }) { + // Send properties to the client, like an access_token from a provider. + if (token) { + session.accessToken = token.accessToken; + session.user = token.user; + } + + return session; + }, + }, +}; + +export default NextAuth(authOptions); diff --git a/app/pages/api/realms/all.ts b/app/pages/api/realms/all.ts index 21c4fba..b51203d 100644 --- a/app/pages/api/realms/all.ts +++ b/app/pages/api/realms/all.ts @@ -1,8 +1,8 @@ -import { Realms } from 'keycloak-admin/lib/resources/realms'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { runQuery } from 'utils/db'; -import { validateRequest } from 'utils/jwt'; import { getAllowedRealms } from 'controllers/realm'; +import { getSession } from 'next-auth/react'; +import { getServerSession } from 'next-auth/next'; +import { authOptions } from '../auth/[...nextauth]'; interface ErrorData { success: boolean; @@ -13,8 +13,9 @@ type Data = ErrorData | string; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const session = await validateRequest(req, res); - if (!session) return res.status(401).json({ success: false, error: 'jwt expired' }); + const session = await getServerSession(req, res, authOptions); + + if (!session) return res.status(401).json({ success: false, error: 'unauthorized' }); const realms = await getAllowedRealms(session); return res.send(realms); diff --git a/app/pages/api/realms/one.ts b/app/pages/api/realms/one.ts index c5f977a..c2f8364 100644 --- a/app/pages/api/realms/one.ts +++ b/app/pages/api/realms/one.ts @@ -3,6 +3,8 @@ import { runQuery } from 'utils/db'; import { validateRequest } from 'utils/jwt'; import KeycloakCore from 'utils/keycloak-core'; import { sendUpdateEmail } from 'utils/mailer'; +import { getServerSession } from 'next-auth/next'; +import { authOptions } from '../auth/[...nextauth]'; interface ErrorData { success: boolean; @@ -15,12 +17,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< try { const { id } = req.query; - const session = await validateRequest(req, res); - const username = session?.idir_username || ''; - const roles = session?.client_roles || []; - const isAdmin = roles.includes('sso-admin'); + const session: any = await getServerSession(req, res, authOptions); + + if (!session) return res.status(401).json({ success: false, error: 'unauthorized' }); - if (!username) return res.status(401).json({ success: false, error: 'jwt expired' }); + const username = session?.user?.idir_username || ''; + const roles = session?.user?.client_roles || []; + const isAdmin = roles.includes('sso-admin'); const kcCore = new KeycloakCore('prod'); diff --git a/app/pages/api/surveys/1.ts b/app/pages/api/surveys/1.ts index 43e1d76..c82f985 100644 --- a/app/pages/api/surveys/1.ts +++ b/app/pages/api/surveys/1.ts @@ -14,8 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< const session = await validateRequest(req, res); if (!session) return res.status(401).json({ success: false, error: 'jwt expired' }); - const username = session?.idir_username || ''; - const roles = session?.client_roles || []; + const username = session?.user?.idir_username || ''; + const roles = session?.user?.client_roles || []; const isAdmin = roles.includes('sso-admin'); if (req.method === 'GET') { diff --git a/app/pages/api/users/idir.ts b/app/pages/api/users/idir.ts index 5694384..aaa703a 100644 --- a/app/pages/api/users/idir.ts +++ b/app/pages/api/users/idir.ts @@ -1,7 +1,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; -import { validateRequest } from 'utils/jwt'; import KeycloakCore from 'utils/keycloak-core'; -import { getAllowedRealms, getAllowedRealmNames } from 'controllers/realm'; +import { getAllowedRealmNames } from 'controllers/realm'; +import { getServerSession } from 'next-auth/next'; +import { authOptions } from '../auth/[...nextauth]'; interface ErrorData { success: boolean; @@ -12,8 +13,9 @@ type Data = ErrorData | string | any; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const session = await validateRequest(req, res); - if (!session) return res.status(401).json({ success: false, error: 'jwt expired' }); + const session: any = await getServerSession(req, res, authOptions); + + if (!session) return res.status(401).json({ success: false, error: 'unauthorized' }); const { search, env } = req.query; const searchParam = String(search); diff --git a/app/pages/index.tsx b/app/pages/index.tsx index 6733048..7552adf 100644 --- a/app/pages/index.tsx +++ b/app/pages/index.tsx @@ -1,13 +1,12 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { useRouter } from 'next/router'; -import Image from 'next/image'; import Head from 'next/head'; import styled from 'styled-components'; import Grid from '@button-inc/bcgov-theme/Grid'; import Button from '@button-inc/bcgov-theme/Button'; import ResponsiveContainer, { MediaRule } from 'components/ResponsiveContainer'; -import { UserSession } from 'types/user-session'; -import hero from 'public/home-right.png'; +import IntroRealms from 'svg/IntroRealms'; +import { signIn, useSession } from 'next-auth/react'; const JumbotronH1 = styled.h1` font-size: 2.5rem; @@ -35,15 +34,15 @@ const mediaRules: MediaRule[] = [ }, ]; -interface Props { - currentUser: UserSession; -} - -const Home = ({ currentUser }: Props) => { +const Home = () => { const router = useRouter(); + const session = useSession(); const handleLogin = async () => { - window.location.href = '/api/oidc/keycloak/login'; + signIn('keycloak', { + callbackUrl: '/my-dashboard', + redirect: true, + }); }; const handleDashboard = async () => { @@ -69,7 +68,7 @@ const Home = ({ currentUser }: Props) => { Custom realm. - {currentUser ? ( + {session ? ( My Dashboard @@ -79,9 +78,7 @@ const Home = ({ currentUser }: Props) => { )} - - - + {IntroRealms} diff --git a/app/pages/my-dashboard.tsx b/app/pages/my-dashboard.tsx index f5fe733..26958d6 100644 --- a/app/pages/my-dashboard.tsx +++ b/app/pages/my-dashboard.tsx @@ -1,22 +1,20 @@ import React, { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; import Head from 'next/head'; import { Grid as SpinnerGrid } from 'react-loader-spinner'; import styled from 'styled-components'; -import Button from '@button-inc/bcgov-theme/Button'; -import Modal from '@button-inc/bcgov-theme/Modal'; import Grid from '@button-inc/bcgov-theme/Grid'; import Alert from '@button-inc/bcgov-theme/Alert'; import StyledLink from '@button-inc/bcgov-theme/Link'; import { RealmProfile } from 'types/realm-profile'; -import { UserSession } from 'types/user-session'; import RealmLeftPanel from 'page-partials/my-dashboard/RealmLeftPanel'; import RealmRightPanel from 'page-partials/my-dashboard/RealmRightPanel'; import PopupModal from 'page-partials/my-dashboard/PopupModal'; import TopAlertWrapper from 'components/TopAlertWrapper'; import ResponsiveContainer, { MediaRule } from 'components/ResponsiveContainer'; import { getRealmProfiles } from 'services/realm'; -import { getSurvey, answerSurvey } from 'services/survey'; +import { getSurvey } from 'services/survey'; +import { useSession } from 'next-auth/react'; +import { User } from 'next-auth'; const AlignCenter = styled.div` text-align: center; @@ -40,12 +38,9 @@ const mediaRules: MediaRule[] = [ }, ]; -interface Props { - currentUser: UserSession; -} - -function MyDashboard({ currentUser }: Props) { - const router = useRouter(); +function MyDashboard() { + const { data } = useSession(); + const currentUser: Partial = data?.user!; const [loading, setLoading] = useState(false); const [answered, setAnswered] = useState(true); const [selectedId, setSelectedId] = useState(null); diff --git a/app/pages/realm/[rid].tsx b/app/pages/realm/[rid].tsx index 9250afb..c765a4c 100644 --- a/app/pages/realm/[rid].tsx +++ b/app/pages/realm/[rid].tsx @@ -60,10 +60,9 @@ const mediaRules: MediaRule[] = [ interface Props { alert: BottomAlert; - currentUser: UserSession; } -function EditRealm({ alert, currentUser }: Props) { +function EditRealm({ alert }: Props) { const router = useRouter(); const { rid } = router.query; diff --git a/app/services/axios.ts b/app/services/axios.ts index cfcc07d..b3785b4 100644 --- a/app/services/axios.ts +++ b/app/services/axios.ts @@ -9,8 +9,7 @@ const instance = axios.create({ instance?.interceptors.request.use( async function (config) { - const appToken = store2.session.get('app-token'); - return { ...config, headers: { ...config.headers, Authorization: `Bearer ${appToken}` } }; + return { ...config, headers: { ...config.headers } }; }, function (error) { return Promise.reject(error); diff --git a/app/svg/IntroRealms.svg b/app/svg/IntroRealms.svg new file mode 100644 index 0000000..6484e43 --- /dev/null +++ b/app/svg/IntroRealms.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/svg/IntroRealms.tsx b/app/svg/IntroRealms.tsx new file mode 100644 index 0000000..ee5fa81 --- /dev/null +++ b/app/svg/IntroRealms.tsx @@ -0,0 +1,324 @@ +import React from 'react'; + +export default ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/app/types/next-auth.d.ts b/app/types/next-auth.d.ts new file mode 100644 index 0000000..5d3c26b --- /dev/null +++ b/app/types/next-auth.d.ts @@ -0,0 +1,24 @@ +import NextAuth, { DefaultSession } from 'next-auth'; + +declare module 'next-auth' { + /** + * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context + */ + interface Session { + accessToken: string; + user: User & DefaultSession['user']; + } + + interface User { + id: string; + name?: string | null; + email?: string | null; + image?: string | null; + client_roles: string[]; + given_name: string | null; + family_name: string | null; + preferred_username: string | null; + email: string | null; + idir_username: string | null; + } +} diff --git a/app/yarn.lock b/app/yarn.lock index c3ab39a..30221e4 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -113,7 +113,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3": +"@babel/runtime@^7.12.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== @@ -601,6 +601,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@panva/hkdf@^1.0.2": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@panva/hkdf/-/hkdf-1.1.1.tgz#ab9cd8755d1976e72fc77a00f7655a64efe6cd5d" + integrity sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA== + "@popperjs/core@^2.10.1": version "2.11.0" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7" @@ -1564,6 +1569,11 @@ convert-source-map@^1.5.0: dependencies: safe-buffer "~5.1.1" +cookie@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + core-js-pure@^3.16.0: version "3.18.3" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.18.3.tgz#7eed77dcce1445ab68fd68715856633e2fb3b90c" @@ -2737,6 +2747,11 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +jose@^4.11.4, jose@^4.14.4: + version "4.14.6" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.6.tgz#94dca1d04a0ad8c6bff0998cdb51220d473cc3af" + integrity sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ== + js-sha256@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" @@ -3110,6 +3125,21 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +next-auth@^4.23.1: + version "4.23.1" + resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.23.1.tgz#7a82f5327cf4c7e32819da4eb977f2251a23c3cf" + integrity sha512-mL083z8KgRtlrIV6CDca2H1kduWJuK/3pTS0Fe2og15KOm4v2kkLGdSDfc2g+019aEBrJUT0pPW2Xx42ImN1WA== + dependencies: + "@babel/runtime" "^7.20.13" + "@panva/hkdf" "^1.0.2" + cookie "^0.5.0" + jose "^4.11.4" + oauth "^0.9.15" + openid-client "^5.4.0" + preact "^10.6.3" + preact-render-to-string "^5.1.19" + uuid "^8.3.2" + next@12.1.6: version "12.1.6" resolved "https://registry.yarnpkg.com/next/-/next-12.1.6.tgz#eb205e64af1998651f96f9df44556d47d8bbc533" @@ -3140,11 +3170,21 @@ node-cron@^3.0.2: dependencies: uuid "8.3.2" +oauth@^0.9.15: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== + object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" @@ -3205,6 +3245,11 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +oidc-token-hash@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz#9a229f0a1ce9d4fc89bcaee5478c97a889e7b7b6" + integrity sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3212,6 +3257,16 @@ once@^1.3.0: dependencies: wrappy "1" +openid-client@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/openid-client/-/openid-client-5.5.0.tgz#0c631b33c6a2c3e01197506978d6bff70e75c858" + integrity sha512-Y7Xl8BgsrkzWLHkVDYuroM67hi96xITyEDSkmWaGUiNX6CkcXC3XyQGdv5aWZ6dukVKBFVQCADi9gCavOmU14w== + dependencies: + jose "^4.14.4" + lru-cache "^6.0.0" + object-hash "^2.2.0" + oidc-token-hash "^5.0.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -3445,11 +3500,28 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +preact-render-to-string@^5.1.19: + version "5.2.6" + resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz#0ff0c86cd118d30affb825193f18e92bd59d0604" + integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw== + dependencies: + pretty-format "^3.8.0" + +preact@^10.6.3: + version "10.17.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.17.1.tgz#0a1b3c658c019e759326b9648c62912cf5c2dde1" + integrity sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +pretty-format@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" + integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== + prismjs@1.27.0, prismjs@^1.21.0, prismjs@^1.22.0, prismjs@~1.25.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" @@ -4299,7 +4371,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== diff --git a/helm/webapp/Chart.yaml b/helm/webapp/Chart.yaml index 4f09c8f..17fcb1e 100644 --- a/helm/webapp/Chart.yaml +++ b/helm/webapp/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v1 name: realm-registry -version: 0.2.0 +version: 0.2.1 appVersion: 0.1.0 description: Nextjs application to manage SSO keycloak custom realm profiles dependencies: diff --git a/helm/webapp/values-c6af30-dev.yaml b/helm/webapp/values-c6af30-dev.yaml index 9bb69da..6ba5d2d 100644 --- a/helm/webapp/values-c6af30-dev.yaml +++ b/helm/webapp/values-c6af30-dev.yaml @@ -16,6 +16,7 @@ env: IDIR_ISSUER: https://dev.loginproxy.gov.bc.ca/auth/realms/standard IDIR_AUDIENCE: css-app-in-gold-4128 CHES_API_ENDPOINT: https://ches.api.gov.bc.ca/api/v1/email + NEXTAUTH_URL: http://realm-registry-sandbox.apps.gold.devops.gov.bc.ca vault: vaultSecretEngine: c6af30-nonprod diff --git a/helm/webapp/values-c6af30-prod.yaml b/helm/webapp/values-c6af30-prod.yaml index abeb7b9..8295cff 100644 --- a/helm/webapp/values-c6af30-prod.yaml +++ b/helm/webapp/values-c6af30-prod.yaml @@ -16,6 +16,7 @@ env: IDIR_ISSUER: https://loginproxy.gov.bc.ca/auth/realms/standard IDIR_AUDIENCE: css-app-in-gold-4128 CHES_API_ENDPOINT: https://ches.api.gov.bc.ca/api/v1/email + NEXTAUTH_URL: http://realm-registry.apps.gold.devops.gov.bc.ca vault: vaultSecretEngine: c6af30-prod From 5fd1dfc27cd2f815158f7a9c0f3f611c858d8a75 Mon Sep 17 00:00:00 2001 From: Nithin Shekar Kuruba Date: Tue, 12 Sep 2023 13:32:19 -0700 Subject: [PATCH 4/4] feat: allow edits by second tech contact --- app/controllers/realm.ts | 2 +- app/page-partials/my-dashboard/RealmEdit.tsx | 10 +++++----- app/pages/api/realms/one.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/realm.ts b/app/controllers/realm.ts index bf01d21..181a462 100644 --- a/app/controllers/realm.ts +++ b/app/controllers/realm.ts @@ -66,7 +66,7 @@ export async function getAllowedRealmNames(session: any) { ` SELECT realm, - FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC + FROM rosters WHERE LOWER(technical_contact_idir_userid)=LOWER($1) OR LOWER(second_technical_contact_idir_userid)=LOWER($1) OR LOWER(product_owner_idir_userid)=LOWER($1) ORDER BY id ASC `, [username], ); diff --git a/app/page-partials/my-dashboard/RealmEdit.tsx b/app/page-partials/my-dashboard/RealmEdit.tsx index 300913c..dfcae16 100644 --- a/app/page-partials/my-dashboard/RealmEdit.tsx +++ b/app/page-partials/my-dashboard/RealmEdit.tsx @@ -169,7 +169,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { } }; - const isAdmin = currentUser.client_roles.includes('sso-admin'); + const isAdmin = currentUser?.client_roles?.includes('sso-admin'); const isPO = currentUser.idir_username.toLocaleLowerCase() === realm.product_owner_idir_userid.toLocaleLowerCase(); if (!realm) return null; @@ -307,7 +307,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { {...register('technical_contact_email', { required: true, pattern: /^\S+@\S+$/i })} /> - Technical Contact Idir + Technical Contact Idir* If not dithered, you can update this field with the appropriate technical contact Idir @@ -316,10 +316,10 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { type="text" placeholder="Technical Contact Idir" disabled={!isAdmin && !isPO} - {...register('technical_contact_idir_userid', { required: false, minLength: 2, maxLength: 1000 })} + {...register('technical_contact_idir_userid', { required: true, minLength: 2, maxLength: 1000 })} /> - Second Technical Contact Email(optional)* + Second Technical Contact Email(optional) If not dithered, you can update this field with the appropriate optional technical contact email @@ -327,7 +327,7 @@ function RealmTable({ alert, realm, currentUser, onUpdate, onCancel }: Props) { Second Technical Contact Idir(optional) diff --git a/app/pages/api/realms/one.ts b/app/pages/api/realms/one.ts index c2f8364..27aaf3a 100644 --- a/app/pages/api/realms/one.ts +++ b/app/pages/api/realms/one.ts @@ -175,7 +175,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< second_technical_contact_email=$8, second_technical_contact_idir_userid=$9, updated_at=now() - WHERE id=$1 AND LOWER(technical_contact_idir_userid)=LOWER($2) + WHERE id=$1 AND (LOWER(technical_contact_idir_userid)=LOWER($2) OR LOWER(second_technical_contact_idir_userid)=LOWER($2)) RETURNING *`, [ id,
Last Updated: {new Date(realm.updated_at).toLocaleString()}