From 5f03381f310e2b8ad5b217c7a087d1336b6924ac Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Wed, 19 Apr 2023 10:37:15 -0300 Subject: [PATCH 001/113] Handle improper max_age directive (#4478) * Handle improper max_age directive * Clean up --- scanners/web-processor/web_processor/web_processor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scanners/web-processor/web_processor/web_processor.py b/scanners/web-processor/web_processor/web_processor.py index cd1035356e..bf0bc72e0f 100644 --- a/scanners/web-processor/web_processor/web_processor.py +++ b/scanners/web-processor/web_processor/web_processor.py @@ -306,7 +306,10 @@ def check_https_downgrades(connections): "preload": preload } - hsts_status = "pass" if hsts and max_age > 0 and "https14" not in negative_tags else "fail" + if hsts and isinstance(max_age, int) and max_age > 0 and "https14" not in negative_tags: + hsts_status = "pass" + else: + hsts_status = "fail" http_down_or_redirect = not http_live or http_immediately_upgrades From 3511f7a3aef0e1e3b8df636e37300dee67143bc3 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Wed, 19 Apr 2023 13:38:53 +0000 Subject: [PATCH 002/113] [ci skip] gcr.io/track-compliance/web-processor:master-5f03381-1681911454 --- k8s/apps/bases/scanners/web-processor/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/apps/bases/scanners/web-processor/deployment.yaml b/k8s/apps/bases/scanners/web-processor/deployment.yaml index 9e9f2ca57c..b69d8eff90 100644 --- a/k8s/apps/bases/scanners/web-processor/deployment.yaml +++ b/k8s/apps/bases/scanners/web-processor/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: web-processor - image: gcr.io/track-compliance/web-processor:master-1f34ed3-1681232023 # {"$imagepolicy": "flux-system:web-processor"} + image: gcr.io/track-compliance/web-processor:master-5f03381-1681911454 # {"$imagepolicy": "flux-system:web-processor"} env: - name: DB_NAME value: track_dmarc From 5f969f09af242795f41de9f6ab32c926c9b736aa Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:23:34 -0300 Subject: [PATCH 003/113] Getting started documentation (#4435) * new info buttons * info button on DmarcReportPage * init getting started doc * update read guidance * getting started with tracker text on read guidance page * reformat docs to remove redundant information * fix tests, add wiki links --- frontend/src/app/ReadGuidancePage.js | 874 +++++++++++------- .../app/__tests__/ReadGuidancePage.test.js | 6 +- frontend/src/components/InfoPanel.js | 74 +- frontend/src/components/SearchBox.js | 4 + frontend/src/components/TrackerTable.js | 54 +- .../components/__tests__/InfoPanel.test.js | 2 +- frontend/src/dmarc/DmarcByDomainPage.js | 38 +- frontend/src/dmarc/DmarcReportPage.js | 208 ++++- frontend/src/domains/DomainsPage.js | 4 +- frontend/src/locales/en.po | 461 ++++++--- frontend/src/locales/fr.po | 465 +++++++--- .../OrganizationDomains.js | 4 +- frontend/src/organizations/Organizations.js | 4 +- 13 files changed, 1527 insertions(+), 671 deletions(-) diff --git a/frontend/src/app/ReadGuidancePage.js b/frontend/src/app/ReadGuidancePage.js index 4924db104f..03a35da664 100644 --- a/frontend/src/app/ReadGuidancePage.js +++ b/frontend/src/app/ReadGuidancePage.js @@ -2,18 +2,13 @@ import React from 'react' import { Trans } from '@lingui/macro' import { Box, - Divider, Heading, Text, Link, ListItem, OrderedList, UnorderedList, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, + Divider, } from '@chakra-ui/react' import { useLingui } from '@lingui/react' @@ -21,392 +16,609 @@ export default function ReadGuidancePage() { const { i18n } = useLingui() return ( - - - - - Guidance - - - FAQ - - - - - {' '} - - Read Guidance - - - - - The Government of Canada’s (GC){' '} - - Directive on Service and Digital - {' '} - provides expectations on how GC organizations are to manage - their Information Technology (IT) services. The focus of the - Tracker tool is to help organizations stay in compliance with - the directives{' '} - - Email Management Service Configuration Requirements - {' '} - and the directives{' '} - - Web Site and Service Management Configuration Requirements - - . - + + + Getting Started + + + + + + The Government of Canada’s (GC){' '} + + Directive on Service and Digital + {' '} + provides expectations on how GC organizations are to manage their + Information Technology (IT) services. The focus of the Tracker tool + is to help organizations stay in compliance with the directives{' '} + + Email Management Service Configuration Requirements + {' '} + and the directives{' '} + + Web Site and Service Management Configuration Requirements + + . + + + + + Below are steps on how government organizations can leverage the + Tracker platform: + + + + {/* 1 */} + + + Getting an Account: - - - Below are steps on how government organizations can leverage the - Tracker platform: - - - - {/* 1 */} + - - Identify resources required to act as central point(s) of - contact with Treasury Board of Canada Secretariat (TBS). Share - the contact list with{' '} - - TBS Cyber Security - - , as required. - + + + Identify any current affiliated Tracker users within your + organization and develop a plan with them. + + - {/* 2 */} - - Perform an inventory of all organizational domains and - subdomains. Sources of information include: - + + + If your organization has no affiliated users within Tracker, + contact the{' '} + + TBS Cyber Security + {' '} + to assist in onboarding. + + - - The{' '} - - Tracker - {' '} - platform - - - - - Application Portfolio Management (APM) systems; and - - - - Business units within your organization. + + + Once access is given to your department by the TBS Cyber + team, they will be able to invite and manage other users + within the organization and manage the domain list. + + - {/* 3 */} + + + {/* 2 */} + + + Managing Your Domains: + + - - Provide an up-to-date list of all domain and sub-domains of - publicly accessible websites and web services to TBS Cyber - Security. The TBS Cyber Security team is responsible for - updating the domain and sub-domain lists within Tracker. - + + + Each organization’s domain list should include every + internet-facing service. It is the responsibility of + organization admins to manage the current list and identify + new domains to add. + + - {/* 4 */} - - Use Tracker and{' '} - - ITSP.40.062 Transport Layer Security (TLS) guidance - {' '} - to monitor the domains and sub-domains of your organization. - Other tools available to support this activity include,{' '} - - SSL Labs - - ,{' '} - - Hardenize - - ,{' '} - - SSLShopper - - , etc.. - + + + To receive DKIM scan results and guidance, you must add the + DKIM selectors used for each domain. Organization + administrators can add selectors in the “Admin Profile” by + clicking the edit button of the domain for which they wish + to add the selector. Common selectors to keep an for are + “selector1”, and “selector2”. + + + + + + + Domains are only to be removed from your list when 1) they + no longer exist, meaning they are deleted from the DNS + returning an error code of NX DOMAIN (domain name does not + exist); or 2) if you have identified that they do not belong + to your organization. + + - Tracker results refresh every 24 hours. + + + If a domain is no longer in use but still exists on the + DNS, it is still vulnerable to email spoofing attacks, + where an attacker can send an email that appears to be + coming from your domain. + + - {/* 5 */} + + + {/* 3 */} + + + Understanding Scan Metrics: + + - - Develop a prioritized schedule to address any failings. - Consider prioritizing websites and web services that exchange - Protected data. - - - - - Where necessary adjust IT Plans and budget estimates where - work is expected. - - - - - It is recommended that Shared Service Canada (SSC) - partners contact their SSC Service Delivery Manager to - discuss action plans and required steps to submit a - request for change. - - + + + The summary cards show two metrics that Tracker scans: + + + - Obtain certificates from a GC-approved certificate source - as outlined in the Recommendations for TLS Server - Certificates for GC public facing web services + The percentage of web-hosting services that strongly + enforce HTTPS - Obtain the configuration guidance for the appropriate - endpoints (e.g., web server, network/security appliances, - etc.) and implement recommended configurations. + The percentage of internet-facing services that have a + DMARC policy of at least p=”none” - + - - - - - Frequently Asked Questions - - - - {/* 1 */} - It is not clear to me why a domain has failed? - - - - Please contact{' '} - - TBS Cyber Security - {' '} - for help. - - - + + + These metrics are an important first step in securing your + services and should be treated as minimum requirements. + Further metrics are available in your organization's domain + list. + + - {/* 2 */} - How can I edit my domain list? - - - - Please direct all updates to TBS Cyber Security. - - - + Tracker results refresh every 24 hours. - {/* 3 */} + + + {/* 4 */} + + + Develop a prioritized schedule to address any failings: + + - Why do other tools ( - - Hardenize - - ,{' '} - - SSL Labs - - , etc.) show positive results for a domain while Tracker shows - negative results? + Consider prioritizing websites and web services that exchange + Protected data. - - - - While other tools are useful to work alongside Tracker, - they do not specifically adhere to the configuration - requirements specified in the{' '} - - Email Management Service Configuration Requirements - {' '} - and the{' '} - - Web Site and Service Management Configuration - Requirements - - . For a list of allowed protocols, ciphers, and curves - review the{' '} - - ITSP.40.062 TLS guidance - - . - - - - {/* 4 */} - - What does it mean if a domain is “unreachable”? - - - - - By default our scanners check domains ending in “.gc.ca” - and “.canada.ca”. If your domain is outside that set, you - need to contact us to let us know. Send an email to TBS - Cyber Security to confirm your ownership of that domain. - - - - - Another possibility is that your domain is not internet - facing. - - - + + Where necessary adjust IT Plans and budget estimates where + work is expected. + - Where can I get a GC-approved TLS certificate? - - - - Options include contacting the{' '} - - SSC WebSSL services team - {' '} - and/or using{' '} - - Let's Encrypt - - . For more information, please refer to the guidance on{' '} - - Recommendations for TLS Server Certificates - - . - - - + + It is recommended that Shared Service Canada (SSC) partners + contact their SSC Service Delivery Manager to discuss action + plans and required steps to submit a request for change. + + + + + Obtain certificates from a GC-approved certificate source as + outlined in the Recommendations for TLS Server Certificates + for GC public facing web services + + + + + Obtain the configuration guidance for the appropriate + endpoints (e.g., web server, network/security appliances, + etc.) and implement recommended configurations. + - {/* 6 */} + + + + + Links to Review: + + - References: + Tracker: - - Domain Name System (DNS) Services Management - Configuration Requirements - Canada.ca - + Wiki + List of guidance tags + + + + + + + Web Security: + + + + - Email Management Services Configuration Requirements - - Canada.ca + Requirements:{' '} + + Web Sites and Services Management Configuration + Requirements + - + - + - Implementation guidance: email domain protection - (ITSP.40.065 v1.1) - Canadian Centre for Cyber Security + Implementation:{' '} + + Guidance on securely configuring network protocols + (ITSP.40.062) + - + + + + + + + Email Security: + + + + + + Requirements:{' '} + + Email Management Services Configuration Requirements + + + + + + + + Implementation:{' '} + + Implementation guidance: email domain protection + (ITSP.40.065 v1.1) + + + - - - - + + + + - + + Frequently Asked Questions + + + + + {/* 1 */} + + It is not clear to me why a domain has failed? + + + + Please contact{' '} + + TBS Cyber Security + {' '} + for help. + + + + + {/* 2 */} + + How can I edit my domain list? + + + + Admins of an organization can add domains to their list. + + + + + Requests for updates can be sent directly to{' '} + + TBS Cyber Security + + . + + + + + Only{' '} + + TBS Cyber Security + {' '} + can remove domains from your organization. Domains are only to + be removed from your list when 1) they no longer exist, + meaning they are deleted from the DNS returning an error code + of NX DOMAIN (domain name does not exist); or 2) if you have + identified that they do not belong to your organization. + + + + + {/* 3 */} + + + Why do other tools show positive results for a domain while + Tracker shows negative results? + + + + + While other tools are useful to work alongside Tracker, they + do not specifically adhere to the configuration requirements + specified in the{' '} + + Email Management Service Configuration Requirements + {' '} + and the{' '} + + Web Site and Service Management Configuration Requirements + + . For a list of allowed protocols, ciphers, and curves review + the{' '} + + ITSP.40.062 TLS guidance + + . + + + + + {/* 4 */} + + + What does it mean if a domain is “unreachable”? + + + + + By default our scanners check domains ending in “.gc.ca” and + “.canada.ca”. If your domain is outside that set, you need to + contact us to let us know. Send an email to{' '} + + TBS Cyber Security + {' '} + to confirm your ownership of that domain. + + + + + Another possibility is that your domain is not internet + facing. + + + + + {/* 5 */} + + Where can I get a GC-approved TLS certificate? + + + + Options include contacting the{' '} + + SSC WebSSL services team + {' '} + and/or using{' '} + + Let's Encrypt + + . For more information, please refer to the guidance on{' '} + + Recommendations for TLS Server Certificates + + . + + + + + + + Why does the guidance page not show the domain’s DKIM selectors + even though they exist? + + + + + Tracker does not automatically add selectors, so it is likely + that they are not in the system yet. More information can be + found above in Getting Started. + + + + + {/* 7 */} + + References: + + + + + Domain Name System (DNS) Services Management Configuration + Requirements - Canada.ca + + + + + + + Email Management Services Configuration Requirements - + Canada.ca + + + + + + + Implementation guidance: email domain protection + (ITSP.40.065 v1.1) - Canadian Centre for Cyber Security + + + + + + Mozilla SSL Configuration Generator + + + + + + Protect domains that do not send email - GOV.UK (www.gov.uk) + + + + + + + + For any questions or concerns, please contact{' '} diff --git a/frontend/src/app/__tests__/ReadGuidancePage.test.js b/frontend/src/app/__tests__/ReadGuidancePage.test.js index 86d226a012..0f9c919045 100644 --- a/frontend/src/app/__tests__/ReadGuidancePage.test.js +++ b/frontend/src/app/__tests__/ReadGuidancePage.test.js @@ -22,7 +22,7 @@ const i18n = setupI18n({ }, }) -describe('', () => { +describe('', () => { afterEach(cleanup) it('renders the page', async () => { @@ -45,6 +45,8 @@ describe('', () => { , ) - await waitFor(() => expect(getByText(/Read guidance/i))) + await waitFor(() => + expect(getByText('Getting Started')).toBeInTheDocument(), + ) }) }) diff --git a/frontend/src/components/InfoPanel.js b/frontend/src/components/InfoPanel.js index 33a0347c90..75c1d1d8f8 100644 --- a/frontend/src/components/InfoPanel.js +++ b/frontend/src/components/InfoPanel.js @@ -1,25 +1,44 @@ import React from 'react' import { any, bool, func, string } from 'prop-types' import { Trans } from '@lingui/macro' -import { Box, IconButton, Slide, Stack, Text } from '@chakra-ui/react' -import { ArrowDownIcon, ArrowUpIcon } from '@chakra-ui/icons' +import { + Box, + Drawer, + DrawerBody, + DrawerCloseButton, + DrawerContent, + DrawerFooter, + DrawerHeader, + DrawerOverlay, + IconButton, + Stack, + Text, +} from '@chakra-ui/react' +import { QuestionOutlineIcon } from '@chakra-ui/icons' export function InfoPanel({ isOpen, onToggle, children }) { + const btnRef = React.useRef() return ( - - - - {children} - - + + + + + + Glossary + + + + {children} + + + + + ) } @@ -35,25 +54,15 @@ export function InfoBox({ title, info }) { ) } -export function InfoButton({ isOpen, onToggle, ...props }) { +export function InfoButton({ onToggle, ...props }) { return ( - ) : ( - - ) - } - aria-label={isOpen ? 'Close glossary' : 'Open glossary'} - onClick={onToggle} - color="black" - bg="white" - borderColor="black" - borderWidth="2px" - isRound - my="2" {...props} + icon={} + aria-label="Open glossary" + variant="outline" + mx="2" + onClick={onToggle} /> ) } @@ -70,6 +79,5 @@ InfoBox.propTypes = { } InfoButton.propTypes = { - isOpen: bool, onToggle: func, } diff --git a/frontend/src/components/SearchBox.js b/frontend/src/components/SearchBox.js index d4899648e7..4892593c3d 100644 --- a/frontend/src/components/SearchBox.js +++ b/frontend/src/components/SearchBox.js @@ -15,6 +15,7 @@ import { Trans } from '@lingui/macro' import { ArrowDownIcon, ArrowUpIcon, SearchIcon } from '@chakra-ui/icons' import { RelayPaginationControls } from './RelayPaginationControls' import { array, bool, func, number, string } from 'prop-types' +import { InfoButton } from './InfoPanel' export function SearchBox({ selectedDisplayLimit, @@ -31,6 +32,7 @@ export function SearchBox({ resetToFirstPage, orderByOptions, placeholder, + onToggle, ...props }) { const orderIconName = @@ -81,6 +83,7 @@ export function SearchBox({ /> + )} - {fileName && } + {typeof onToggle !== 'undefined' && ( + + )} + {fileName && ( + + )} {headerGroups.map((headerGroup) => ( - + {headerGroup.headers.map((column) => ( - {nodes.map( - ({ id, timestamp, initiatedBy, action, target, reason }) => { - const formatTimestamp = (ts) => { - const dateTime = ts.split('T') - return dateTime[0] + ', ' + dateTime[1].substring(0, 5) + {nodes.map(({ id, timestamp, initiatedBy, action, target, reason }) => { + const formatTimestamp = (ts) => { + const dateTime = ts.split('T') + return dateTime[0] + ', ' + dateTime[1].substring(0, 5) + } + const resourceType = resourceFilters.find(({ value }) => target.resourceType.toUpperCase() === value) + action = actionFilters.find(({ value }) => action.toUpperCase() === value) + if (typeof reason !== 'undefined') { + if (reason === 'NONEXISTENT') { + reason = This domain no longer exists + } else if (reason === 'WRONG_ORG') { + reason = This domain does not belong to this organization } - const resourceType = resourceFilters.find( - ({ value }) => target.resourceType.toUpperCase() === value, - ) - action = actionFilters.find( - ({ value }) => action.toUpperCase() === value, - ) - if (typeof reason !== 'undefined') { - if (reason === 'NONEXISTENT') { - reason = This domain no longer exists - } else if (reason === 'WRONG_ORG') { - reason = ( - - This domain does not belong to this organization - - ) - } - } - return ( - - - - - - - - - - - ) - }, - )} + } + return ( + + + + + + + + + + + ) + })}
{row.cells.map((cell) => ( - + {cell.column.id === 'guidanceTag' ? ( cell?.value?.refLinks ? ( - + {cell.value.guidance} ) : ( @@ -172,7 +206,12 @@ export function TrackerTable({ ...props }) { {frontendPagination && ( - + { @@ -278,4 +317,5 @@ TrackerTable.propTypes = { manualFilters: bool, fileName: string, exportDataFunction: func, + onToggle: func, } diff --git a/frontend/src/components/__tests__/InfoPanel.test.js b/frontend/src/components/__tests__/InfoPanel.test.js index b5418d1093..f9288cef9d 100644 --- a/frontend/src/components/__tests__/InfoPanel.test.js +++ b/frontend/src/components/__tests__/InfoPanel.test.js @@ -19,7 +19,7 @@ const i18n = setupI18n({ describe('', () => { it('successfully renders with mocked data', async () => { - const isOpen = false + const isOpen = true const { getByText } = render( diff --git a/frontend/src/dmarc/DmarcByDomainPage.js b/frontend/src/dmarc/DmarcByDomainPage.js index 79903a2405..c25955863a 100644 --- a/frontend/src/dmarc/DmarcByDomainPage.js +++ b/frontend/src/dmarc/DmarcByDomainPage.js @@ -278,19 +278,32 @@ export default function DmarcByDomainPage() { )} - - - - - { - setSearchTerm(e.target.value) - resetToFirstPage() - }} + + + + + + { + setSearchTerm(e.target.value) + resetToFirstPage() + }} + /> + + + - + {tableDisplay} @@ -307,7 +320,6 @@ export default function DmarcByDomainPage() { previous={previous} isLoadingMore={isLoadingMore} /> - + + + + + + ) + const dataToCsv = (columns, data) => { let csvOutput = columns.map((column) => column.Header).join(',') data.forEach((entry) => { @@ -296,6 +376,7 @@ export default function DmarcReportPage() { exportDataFunction={() => dataToCsv(dkimFailureColumns[0].columns, dkimFailureNodes) } + onToggle={failDkimToggle} /> ) @@ -371,6 +452,7 @@ export default function DmarcReportPage() { exportDataFunction={() => dataToCsv(fullPassColumns[0].columns, fullPassNodes) } + onToggle={fullPassToggle} /> ) @@ -445,6 +527,7 @@ export default function DmarcReportPage() { exportDataFunction={() => dataToCsv(spfFailureColumns[0].columns, spfFailureNodes) } + onToggle={failSpfToggle} /> ) @@ -530,6 +613,7 @@ export default function DmarcReportPage() { exportDataFunction={() => dataToCsv(dmarcFailureColumns[0].columns, dmarcFailureNodes) } + onToggle={failDmarcToggle} /> ) @@ -630,61 +714,121 @@ export default function DmarcReportPage() { {tableDisplay} - - + + + {generalGlossary} + + + + + + + https://github.com/canada-ca/tracker/wiki/Guidance-Tags + + + + + {generalGlossary} + + + + + https://github.com/canada-ca/tracker/wiki/Guidance-Tags + + + + + {generalGlossary} + + + + https://github.com/canada-ca/tracker/wiki/Guidance-Tags + + + + + {generalGlossary} - - {domainList} @@ -202,7 +203,6 @@ export default function DomainsPage() { previous={previous} isLoadingMore={isLoadingMore} /> - ) diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index ed3bb5571e..14488d8fcf 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -69,7 +69,7 @@ msgstr "A domain may only be removed for one of the reasons below. For a domain msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" -#: src/dmarc/DmarcByDomainPage.js:334 +#: src/dmarc/DmarcByDomainPage.js:346 msgid "A more detailed breakdown of each domain can be found by clicking on its address in the first column." msgstr "A more detailed breakdown of each domain can be found by clicking on its address in the first column." @@ -199,6 +199,10 @@ msgstr "Admin Profile" msgid "Admin accounts must activate a multi-factor authentication option." msgstr "Admin accounts must activate a multi-factor authentication option." +#: src/app/ReadGuidancePage.js:399 +msgid "Admins of an organization can add domains to their list." +msgstr "Admins of an organization can add domains to their list." + #: src/admin/SuperAdminUserList.js:354 msgid "Affiliations:" msgstr "Affiliations:" @@ -287,7 +291,7 @@ msgstr "An error occurred while verifying your phone number." msgid "An error occurred." msgstr "An error occurred." -#: src/app/ReadGuidancePage.js:321 +#: src/app/ReadGuidancePage.js:505 msgid "Another possibility is that your domain is not internet facing." msgstr "Another possibility is that your domain is not internet facing." @@ -300,8 +304,8 @@ msgid "Any products or related services provided to you by TBS are and will rema msgstr "Any products or related services provided to you by TBS are and will remain the intellectual property of the Government of Canada." #: src/app/ReadGuidancePage.js:121 -msgid "Application Portfolio Management (APM) systems; and" -msgstr "Application Portfolio Management (APM) systems; and" +#~ msgid "Application Portfolio Management (APM) systems; and" +#~ msgstr "Application Portfolio Management (APM) systems; and" #: src/organizationDetails/OrganizationDomains.js:237 msgid "Apply" @@ -369,7 +373,7 @@ msgstr "Back" #~ msgid "Based on the assessment, and using the <0>HTTPS Everywhere Guidance Wiki, the following activities may be required:" #~ msgstr "Based on the assessment, and using the <0>HTTPS Everywhere Guidance Wiki, the following activities may be required:" -#: src/app/ReadGuidancePage.js:83 +#: src/app/ReadGuidancePage.js:66 msgid "Below are steps on how government organizations can leverage the Tracker platform:" msgstr "Below are steps on how government organizations can leverage the Tracker platform:" @@ -383,16 +387,20 @@ msgid "Blocked" msgstr "Blocked" #: src/app/ReadGuidancePage.js:126 -msgid "Business units within your organization." -msgstr "Business units within your organization." +#~ msgid "Business units within your organization." +#~ msgstr "Business units within your organization." #: src/termsConditions/TermsConditionsPage.js:26 msgid "By accessing, browsing, or using our website or our services, you acknowledge that you have read, understood, and agree to be bound by these Terms and Conditions, and to comply with all applicable laws and regulations. We recommend that you review all Terms and Conditions periodically to understand any updates or changes that may affect you. If you do not agree to these Terms and Conditions, please refrain from using our website, products and services." msgstr "By accessing, browsing, or using our website or our services, you acknowledge that you have read, understood, and agree to be bound by these Terms and Conditions, and to comply with all applicable laws and regulations. We recommend that you review all Terms and Conditions periodically to understand any updates or changes that may affect you. If you do not agree to these Terms and Conditions, please refrain from using our website, products and services." +#: src/app/ReadGuidancePage.js:491 +msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to <0>TBS Cyber Security to confirm your ownership of that domain." +msgstr "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to <0>TBS Cyber Security to confirm your ownership of that domain." + #: src/app/ReadGuidancePage.js:313 -msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." -msgstr "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." +#~ msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." +#~ msgstr "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." #: src/guidance/ScanDetails.js:102 #~ msgid "CCS Injection Vulnerability:" @@ -554,6 +562,10 @@ msgstr "Confirm removal of domain:" #~ msgid "Confirm removal of user:" #~ msgstr "Confirm removal of user:" +#: src/app/ReadGuidancePage.js:216 +msgid "Consider prioritizing websites and web services that exchange Protected data." +msgstr "Consider prioritizing websites and web services that exchange Protected data." + #: src/guidance/WebConnectionResults.js:99 msgid "Connection Results" msgstr "Connection Results" @@ -666,25 +678,25 @@ msgstr "Curves Status" msgid "DKIM" msgstr "DKIM" -#: src/dmarc/DmarcReportPage.js:205 +#: src/dmarc/DmarcReportPage.js:208 msgid "DKIM Aligned" msgstr "DKIM Aligned" -#: src/dmarc/DmarcReportPage.js:169 +#: src/dmarc/DmarcReportPage.js:172 msgid "DKIM Domains" msgstr "DKIM Domains" -#: src/dmarc/DmarcReportPage.js:245 +#: src/dmarc/DmarcReportPage.js:325 msgid "DKIM Failure Table" msgstr "DKIM Failure Table" -#: src/dmarc/DmarcReportPage.js:256 -#: src/dmarc/DmarcReportPage.js:291 -#: src/dmarc/DmarcReportPage.js:560 +#: src/dmarc/DmarcReportPage.js:336 +#: src/dmarc/DmarcReportPage.js:371 +#: src/dmarc/DmarcReportPage.js:644 msgid "DKIM Failures by IP Address" msgstr "DKIM Failures by IP Address" -#: src/dmarc/DmarcReportPage.js:209 +#: src/dmarc/DmarcReportPage.js:212 msgid "DKIM Results" msgstr "DKIM Results" @@ -692,7 +704,7 @@ msgstr "DKIM Results" msgid "DKIM Selector" msgstr "DKIM Selector" -#: src/dmarc/DmarcReportPage.js:173 +#: src/dmarc/DmarcReportPage.js:176 msgid "DKIM Selectors" msgstr "DKIM Selectors" @@ -726,13 +738,13 @@ msgstr "DMARC Configuration Summary" msgid "DMARC Configured" msgstr "DMARC Configured" -#: src/dmarc/DmarcReportPage.js:477 +#: src/dmarc/DmarcReportPage.js:560 msgid "DMARC Failure Table" msgstr "DMARC Failure Table" -#: src/dmarc/DmarcReportPage.js:488 -#: src/dmarc/DmarcReportPage.js:525 -#: src/dmarc/DmarcReportPage.js:566 +#: src/dmarc/DmarcReportPage.js:571 +#: src/dmarc/DmarcReportPage.js:608 +#: src/dmarc/DmarcReportPage.js:650 msgid "DMARC Failures by IP Address" msgstr "DMARC Failures by IP Address" @@ -773,6 +785,7 @@ msgstr "DMARC Summaries" #~ msgid "DMARC phase summary" #~ msgstr "DMARC phase summary" +#: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." #~ msgstr "DMARC record could not be found during the scan." @@ -841,13 +854,21 @@ msgstr "Deploy SPF records for all domains;" msgid "Deploy initial DMARC records with policy of none; and" msgstr "Deploy initial DMARC records with policy of none; and" +#: src/dmarc/DmarcReportPage.js:255 +msgid "Details for a given guidance tag can be found on the wiki, see below." +msgstr "Details for a given guidance tag can be found on the wiki, see below." + #: src/app/ReadGuidancePage.js:109 #~ msgid "Develop a prioritized implementation schedule for each of the affected websites and web services, following the recommended prioritization approach in the ITPIN:" #~ msgstr "Develop a prioritized implementation schedule for each of the affected websites and web services, following the recommended prioritization approach in the ITPIN:" -#: src/app/ReadGuidancePage.js:175 -msgid "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." -msgstr "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." +#: src/app/ReadGuidancePage.js:358 +#~ msgid "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." +#~ msgstr "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." + +#: src/app/ReadGuidancePage.js:211 +msgid "Develop a prioritized schedule to address any failings:" +msgstr "Develop a prioritized schedule to address any failings:" #: src/admin/SuperAdminUserList.js:149 #: src/components/fields/DisplayNameField.js:14 @@ -867,7 +888,7 @@ msgstr "Display name cannot be empty" msgid "Displays the Name of the organization, its acronym, and a blue check mark if it is a verified organization." msgstr "Displays the Name of the organization, its acronym, and a blue check mark if it is a verified organization." -#: src/dmarc/DmarcReportPage.js:213 +#: src/dmarc/DmarcReportPage.js:216 msgid "Disposition" msgstr "Disposition" @@ -885,7 +906,7 @@ msgstr "Domain" msgid "Domain List" msgstr "Domain List" -#: src/app/ReadGuidancePage.js:366 +#: src/app/ReadGuidancePage.js:566 msgid "Domain Name System (DNS) Services Management Configuration Requirements - Canada.ca" msgstr "Domain Name System (DNS) Services Management Configuration Requirements - Canada.ca" @@ -902,6 +923,10 @@ msgstr "Domain URL:" msgid "Domain added" msgstr "Domain added" +#: src/dmarc/DmarcReportPage.js:227 +msgid "Domain from Simple Mail Transfer Protocol (SMTP) banner message." +msgstr "Domain from Simple Mail Transfer Protocol (SMTP) banner message." + #: src/admin/AdminDomains.js:115 msgid "Domain removed" msgstr "Domain removed" @@ -939,6 +964,14 @@ msgstr "Domain:" msgid "Domains" msgstr "Domains" +#: src/app/ReadGuidancePage.js:144 +msgid "Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." +msgstr "Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." + +#: src/dmarc/DmarcReportPage.js:247 +msgid "Domains used for SPF validation." +msgstr "Domains used for SPF validation." + #: src/auth/SignInPage.js:193 msgid "Don't have an account? <0>Sign up" msgstr "Don't have an account? <0>Sign up" @@ -951,6 +984,14 @@ msgstr "Don't have an account? <0>Sign up" msgid "EQUALS" msgstr "EQUALS" +#: src/app/ReadGuidancePage.js:115 +#~ msgid "Each organization’s domain list should include every internet-facing service. It is the responsibility of org admins to manage the current list and identify new domains to add." +#~ msgstr "Each organization’s domain list should include every internet-facing service. It is the responsibility of org admins to manage the current list and identify new domains to add." + +#: src/app/ReadGuidancePage.js:122 +msgid "Each organization’s domain list should include every internet-facing service. It is the responsibility of organization admins to manage the current list and identify new domains to add." +msgstr "Each organization’s domain list should include every internet-facing service. It is the responsibility of organization admins to manage the current list and identify new domains to add." + #: src/user/EditableUserDisplayName.js:109 #: src/user/EditableUserEmail.js:109 #: src/user/EditableUserPassword.js:112 @@ -994,7 +1035,7 @@ msgstr "Email" msgid "Email Guidance" msgstr "Email Guidance" -#: src/app/ReadGuidancePage.js:381 +#: src/app/ReadGuidancePage.js:581 msgid "Email Management Services Configuration Requirements - Canada.ca" msgstr "Email Management Services Configuration Requirements - Canada.ca" @@ -1002,6 +1043,10 @@ msgstr "Email Management Services Configuration Requirements - Canada.ca" msgid "Email Scan Results" msgstr "Email Scan Results" +#: src/app/ReadGuidancePage.js:323 +msgid "Email Security:" +msgstr "Email Security:" + #: src/auth/ForgotPasswordPage.js:38 msgid "Email Sent" msgstr "Email Sent" @@ -1098,7 +1143,7 @@ msgstr "Enter two factor code" msgid "Enter your user account's verified email address and we will send you a password reset link." msgstr "Enter your user account's verified email address and we will send you a password reset link." -#: src/dmarc/DmarcReportPage.js:164 +#: src/dmarc/DmarcReportPage.js:167 msgid "Envelope From" msgstr "Envelope From" @@ -1116,8 +1161,8 @@ msgid "Export to CSV" msgstr "Export to CSV" #: src/app/ReadGuidancePage.js:31 -msgid "FAQ" -msgstr "FAQ" +#~ msgid "FAQ" +#~ msgstr "FAQ" #: src/dmarc/DmarcReportPage.js:126 #: src/dmarc/DmarcReportPage.js:127 @@ -1125,27 +1170,27 @@ msgstr "FAQ" msgid "Fail" msgstr "Fail" -#: src/dmarc/DmarcReportPage.js:120 -#: src/dmarc/DmarcReportPage.js:121 +#: src/dmarc/DmarcReportPage.js:123 +#: src/dmarc/DmarcReportPage.js:124 msgid "Fail DKIM" msgstr "Fail DKIM" #: src/dmarc/DmarcByDomainPage.js:156 -#: src/dmarc/DmarcByDomainPage.js:326 +#: src/dmarc/DmarcByDomainPage.js:338 msgid "Fail DKIM %" msgstr "Fail DKIM %" -#: src/dmarc/DmarcReportPage.js:123 -#: src/dmarc/DmarcReportPage.js:124 +#: src/dmarc/DmarcReportPage.js:126 +#: src/dmarc/DmarcReportPage.js:127 msgid "Fail SPF" msgstr "Fail SPF" #: src/dmarc/DmarcByDomainPage.js:163 -#: src/dmarc/DmarcByDomainPage.js:322 +#: src/dmarc/DmarcByDomainPage.js:334 msgid "Fail SPF %" msgstr "Fail SPF %" -#: src/dmarc/DmarcReportPage.js:571 +#: src/dmarc/DmarcReportPage.js:655 msgid "Fake email domain blocks (reject + quarantine):" msgstr "Fake email domain blocks (reject + quarantine):" @@ -1182,7 +1227,7 @@ msgstr "Filters:" #~ msgid "For any questions or concerns related to the ITPIN and related implementation guidance, contact TBS Cybersecurity." #~ msgstr "For any questions or concerns related to the ITPIN and related implementation guidance, contact TBS Cybersecurity." -#: src/app/ReadGuidancePage.js:410 +#: src/app/ReadGuidancePage.js:622 msgid "For any questions or concerns, please contact <0>TBS Cyber Security ." msgstr "For any questions or concerns, please contact <0>TBS Cyber Security ." @@ -1227,27 +1272,27 @@ msgstr "Forgot your password?" msgid "French" msgstr "French" -#: src/app/ReadGuidancePage.js:215 +#: src/app/ReadGuidancePage.js:371 msgid "Frequently Asked Questions" msgstr "Frequently Asked Questions" #: src/dmarc/DmarcByDomainPage.js:170 -#: src/dmarc/DmarcByDomainPage.js:330 +#: src/dmarc/DmarcByDomainPage.js:342 msgid "Full Fail %" msgstr "Full Fail %" #: src/dmarc/DmarcByDomainPage.js:149 -#: src/dmarc/DmarcByDomainPage.js:318 +#: src/dmarc/DmarcByDomainPage.js:330 msgid "Full Pass %" msgstr "Full Pass %" -#: src/dmarc/DmarcReportPage.js:323 +#: src/dmarc/DmarcReportPage.js:404 msgid "Fully Aligned Table" msgstr "Fully Aligned Table" -#: src/dmarc/DmarcReportPage.js:334 -#: src/dmarc/DmarcReportPage.js:366 -#: src/dmarc/DmarcReportPage.js:557 +#: src/dmarc/DmarcReportPage.js:415 +#: src/dmarc/DmarcReportPage.js:447 +#: src/dmarc/DmarcReportPage.js:641 msgid "Fully Aligned by IP Address" msgstr "Fully Aligned by IP Address" @@ -1259,17 +1304,27 @@ msgstr "Further details for each organization can be found by clicking on its ro #~ msgid "General Public" #~ msgstr "General Public" -#: src/domains/DomainsPage.js:125 +#: src/app/ReadGuidancePage.js:21 +msgid "Getting Started" +msgstr "Getting Started" + +#: src/app/ReadGuidancePage.js:28 +#~ msgid "Getting Started Using Tracker" +#~ msgstr "Getting Started Using Tracker" + +#: src/app/ReadGuidancePage.js:75 +msgid "Getting an Account:" +msgstr "Getting an Account:" + +#: src/domains/DomainsPage.js:150 msgid "Getting domain statuses" msgstr "Getting domain statuses" -#: src/dmarc/DmarcByDomainPage.js:290 -#: src/domains/DomainsPage.js:113 -#: src/organizations/Organizations.js:118 -#~ msgid "Glossary" -#~ msgstr "Glossary" +#: src/components/InfoPanel.js:32 +msgid "Glossary" +msgstr "Glossary" -#: src/components/TrackerTable.js:215 +#: src/components/TrackerTable.js:254 msgid "Go to page:" msgstr "Go to page:" @@ -1439,6 +1494,11 @@ msgstr "Home" #~ msgid "Horizontal View" #~ msgstr "Horizontal View" +#: src/dmarc/DmarcReportPage.js:243 +msgid "Host from reverse DNS of source IP address." +msgstr "Host from reverse DNS of source IP address." + +#: src/app/ReadGuidancePage.js:396 #: src/guidance/WebTLSResults.js:247 msgid "Hostname Matches" msgstr "Hostname Matches" @@ -1484,13 +1544,21 @@ msgstr "Identify all authorized senders;" msgid "Identify all domains and subdomains used to send mail;" msgstr "Identify all domains and subdomains used to send mail;" +#: src/app/ReadGuidancePage.js:80 +msgid "Identify any current affiliated Tracker users within your organization and develop a plan with them." +msgstr "Identify any current affiliated Tracker users within your organization and develop a plan with them." + #: src/app/ReadGuidancePage.js:36 #~ msgid "Identify key resources required to act as central point(s) of contact with TBS and the HTTPS Community of Practice." #~ msgstr "Identify key resources required to act as central point(s) of contact with TBS and the HTTPS Community of Practice." -#: src/app/ReadGuidancePage.js:91 -msgid "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." -msgstr "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." +#: src/app/ReadGuidancePage.js:315 +#~ msgid "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." +#~ msgstr "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." + +#: src/app/ReadGuidancePage.js:155 +msgid "If a domain is no longer in use but still exists on the DNS, it is still vulnerable to email spoofing attacks, where an attacker can send an email that appears to be coming from your domain." +msgstr "If a domain is no longer in use but still exists on the DNS, it is still vulnerable to email spoofing attacks, where an attacker can send an email that appears to be coming from your domain." #: src/termsConditions/TermsConditionsPage.js:392 msgid "If at any time you or your representatives wish to adjust or cancel these services, please" @@ -1517,11 +1585,15 @@ msgstr "If you believe this was caused by a problem with Tracker, please <0>Repo msgid "Immediately" msgstr "Immediately" +#: src/app/ReadGuidancePage.js:88 +msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." +msgstr "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." + #: src/guidance/WebGuidance.js:19 #~ msgid "Implementation" #~ msgstr "Implementation" -#: src/app/ReadGuidancePage.js:396 +#: src/app/ReadGuidancePage.js:596 msgid "Implementation guidance: email domain protection (ITSP.40.065 v1.1) - Canadian Centre for Cyber Security" msgstr "Implementation guidance: email domain protection (ITSP.40.065 v1.1) - Canadian Centre for Cyber Security" @@ -1529,6 +1601,14 @@ msgstr "Implementation guidance: email domain protection (ITSP.40.065 v1.1) - Ca #~ msgid "Implementation:" #~ msgstr "Implementation:" +#: src/app/ReadGuidancePage.js:302 +msgid "Implementation: <0>Guidance on securely configuring network protocols (ITSP.40.062)" +msgstr "Implementation: <0>Guidance on securely configuring network protocols (ITSP.40.062)" + +#: src/app/ReadGuidancePage.js:346 +msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" +msgstr "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" + #: src/summaries/SummaryGroup.js:58 msgid "Implemented" msgstr "Implemented" @@ -1726,6 +1806,15 @@ msgstr "Invalid email" msgid "Invite User" msgstr "Invite User" +#: src/dmarc/DmarcReportPage.js:267 +msgid "Is DKIM aligned. Can be true or false." +msgstr "Is DKIM aligned. Can be true or false." + +#: src/dmarc/DmarcReportPage.js:259 +msgid "Is SPF aligned. Can be true or false." +msgstr "Is SPF aligned. Can be true or false." + +#: src/app/ReadGuidancePage.js:378 #: src/guidance/WebTLSResults.js:383 msgid "Issuer:" msgstr "Issuer:" @@ -1738,12 +1827,12 @@ msgstr "It is not clear to me why a domain has failed?" #~ msgid "It is recommended that SSC partners contact their SSC Service Delivery Manager to discuss the departmental action plan and required steps to submit a request for change." #~ msgstr "It is recommended that SSC partners contact their SSC Service Delivery Manager to discuss the departmental action plan and required steps to submit a request for change." -#: src/app/ReadGuidancePage.js:188 +#: src/app/ReadGuidancePage.js:228 msgid "It is recommended that Shared Service Canada (SSC) partners contact their SSC Service Delivery Manager to discuss action plans and required steps to submit a request for change." msgstr "It is recommended that Shared Service Canada (SSC) partners contact their SSC Service Delivery Manager to discuss action plans and required steps to submit a request for change." #: src/components/RelayPaginationControls.js:33 -#: src/components/TrackerTable.js:243 +#: src/components/TrackerTable.js:282 msgid "Items per page:" msgstr "Items per page:" @@ -1815,6 +1904,14 @@ msgstr "Let's get you set up so you can verify your account information and begi msgid "Limitation of Liability" msgstr "Limitation of Liability" +#: src/app/ReadGuidancePage.js:252 +msgid "Links to Review:" +msgstr "Links to Review:" + +#: src/app/ReadGuidancePage.js:271 +msgid "List of guidance tags" +msgstr "List of guidance tags" + #: src/dmarc/DmarcByDomainPage.js:268 msgid "Loading Data..." msgstr "Loading Data..." @@ -1831,6 +1928,10 @@ msgstr "Login" msgid "Login to your account" msgstr "Login to your account" +#: src/app/ReadGuidancePage.js:117 +msgid "Managing Your Domains:" +msgstr "Managing Your Domains:" + #: src/guidance/EmailGuidance.js:178 msgid "Lookups:" msgstr "Lookups:" @@ -1864,6 +1965,10 @@ msgstr "Monitor DMARC reports and correct misconfigurations." msgid "Monitor DMARC reports;" msgstr "Monitor DMARC reports;" +#: src/app/ReadGuidancePage.js:604 +msgid "Mozilla SSL Configuration Generator" +msgstr "Mozilla SSL Configuration Generator" + #: src/guidance/WebTLSResults.js:361 msgid "More details" msgstr "More details" @@ -2014,23 +2119,23 @@ msgstr "No activity logs" msgid "No current phone number" msgstr "No current phone number" -#: src/dmarc/DmarcReportPage.js:311 +#: src/dmarc/DmarcReportPage.js:392 msgid "No data for the DKIM Failures by IP Address table" msgstr "No data for the DKIM Failures by IP Address table" -#: src/dmarc/DmarcReportPage.js:545 +#: src/dmarc/DmarcReportPage.js:629 msgid "No data for the DMARC Failures by IP Address table" msgstr "No data for the DMARC Failures by IP Address table" -#: src/dmarc/DmarcReportPage.js:153 +#: src/dmarc/DmarcReportPage.js:156 msgid "No data for the DMARC yearly report graph" msgstr "No data for the DMARC yearly report graph" -#: src/dmarc/DmarcReportPage.js:386 +#: src/dmarc/DmarcReportPage.js:468 msgid "No data for the Fully Aligned by IP Address table" msgstr "No data for the Fully Aligned by IP Address table" -#: src/dmarc/DmarcReportPage.js:460 +#: src/dmarc/DmarcReportPage.js:543 msgid "No data for the SPF Failures by IP Address table" msgstr "No data for the SPF Failures by IP Address table" @@ -2126,7 +2231,7 @@ msgstr "November" #~ msgid "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC Public Facing Web Services" #~ msgstr "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC Public Facing Web Services" -#: src/app/ReadGuidancePage.js:196 +#: src/app/ReadGuidancePage.js:235 msgid "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC public facing web services" msgstr "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC public facing web services" @@ -2134,7 +2239,7 @@ msgstr "Obtain certificates from a GC-approved certificate source as outlined in #~ msgid "Obtain the configuration guidance for the appropriate endpoints (e.g. web server, network/security appliances, etc.) and implement recommended configurations to support HTTPS." #~ msgstr "Obtain the configuration guidance for the appropriate endpoints (e.g. web server, network/security appliances, etc.) and implement recommended configurations to support HTTPS." -#: src/app/ReadGuidancePage.js:203 +#: src/app/ReadGuidancePage.js:242 msgid "Obtain the configuration guidance for the appropriate endpoints (e.g., web server, network/security appliances, etc.) and implement recommended configurations." msgstr "Obtain the configuration guidance for the appropriate endpoints (e.g., web server, network/security appliances, etc.) and implement recommended configurations." @@ -2147,7 +2252,15 @@ msgstr "October" msgid "Old Value:" msgstr "Old Value:" -#: src/app/ReadGuidancePage.js:332 +#: src/app/ReadGuidancePage.js:103 +msgid "Once access is given to your department by the TBS Cyber team, they will be able to invite and manage other users within the organization and manage the domain list." +msgstr "Once access is given to your department by the TBS Cyber team, they will be able to invite and manage other users within the organization and manage the domain list." + +#: src/app/ReadGuidancePage.js:416 +msgid "Only <0>TBS Cyber Security can remove domains from your organization. Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." +msgstr "Only <0>TBS Cyber Security can remove domains from your organization. Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." + +#: src/app/ReadGuidancePage.js:517 msgid "Options include contacting the <0>SSC WebSSL services team and/or using <1>Let's Encrypt. For more information, please refer to the guidance on <2>Recommendations for TLS Server Certificates." msgstr "Options include contacting the <0>SSC WebSSL services team and/or using <1>Let's Encrypt. For more information, please refer to the guidance on <2>Recommendations for TLS Server Certificates." @@ -2209,7 +2322,7 @@ msgstr "Organizations" msgid "PROD" msgstr "PROD" -#: src/components/TrackerTable.js:231 +#: src/components/TrackerTable.js:270 msgid "Page {0} of {1}" msgstr "Page {0} of {1}" @@ -2269,8 +2382,8 @@ msgstr "Passwords must match" #~ msgstr "Perform an inventory of all departmental domains and subdomains. Sources of information include:" #: src/app/ReadGuidancePage.js:106 -msgid "Perform an inventory of all organizational domains and subdomains. Sources of information include:" -msgstr "Perform an inventory of all organizational domains and subdomains. Sources of information include:" +#~ msgid "Perform an inventory of all organizational domains and subdomains. Sources of information include:" +#~ msgstr "Perform an inventory of all organizational domains and subdomains. Sources of information include:" #: src/app/ReadGuidancePage.js:189 #~ msgid "Perform another assessment of the applicable domains and sub-domains to confirm that the configuration has been updated and that HTTPS is enforced in accordance with the ITPIN. Results will appear in the Tracker Dashboard within 24 hours." @@ -2305,13 +2418,13 @@ msgstr "Please allow up to 24 hours for summaries to reflect any changes." msgid "Please choose your preferred language" msgstr "Please choose your preferred language" -#: src/app/ReadGuidancePage.js:224 +#: src/app/ReadGuidancePage.js:381 msgid "Please contact <0>TBS Cyber Security for help." msgstr "Please contact <0>TBS Cyber Security for help." #: src/app/ReadGuidancePage.js:242 -msgid "Please direct all updates to TBS Cyber Security." -msgstr "Please direct all updates to TBS Cyber Security." +#~ msgid "Please direct all updates to TBS Cyber Security." +#~ msgstr "Please direct all updates to TBS Cyber Security." #: src/utilities/fieldRequirements.js:27 msgid "Please enter your current password." @@ -2325,6 +2438,10 @@ msgstr "Please enter your two factor code below." msgid "Please follow the link in order to verify your account and start using Tracker." msgstr "Please follow the link in order to verify your account and start using Tracker." +#: src/dmarc/DmarcReportPage.js:235 +msgid "Pointer to a DKIM public key record in DNS." +msgstr "Pointer to a DKIM public key record in DNS." + #: src/domains/DomainCard.js:54 #: src/domains/DomainsPage.js:123 #: src/organizationDetails/OrganizationDomains.js:119 @@ -2383,6 +2500,10 @@ msgstr "Privacy Notice Statement" msgid "Prod" msgstr "Prod" +#: src/app/ReadGuidancePage.js:612 +msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" +msgstr "Protect domains that do not send email - GOV.UK (www.gov.uk)" + #: src/domains/DomainCard.js:188 #: src/domains/DomainsPage.js:161 #: src/guidance/WebTLSResults.js:52 @@ -2396,9 +2517,13 @@ msgstr "Protocols" msgid "Protocols Status" msgstr "Protocols Status" +#: src/app/ReadGuidancePage.js:330 +#~ msgid "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The <0>TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." +#~ msgstr "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The <0>TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." + #: src/app/ReadGuidancePage.js:132 -msgid "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." -msgstr "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." +#~ msgid "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." +#~ msgstr "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." #: src/app/ReadGuidancePage.js:68 #~ msgid "Provide an up-to-date list of all domain and sub-domains of the publicly-accessible websites and web services to <0>TBS Cybersecurity." @@ -2421,9 +2546,9 @@ msgstr "Province (FR)" msgid "Province:" msgstr "Province:" -#: src/app/ReadGuidancePage.js:38 -msgid "Read Guidance" -msgstr "Read Guidance" +#: src/app/ReadGuidancePage.js:259 +#~ msgid "Read Guidance" +#~ msgstr "Read Guidance" #: src/app/App.js:193 msgid "Read guidance" @@ -2504,7 +2629,19 @@ msgstr "Request successfully sent to get all domain statuses - this may take a m msgid "Requested Scan" msgstr "Requested Scan" -#: src/app/App.js:187 +#: src/app/ReadGuidancePage.js:404 +msgid "Requests for updates can be sent directly to <0>TBS Cyber Security." +msgstr "Requests for updates can be sent directly to <0>TBS Cyber Security." + +#: src/app/ReadGuidancePage.js:328 +msgid "Requirements: <0>Email Management Services Configuration Requirements" +msgstr "Requirements: <0>Email Management Services Configuration Requirements" + +#: src/app/ReadGuidancePage.js:283 +msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" +msgstr "Requirements: <0>Web Sites and Services Management Configuration Requirements" + +#: src/app/App.js:220 msgid "Reset Password" msgstr "Reset Password" @@ -2569,25 +2706,25 @@ msgstr "SAN List:" msgid "SPF" msgstr "SPF" -#: src/dmarc/DmarcReportPage.js:197 +#: src/dmarc/DmarcReportPage.js:200 msgid "SPF Aligned" msgstr "SPF Aligned" -#: src/dmarc/DmarcReportPage.js:184 +#: src/dmarc/DmarcReportPage.js:187 msgid "SPF Domains" msgstr "SPF Domains" -#: src/dmarc/DmarcReportPage.js:398 +#: src/dmarc/DmarcReportPage.js:480 msgid "SPF Failure Table" msgstr "SPF Failure Table" -#: src/dmarc/DmarcReportPage.js:409 -#: src/dmarc/DmarcReportPage.js:440 -#: src/dmarc/DmarcReportPage.js:563 +#: src/dmarc/DmarcReportPage.js:491 +#: src/dmarc/DmarcReportPage.js:522 +#: src/dmarc/DmarcReportPage.js:647 msgid "SPF Failures by IP Address" msgstr "SPF Failures by IP Address" -#: src/dmarc/DmarcReportPage.js:201 +#: src/dmarc/DmarcReportPage.js:204 msgid "SPF Results" msgstr "SPF Results" @@ -2647,19 +2784,19 @@ msgstr "Scan Request" msgid "Scan of domain successfully requested" msgstr "Scan of domain successfully requested" -#: src/dmarc/DmarcReportPage.js:294 +#: src/dmarc/DmarcReportPage.js:374 msgid "Search DKIM Failing Items" msgstr "Search DKIM Failing Items" -#: src/dmarc/DmarcReportPage.js:528 +#: src/dmarc/DmarcReportPage.js:611 msgid "Search DMARC Failing Items" msgstr "Search DMARC Failing Items" -#: src/dmarc/DmarcReportPage.js:369 +#: src/dmarc/DmarcReportPage.js:450 msgid "Search Fully Aligned Items" msgstr "Search Fully Aligned Items" -#: src/dmarc/DmarcReportPage.js:443 +#: src/dmarc/DmarcReportPage.js:525 msgid "Search SPF Failing Items" msgstr "Search SPF Failing Items" @@ -2672,9 +2809,9 @@ msgid "Search by initiated by, resource name" msgstr "Search by initiated by, resource name" #: src/dmarc/DmarcByDomainPage.js:221 -#: src/dmarc/DmarcByDomainPage.js:287 -#: src/domains/DomainsPage.js:188 -#: src/organizationDetails/OrganizationDomains.js:317 +#: src/dmarc/DmarcByDomainPage.js:292 +#: src/domains/DomainsPage.js:228 +#: src/organizationDetails/OrganizationDomains.js:351 msgid "Search for a domain" msgstr "Search for a domain" @@ -2697,7 +2834,7 @@ msgstr "Search for an organization" #: src/admin/AdminDomains.js:252 #: src/admin/UserList.js:149 #: src/components/ReactTableGlobalFilter.js:36 -#: src/components/SearchBox.js:65 +#: src/components/SearchBox.js:67 msgid "Search:" msgstr "Search:" @@ -2764,12 +2901,12 @@ msgstr "Services" msgid "Services: {domainCount}" msgstr "Services: {domainCount}" -#: src/components/TrackerTable.js:256 +#: src/components/TrackerTable.js:295 msgid "Show {pageSize}" msgstr "Show {pageSize}" #: src/dmarc/DmarcByDomainPage.js:252 -#: src/dmarc/DmarcReportPage.js:622 +#: src/dmarc/DmarcReportPage.js:706 msgid "Showing data for period:" msgstr "Showing data for period:" @@ -2904,23 +3041,23 @@ msgstr "Shows the percentage of domains which have HTTPS configured and upgrade msgid "Shows the percentage of domains which have a valid DMARC policy configuration." msgstr "Shows the percentage of domains which have a valid DMARC policy configuration." -#: src/dmarc/DmarcByDomainPage.js:327 +#: src/dmarc/DmarcByDomainPage.js:339 msgid "Shows the percentage of emails from the domain that fail DKIM requirments, but pass SPF requirments." msgstr "Shows the percentage of emails from the domain that fail DKIM requirments, but pass SPF requirments." -#: src/dmarc/DmarcByDomainPage.js:323 +#: src/dmarc/DmarcByDomainPage.js:335 msgid "Shows the percentage of emails from the domain that fail SPF requirments, but pass DKIM requirments." msgstr "Shows the percentage of emails from the domain that fail SPF requirments, but pass DKIM requirments." -#: src/dmarc/DmarcByDomainPage.js:331 +#: src/dmarc/DmarcByDomainPage.js:343 msgid "Shows the percentage of emails from the domain that fail both SPF and DKIM requirments." msgstr "Shows the percentage of emails from the domain that fail both SPF and DKIM requirments." -#: src/dmarc/DmarcByDomainPage.js:319 +#: src/dmarc/DmarcByDomainPage.js:331 msgid "Shows the percentage of emails from the domain that have passed both SPF and DKIM requirments." msgstr "Shows the percentage of emails from the domain that have passed both SPF and DKIM requirments." -#: src/dmarc/DmarcByDomainPage.js:315 +#: src/dmarc/DmarcByDomainPage.js:327 msgid "Shows the total number of emails that have been sent by this domain during the selected time range." msgstr "Shows the total number of emails that have been sent by this domain during the selected time range." @@ -2966,11 +3103,11 @@ msgstr "Skip to main content" msgid "Slug:" msgstr "Slug:" -#: src/components/SearchBox.js:92 +#: src/components/SearchBox.js:95 msgid "Sort by:" msgstr "Sort by:" -#: src/dmarc/DmarcReportPage.js:159 +#: src/dmarc/DmarcReportPage.js:162 msgid "Source IP Address" msgstr "Source IP Address" @@ -3103,17 +3240,37 @@ msgid "Test" msgstr "Test" #: src/app/ReadGuidancePage.js:112 -msgid "The <0>Tracker platform" -msgstr "The <0>Tracker platform" +#~ msgid "The <0>Tracker platform" +#~ msgstr "The <0>Tracker platform" -#: src/app/ReadGuidancePage.js:42 +#: src/dmarc/DmarcReportPage.js:275 +msgid "The DMARC enforcement action that the receiver took, either none, quarantine, or reject." +msgstr "The DMARC enforcement action that the receiver took, either none, quarantine, or reject." + +#: src/app/ReadGuidancePage.js:26 msgid "The Government of Canada’s (GC) <0>Directive on Service and Digital provides expectations on how GC organizations are to manage their Information Technology (IT) services. The focus of the Tracker tool is to help organizations stay in compliance with the directives <1>Email Management Service Configuration Requirements and the directives <2>Web Site and Service Management Configuration Requirements." msgstr "The Government of Canada’s (GC) <0>Directive on Service and Digital provides expectations on how GC organizations are to manage their Information Technology (IT) services. The focus of the Tracker tool is to help organizations stay in compliance with the directives <1>Email Management Service Configuration Requirements and the directives <2>Web Site and Service Management Configuration Requirements." +#: src/dmarc/DmarcReportPage.js:223 +msgid "The IP address of sending server." +msgstr "The IP address of sending server." + +#: src/dmarc/DmarcReportPage.js:239 +msgid "The Total Messages from this sender." +msgstr "The Total Messages from this sender." + +#: src/dmarc/DmarcReportPage.js:251 +msgid "The address/domain used in the \"From\" field." +msgstr "The address/domain used in the \"From\" field." + #: src/termsConditions/TermsConditionsPage.js:287 msgid "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." msgstr "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." +#: src/dmarc/DmarcReportPage.js:231 +msgid "The domains used for DKIM validation." +msgstr "The domains used for DKIM validation." + #: src/dmarc/DmarcByDomainPage.js:312 #: src/domains/DomainsPage.js:153 #: src/organizationDetails/OrganizationDomains.js:280 @@ -3136,14 +3293,38 @@ msgstr "The material available on this web site is subject to the" msgid "The page you are looking for has moved or does not exist." msgstr "The page you are looking for has moved or does not exist." +#: src/app/ReadGuidancePage.js:187 +msgid "The percentage of internet-facing services that have a DMARC policy of at least p=”none”" +msgstr "The percentage of internet-facing services that have a DMARC policy of at least p=”none”" + +#: src/app/ReadGuidancePage.js:181 +msgid "The percentage of web-hosting services that strongly enforce HTTPS" +msgstr "The percentage of web-hosting services that strongly enforce HTTPS" + #: src/termsConditions/TermsConditionsPage.js:349 msgid "The reproduction is not represented as an official version of the materials reproduced, nor as having been made, in affiliation with or under the direction of TBS." msgstr "The reproduction is not represented as an official version of the materials reproduced, nor as having been made, in affiliation with or under the direction of TBS." +#: src/dmarc/DmarcReportPage.js:263 +msgid "The results of DKIM verification of the message. Can be pass, fail, neutral, soft-fail, temp-error, or perm-error." +msgstr "The results of DKIM verification of the message. Can be pass, fail, neutral, soft-fail, temp-error, or perm-error." + +#: src/dmarc/DmarcReportPage.js:271 +msgid "The results of DKIM verification of the message. Can be pass, fail, neutral, temp-error, or perm-error." +msgstr "The results of DKIM verification of the message. Can be pass, fail, neutral, temp-error, or perm-error." + +#: src/app/ReadGuidancePage.js:175 +msgid "The summary cards show two metrics that Tracker scans:" +msgstr "The summary cards show two metrics that Tracker scans:" + #: src/admin/UserListModal.js:114 msgid "The user's role has been successfully updated" msgstr "The user's role has been successfully updated" +#: src/app/ReadGuidancePage.js:196 +msgid "These metrics are an important first step in securing your services and should be treated as minimum requirements. Further metrics are available in your organization's domain list." +msgstr "These metrics are an important first step in securing your services and should be treated as minimum requirements. Further metrics are available in your organization's domain list." + #: src/termsConditions/TermsConditionsPage.js:412 msgid "These terms and conditions shall be governed by and interpreted under the laws of Canada, without regard for any choice of law rules. The courts of Canada shall have exclusive jurisdiction over all matters arising in relation to these terms and conditions." msgstr "These terms and conditions shall be governed by and interpreted under the laws of Canada, without regard for any choice of law rules. The courts of Canada shall have exclusive jurisdiction over all matters arising in relation to these terms and conditions." @@ -3214,9 +3395,13 @@ msgstr "To enable full app functionality and maximize your account's security, < msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "To maximize your account's security, <0>please activate a multi-factor authentication option." +#: src/app/ReadGuidancePage.js:132 +msgid "To receive DKIM scan results and guidance, you must add the DKIM selectors used for each domain. Organization administrators can add selectors in the “Admin Profile” by clicking the edit button of the domain for which they wish to add the selector. Common selectors to keep an for are “selector1”, and “selector2”." +msgstr "To receive DKIM scan results and guidance, you must add the DKIM selectors used for each domain. Organization administrators can add selectors in the “Admin Profile” by clicking the edit button of the domain for which they wish to add the selector. Common selectors to keep an for are “selector1”, and “selector2”." + #: src/dmarc/DmarcByDomainPage.js:142 -#: src/dmarc/DmarcByDomainPage.js:314 -#: src/dmarc/DmarcReportPage.js:177 +#: src/dmarc/DmarcByDomainPage.js:326 +#: src/dmarc/DmarcReportPage.js:180 msgid "Total Messages" msgstr "Total Messages" @@ -3242,6 +3427,14 @@ msgstr "Tracker HSTS and HTTPS results display incorrectly when a domain has a n msgid "Tracker account has been successfully closed." msgstr "Tracker account has been successfully closed." +#: src/app/ReadGuidancePage.js:545 +msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found above in Getting Started." +msgstr "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found above in Getting Started." + +#: src/app/ReadGuidancePage.js:579 +#~ msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." +#~ msgstr "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." + #: src/app/TopBanner.js:81 msgid "Tracker logo outline" msgstr "Tracker logo outline" @@ -3250,10 +3443,14 @@ msgstr "Tracker logo outline" msgid "Tracker logo text" msgstr "Tracker logo text" -#: src/app/ReadGuidancePage.js:169 +#: src/app/ReadGuidancePage.js:205 msgid "Tracker results refresh every 24 hours." msgstr "Tracker results refresh every 24 hours." +#: src/app/ReadGuidancePage.js:256 +msgid "Tracker:" +msgstr "Tracker:" + #: src/termsConditions/TermsConditionsPage.js:181 msgid "Trademarks Act" msgstr "Trademarks Act" @@ -3396,7 +3593,11 @@ msgstr "Unable to update your phone number, please try again." msgid "Unable to verify your phone number, please try again." msgstr "Unable to verify your phone number, please try again." -#: src/domains/DomainCard.js:85 +#: src/app/ReadGuidancePage.js:170 +msgid "Understanding Scan Metrics:" +msgstr "Understanding Scan Metrics:" + +#: src/domains/DomainCard.js:84 msgid "Unfavourited Domain" msgstr "Unfavourited Domain" @@ -3435,8 +3636,12 @@ msgid "Upgrade DMARC policy to reject (gradually increment enforcement from 25%t msgstr "Upgrade DMARC policy to reject (gradually increment enforcement from 25%to 100%); and" #: src/app/ReadGuidancePage.js:141 -msgid "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." -msgstr "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." +#~ msgid "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." +#~ msgstr "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." + +#: src/app/ReadGuidancePage.js:346 +#~ msgid "Use Tracker to monitor the domains and sub-domains of your organization." +#~ msgstr "Use Tracker to monitor the domains and sub-domains of your organization." #: src/termsConditions/TermsConditionsPage.js:191 msgid "Use of intellectual property in breach of this agreement may result in the termination of access to the Tracker website, product or services." @@ -3597,6 +3802,10 @@ msgstr "Web Guidance" msgid "Web Scan Results" msgstr "Web Scan Results" +#: src/app/ReadGuidancePage.js:278 +msgid "Web Security:" +msgstr "Web Security:" + #: src/guidance/ScanCard.js:56 #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Web Sites and Services Management Configuration Requirements Compliant" @@ -3630,11 +3839,11 @@ msgstr "Welcome, you are successfully signed in to your new account!" msgid "Welcome, you are successfully signed in!" msgstr "Welcome, you are successfully signed in!" -#: src/app/ReadGuidancePage.js:309 +#: src/app/ReadGuidancePage.js:487 msgid "What does it mean if a domain is “unreachable”?" msgstr "What does it mean if a domain is “unreachable”?" -#: src/app/ReadGuidancePage.js:329 +#: src/app/ReadGuidancePage.js:514 msgid "Where can I get a GC-approved TLS certificate?" msgstr "Where can I get a GC-approved TLS certificate?" @@ -3642,17 +3851,29 @@ msgstr "Where can I get a GC-approved TLS certificate?" #~ msgid "Where necessary adjust IT Plans and budget estimates for the FY where work is expected." #~ msgstr "Where necessary adjust IT Plans and budget estimates for the FY where work is expected." -#: src/app/ReadGuidancePage.js:182 +#: src/app/ReadGuidancePage.js:222 msgid "Where necessary adjust IT Plans and budget estimates where work is expected." msgstr "Where necessary adjust IT Plans and budget estimates where work is expected." -#: src/app/ReadGuidancePage.js:267 +#: src/app/ReadGuidancePage.js:441 msgid "While other tools are useful to work alongside Tracker, they do not specifically adhere to the configuration requirements specified in the <0>Email Management Service Configuration Requirements and the <1>Web Site and Service Management Configuration Requirements. For a list of allowed protocols, ciphers, and curves review the <2>ITSP.40.062 TLS guidance." msgstr "While other tools are useful to work alongside Tracker, they do not specifically adhere to the configuration requirements specified in the <0>Email Management Service Configuration Requirements and the <1>Web Site and Service Management Configuration Requirements. For a list of allowed protocols, ciphers, and curves review the <2>ITSP.40.062 TLS guidance." #: src/app/ReadGuidancePage.js:250 -msgid "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" -msgstr "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" +#~ msgid "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" +#~ msgstr "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" + +#: src/app/ReadGuidancePage.js:435 +msgid "Why do other tools show positive results for a domain while Tracker shows negative results?" +msgstr "Why do other tools show positive results for a domain while Tracker shows negative results?" + +#: src/app/ReadGuidancePage.js:539 +msgid "Why does the guidance page not show the domain’s DKIM selectors even though they exist?" +msgstr "Why does the guidance page not show the domain’s DKIM selectors even though they exist?" + +#: src/app/ReadGuidancePage.js:263 +msgid "Wiki" +msgstr "Wiki" #: src/guidance/WebConnectionResults.js:126 #: src/guidance/WebConnectionResults.js:166 @@ -3849,7 +4070,7 @@ msgstr "{buttonLabel}" msgid "{count} records..." msgstr "{count} records..." -#: src/dmarc/DmarcReportPage.js:101 +#: src/dmarc/DmarcReportPage.js:104 msgid "{domainSlug} does not support aggregate data" msgstr "{domainSlug} does not support aggregate data" @@ -3857,7 +4078,7 @@ msgstr "{domainSlug} does not support aggregate data" msgid "{editingDomainUrl} from {orgSlug} successfully updated to {0}" msgstr "{editingDomainUrl} from {orgSlug} successfully updated to {0}" -#: src/components/InfoPanel.js:33 +#: src/components/InfoPanel.js:52 msgid "{info}" msgstr "{info}" @@ -3865,7 +4086,7 @@ msgstr "{info}" #~ msgid "{label}" #~ msgstr "{label}" -#: src/components/InfoPanel.js:30 +#: src/components/InfoPanel.js:49 msgid "{title}" msgstr "{title}" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 03f3480d30..317a455afd 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -69,7 +69,7 @@ msgstr "Un domaine ne peut être supprimé que pour l'une des raisons ci-dessous msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "Une politique DMARC minimale de \"p=none\" avec au moins une adresse définie comme destinataire des rapports agrégés." -#: src/dmarc/DmarcByDomainPage.js:334 +#: src/dmarc/DmarcByDomainPage.js:346 msgid "A more detailed breakdown of each domain can be found by clicking on its address in the first column." msgstr "Une ventilation plus détaillée de chaque domaine peut être trouvée en cliquant sur son adresse dans la première colonne." @@ -199,6 +199,10 @@ msgstr "Profil de l'administrateur" msgid "Admin accounts must activate a multi-factor authentication option." msgstr "Les comptes administrateurs doivent activer une option d'authentification multifactorielle." +#: src/app/ReadGuidancePage.js:399 +msgid "Admins of an organization can add domains to their list." +msgstr "Les administrateurs d'une organisation peuvent ajouter des domaines à leur liste." + #: src/admin/SuperAdminUserList.js:354 msgid "Affiliations:" msgstr "Affiliations :" @@ -287,7 +291,7 @@ msgstr "Une erreur s'est produite lors de la mise à jour de votre numéro de t msgid "An error occurred." msgstr "Une erreur s'est produite." -#: src/app/ReadGuidancePage.js:321 +#: src/app/ReadGuidancePage.js:505 msgid "Another possibility is that your domain is not internet facing." msgstr "Il se peut aussi que votre domaine ne soit pas connecté à Internet." @@ -300,8 +304,8 @@ msgid "Any products or related services provided to you by TBS are and will rema msgstr "Tous les produits ou services connexes qui vous sont fournis par le SCT sont et demeureront la propriété intellectuelle du gouvernement du Canada." #: src/app/ReadGuidancePage.js:121 -msgid "Application Portfolio Management (APM) systems; and" -msgstr "les systèmes de gestion du portefeuille d’applications (GPA);" +#~ msgid "Application Portfolio Management (APM) systems; and" +#~ msgstr "les systèmes de gestion du portefeuille d’applications (GPA);" #: src/organizationDetails/OrganizationDomains.js:237 msgid "Apply" @@ -369,7 +373,7 @@ msgstr "Retour" #~ msgid "Based on the assessment, and using the <0>HTTPS Everywhere Guidance Wiki, the following activities may be required:" #~ msgstr "Sur la base de l'évaluation, et en utilisant le <0>HTTPS Everywhere Guidance Wiki, les activités suivantes peuvent être requises :" -#: src/app/ReadGuidancePage.js:83 +#: src/app/ReadGuidancePage.js:66 msgid "Below are steps on how government organizations can leverage the Tracker platform:" msgstr "Voici la façon dont les organisations gouvernementales peuvent tirer parti de la plateforme Suivi:" @@ -383,16 +387,20 @@ msgid "Blocked" msgstr "Bloqué" #: src/app/ReadGuidancePage.js:126 -msgid "Business units within your organization." -msgstr "les unités fonctionnelles au sein de votre organisation." +#~ msgid "Business units within your organization." +#~ msgstr "les unités fonctionnelles au sein de votre organisation." #: src/termsConditions/TermsConditionsPage.js:26 msgid "By accessing, browsing, or using our website or our services, you acknowledge that you have read, understood, and agree to be bound by these Terms and Conditions, and to comply with all applicable laws and regulations. We recommend that you review all Terms and Conditions periodically to understand any updates or changes that may affect you. If you do not agree to these Terms and Conditions, please refrain from using our website, products and services." msgstr "En accédant, en naviguant ou en utilisant notre site web ou nos services, vous reconnaissez avoir lu, compris et accepté d'être lié par les présentes conditions générales, et de vous conformer à toutes les lois et réglementations applicables. Nous vous recommandons de consulter périodiquement les Conditions générales afin de comprendre les mises à jour ou les modifications qui pourraient vous concerner. Si vous n'acceptez pas les présentes conditions générales, veuillez vous abstenir d'utiliser notre site Web, nos produits et nos services." +#: src/app/ReadGuidancePage.js:491 +msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to <0>TBS Cyber Security to confirm your ownership of that domain." +msgstr "Par défaut, nos scanners vérifient les domaines se terminant par \".gc.ca\" et \".canada.ca\". Si votre domaine ne fait pas partie de cette liste, vous devez nous contacter pour nous en informer. Envoyez un courriel à l’<0>équipe responsable de la cybersécurité du SCT pour confirmer que vous êtes propriétaire de ce domaine." + #: src/app/ReadGuidancePage.js:313 -msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." -msgstr "Par défaut, nos analyseurs vérifient les domaines se terminant par « .gc.ca » et « .canada.ca ». Si votre domaine se termine autrement, vous devez communiquer avec nous pour nous en aviser. Envoyez un courriel à l’équipe responsable de la cybersécurité du SCT pour confirmer que ce domaine vous appartient. " +#~ msgid "By default our scanners check domains ending in “.gc.ca” and “.canada.ca”. If your domain is outside that set, you need to contact us to let us know. Send an email to TBS Cyber Security to confirm your ownership of that domain." +#~ msgstr "Par défaut, nos analyseurs vérifient les domaines se terminant par « .gc.ca » et « .canada.ca ». Si votre domaine se termine autrement, vous devez communiquer avec nous pour nous en aviser. Envoyez un courriel à l’équipe responsable de la cybersécurité du SCT pour confirmer que ce domaine vous appartient. " #: src/guidance/ScanDetails.js:102 #~ msgid "CCS Injection Vulnerability:" @@ -554,6 +562,10 @@ msgstr "Confirmer la suppression du domaine:" #~ msgid "Confirm removal of user:" #~ msgstr "Confirmer le retrait de l'utilisateur:" +#: src/app/ReadGuidancePage.js:216 +msgid "Consider prioritizing websites and web services that exchange Protected data." +msgstr "Envisagez de donner la priorité aux sites web et aux services web qui échangent des données protégées." + #: src/guidance/WebConnectionResults.js:99 msgid "Connection Results" msgstr "Résultats de la connexion" @@ -666,25 +678,25 @@ msgstr "État des courbes" msgid "DKIM" msgstr "DKIM" -#: src/dmarc/DmarcReportPage.js:205 +#: src/dmarc/DmarcReportPage.js:208 msgid "DKIM Aligned" msgstr "DKIM Aligné" -#: src/dmarc/DmarcReportPage.js:169 +#: src/dmarc/DmarcReportPage.js:172 msgid "DKIM Domains" msgstr "Domaines DKIM" -#: src/dmarc/DmarcReportPage.js:245 +#: src/dmarc/DmarcReportPage.js:325 msgid "DKIM Failure Table" msgstr "Tableau des échecs DKIM" -#: src/dmarc/DmarcReportPage.js:256 -#: src/dmarc/DmarcReportPage.js:291 -#: src/dmarc/DmarcReportPage.js:560 +#: src/dmarc/DmarcReportPage.js:336 +#: src/dmarc/DmarcReportPage.js:371 +#: src/dmarc/DmarcReportPage.js:644 msgid "DKIM Failures by IP Address" msgstr "Défaillances DKIM par adresse IP" -#: src/dmarc/DmarcReportPage.js:209 +#: src/dmarc/DmarcReportPage.js:212 msgid "DKIM Results" msgstr "Résultats DKIM" @@ -692,7 +704,7 @@ msgstr "Résultats DKIM" msgid "DKIM Selector" msgstr "Sélecteur DKIM" -#: src/dmarc/DmarcReportPage.js:173 +#: src/dmarc/DmarcReportPage.js:176 msgid "DKIM Selectors" msgstr "Sélecteurs DKIM" @@ -726,13 +738,13 @@ msgstr "Résumé de la configuration DMARC" msgid "DMARC Configured" msgstr "DMARC configuré" -#: src/dmarc/DmarcReportPage.js:477 +#: src/dmarc/DmarcReportPage.js:560 msgid "DMARC Failure Table" msgstr "Tableau des échecs de la DMARC" -#: src/dmarc/DmarcReportPage.js:488 -#: src/dmarc/DmarcReportPage.js:525 -#: src/dmarc/DmarcReportPage.js:566 +#: src/dmarc/DmarcReportPage.js:571 +#: src/dmarc/DmarcReportPage.js:608 +#: src/dmarc/DmarcReportPage.js:650 msgid "DMARC Failures by IP Address" msgstr "Défaillances du DMARC par adresse IP" @@ -773,6 +785,7 @@ msgstr "Résumés DMARC" #~ msgid "DMARC phase summary" #~ msgstr "Résumé de la phase DMARC" +#: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." #~ msgstr "L'enregistrement DMARC n'a pas pu être trouvé pendant le scan." @@ -841,13 +854,21 @@ msgstr "Déployer les enregistrements SPF pour tous les domaines." msgid "Deploy initial DMARC records with policy of none; and" msgstr "Déployer les enregistrements DMARC initiaux en utilisant la stratégie Aucune (None)" +#: src/dmarc/DmarcReportPage.js:255 +msgid "Details for a given guidance tag can be found on the wiki, see below." +msgstr "Les détails d'une balise d'orientation donnée peuvent être trouvés sur le wiki, voir ci-dessous." + #: src/app/ReadGuidancePage.js:109 #~ msgid "Develop a prioritized implementation schedule for each of the affected websites and web services, following the recommended prioritization approach in the ITPIN:" #~ msgstr "Élaborer un calendrier de mise en œuvre prioritaire pour chacun des sites Web et services Web concernés, en suivant l'approche de hiérarchisation recommandée dans l'ITPIN :" -#: src/app/ReadGuidancePage.js:175 -msgid "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." -msgstr "Élaborer un calendrier de priorités pour corriger tout échec. Envisager de donner la priorité aux sites Web et aux services Web qui échangent des données protégées." +#: src/app/ReadGuidancePage.js:358 +#~ msgid "Develop a prioritized schedule to address any failings. Consider prioritizing websites and web services that exchange Protected data." +#~ msgstr "Élaborer un calendrier de priorités pour corriger tout échec. Envisager de donner la priorité aux sites Web et aux services Web qui échangent des données protégées." + +#: src/app/ReadGuidancePage.js:211 +msgid "Develop a prioritized schedule to address any failings:" +msgstr "Élaborer un calendrier de mesures prioritaires pour remédier à toute défaillance :" #: src/admin/SuperAdminUserList.js:149 #: src/components/fields/DisplayNameField.js:14 @@ -867,7 +888,7 @@ msgstr "Le nom d'affichage ne peut pas être vide" msgid "Displays the Name of the organization, its acronym, and a blue check mark if it is a verified organization." msgstr "Affiche le nom de l'organisation, son acronyme et une coche bleue s'il s'agit d'une organisation vérifiée." -#: src/dmarc/DmarcReportPage.js:213 +#: src/dmarc/DmarcReportPage.js:216 msgid "Disposition" msgstr "Disposition" @@ -885,7 +906,7 @@ msgstr "Domaine" msgid "Domain List" msgstr "Liste des domaines" -#: src/app/ReadGuidancePage.js:366 +#: src/app/ReadGuidancePage.js:566 msgid "Domain Name System (DNS) Services Management Configuration Requirements - Canada.ca" msgstr "Exigences de configuration pour la gestion des sites Web et des services" @@ -902,6 +923,10 @@ msgstr "URL du domaine:" msgid "Domain added" msgstr "Domaine ajouté" +#: src/dmarc/DmarcReportPage.js:227 +msgid "Domain from Simple Mail Transfer Protocol (SMTP) banner message." +msgstr "Domaine du message de bannière du protocole de transfert de courrier simple (PTCS)." + #: src/admin/AdminDomains.js:115 msgid "Domain removed" msgstr "Domaine supprimé" @@ -939,6 +964,14 @@ msgstr "Domaine:" msgid "Domains" msgstr "Domaines" +#: src/app/ReadGuidancePage.js:144 +msgid "Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." +msgstr "Les domaines ne peuvent être supprimés de votre liste que 1) s'ils n'existent plus, c'est-à-dire s'ils sont supprimés du DNS et renvoient un code d'erreur NX DOMAIN (le nom de domaine n'existe pas) ; ou 2) si vous avez constaté qu'ils n'appartiennent pas à votre organisation." + +#: src/dmarc/DmarcReportPage.js:247 +msgid "Domains used for SPF validation." +msgstr "Domaines utilisés pour la validation SPF." + #: src/auth/SignInPage.js:193 msgid "Don't have an account? <0>Sign up" msgstr "Vous n'avez pas de compte ? <0>S'inscrire" @@ -947,6 +980,10 @@ msgstr "Vous n'avez pas de compte ? <0>S'inscrire" msgid "EQUALS" msgstr "ÉGAUX" +#: src/app/ReadGuidancePage.js:122 +msgid "Each organization’s domain list should include every internet-facing service. It is the responsibility of organization admins to manage the current list and identify new domains to add." +msgstr "La liste des domaines de chaque organisation doit inclure tous les services en contact avec l'internet. Il incombe aux administrateurs de l'organisation de gérer la liste actuelle et d'identifier les nouveaux domaines à ajouter." + #: src/user/EditableUserDisplayName.js:109 #: src/user/EditableUserEmail.js:109 #: src/user/EditableUserPassword.js:112 @@ -990,7 +1027,7 @@ msgstr "Courriel" msgid "Email Guidance" msgstr "Conseils par courriel" -#: src/app/ReadGuidancePage.js:381 +#: src/app/ReadGuidancePage.js:581 msgid "Email Management Services Configuration Requirements - Canada.ca" msgstr "Exigences en matière de configuration des services de gestion des courriels" @@ -998,6 +1035,10 @@ msgstr "Exigences en matière de configuration des services de gestion des courr msgid "Email Scan Results" msgstr "Résultats de l'analyse des courriels" +#: src/app/ReadGuidancePage.js:323 +msgid "Email Security:" +msgstr "Sécurité du courrier électronique :" + #: src/auth/ForgotPasswordPage.js:38 msgid "Email Sent" msgstr "Courriel envoyé" @@ -1086,7 +1127,7 @@ msgstr "Entrez le code à deux facteurs" msgid "Enter your user account's verified email address and we will send you a password reset link." msgstr "Saisissez l'adresse électronique vérifiée de votre compte d'utilisateur et nous vous enverrons un lien pour réinitialiser votre mot de passe." -#: src/dmarc/DmarcReportPage.js:164 +#: src/dmarc/DmarcReportPage.js:167 msgid "Envelope From" msgstr "Enveloppe De" @@ -1104,8 +1145,8 @@ msgid "Export to CSV" msgstr "Exportation vers CSV" #: src/app/ReadGuidancePage.js:31 -msgid "FAQ" -msgstr "FAQ" +#~ msgid "FAQ" +#~ msgstr "FAQ" #: src/dmarc/DmarcReportPage.js:126 #: src/dmarc/DmarcReportPage.js:127 @@ -1113,27 +1154,27 @@ msgstr "FAQ" msgid "Fail" msgstr "Échec" -#: src/dmarc/DmarcReportPage.js:120 -#: src/dmarc/DmarcReportPage.js:121 +#: src/dmarc/DmarcReportPage.js:123 +#: src/dmarc/DmarcReportPage.js:124 msgid "Fail DKIM" msgstr "Échec DKIM" #: src/dmarc/DmarcByDomainPage.js:156 -#: src/dmarc/DmarcByDomainPage.js:326 +#: src/dmarc/DmarcByDomainPage.js:338 msgid "Fail DKIM %" msgstr "Échec DKIM %" -#: src/dmarc/DmarcReportPage.js:123 -#: src/dmarc/DmarcReportPage.js:124 +#: src/dmarc/DmarcReportPage.js:126 +#: src/dmarc/DmarcReportPage.js:127 msgid "Fail SPF" msgstr "Échec du SPF" #: src/dmarc/DmarcByDomainPage.js:163 -#: src/dmarc/DmarcByDomainPage.js:322 +#: src/dmarc/DmarcByDomainPage.js:334 msgid "Fail SPF %" msgstr "Échec du SPF %" -#: src/dmarc/DmarcReportPage.js:571 +#: src/dmarc/DmarcReportPage.js:655 msgid "Fake email domain blocks (reject + quarantine):" msgstr "Blocs de domaines de faux e-mails (rejet + quarantaine) :" @@ -1160,13 +1201,13 @@ msgstr "Filtres :" #: src/app/ReadGuidancePage.js:198 #~ msgid "For any questions or concerns related to the ITPIN and related implementation guidance, contact TBS Cybersecurity (<0>zzTBSCybers@tbs-sct.gc.ca)." -#~ msgstr "Pour toute question ou préoccupation relative à l'ITPIN et aux orientations de mise en œuvre connexes, contactez TBS Cybersecurity (<0>zzTBSCybers@tbs-sct.gc.ca)." +#~ msgstr "Pour toute question ou préoccupation relative à l'ITPIN et aux orientations de mise en œuvre connexes, contactez l’équipe responsable de la cybersécurité du SCT (<0>zzTBSCybers@tbs-sct.gc.ca)." #: src/app/ReadGuidancePage.js:410 #~ msgid "For any questions or concerns related to the ITPIN and related implementation guidance, contact TBS Cybersecurity." #~ msgstr "Si vous avez des questions ou des préoccupations, n’hésitez pas à communiquer avec l’équipe responsable de la cybersécurité du SCT." -#: src/app/ReadGuidancePage.js:410 +#: src/app/ReadGuidancePage.js:622 msgid "For any questions or concerns, please contact <0>TBS Cyber Security ." msgstr "Si vous avez des questions ou des préoccupations, n’hésitez pas à communiquer avec l’<0>équipe responsable de la cybersécurité du SCT." @@ -1203,27 +1244,27 @@ msgstr "Oublié votre mot de passe?" msgid "French" msgstr "Français" -#: src/app/ReadGuidancePage.js:215 +#: src/app/ReadGuidancePage.js:371 msgid "Frequently Asked Questions" msgstr "Foire aux questions" #: src/dmarc/DmarcByDomainPage.js:170 -#: src/dmarc/DmarcByDomainPage.js:330 +#: src/dmarc/DmarcByDomainPage.js:342 msgid "Full Fail %" msgstr "Échec total %" #: src/dmarc/DmarcByDomainPage.js:149 -#: src/dmarc/DmarcByDomainPage.js:318 +#: src/dmarc/DmarcByDomainPage.js:330 msgid "Full Pass %" msgstr "Passage complet %" -#: src/dmarc/DmarcReportPage.js:323 +#: src/dmarc/DmarcReportPage.js:404 msgid "Fully Aligned Table" msgstr "Tableau entièrement aligné" -#: src/dmarc/DmarcReportPage.js:334 -#: src/dmarc/DmarcReportPage.js:366 -#: src/dmarc/DmarcReportPage.js:557 +#: src/dmarc/DmarcReportPage.js:415 +#: src/dmarc/DmarcReportPage.js:447 +#: src/dmarc/DmarcReportPage.js:641 msgid "Fully Aligned by IP Address" msgstr "Entièrement aligné par adresse IP" @@ -1235,17 +1276,27 @@ msgstr "Vous trouverez de plus amples informations sur chaque organisation en cl #~ msgid "General Public" #~ msgstr "Grand public" -#: src/domains/DomainsPage.js:125 +#: src/app/ReadGuidancePage.js:21 +msgid "Getting Started" +msgstr "Pour commencer" + +#: src/app/ReadGuidancePage.js:28 +#~ msgid "Getting Started Using Tracker" +#~ msgstr "Premiers pas dans l'utilisation de Suivi" + +#: src/app/ReadGuidancePage.js:75 +msgid "Getting an Account:" +msgstr "Ouverture d'un compte :" + +#: src/domains/DomainsPage.js:150 msgid "Getting domain statuses" msgstr "Obtenir les statuts des domaines" -#: src/dmarc/DmarcByDomainPage.js:290 -#: src/domains/DomainsPage.js:113 -#: src/organizations/Organizations.js:118 -#~ msgid "Glossary" -#~ msgstr "Glossaire" +#: src/components/InfoPanel.js:32 +msgid "Glossary" +msgstr "Glossaire" -#: src/components/TrackerTable.js:215 +#: src/components/TrackerTable.js:254 msgid "Go to page:" msgstr "Aller à la page" @@ -1415,6 +1466,11 @@ msgstr "Accueil" #~ msgid "Horizontal View" #~ msgstr "Vue horizontale" +#: src/dmarc/DmarcReportPage.js:243 +msgid "Host from reverse DNS of source IP address." +msgstr "Hôte du DNS inversé de l'adresse IP source." + +#: src/app/ReadGuidancePage.js:396 #: src/guidance/WebTLSResults.js:247 msgid "Hostname Matches" msgstr "Correspondance des noms d'hôtes" @@ -1460,13 +1516,21 @@ msgstr "Déterminer tous les expéditeurs autorisés." msgid "Identify all domains and subdomains used to send mail;" msgstr "Déterminer tous les domaines et sous-domaines utilisés pour envoyer des courriels." +#: src/app/ReadGuidancePage.js:80 +msgid "Identify any current affiliated Tracker users within your organization and develop a plan with them." +msgstr "Identifiez les utilisateurs affiliés à Suivi au sein de votre organisation et élaborez un plan avec eux." + #: src/app/ReadGuidancePage.js:36 #~ msgid "Identify key resources required to act as central point(s) of contact with TBS and the HTTPS Community of Practice." #~ msgstr "Identifier les ressources clés nécessaires pour agir comme point(s) de contact central(aux) avec le SCT et la communauté de pratique HTTPS." -#: src/app/ReadGuidancePage.js:91 -msgid "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." -msgstr "Déterminer les ressources nécessaires qui agiront en tant que point de contact central auprès du Secrétariat du Conseil du Trésor du Canada (SCT). Communiquer la liste de personnes-ressources à l’<0>équipe responsable de la cybersécurité du SCT et la mettre à jour, au besoin." +#: src/app/ReadGuidancePage.js:315 +#~ msgid "Identify resources required to act as central point(s) of contact with Treasury Board of Canada Secretariat (TBS). Share the contact list with <0>TBS Cyber Security, as required." +#~ msgstr "Déterminer les ressources nécessaires qui agiront en tant que point de contact central auprès du Secrétariat du Conseil du Trésor du Canada (SCT). Communiquer la liste de personnes-ressources à l’<0>équipe responsable de la cybersécurité du SCT et la mettre à jour, au besoin." + +#: src/app/ReadGuidancePage.js:155 +msgid "If a domain is no longer in use but still exists on the DNS, it is still vulnerable to email spoofing attacks, where an attacker can send an email that appears to be coming from your domain." +msgstr "Si un domaine n'est plus utilisé mais existe toujours dans le DNS, il reste vulnérable aux attaques par usurpation d'adresse électronique, c'est-à-dire qu'un pirate peut envoyer un courrier électronique semblant provenir de votre domaine." #: src/termsConditions/TermsConditionsPage.js:392 msgid "If at any time you or your representatives wish to adjust or cancel these services, please" @@ -1493,11 +1557,15 @@ msgstr "Si vous pensez que cela a été causé par un problème avec Tracker, ve msgid "Immediately" msgstr "Immédiatement" +#: src/app/ReadGuidancePage.js:88 +msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." +msgstr "Si votre organisation n'a pas d'utilisateurs affiliés à Suivi, contactez l’<0>équipe responsable de la cybersécurité du SCT pour vous aider à l'intégrer." + #: src/guidance/WebGuidance.js:19 #~ msgid "Implementation" #~ msgstr "Mise en œuvre" -#: src/app/ReadGuidancePage.js:396 +#: src/app/ReadGuidancePage.js:596 msgid "Implementation guidance: email domain protection (ITSP.40.065 v1.1) - Canadian Centre for Cyber Security" msgstr "Directives de mise en œuvre – protection du domaine de courrier (ITSP.40.065 v1.1) – Centre canadien pour la cybersécurité" @@ -1505,6 +1573,14 @@ msgstr "Directives de mise en œuvre – protection du domaine de courrier (ITS #~ msgid "Implementation:" #~ msgstr "Mise en œuvre:" +#: src/app/ReadGuidancePage.js:302 +msgid "Implementation: <0>Guidance on securely configuring network protocols (ITSP.40.062)" +msgstr "Mise en œuvre : <0>Conseils sur la configuration sécurisée des protocoles réseau (ITSP.40.062)" + +#: src/app/ReadGuidancePage.js:346 +msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" +msgstr "Mise en œuvre : <0>Conseils de mise en œuvre : protection du domaine de messagerie (ITSP.40.065 v1.1)" + #: src/summaries/SummaryGroup.js:58 msgid "Implemented" msgstr "Mis en œuvre" @@ -1683,7 +1759,7 @@ msgstr "Propriété intellectuelle, droits d'auteur et marques de commerce" #: src/app/ReadGuidancePage.js:48 #~ msgid "Internally available <0>Tracker Dashboard" -#~ msgstr "Tableau de bord du traqueur disponible en interne <0>Tracker." +#~ msgstr "Tableau de bord du traqueur disponible en interne <0>Suivi." #: src/organizationDetails/OrganizationSummary.js:14 #~ msgid "Internet facing domains" @@ -1702,6 +1778,15 @@ msgstr "Courriel non valide" msgid "Invite User" msgstr "Inviter l'utilisateur" +#: src/dmarc/DmarcReportPage.js:267 +msgid "Is DKIM aligned. Can be true or false." +msgstr "Est aligné sur la norme DKIM. Peut être vrai ou faux." + +#: src/dmarc/DmarcReportPage.js:259 +msgid "Is SPF aligned. Can be true or false." +msgstr "Est aligné sur le SPF. Peut être vrai ou faux." + +#: src/app/ReadGuidancePage.js:378 #: src/guidance/WebTLSResults.js:383 msgid "Issuer:" msgstr "Émetteur :" @@ -1714,12 +1799,12 @@ msgstr "Je ne comprends pas pourquoi un domaine a échoué." #~ msgid "It is recommended that SSC partners contact their SSC Service Delivery Manager to discuss the departmental action plan and required steps to submit a request for change." #~ msgstr "Il est recommandé aux partenaires du SSC de contacter leur gestionnaire de prestation de services du SSC afin de discuter du plan d'action ministériel et des étapes nécessaires pour soumettre une demande de changement." -#: src/app/ReadGuidancePage.js:188 +#: src/app/ReadGuidancePage.js:228 msgid "It is recommended that Shared Service Canada (SSC) partners contact their SSC Service Delivery Manager to discuss action plans and required steps to submit a request for change." msgstr "On recommande aux partenaires de Services partagés Canada (SPC) de communiquer avec leur gestionnaire de prestation de services de SPC pour discuter des plans d’action et des étapes requises afin de soumettre une demande de changement." #: src/components/RelayPaginationControls.js:33 -#: src/components/TrackerTable.js:243 +#: src/components/TrackerTable.js:282 msgid "Items per page:" msgstr "Objets par page:" @@ -1791,6 +1876,14 @@ msgstr "Nous allons vous configurer pour que vous puissiez vérifier les informa msgid "Limitation of Liability" msgstr "Limitation de la responsabilité" +#: src/app/ReadGuidancePage.js:252 +msgid "Links to Review:" +msgstr "Liens à revoir :" + +#: src/app/ReadGuidancePage.js:271 +msgid "List of guidance tags" +msgstr "Liste des balises d'orientation" + #: src/dmarc/DmarcByDomainPage.js:268 msgid "Loading Data..." msgstr "Chargement des données..." @@ -1807,6 +1900,10 @@ msgstr "Connexion" msgid "Login to your account" msgstr "Connectez-vous à votre compte" +#: src/app/ReadGuidancePage.js:117 +msgid "Managing Your Domains:" +msgstr "Gérer vos domaines :" + #: src/guidance/EmailGuidance.js:178 msgid "Lookups:" msgstr "Les recherches :" @@ -1840,6 +1937,10 @@ msgstr "Surveiller les rapports DMARC et corriger les erreurs de configuration." msgid "Monitor DMARC reports;" msgstr "Surveiller les rapports DMARC." +#: src/app/ReadGuidancePage.js:604 +msgid "Mozilla SSL Configuration Generator" +msgstr "Générateur de configuration SSL de Mozilla" + #: src/guidance/WebTLSResults.js:361 msgid "More details" msgstr "Plus de détails" @@ -1986,23 +2087,23 @@ msgstr "Aucun journal d'activité" msgid "No current phone number" msgstr "Pas de numéro de téléphone actuel" -#: src/dmarc/DmarcReportPage.js:311 +#: src/dmarc/DmarcReportPage.js:392 msgid "No data for the DKIM Failures by IP Address table" msgstr "Aucune donnée pour le tableau des défaillances DKIM par adresse IP" -#: src/dmarc/DmarcReportPage.js:545 +#: src/dmarc/DmarcReportPage.js:629 msgid "No data for the DMARC Failures by IP Address table" msgstr "Pas de données pour le tableau des défaillances DMARC par adresse IP" -#: src/dmarc/DmarcReportPage.js:153 +#: src/dmarc/DmarcReportPage.js:156 msgid "No data for the DMARC yearly report graph" msgstr "Pas de données pour le graphique du rapport annuel de la DMARC" -#: src/dmarc/DmarcReportPage.js:386 +#: src/dmarc/DmarcReportPage.js:468 msgid "No data for the Fully Aligned by IP Address table" msgstr "Pas de données pour le tableau Entièrement aligné par adresse IP" -#: src/dmarc/DmarcReportPage.js:460 +#: src/dmarc/DmarcReportPage.js:543 msgid "No data for the SPF Failures by IP Address table" msgstr "Aucune donnée pour le tableau des défaillances du SPF par adresse IP" @@ -2098,7 +2199,7 @@ msgstr "Novembre" #~ msgid "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC Public Facing Web Services" #~ msgstr "Obtenez des certificats auprès d'une source de certificats approuvée par le GC, comme indiqué dans les Recommandations relatives aux certificats de serveur TLS pour les services Web publics du GC." -#: src/app/ReadGuidancePage.js:196 +#: src/app/ReadGuidancePage.js:235 msgid "Obtain certificates from a GC-approved certificate source as outlined in the Recommendations for TLS Server Certificates for GC public facing web services" msgstr "Obtenir des certificats d’une source de certificats approuvée par le GC, comme l’indiquent les Recommandations pour les certificats de serveur TLS pour les services Web publics du GC." @@ -2106,7 +2207,7 @@ msgstr "Obtenir des certificats d’une source de certificats approuvée par le #~ msgid "Obtain the configuration guidance for the appropriate endpoints (e.g. web server, network/security appliances, etc.) and implement recommended configurations to support HTTPS." #~ msgstr "Obtenez les conseils de configuration pour les points d'extrémité appropriés (par exemple, serveur Web, appareils de réseau/sécurité, etc.) et mettez en œuvre les configurations recommandées pour prendre en charge HTTPS." -#: src/app/ReadGuidancePage.js:203 +#: src/app/ReadGuidancePage.js:242 msgid "Obtain the configuration guidance for the appropriate endpoints (e.g., web server, network/security appliances, etc.) and implement recommended configurations." msgstr "Obtenir une orientation en matière de configuration pour les points terminaux appropriés (p. ex., serveur Web, dispositifs de réseau ou de sécurité) et mettre en œuvre les configurations recommandées." @@ -2119,7 +2220,15 @@ msgstr "Octobre" msgid "Old Value:" msgstr "Ancienne valeur :" -#: src/app/ReadGuidancePage.js:332 +#: src/app/ReadGuidancePage.js:103 +msgid "Once access is given to your department by the TBS Cyber team, they will be able to invite and manage other users within the organization and manage the domain list." +msgstr "Une fois que l’équipe responsable de la cybersécurité du SCT a donné l'accès à votre département, celui-ci pourra inviter et gérer d'autres utilisateurs au sein de l'organisation et gérer la liste du domaine." + +#: src/app/ReadGuidancePage.js:416 +msgid "Only <0>TBS Cyber Security can remove domains from your organization. Domains are only to be removed from your list when 1) they no longer exist, meaning they are deleted from the DNS returning an error code of NX DOMAIN (domain name does not exist); or 2) if you have identified that they do not belong to your organization." +msgstr "Seul l’<0>équipe responsable de la cybersécurité du SCT peut supprimer des domaines de votre organisation. Les domaines ne peuvent être supprimés de votre liste que 1) s'ils n'existent plus, c'est-à-dire s'ils sont supprimés du DNS et renvoient un code d'erreur NX DOMAIN (le nom de domaine n'existe pas) ; ou 2) si vous avez constaté qu'ils n'appartiennent pas à votre organisation." + +#: src/app/ReadGuidancePage.js:517 msgid "Options include contacting the <0>SSC WebSSL services team and/or using <1>Let's Encrypt. For more information, please refer to the guidance on <2>Recommendations for TLS Server Certificates." msgstr "Vous pouvez notamment communiquer avec l’<0>équipe responsable des services WebSSL de SPC ou utiliser <1>Let’sEncrypt. Pour en apprendre davantage, veuillez vous reporter aux <2>Recommandations pour les certificats de serveur TLS." @@ -2181,7 +2290,7 @@ msgstr "Organisations" msgid "PROD" msgstr "PROD" -#: src/components/TrackerTable.js:231 +#: src/components/TrackerTable.js:270 msgid "Page {0} of {1}" msgstr "Page {0} de {1}" @@ -2241,8 +2350,8 @@ msgstr "Les mots de passe doivent correspondre" #~ msgstr "Réaliser un inventaire de tous les domaines et sous-domaines du ministère. Les sources d'information comprennent :" #: src/app/ReadGuidancePage.js:106 -msgid "Perform an inventory of all organizational domains and subdomains. Sources of information include:" -msgstr "Dresser la liste de tous les domaines et sous-domaines organisationnels. Les sources d’information comprennent :" +#~ msgid "Perform an inventory of all organizational domains and subdomains. Sources of information include:" +#~ msgstr "Dresser la liste de tous les domaines et sous-domaines organisationnels. Les sources d’information comprennent :" #: src/app/ReadGuidancePage.js:189 #~ msgid "Perform another assessment of the applicable domains and sub-domains to confirm that the configuration has been updated and that HTTPS is enforced in accordance with the ITPIN. Results will appear in the Tracker Dashboard within 24 hours." @@ -2277,13 +2386,13 @@ msgstr "Veuillez prévoir jusqu'à 24 heures pour que les résumés reflètent l msgid "Please choose your preferred language" msgstr "Veuillez choisir votre langue préférée" -#: src/app/ReadGuidancePage.js:224 +#: src/app/ReadGuidancePage.js:381 msgid "Please contact <0>TBS Cyber Security for help." msgstr "Veuillez communiquer avec l’<0>équipe responsable de la cybersécurité du SCT pour obtenir de l’aide." #: src/app/ReadGuidancePage.js:242 -msgid "Please direct all updates to TBS Cyber Security." -msgstr "Veuillez envoyer toutes les mises à jour de domaine par courriel à l’équipe responsable de la cybersécurité du SCT." +#~ msgid "Please direct all updates to TBS Cyber Security." +#~ msgstr "Veuillez envoyer toutes les mises à jour de domaine par courriel à l’équipe responsable de la cybersécurité du SCT." #: src/utilities/fieldRequirements.js:27 msgid "Please enter your current password." @@ -2297,6 +2406,10 @@ msgstr "Veuillez entrer votre code à deux facteurs ci-dessous." msgid "Please follow the link in order to verify your account and start using Tracker." msgstr "Veuillez suivre le lien afin de vérifier votre compte et commencer à utiliser Suivi." +#: src/dmarc/DmarcReportPage.js:235 +msgid "Pointer to a DKIM public key record in DNS." +msgstr "Pointeur vers un enregistrement de clé publique DKIM dans le DNS." + #: src/domains/DomainCard.js:54 #: src/domains/DomainsPage.js:123 #: src/organizationDetails/OrganizationDomains.js:119 @@ -2355,6 +2468,10 @@ msgstr "Déclaration de confidentialité" msgid "Prod" msgstr "Prod" +#: src/app/ReadGuidancePage.js:612 +msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" +msgstr "Protéger les domaines qui n'envoient pas de courrier électronique - GOV.UK (www.gov.uk)" + #: src/domains/DomainCard.js:188 #: src/domains/DomainsPage.js:161 #: src/guidance/WebTLSResults.js:52 @@ -2369,12 +2486,12 @@ msgid "Protocols Status" msgstr "Statut des protocoles" #: src/app/ReadGuidancePage.js:132 -msgid "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." -msgstr "Fournir à l’équipe responsable de la cybersécurité du SCT une liste à jour de tous les domaines et sous-domaines des sites Web et des services Web accessibles au public. L’équipe responsable de la cybersécurité du SCT est responsable de la mise à jour des listes de domaines et de sous-domaines qui se trouvent dans Tracker." +#~ msgid "Provide an up-to-date list of all domain and sub-domains of publicly accessible websites and web services to TBS Cyber Security. The TBS Cyber Security team is responsible for updating the domain and sub-domain lists within Tracker." +#~ msgstr "Fournir à l’équipe responsable de la cybersécurité du SCT une liste à jour de tous les domaines et sous-domaines des sites Web et des services Web accessibles au public. L’équipe responsable de la cybersécurité du SCT est responsable de la mise à jour des listes de domaines et de sous-domaines qui se trouvent dans Suivi." #: src/app/ReadGuidancePage.js:68 #~ msgid "Provide an up-to-date list of all domain and sub-domains of the publicly-accessible websites and web services to <0>TBS Cybersecurity." -#~ msgstr "Fournir une liste actualisée de tous les domaines et sous-domaines des sites web et services web accessibles au public à <0>TBS Cybersecurity." +#~ msgstr "Fournir une liste actualisée de tous les domaines et sous-domaines des sites web et services web accessibles au public à l’<0>équipe responsable de la cybersécurité du SCT." #: src/createOrganization/CreateOrganizationPage.js:206 #: src/createOrganization/CreateOrganizationPage.js:211 @@ -2393,9 +2510,9 @@ msgstr "Province (FR)" msgid "Province:" msgstr "Province:" -#: src/app/ReadGuidancePage.js:38 -msgid "Read Guidance" -msgstr "Conseils de lecture" +#: src/app/ReadGuidancePage.js:259 +#~ msgid "Read Guidance" +#~ msgstr "Conseils de lecture" #: src/app/App.js:193 msgid "Read guidance" @@ -2476,7 +2593,19 @@ msgstr "La requête a été envoyée avec succès pour obtenir les statuts de to msgid "Requested Scan" msgstr "Numérisation demandée" -#: src/app/App.js:187 +#: src/app/ReadGuidancePage.js:404 +msgid "Requests for updates can be sent directly to <0>TBS Cyber Security." +msgstr "Les demandes de mise à jour peuvent être envoyées directement à l’<0>équipe responsable de la cybersécurité du SCT." + +#: src/app/ReadGuidancePage.js:328 +msgid "Requirements: <0>Email Management Services Configuration Requirements" +msgstr "Exigences : <0>Configuration requise pour les services de gestion du courrier électronique" + +#: src/app/ReadGuidancePage.js:283 +msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" +msgstr "Exigences : <0>Exigences de configuration de la gestion des sites et services web" + +#: src/app/App.js:220 msgid "Reset Password" msgstr "Réinitialiser le mot de passe" @@ -2541,25 +2670,25 @@ msgstr "Liste des SAN :" msgid "SPF" msgstr "SPF" -#: src/dmarc/DmarcReportPage.js:197 +#: src/dmarc/DmarcReportPage.js:200 msgid "SPF Aligned" msgstr "Alignement du SPF" -#: src/dmarc/DmarcReportPage.js:184 +#: src/dmarc/DmarcReportPage.js:187 msgid "SPF Domains" msgstr "Domaine SPF" -#: src/dmarc/DmarcReportPage.js:398 +#: src/dmarc/DmarcReportPage.js:480 msgid "SPF Failure Table" msgstr "Tableau des échecs du SPF" -#: src/dmarc/DmarcReportPage.js:409 -#: src/dmarc/DmarcReportPage.js:440 -#: src/dmarc/DmarcReportPage.js:563 +#: src/dmarc/DmarcReportPage.js:491 +#: src/dmarc/DmarcReportPage.js:522 +#: src/dmarc/DmarcReportPage.js:647 msgid "SPF Failures by IP Address" msgstr "Défaillances du SPF par adresse IP" -#: src/dmarc/DmarcReportPage.js:201 +#: src/dmarc/DmarcReportPage.js:204 msgid "SPF Results" msgstr "Résultats du SPF" @@ -2619,19 +2748,19 @@ msgstr "Demande de numérisation" msgid "Scan of domain successfully requested" msgstr "Scan du domaine demandé avec succès" -#: src/dmarc/DmarcReportPage.js:294 +#: src/dmarc/DmarcReportPage.js:374 msgid "Search DKIM Failing Items" msgstr "Rechercher les éléments en échec de DKIM" -#: src/dmarc/DmarcReportPage.js:528 +#: src/dmarc/DmarcReportPage.js:611 msgid "Search DMARC Failing Items" msgstr "Recherche d'éléments défaillants DMARC" -#: src/dmarc/DmarcReportPage.js:369 +#: src/dmarc/DmarcReportPage.js:450 msgid "Search Fully Aligned Items" msgstr "Recherche d'éléments entièrement alignés" -#: src/dmarc/DmarcReportPage.js:443 +#: src/dmarc/DmarcReportPage.js:525 msgid "Search SPF Failing Items" msgstr "Rechercher les éléments défaillants du SPF" @@ -2644,9 +2773,9 @@ msgid "Search by initiated by, resource name" msgstr "Recherche par initié par, nom de la ressource" #: src/dmarc/DmarcByDomainPage.js:221 -#: src/dmarc/DmarcByDomainPage.js:287 -#: src/domains/DomainsPage.js:188 -#: src/organizationDetails/OrganizationDomains.js:317 +#: src/dmarc/DmarcByDomainPage.js:292 +#: src/domains/DomainsPage.js:228 +#: src/organizationDetails/OrganizationDomains.js:351 msgid "Search for a domain" msgstr "Rechercher un domaine" @@ -2669,7 +2798,7 @@ msgstr "Rechercher une organisation" #: src/admin/AdminDomains.js:252 #: src/admin/UserList.js:149 #: src/components/ReactTableGlobalFilter.js:36 -#: src/components/SearchBox.js:65 +#: src/components/SearchBox.js:67 msgid "Search:" msgstr "Recherche:" @@ -2736,12 +2865,12 @@ msgstr "Services" msgid "Services: {domainCount}" msgstr "Services: {domainCount}" -#: src/components/TrackerTable.js:256 +#: src/components/TrackerTable.js:295 msgid "Show {pageSize}" msgstr "Voir {pageSize}" #: src/dmarc/DmarcByDomainPage.js:252 -#: src/dmarc/DmarcReportPage.js:622 +#: src/dmarc/DmarcReportPage.js:706 msgid "Showing data for period:" msgstr "Affichage des données pour la période:" @@ -2882,23 +3011,23 @@ msgstr "Indique le pourcentage de domaines qui ont configuré HTTPS et qui mette msgid "Shows the percentage of domains which have a valid DMARC policy configuration." msgstr "Indique le pourcentage de domaines qui ont une configuration de politique DMARC valide." -#: src/dmarc/DmarcByDomainPage.js:327 +#: src/dmarc/DmarcByDomainPage.js:339 msgid "Shows the percentage of emails from the domain that fail DKIM requirments, but pass SPF requirments." msgstr "Indique le pourcentage d'e-mails du domaine qui ne répondent pas aux exigences DKIM, mais qui répondent aux exigences SPF." -#: src/dmarc/DmarcByDomainPage.js:323 +#: src/dmarc/DmarcByDomainPage.js:335 msgid "Shows the percentage of emails from the domain that fail SPF requirments, but pass DKIM requirments." msgstr "Indique le pourcentage d'e-mails du domaine qui ne répondent pas aux exigences SPF, mais qui répondent aux exigences DKIM." -#: src/dmarc/DmarcByDomainPage.js:331 +#: src/dmarc/DmarcByDomainPage.js:343 msgid "Shows the percentage of emails from the domain that fail both SPF and DKIM requirments." msgstr "Indique le pourcentage d'e-mails du domaine qui ne répondent pas aux exigences SPF et DKIM." -#: src/dmarc/DmarcByDomainPage.js:319 +#: src/dmarc/DmarcByDomainPage.js:331 msgid "Shows the percentage of emails from the domain that have passed both SPF and DKIM requirments." msgstr "Indique le pourcentage d'e-mails du domaine qui ont passé les exigences SPF et DKIM." -#: src/dmarc/DmarcByDomainPage.js:315 +#: src/dmarc/DmarcByDomainPage.js:327 msgid "Shows the total number of emails that have been sent by this domain during the selected time range." msgstr "Indique le nombre total d'e-mails qui ont été envoyés par ce domaine pendant la période sélectionnée." @@ -2944,11 +3073,11 @@ msgstr "Passer au contenu principal" msgid "Slug:" msgstr "Slug:" -#: src/components/SearchBox.js:92 +#: src/components/SearchBox.js:95 msgid "Sort by:" msgstr "Trier par:" -#: src/dmarc/DmarcReportPage.js:159 +#: src/dmarc/DmarcReportPage.js:162 msgid "Source IP Address" msgstr "Adresse IP source" @@ -3073,17 +3202,37 @@ msgid "Test" msgstr "Test" #: src/app/ReadGuidancePage.js:112 -msgid "The <0>Tracker platform" -msgstr "la plateforme <0>Tracker;" +#~ msgid "The <0>Tracker platform" +#~ msgstr "la plateforme <0>Tracker;" -#: src/app/ReadGuidancePage.js:42 +#: src/dmarc/DmarcReportPage.js:275 +msgid "The DMARC enforcement action that the receiver took, either none, quarantine, or reject." +msgstr "La mesure d'application de DMARC prise par le destinataire, soit aucune, soit la mise en quarantaine, soit le rejet." + +#: src/app/ReadGuidancePage.js:26 msgid "The Government of Canada’s (GC) <0>Directive on Service and Digital provides expectations on how GC organizations are to manage their Information Technology (IT) services. The focus of the Tracker tool is to help organizations stay in compliance with the directives <1>Email Management Service Configuration Requirements and the directives <2>Web Site and Service Management Configuration Requirements." -msgstr "La <0>Directive sur les services et le numérique du gouvernement du Canada (GC) définit les attentes quant à la façon dont les organisations du GC doivent gérer leurs services de la technologie de l’information (TI). L’objectif de l’outil Tracker est d’aider les organisations à demeurer conformes aux directives relatives aux <1>Exigences en matière de configuration pour les services de gestion des courriels et les directives ayant trait aux <2>Exigences de configuration de la gestion des sites Web et des services. " +msgstr "La <0>Directive sur les services et le numérique du gouvernement du Canada (GC) définit les attentes quant à la façon dont les organisations du GC doivent gérer leurs services de la technologie de l’information (TI). L’objectif de l’outil Suivi est d’aider les organisations à demeurer conformes aux directives relatives aux <1>Exigences en matière de configuration pour les services de gestion des courriels et les directives ayant trait aux <2>Exigences de configuration de la gestion des sites Web et des services. " + +#: src/dmarc/DmarcReportPage.js:223 +msgid "The IP address of sending server." +msgstr "L'adresse IP du serveur d'envoi." + +#: src/dmarc/DmarcReportPage.js:239 +msgid "The Total Messages from this sender." +msgstr "Total des messages de cet expéditeur." + +#: src/dmarc/DmarcReportPage.js:251 +msgid "The address/domain used in the \"From\" field." +msgstr "Adresse/domaine utilisé(e) dans le champ \"From\"." #: src/termsConditions/TermsConditionsPage.js:287 msgid "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." msgstr "Les conseils, orientations ou services qui vous sont fournis par le SCT le seront “tels quels“, sans garantie ni déclaration d'aucune sorte, et le SCT ne pourra être tenu responsable de toute perte, responsabilité, dommage ou coût, y compris la perte de données ou les interruptions d'activité découlant de la fourniture de ces conseils, orientations ou services par Suivi. Par conséquent, TBS recommande aux utilisateurs d'exercer leur propre compétence et leur propre prudence en ce qui concerne l'utilisation des conseils, orientations et services fournis par Suivi." +#: src/dmarc/DmarcReportPage.js:231 +msgid "The domains used for DKIM validation." +msgstr "Les domaines utilisés pour la validation DKIM." + #: src/dmarc/DmarcByDomainPage.js:312 #: src/domains/DomainsPage.js:153 #: src/organizationDetails/OrganizationDomains.js:280 @@ -3106,14 +3255,38 @@ msgstr "Le matériel disponible sur ce site web est soumis à l'approbation de l msgid "The page you are looking for has moved or does not exist." msgstr "La page que vous recherchez a été déplacée ou n'existe pas." +#: src/app/ReadGuidancePage.js:187 +msgid "The percentage of internet-facing services that have a DMARC policy of at least p=”none”" +msgstr "Le pourcentage de services en contact avec l'internet qui ont une politique DMARC d'au moins p=”none”." + +#: src/app/ReadGuidancePage.js:181 +msgid "The percentage of web-hosting services that strongly enforce HTTPS" +msgstr "Le pourcentage de services d'hébergement web qui appliquent fortement le protocole HTTPS" + #: src/termsConditions/TermsConditionsPage.js:349 msgid "The reproduction is not represented as an official version of the materials reproduced, nor as having been made, in affiliation with or under the direction of TBS." msgstr "La reproduction n'est pas présentée comme une version officielle des documents reproduits, ni comme ayant été faite en affiliation avec le SCT ou sous sa direction." +#: src/dmarc/DmarcReportPage.js:263 +msgid "The results of DKIM verification of the message. Can be pass, fail, neutral, soft-fail, temp-error, or perm-error." +msgstr "Résultats de la vérification DKIM du message. Il peut s'agir d'un succès, d'un échec, d'un résultat neutre, d'un échec léger, d'une erreur temporaire ou d'une erreur permanente." + +#: src/dmarc/DmarcReportPage.js:271 +msgid "The results of DKIM verification of the message. Can be pass, fail, neutral, temp-error, or perm-error." +msgstr "Résultats de la vérification DKIM du message. Il peut s'agir d'un succès, d'un échec, d'un résultat neutre, d'une erreur temporaire ou d'une erreur permanente." + +#: src/app/ReadGuidancePage.js:175 +msgid "The summary cards show two metrics that Tracker scans:" +msgstr "Les cartes récapitulatives présentent deux mesures que Suivi analyse :" + #: src/admin/UserListModal.js:114 msgid "The user's role has been successfully updated" msgstr "Le rôle de l'utilisateur a été mis à jour avec succès" +#: src/app/ReadGuidancePage.js:196 +msgid "These metrics are an important first step in securing your services and should be treated as minimum requirements. Further metrics are available in your organization's domain list." +msgstr "Ces paramètres constituent une première étape importante dans la sécurisation de vos services et doivent être considérés comme des exigences minimales. D'autres paramètres sont disponibles dans la liste des domaines de votre organisation." + #: src/termsConditions/TermsConditionsPage.js:412 msgid "These terms and conditions shall be governed by and interpreted under the laws of Canada, without regard for any choice of law rules. The courts of Canada shall have exclusive jurisdiction over all matters arising in relation to these terms and conditions." msgstr "Les présentes conditions générales sont régies et interprétées en vertu des lois du Canada, sans égard aux règles de droit applicables. Les tribunaux du Canada auront la compétence exclusive sur toutes les questions relatives à ces conditions générales." @@ -3180,9 +3353,13 @@ msgstr "Pour activer toutes les fonctionnalités de l'application et maximiser l msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "Pour maximiser la sécurité de votre compte, <0>vous devez activer une option d'authentification multifactorielle." +#: src/app/ReadGuidancePage.js:132 +msgid "To receive DKIM scan results and guidance, you must add the DKIM selectors used for each domain. Organization administrators can add selectors in the “Admin Profile” by clicking the edit button of the domain for which they wish to add the selector. Common selectors to keep an for are “selector1”, and “selector2”." +msgstr "Pour recevoir les résultats de l'analyse DKIM et des conseils, vous devez ajouter les sélecteurs DKIM utilisés pour chaque domaine. Les administrateurs de l'organisation peuvent ajouter des sélecteurs dans le \"profil administrateur\" en cliquant sur le bouton d'édition du domaine pour lequel ils souhaitent ajouter le sélecteur. Les sélecteurs les plus courants sont “selector1“ et “selector2“." + #: src/dmarc/DmarcByDomainPage.js:142 -#: src/dmarc/DmarcByDomainPage.js:314 -#: src/dmarc/DmarcReportPage.js:177 +#: src/dmarc/DmarcByDomainPage.js:326 +#: src/dmarc/DmarcReportPage.js:180 msgid "Total Messages" msgstr "Total des messages" @@ -3204,6 +3381,14 @@ msgstr "Les résultats de suivi des domaines HSTS et HTTPS s’affichent incorre msgid "Tracker account has been successfully closed." msgstr "Le compte du traqueur a été fermé avec succès." +#: src/app/ReadGuidancePage.js:545 +msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found above in Getting Started." +msgstr "Suivi n'ajoute pas automatiquement les sélecteurs, il est donc probable qu'ils ne soient pas encore dans le système. Vous trouverez plus d'informations à ce sujet dans la section Démarrage." + +#: src/app/ReadGuidancePage.js:579 +#~ msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." +#~ msgstr "Suivi n'ajoute pas automatiquement les sélecteurs, il est donc probable qu'ils ne soient pas encore dans le système. Pour plus d'informations, consultez la section Premiers pas avec Suivi - Gérer vos domaines." + #: src/app/TopBanner.js:81 msgid "Tracker logo outline" msgstr "Contour du logo Suivi" @@ -3212,9 +3397,13 @@ msgstr "Contour du logo Suivi" msgid "Tracker logo text" msgstr "Texte du logo du Suivi" -#: src/app/ReadGuidancePage.js:169 +#: src/app/ReadGuidancePage.js:205 msgid "Tracker results refresh every 24 hours." -msgstr "Les résultats de Tracker sont actualisés toutes les 24 heures." +msgstr "Les résultats de Suivi sont actualisés toutes les 24 heures." + +#: src/app/ReadGuidancePage.js:256 +msgid "Tracker:" +msgstr "Suivi :" #: src/termsConditions/TermsConditionsPage.js:181 msgid "Trademarks Act" @@ -3358,7 +3547,11 @@ msgstr "Impossible de mettre à jour votre numéro de téléphone, veuillez rée msgid "Unable to verify your phone number, please try again." msgstr "Impossible de vérifier votre numéro de téléphone, veuillez réessayer." -#: src/domains/DomainCard.js:85 +#: src/app/ReadGuidancePage.js:170 +msgid "Understanding Scan Metrics:" +msgstr "Comprendre les métriques d'analyse :" + +#: src/domains/DomainCard.js:84 msgid "Unfavourited Domain" msgstr "Domaine non favorisé" @@ -3397,8 +3590,12 @@ msgid "Upgrade DMARC policy to reject (gradually increment enforcement from 25%t msgstr "Faire passer la stratégie DMARC à Rejeter (Reject) (l’appliquer progressivement pour passer de 25 % à 100 %)." #: src/app/ReadGuidancePage.js:141 -msgid "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." -msgstr "Utiliser Tracker et <0>l’orientation du protocole de sécurité de la couche transport (TLS) ITSP.40.062 pour surveiller les domaines et sous-domaines de votre organisation. Les autres outils disponibles pour appuyer cette activité incluent <1>Laboratoires SSL, <2>Hardenize, <3>SSLShopper, etc." +#~ msgid "Use Tracker and <0>ITSP.40.062 Transport Layer Security (TLS) guidance to monitor the domains and sub-domains of your organization. Other tools available to support this activity include, <1>SSL Labs, <2>Hardenize, <3>SSLShopper, etc.." +#~ msgstr "Utiliser Tracker et <0>l’orientation du protocole de sécurité de la couche transport (TLS) ITSP.40.062 pour surveiller les domaines et sous-domaines de votre organisation. Les autres outils disponibles pour appuyer cette activité incluent <1>Laboratoires SSL, <2>Hardenize, <3>SSLShopper, etc." + +#: src/app/ReadGuidancePage.js:346 +#~ msgid "Use Tracker to monitor the domains and sub-domains of your organization." +#~ msgstr "Utilisez Suivi pour surveiller les domaines et sous-domaines de votre organisation." #: src/termsConditions/TermsConditionsPage.js:191 msgid "Use of intellectual property in breach of this agreement may result in the termination of access to the Tracker website, product or services." @@ -3555,6 +3752,10 @@ msgstr "Conseils sur le Web" msgid "Web Scan Results" msgstr "Résultats de l'analyse du Web" +#: src/app/ReadGuidancePage.js:278 +msgid "Web Security:" +msgstr "Sécurité du Web :" + #: src/guidance/ScanCard.js:56 #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Gestion des sites et services Web - Exigences de configuration conformes" @@ -3580,11 +3781,11 @@ msgstr "Veuillez choisir votre langue préférée" msgid "Welcome, you are successfully signed in!" msgstr "Bienvenue, vous êtes connecté avec succès!" -#: src/app/ReadGuidancePage.js:309 +#: src/app/ReadGuidancePage.js:487 msgid "What does it mean if a domain is “unreachable”?" msgstr "Que veut dire le message « inaccessible » en parlant d’un domaine?" -#: src/app/ReadGuidancePage.js:329 +#: src/app/ReadGuidancePage.js:514 msgid "Where can I get a GC-approved TLS certificate?" msgstr "Où puis-je obtenir un certificat TLS approuvé par le GC?" @@ -3592,17 +3793,29 @@ msgstr "Où puis-je obtenir un certificat TLS approuvé par le GC?" #~ msgid "Where necessary adjust IT Plans and budget estimates for the FY where work is expected." #~ msgstr "Si nécessaire, ajustez les plans informatiques et les estimations budgétaires pour l'exercice financier où des travaux sont prévus." -#: src/app/ReadGuidancePage.js:182 +#: src/app/ReadGuidancePage.js:222 msgid "Where necessary adjust IT Plans and budget estimates where work is expected." msgstr "Au besoin, adapter les plans de la TI et les estimations budgétaires là où des travaux sont attendus." -#: src/app/ReadGuidancePage.js:267 +#: src/app/ReadGuidancePage.js:441 msgid "While other tools are useful to work alongside Tracker, they do not specifically adhere to the configuration requirements specified in the <0>Email Management Service Configuration Requirements and the <1>Web Site and Service Management Configuration Requirements. For a list of allowed protocols, ciphers, and curves review the <2>ITSP.40.062 TLS guidance." -msgstr "Même si d’autres outils sont utiles en complément de Tracker, ils ne respectent pas précisément les exigences de configuration indiquées dans les <0>Exigences en matière de configuration des services de gestion des courriels et les <1>Exigences de configuration de la gestion des sites Web et des services. Pour une liste des protocoles, chiffrements et courbes autorisés, veuillez consulter les <2>Directives du protocole TLS ITSP.40.062." +msgstr "Même si d’autres outils sont utiles en complément de Suivi, ils ne respectent pas précisément les exigences de configuration indiquées dans les <0>Exigences en matière de configuration des services de gestion des courriels et les <1>Exigences de configuration de la gestion des sites Web et des services. Pour une liste des protocoles, chiffrements et courbes autorisés, veuillez consulter les <2>Directives du protocole TLS ITSP.40.062." #: src/app/ReadGuidancePage.js:250 -msgid "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" -msgstr "Pourquoi d’autres outils (<0>Hardenize, <1>Laboratoires SSL, etc.) affichent-ils des résultats positifs pour un domaine alors que Tracker affiche des résultats négatifs?" +#~ msgid "Why do other tools (<0>Hardenize, <1>SSL Labs, etc.) show positive results for a domain while Tracker shows negative results?" +#~ msgstr "Pourquoi d’autres outils (<0>Hardenize, <1>Laboratoires SSL, etc.) affichent-ils des résultats positifs pour un domaine alors que Tracker affiche des résultats négatifs?" + +#: src/app/ReadGuidancePage.js:435 +msgid "Why do other tools show positive results for a domain while Tracker shows negative results?" +msgstr "Pourquoi d'autres outils affichent-ils des résultats positifs pour un domaine alors que Suivi affiche des résultats négatifs ?" + +#: src/app/ReadGuidancePage.js:539 +msgid "Why does the guidance page not show the domain’s DKIM selectors even though they exist?" +msgstr "Pourquoi la page d'orientation n'affiche-t-elle pas les sélecteurs DKIM du domaine alors qu'ils existent ?" + +#: src/app/ReadGuidancePage.js:263 +msgid "Wiki" +msgstr "Wiki" #: src/guidance/WebConnectionResults.js:126 #: src/guidance/WebConnectionResults.js:166 @@ -3799,7 +4012,7 @@ msgstr "{buttonLabel}" msgid "{count} records..." msgstr "{count} enregistrements..." -#: src/dmarc/DmarcReportPage.js:101 +#: src/dmarc/DmarcReportPage.js:104 msgid "{domainSlug} does not support aggregate data" msgstr "{domainSlug} ne supporte pas les données agrégées" @@ -3807,7 +4020,7 @@ msgstr "{domainSlug} ne supporte pas les données agrégées" msgid "{editingDomainUrl} from {orgSlug} successfully updated to {0}" msgstr "{editingDomainUrl} de {orgSlug} mis à jour avec succès à {0}" -#: src/components/InfoPanel.js:33 +#: src/components/InfoPanel.js:52 msgid "{info}" msgstr "{info}" @@ -3815,7 +4028,7 @@ msgstr "{info}" #~ msgid "{label}" #~ msgstr "{label}" -#: src/components/InfoPanel.js:30 +#: src/components/InfoPanel.js:49 msgid "{title}" msgstr "{title}" diff --git a/frontend/src/organizationDetails/OrganizationDomains.js b/frontend/src/organizationDetails/OrganizationDomains.js index 01e369e2a3..4cf3ac904e 100644 --- a/frontend/src/organizationDetails/OrganizationDomains.js +++ b/frontend/src/organizationDetails/OrganizationDomains.js @@ -20,7 +20,7 @@ import { ListOf } from '../components/ListOf' import { LoadingMessage } from '../components/LoadingMessage' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { RelayPaginationControls } from '../components/RelayPaginationControls' -import { InfoButton, InfoBox, InfoPanel } from '../components/InfoPanel' +import { InfoBox, InfoPanel } from '../components/InfoPanel' import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' import { PAGINATED_ORG_DOMAINS as FORWARD, MY_TRACKER_DOMAINS } from '../graphql/queries' @@ -315,6 +315,7 @@ export function OrganizationDomains({ orgSlug }) { ...orderByOptions, ]} placeholder={t`Search for a domain`} + onToggle={onToggle} /> @@ -400,7 +401,6 @@ export function OrganizationDomains({ orgSlug }) { previous={previous} isLoadingMore={isLoadingMore} /> - ) } diff --git a/frontend/src/organizations/Organizations.js b/frontend/src/organizations/Organizations.js index f409fc16e9..9627b27dc8 100644 --- a/frontend/src/organizations/Organizations.js +++ b/frontend/src/organizations/Organizations.js @@ -9,7 +9,7 @@ import { OrganizationCard } from './OrganizationCard' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { LoadingMessage } from '../components/LoadingMessage' import { RelayPaginationControls } from '../components/RelayPaginationControls' -import { InfoButton, InfoBox, InfoPanel } from '../components/InfoPanel' +import { InfoBox, InfoPanel } from '../components/InfoPanel' import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' import { PAGINATED_ORGANIZATIONS as FORWARD } from '../graphql/queries' @@ -149,6 +149,7 @@ export default function Organizations() { resetToFirstPage={resetToFirstPage} orderByOptions={orderByOptions} placeholder={t`Search for an organization`} + onToggle={onToggle} /> {orgList} - ) } From fe3905bf4becce7fc9bb70369613d8b1dacbe39c Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 20 Apr 2023 19:31:19 +0000 Subject: [PATCH 004/113] [ci skip] gcr.io/track-compliance/frontend:master-5f969f0-1682018919 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index cbd7548d00..8fc893ed63 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-a802403-1680797001 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-5f969f0-1682018919 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 0180e35d38..fb5ffe5e79 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-a802403-1680797001 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-5f969f0-1682018919 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From cdbea07e25a7ff0f72fe72cd828ddd4e26e014ae Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Tue, 25 Apr 2023 12:44:16 -0400 Subject: [PATCH 005/113] Make scanner service sleep at beginning of handler - hopefully fix async (#4494) --- scanners/dns-processor/service.py | 1 + scanners/dns-scanner/service.py | 1 + scanners/web-processor/service.py | 1 + 3 files changed, 3 insertions(+) diff --git a/scanners/dns-processor/service.py b/scanners/dns-processor/service.py index a5cd136e6b..c3c4d4eb22 100644 --- a/scanners/dns-processor/service.py +++ b/scanners/dns-processor/service.py @@ -82,6 +82,7 @@ async def reconnected_cb(): logger.info(f"Connected to NATS at {nc.connected_url.netloc}...") async def subscribe_handler(msg): + await asyncio.sleep(0.01) subject = msg.subject reply = msg.reply data = msg.data.decode() diff --git a/scanners/dns-scanner/service.py b/scanners/dns-scanner/service.py index 23cf836692..d804ec4f29 100644 --- a/scanners/dns-scanner/service.py +++ b/scanners/dns-scanner/service.py @@ -52,6 +52,7 @@ async def reconnected_cb(): logger.info(f"Connected to NATS at {nc.connected_url.netloc}...") async def subscribe_handler(msg): + await asyncio.sleep(0.01) subject = msg.subject reply = msg.reply data = msg.data.decode() diff --git a/scanners/web-processor/service.py b/scanners/web-processor/service.py index c25f8a0f0a..971c73260a 100644 --- a/scanners/web-processor/service.py +++ b/scanners/web-processor/service.py @@ -85,6 +85,7 @@ async def reconnected_cb(): logger.info(f"Connected to NATS at {nc.connected_url.netloc}...") async def subscribe_handler(msg): + await asyncio.sleep(0.01) subject = msg.subject reply = msg.reply data = msg.data.decode() From 598233bbca1252dbd94987fa480a6d5f672e981e Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 25 Apr 2023 16:48:32 +0000 Subject: [PATCH 006/113] [ci skip] gcr.io/track-compliance/dns-processor:master-cdbea07-1682441075 gcr.io/track-compliance/dns-scanner:master-cdbea07-1682441074 gcr.io/track-compliance/web-processor:master-cdbea07-1682441075 --- k8s/apps/bases/scanners/dns-processor/deployment.yaml | 2 +- k8s/apps/bases/scanners/dns-scanner/deployment.yaml | 2 +- k8s/apps/bases/scanners/web-processor/deployment.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k8s/apps/bases/scanners/dns-processor/deployment.yaml b/k8s/apps/bases/scanners/dns-processor/deployment.yaml index 0745864eb4..35d8c6c9be 100644 --- a/k8s/apps/bases/scanners/dns-processor/deployment.yaml +++ b/k8s/apps/bases/scanners/dns-processor/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: dns-processor - image: gcr.io/track-compliance/dns-processor:master-c111971-1681319424 # {"$imagepolicy": "flux-system:dns-processor"} + image: gcr.io/track-compliance/dns-processor:master-cdbea07-1682441075 # {"$imagepolicy": "flux-system:dns-processor"} env: - name: DB_NAME value: track_dmarc diff --git a/k8s/apps/bases/scanners/dns-scanner/deployment.yaml b/k8s/apps/bases/scanners/dns-scanner/deployment.yaml index c93779bfc7..25d79a30c9 100644 --- a/k8s/apps/bases/scanners/dns-scanner/deployment.yaml +++ b/k8s/apps/bases/scanners/dns-scanner/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: dns-scanner - image: gcr.io/track-compliance/dns-scanner:master-5474a22-1681310508 # {"$imagepolicy": "flux-system:dns-scanner"} + image: gcr.io/track-compliance/dns-scanner:master-cdbea07-1682441074 # {"$imagepolicy": "flux-system:dns-scanner"} env: - name: PYTHONWARNINGS value: ignore diff --git a/k8s/apps/bases/scanners/web-processor/deployment.yaml b/k8s/apps/bases/scanners/web-processor/deployment.yaml index b69d8eff90..0a962ef22f 100644 --- a/k8s/apps/bases/scanners/web-processor/deployment.yaml +++ b/k8s/apps/bases/scanners/web-processor/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: web-processor - image: gcr.io/track-compliance/web-processor:master-5f03381-1681911454 # {"$imagepolicy": "flux-system:web-processor"} + image: gcr.io/track-compliance/web-processor:master-cdbea07-1682441075 # {"$imagepolicy": "flux-system:web-processor"} env: - name: DB_NAME value: track_dmarc From d8892e166a4ad145e0828908bdf5b29ff89b24d0 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:26:17 -0300 Subject: [PATCH 007/113] Fix info button styling (#4497) * fix info button styling in search boxes * add border to all searchbox components --- frontend/src/components/InfoPanel.js | 9 +--- .../src/components/RelayPaginationControls.js | 1 + frontend/src/components/SearchBox.js | 49 +++++-------------- frontend/src/theme/components/Button.js | 4 ++ 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/frontend/src/components/InfoPanel.js b/frontend/src/components/InfoPanel.js index 75c1d1d8f8..82793ff3ca 100644 --- a/frontend/src/components/InfoPanel.js +++ b/frontend/src/components/InfoPanel.js @@ -19,12 +19,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons' export function InfoPanel({ isOpen, onToggle, children }) { const btnRef = React.useRef() return ( - + @@ -60,7 +55,7 @@ export function InfoButton({ onToggle, ...props }) { {...props} icon={} aria-label="Open glossary" - variant="outline" + variant="primaryOutline" mx="2" onClick={onToggle} /> diff --git a/frontend/src/components/RelayPaginationControls.js b/frontend/src/components/RelayPaginationControls.js index acb60b6621..7b4d25bd50 100644 --- a/frontend/src/components/RelayPaginationControls.js +++ b/frontend/src/components/RelayPaginationControls.js @@ -38,6 +38,7 @@ export function RelayPaginationControls({ aria-label="Items per page" isDisabled={isLoadingMore} value={selectedDisplayLimit} + borderColor="black" onChange={(e) => { setSelectedDisplayLimit(parseInt(e.target.value)) resetToFirstPage() // Make sure to provide this as a prop if !onlyPagination diff --git a/frontend/src/components/SearchBox.js b/frontend/src/components/SearchBox.js index 4892593c3d..68f343bc4c 100644 --- a/frontend/src/components/SearchBox.js +++ b/frontend/src/components/SearchBox.js @@ -35,35 +35,12 @@ export function SearchBox({ onToggle, ...props }) { - const orderIconName = - orderDirection === 'ASC' ? : + const orderIconName = orderDirection === 'ASC' ? : return ( - - - - + + + + Search: @@ -83,21 +60,16 @@ export function SearchBox({ /> - + - + Sort by: {errors.filterCategory} - @@ -199,11 +187,7 @@ export function OrganizationDomains({ orgSlug }) { - @@ -245,27 +229,30 @@ export function OrganizationDomains({ orgSlug }) { ( - - No Domains - - )} - mb="4" - > - {({ id, domain, status, hasDMARCReport, claimTags, hidden, archived, rcode, blocked, webScanPending }, index) => ( + elements={nodes} + ifEmpty={() => ( + + No Domains + + )} + mb="4" + > + {( + { id, domain, status, hasDMARCReport, claimTags, hidden, archived, rcode, blocked, webScanPending }, + index, + ) => ( @@ -310,10 +297,7 @@ export function OrganizationDomains({ orgSlug }) { setOrderField={setOrderField} setOrderDirection={setOrderDirection} resetToFirstPage={resetToFirstPage} - orderByOptions={[ - { value: 'DOMAIN', text: t`Domain` }, - ...orderByOptions, - ]} + orderByOptions={[{ value: 'DOMAIN', text: t`Domain` }, ...orderByOptions]} placeholder={t`Search for a domain`} onToggle={onToggle} /> @@ -357,29 +341,13 @@ export function OrganizationDomains({ orgSlug }) { <> {statuses[filterCategory]} )} - - setFilters(filters.filter((_, i) => i !== idx)) - } - /> + setFilters(filters.filter((_, i) => i !== idx))} /> ) })} From 8169ba16e96c1c273e32384b216b14f59b3a5c66 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 27 Apr 2023 14:31:44 +0000 Subject: [PATCH 009/113] [ci skip] gcr.io/track-compliance/api-js:master-53f65de-1682605811 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index e1660eca6c..346c346093 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-a802403-1680796913 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-53f65de-1682605811 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 9a1dae6be9..c9ab37100b 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-a802403-1680796913 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-53f65de-1682605811 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 1dc088507c595703ec80ff5ed49c8c00f85ac5ed Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 27 Apr 2023 14:33:47 +0000 Subject: [PATCH 010/113] [ci skip] gcr.io/track-compliance/frontend:master-d8892e1-1682605884 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 8fc893ed63..7ccb2d30fb 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-5f969f0-1682018919 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-d8892e1-1682605884 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index fb5ffe5e79..d72f61b099 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-5f969f0-1682018919 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-d8892e1-1682605884 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 60e1071f0bf6b44259e46a734d2a3803400f2775 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 27 Apr 2023 14:34:47 +0000 Subject: [PATCH 011/113] [ci skip] gcr.io/track-compliance/frontend:master-53f65de-1682605929 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 7ccb2d30fb..3e172350b3 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-d8892e1-1682605884 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-53f65de-1682605929 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index d72f61b099..04e63babab 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-d8892e1-1682605884 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-53f65de-1682605929 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 769f1baba9ed753e1c0bbea02189c99c9fc6bf0b Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Mon, 1 May 2023 12:33:30 -0400 Subject: [PATCH 012/113] Separate connect and read timeouts for http requests (#4500) * Separate connect and read timeouts for http requests * Change connect timeout to 1 sec * Change connect timeout to 1 sec --- .../endpoint_chain_scanner.py | 19 ++++++++++++++++++- .../scan/tls_scanner/tls_scanner.py | 15 ++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/scanners/web-scanner/scan/endpoint_chain_scanner/endpoint_chain_scanner.py b/scanners/web-scanner/scan/endpoint_chain_scanner/endpoint_chain_scanner.py index dcec0e6214..9a6b6e8059 100644 --- a/scanners/web-scanner/scan/endpoint_chain_scanner/endpoint_chain_scanner.py +++ b/scanners/web-scanner/scan/endpoint_chain_scanner/endpoint_chain_scanner.py @@ -10,9 +10,12 @@ logger = logging.getLogger(__name__) -TIMEOUT = 10 +# Set the default timeout for requests (connect, read) +TIMEOUT = (1.0, 10) CONNECTION_ERROR = "CONNECTION_ERROR" +CONNECTION_TIMEOUT_ERROR = "CONNECTION_TIMEOUT_ERROR" +READ_TIMEOUT_ERROR = "READ_TIMEOUT_ERROR" TIMEOUT_ERROR = "TIMEOUT_ERROR" UNKNOWN_ERROR = "UNKNOWN_ERROR" @@ -128,6 +131,20 @@ def request_connection(uri: Optional[str] = None, connection = HTTPSConnectionRequest(uri=uri, http_response=response) return {"connection": connection, "response": response} + except requests.exceptions.ConnectTimeout as e: + logger.error(f"Connection timeout error {context}: {str(e)}") + if scheme.lower() == "http": + connection = HTTPConnectionRequest(uri=uri, error=CONNECTION_TIMEOUT_ERROR) + elif scheme.lower() == "https": + connection = HTTPSConnectionRequest(uri=uri, error=CONNECTION_TIMEOUT_ERROR) + return {"connection": connection, "response": response} + except requests.exceptions.ReadTimeout as e: + logger.error(f"Read timeout error {context}: {str(e)}") + if scheme.lower() == "http": + connection = HTTPConnectionRequest(uri=uri, error=READ_TIMEOUT_ERROR) + elif scheme.lower() == "https": + connection = HTTPSConnectionRequest(uri=uri, error=READ_TIMEOUT_ERROR) + return {"connection": connection, "response": response} except requests.exceptions.Timeout as e: logger.error(f"Timeout error {context}: {str(e)}") if scheme.lower() == "http": diff --git a/scanners/web-scanner/scan/tls_scanner/tls_scanner.py b/scanners/web-scanner/scan/tls_scanner/tls_scanner.py index 9e5906a19c..2dc5fa3495 100644 --- a/scanners/web-scanner/scan/tls_scanner/tls_scanner.py +++ b/scanners/web-scanner/scan/tls_scanner/tls_scanner.py @@ -24,8 +24,7 @@ logger = logging.getLogger() -TIMEOUT = int(os.getenv("SCAN_TIMEOUT", "80")) - +CONNECT_TIMEOUT = 1 @dataclass class AcceptedCipherSuites: @@ -184,9 +183,15 @@ def __init__(self, domain: str, ip_address: str = None): try: scan_request = ServerScanRequest( - server_location=ServerNetworkLocation(hostname=domain, - ip_address=ip_address), - scan_commands=designated_scans + server_location=ServerNetworkLocation( + hostname=domain, + ip_address=ip_address + ), + scan_commands=designated_scans, + network_configuration=ServerNetworkConfiguration( + tls_server_name_indication=domain, + network_timeout=CONNECT_TIMEOUT + ) ) except ServerHostnameCouldNotBeResolved as e: logger.info(f"Server hostname could not be resolved for domain '{domain}': {str(e)}") From 636dca03ec78a345205861549f2bf1cfef46f9f0 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 1 May 2023 16:48:57 +0000 Subject: [PATCH 013/113] [ci skip] gcr.io/track-compliance/web-scanner:master-769f1ba-1682958826 --- k8s/apps/bases/scanners/web-scanner/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/apps/bases/scanners/web-scanner/deployment.yaml b/k8s/apps/bases/scanners/web-scanner/deployment.yaml index 2b205a989c..7ac6603989 100644 --- a/k8s/apps/bases/scanners/web-scanner/deployment.yaml +++ b/k8s/apps/bases/scanners/web-scanner/deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: web-scanner - image: gcr.io/track-compliance/web-scanner:master-1175b1e-1681325623 # {"$imagepolicy": "flux-system:web-scanner"} + image: gcr.io/track-compliance/web-scanner:master-769f1ba-1682958826 # {"$imagepolicy": "flux-system:web-scanner"} env: - name: PYTHONWARNINGS value: ignore From 6915258a1bdc3f442a75805ef889cea3c6a0fde3 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Thu, 4 May 2023 11:03:30 -0400 Subject: [PATCH 014/113] Only create affiliation on invite if not already affiliated (#4505) * Only create affiliation on invite if not already affiliated * Fix thrown error * Remove all thrown errors, use return _type of 'error' instead * Add missing translations * Fix tests --- .../__tests__/invite-user-to-org.test.js | 867 +++++++++--------- .../mutations/invite-user-to-org.js | 214 +++-- api/src/locale/en/messages.js | 2 +- api/src/locale/en/messages.po | 291 +++--- api/src/locale/fr/messages.js | 2 +- api/src/locale/fr/messages.po | 468 ++++++---- 6 files changed, 989 insertions(+), 855 deletions(-) diff --git a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js index 255f40bd7c..a6d3496867 100644 --- a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js +++ b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js @@ -1,36 +1,23 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import { - checkPermission, - userRequired, - verifiedRequired, - tfaRequired, -} from '../../../auth' -import {createMutationSchema} from '../../../mutation' -import {createQuerySchema} from '../../../query' -import {cleanseInput} from '../../../validators' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey, loadUserByUserName} from '../../../user/loaders' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { cleanseInput } from '../../../validators' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey, loadUserByUserName } from '../../../user/loaders' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY} = process.env +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env describe('invite user to org', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - tokenize, - user + let query, drop, truncate, schema, collections, transaction, i18n, tokenize, user, org, userToInvite const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -53,7 +40,7 @@ describe('invite user to org', () => { describe('given a successful invitation', () => { beforeAll(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -84,8 +71,8 @@ describe('invite user to org', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -96,30 +83,35 @@ describe('invite user to org', () => { }) let org beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', + org = await ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, }, - }, - }) + { returnNew: true }, + ) + ).new }) describe('users role is super admin', () => { beforeEach(async () => { @@ -184,18 +176,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -203,8 +195,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully invited user to organization, and sent notification email.', + status: 'Successfully invited user to organization, and sent notification email.', }, }, }, @@ -281,18 +272,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -300,8 +291,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully invited user to organization, and sent notification email.', + status: 'Successfully invited user to organization, and sent notification email.', }, }, }, @@ -378,18 +368,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -397,8 +387,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully invited user to organization, and sent notification email.', + status: 'Successfully invited user to organization, and sent notification email.', }, }, }, @@ -470,18 +459,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -489,8 +478,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully sent invitation to service, and organization email.', + status: 'Successfully sent invitation to service, and organization email.', }, }, }, @@ -566,18 +554,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -585,8 +573,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully sent invitation to service, and organization email.', + status: 'Successfully sent invitation to service, and organization email.', }, }, }, @@ -663,18 +650,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -682,8 +669,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully sent invitation to service, and organization email.', + status: 'Successfully sent invitation to service, and organization email.', }, }, }, @@ -777,18 +763,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -796,8 +782,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully invited user to organization, and sent notification email.', + status: 'Successfully invited user to organization, and sent notification email.', }, }, }, @@ -874,18 +859,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -893,8 +878,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully invited user to organization, and sent notification email.', + status: 'Successfully invited user to organization, and sent notification email.', }, }, }, @@ -967,18 +951,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -986,8 +970,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully sent invitation to service, and organization email.', + status: 'Successfully sent invitation to service, and organization email.', }, }, }, @@ -1064,18 +1047,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -1083,8 +1066,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - 'Successfully sent invitation to service, and organization email.', + status: 'Successfully sent invitation to service, and organization email.', }, }, }, @@ -1121,8 +1103,8 @@ describe('invite user to org', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1133,30 +1115,35 @@ describe('invite user to org', () => { }) let org beforeEach(async () => { - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', + org = await ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, }, - }, - }) + { returnNew: true }, + ) + ).new }) describe('users role is super admin', () => { beforeEach(async () => { @@ -1221,18 +1208,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -1318,18 +1305,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -1415,18 +1402,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -1508,18 +1495,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -1527,8 +1514,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", }, }, }, @@ -1548,7 +1534,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: {userName: 'test@email.gc.ca', preferredLang: 'french'}, + user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, orgName: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) @@ -1602,18 +1588,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -1621,8 +1607,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", }, }, }, @@ -1642,7 +1627,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: {userName: 'test@email.gc.ca', preferredLang: 'french'}, + user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, orgName: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) @@ -1696,18 +1681,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -1715,8 +1700,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", }, }, }, @@ -1736,7 +1720,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: {userName: 'test@email.gc.ca', preferredLang: 'french'}, + user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, orgName: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) @@ -1807,18 +1791,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -1904,18 +1888,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteEmail: sendOrgInviteEmail}, - validators: {cleanseInput}, + notify: { sendOrgInviteEmail: sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) @@ -1997,18 +1981,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -2016,8 +2000,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", }, }, }, @@ -2037,7 +2020,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: {userName: 'test@email.gc.ca', preferredLang: 'french'}, + user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, orgName: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) @@ -2091,18 +2074,18 @@ describe('invite user to org', () => { tokenize, userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'fr'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'fr' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount }, + validators: { cleanseInput }, }, ) @@ -2110,8 +2093,7 @@ describe('invite user to org', () => { data: { inviteUserToOrg: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", }, }, }, @@ -2131,7 +2113,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: {userName: 'test@email.gc.ca', preferredLang: 'french'}, + user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, orgName: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) @@ -2142,13 +2124,84 @@ describe('invite user to org', () => { }) }) describe('given an unsuccessful invitation', () => { - describe('users language is set to english', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(async () => { + user = ( + await collections.users.save( + { + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + userToInvite = ( + await collections.users.save( + { + userName: 'usertoinvite@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + org = ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to french', () => { beforeAll(() => { i18n = setupI18n({ - locale: 'en', + locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2165,9 +2218,9 @@ describe('invite user to org', () => { mutation { inviteUserToOrg( input: { - userName: "test.account@istio.actually.exists" + userName: "${user.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 1)}" + orgId: "${toGlobalId('organizations', org._key)}" preferredLang: FRENCH } ) { @@ -2205,20 +2258,12 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loaders: { - loadOrgByKey: { - load: jest.fn(), - }, - loadUserByKey: { - load: jest.fn(), - }, - loadUserByUserName: { - load: jest.fn(), - }, - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query, i18n }), }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2227,16 +2272,14 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 400, - description: 'Unable to invite yourself to an org.', + description: "Impossible de s'inviter à un org.", }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite themselves to 1.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to invite themselves to ${org._key}.`]) }) }) describe('user attempts to invite to an org that does not exist', () => { @@ -2297,8 +2340,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2307,7 +2350,7 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 400, - description: 'Unable to invite user to unknown organization.', + description: "Impossible d'inviter un utilisateur à une organisation inconnue.", }, }, }, @@ -2368,7 +2411,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -2377,8 +2420,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2388,7 +2431,7 @@ describe('invite user to org', () => { result: { code: 403, description: - 'Permission Denied: Please contact organization admin for help with user invitations.', + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", }, }, }, @@ -2449,7 +2492,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -2458,8 +2501,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2469,7 +2512,7 @@ describe('invite user to org', () => { result: { code: 403, description: - 'Permission Denied: Please contact organization admin for help with user invitations.', + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", }, }, }, @@ -2530,7 +2573,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -2539,8 +2582,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2550,7 +2593,7 @@ describe('invite user to org', () => { result: { code: 403, description: - 'Permission Denied: Please contact organization admin for help with user invitations.', + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", }, }, }, @@ -2571,9 +2614,9 @@ describe('invite user to org', () => { mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" + userName: "${userToInvite.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" + orgId: "${toGlobalId('organizations', org._key)}" preferredLang: FRENCH } ) { @@ -2613,30 +2656,29 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query, i18n }), }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError('Unable to invite user. Please try again.'), - ] + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: "Impossible d'inviter un utilisateur. Veuillez réessayer.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `Transaction step error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx step err`, + `Transaction step error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: ${org.orgDetails.fr.slug}, error: trx step err`, ]) }) }) @@ -2648,9 +2690,9 @@ describe('invite user to org', () => { mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" + userName: "${userToInvite.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" + orgId: "${toGlobalId('organizations', org._key)}" preferredLang: FRENCH } ) { @@ -2691,33 +2733,32 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, notify: { sendOrgInviteCreateAccount: jest.fn(), sendOrgInviteEmail: jest.fn(), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError('Unable to invite user. Please try again.'), - ] + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: "Impossible d'inviter un utilisateur. Veuillez réessayer.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx commit err`, + `Transaction commit error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: secretariat-conseil-tresor, error: trx commit err`, ]) }) }) @@ -2726,10 +2767,10 @@ describe('invite user to org', () => { describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ - locale: 'fr', + locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2798,8 +2839,8 @@ describe('invite user to org', () => { }, }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2808,16 +2849,14 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 400, - description: "Impossible de s'inviter à un org.", + description: 'Unable to invite yourself to an org.', }, }, }, } expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to invite themselves to 1.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to invite themselves to 1.`]) }) }) describe('user attempts to invite to an org that does not exist', () => { @@ -2878,8 +2917,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2888,8 +2927,7 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 400, - description: - "Impossible d'inviter un utilisateur à une organisation inconnue.", + description: 'Unable to invite user to unknown organization.', }, }, }, @@ -2950,7 +2988,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -2959,8 +2997,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -2969,8 +3007,7 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", + description: 'Permission Denied: Please contact organization admin for help with user invitations.', }, }, }, @@ -3031,7 +3068,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -3040,8 +3077,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -3050,8 +3087,7 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", + description: 'Permission Denied: Please contact organization admin for help with user invitations.', }, }, }, @@ -3112,7 +3148,7 @@ describe('invite user to org', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, loadUserByKey: { load: jest.fn(), @@ -3121,8 +3157,8 @@ describe('invite user to org', () => { load: jest.fn(), }, }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) @@ -3131,8 +3167,7 @@ describe('invite user to org', () => { inviteUserToOrg: { result: { code: 403, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.", + description: 'Permission Denied: Please contact organization admin for help with user invitations.', }, }, }, @@ -3153,9 +3188,9 @@ describe('invite user to org', () => { mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" + userName: "${userToInvite.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" + orgId: "${toGlobalId('organizations', org._key)}" preferredLang: FRENCH } ) { @@ -3195,32 +3230,29 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, - notify: {sendOrgInviteCreateAccount: jest.fn()}, - validators: {cleanseInput}, + notify: { sendOrgInviteCreateAccount: jest.fn() }, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - "Impossible d'inviter un utilisateur. Veuillez réessayer.", - ), - ] + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: 'Unable to invite user. Please try again.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `Transaction step error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx step err`, + `Transaction step error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx step err`, ]) }) }) @@ -3232,9 +3264,9 @@ describe('invite user to org', () => { mutation { inviteUserToOrg( input: { - userName: "test@email.gc.ca" + userName: "${userToInvite.userName}" requestedRole: USER - orgId: "${toGlobalId('organizations', 123)}" + orgId: "${toGlobalId('organizations', org._key)}" preferredLang: FRENCH } ) { @@ -3275,35 +3307,32 @@ describe('invite user to org', () => { tfaRequired: jest.fn(), }, loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - _key: 123, - slug: 'secretariat-conseil-tresor', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, notify: { sendOrgInviteCreateAccount: jest.fn(), sendOrgInviteEmail: jest.fn(), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - "Impossible d'inviter un utilisateur. Veuillez réessayer.", - ), - ] + const error = { + data: { + inviteUserToOrg: { + result: { + code: 500, + description: 'Unable to invite user. Please try again.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `Transaction commit error occurred while user: 123 attempted to invite user: 456 to org: secretariat-conseil-tresor, error: trx commit err`, + `Transaction commit error occurred while user: 123 attempted to invite user: ${userToInvite._key} to org: treasury-board-secretariat, error: trx commit err`, ]) }) }) diff --git a/api/src/affiliation/mutations/invite-user-to-org.js b/api/src/affiliation/mutations/invite-user-to-org.js index 44a347e2a1..4c281608b0 100644 --- a/api/src/affiliation/mutations/invite-user-to-org.js +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -1,10 +1,10 @@ -import {GraphQLNonNull, GraphQLID} from 'graphql' -import {mutationWithClientMutationId, fromGlobalId} from 'graphql-relay' -import {GraphQLEmailAddress} from 'graphql-scalars' -import {t} from '@lingui/macro' +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' -import {inviteUserToOrgUnion} from '../unions' -import {LanguageEnums, RoleEnums} from '../../enums' +import { inviteUserToOrgUnion } from '../unions' +import { LanguageEnums, RoleEnums } from '../../enums' import { logActivity } from '../../audit-logs/mutations/log-activity' export const inviteUserToOrg = new mutationWithClientMutationId({ @@ -47,34 +47,26 @@ able to sign-up and be assigned to that organization in one mutation.`, collections, transaction, userKey, - auth: { - checkPermission, - tokenize, - userRequired, - verifiedRequired, - tfaRequired, - }, - loaders: {loadOrgByKey, loadUserByUserName}, - notify: {sendOrgInviteCreateAccount, sendOrgInviteEmail}, - validators: {cleanseInput}, + auth: { checkPermission, tokenize, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByUserName }, + notify: { sendOrgInviteCreateAccount, sendOrgInviteEmail }, + validators: { cleanseInput }, }, ) => { const userName = cleanseInput(args.userName).toLowerCase() const requestedRole = cleanseInput(args.requestedRole) - const {id: orgId} = fromGlobalId(cleanseInput(args.orgId)) + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) const preferredLang = cleanseInput(args.preferredLang) // Get requesting user const user = await userRequired() - verifiedRequired({user}) - tfaRequired({user}) + verifiedRequired({ user }) + tfaRequired({ user }) // Make sure user is not inviting themselves if (user.userName === userName) { - console.warn( - `User: ${userKey} attempted to invite themselves to ${orgId}.`, - ) + console.warn(`User: ${userKey} attempted to invite themselves to ${orgId}.`) return { _type: 'error', code: 400, @@ -97,7 +89,7 @@ able to sign-up and be assigned to that organization in one mutation.`, } // Check to see requesting users permission to the org is - const permission = await checkPermission({orgId: org._id}) + const permission = await checkPermission({ orgId: org._id }) if ( typeof permission === 'undefined' || @@ -110,9 +102,7 @@ able to sign-up and be assigned to that organization in one mutation.`, return { _type: 'error', code: 403, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with user invitations.`, - ), + description: i18n._(t`Permission Denied: Please contact organization admin for help with user invitations.`), } } @@ -122,22 +112,18 @@ able to sign-up and be assigned to that organization in one mutation.`, // If there is not associated account with that user name send invite to org with create account if (typeof requestedUser === 'undefined') { const token = tokenize({ - parameters: {userName, orgKey: org._key, requestedRole}, + parameters: { userName, orgKey: org._key, requestedRole }, expPeriod: 24, }) - const createAccountLink = `https://${request.get( - 'host', - )}/create-user/${token}` + const createAccountLink = `https://${request.get('host')}/create-user/${token}` await sendOrgInviteCreateAccount({ - user: {userName: userName, preferredLang}, + user: { userName: userName, preferredLang }, orgName: org.name, createAccountLink, }) - console.info( - `User: ${userKey} successfully invited user: ${userName} to the service, and org: ${org.slug}.`, - ) + console.info(`User: ${userKey} successfully invited user: ${userName} to the service, and org: ${org.slug}.`) await logActivity({ transaction, collections, @@ -155,29 +141,58 @@ able to sign-up and be assigned to that organization in one mutation.`, name: org.name, }, // name of resource being acted upon resourceType: 'user', // user, org, domain - updatedProperties: [ - { name: 'role', oldValue: '', newValue: requestedRole }, - ], + updatedProperties: [{ name: 'role', oldValue: '', newValue: requestedRole }], }, }) return { _type: 'regular', - status: i18n._( - t`Successfully sent invitation to service, and organization email.`, - ), + status: i18n._(t`Successfully sent invitation to service, and organization email.`), + } + } + + // If account is found, check if already affiliated with org + let affiliationCursor + try { + affiliationCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 INBOUND ${requestedUser._id} affiliations + FILTER e._from == ${org._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, + ) + return { + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user to organization. Please try again.`), + } + } + + if (affiliationCursor.count > 0) { + // If affiliation is found, return error + console.warn( + `User: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug} however they are already affiliated with that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to invite user to organization. User is already affiliated with organization.`), } } - // If account is found add just add affiliation - else { - // Setup Transaction - const trx = await transaction(collections) - - // Create affiliation - try { - await trx.step( - () => - query` + + // User is not affiliated with org, create affiliation + + // Setup Transaction + const trx = await transaction(collections) + + // Create affiliation + try { + await trx.step( + () => + query` WITH affiliations, organizations, users INSERT { _from: ${org._id}, @@ -186,61 +201,62 @@ able to sign-up and be assigned to that organization in one mutation.`, owner: false } INTO affiliations `, - ) - } catch (err) { - console.error( - `Transaction step error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user. Please try again.`)) + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, + ) + return { + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user. Please try again.`), } + } - await sendOrgInviteEmail({ - user: requestedUser, - orgName: org.name, - }) - - // Commit affiliation - try { - await trx.commit() - } catch (err) { - console.error( - `Transaction commit error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user. Please try again.`)) - } + await sendOrgInviteEmail({ + user: requestedUser, + orgName: org.name, + }) - console.info( - `User: ${userKey} successfully invited user: ${requestedUser._key} to the org: ${org.slug}.`, + // Commit affiliation + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred while user: ${userKey} attempted to invite user: ${requestedUser._key} to org: ${org.slug}, error: ${err}`, ) - await logActivity({ - transaction, - collections, - query, - initiatedBy: { - id: user._key, - userName: user.userName, - role: permission, - }, - action: 'add', - target: { - resource: userName, - organization: { - id: org._key, - name: org.name, - }, // name of resource being acted upon - updatedProperties: [ - { name: 'role', oldValue: '', newValue: requestedRole }, - ], - resourceType: 'user', // user, org, domain - }, - }) - return { - _type: 'regular', - status: i18n._( - t`Successfully invited user to organization, and sent notification email.`, - ), + _type: 'error', + code: 500, + description: i18n._(t`Unable to invite user. Please try again.`), } } + + console.info(`User: ${userKey} successfully invited user: ${requestedUser._key} to the org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + }, + action: 'add', + target: { + resource: userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + updatedProperties: [{ name: 'role', oldValue: '', newValue: requestedRole }], + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully invited user to organization, and sent notification email.`), + } }, }) diff --git a/api/src/locale/en/messages.js b/api/src/locale/en/messages.js index 422ca019c8..deef109e74 100644 --- a/api/src/locale/en/messages.js +++ b/api/src/locale/en/messages.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Authentication error. Please sign in.","Cannot query affiliations on organization without admin permission or higher.":"Cannot query affiliations on organization without admin permission or higher.","Email already in use.":"Email already in use.","If an account with this username is found, a password reset link will be found in your inbox.":"If an account with this username is found, a password reset link will be found in your inbox.","If an account with this username is found, an email verification link will be found in your inbox.":"If an account with this username is found, an email verification link will be found in your inbox.","Incorrect TFA code. Please sign in again.":"Incorrect TFA code. Please sign in again.","Incorrect token value. Please request a new email.":"Incorrect token value. Please request a new email.","Incorrect username or password. Please try again.":"Incorrect username or password. Please try again.","Invalid token, please sign in.":"Invalid token, please sign in.","New passwords do not match.":"New passwords do not match.","No organization with the provided slug could be found.":"No organization with the provided slug could be found.","No verified domain with the provided domain could be found.":"No verified domain with the provided domain could be found.","No verified organization with the provided slug could be found.":"No verified organization with the provided slug could be found.","Organization has already been verified.":"Organization has already been verified.","Organization name already in use, please choose another and try again.":"Organization name already in use, please choose another and try again.","Organization name already in use. Please try again with a different name.":"Organization name already in use. Please try again with a different name.","Ownership check error. Unable to request domain information.":"Ownership check error. Unable to request domain information.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIM` connection is not supported.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passing both `first` and `last` to paginate the `DMARC` connection is not supported.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passing both `first` and `last` to paginate the `Domain` connection is not supported.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passing both `first` and `last` to paginate the `Organization` connection is not supported.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passing both `first` and `last` to paginate the `SPF` connection is not supported.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passing both `first` and `last` to paginate the `SSL` connection is not supported.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passing both `first` and `last` to paginate the `User` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.","Password does not meet requirements.":"Password does not meet requirements.","Password was successfully reset.":"Password was successfully reset.","Password was successfully updated.":"Password was successfully updated.","Passwords do not match.":"Passwords do not match.","Permission Denied: Could not retrieve specified organization.":"Permission Denied: Could not retrieve specified organization.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission Denied: Multi-factor authentication is required for admin accounts","Permission Denied: Please contact org owner to transfer ownership.":"Permission Denied: Please contact org owner to transfer ownership.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission Denied: Please contact organization admin for help with removing domain.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission Denied: Please contact organization admin for help with removing organization.","Permission Denied: Please contact organization admin for help with removing users.":"Permission Denied: Please contact organization admin for help with removing users.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission Denied: Please contact organization admin for help with updating organization.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission Denied: Please contact organization admin for help with updating user roles.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission Denied: Please contact organization admin for help with user invitations.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission Denied: Please contact organization admin for help with user role changes.","Permission Denied: Please contact organization user for help with creating domain.":"Permission Denied: Please contact organization user for help with creating domain.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission Denied: Please contact organization user for help with retrieving this domain.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission Denied: Please contact organization user for help with scanning this domain.","Permission Denied: Please contact organization user for help with updating this domain.":"Permission Denied: Please contact organization user for help with updating this domain.","Permission Denied: Please contact super admin for help with removing domain.":"Permission Denied: Please contact super admin for help with removing domain.","Permission Denied: Please contact super admin for help with removing organization.":"Permission Denied: Please contact super admin for help with removing organization.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission Denied: Please contact super admin for help with verifying this organization.","Permission check error. Unable to request domain information.":"Permission check error. Unable to request domain information.","Permission error, not an admin for this user.":"Permission error, not an admin for this user.","Permission error: Unable to close other user's account.":"Permission error: Unable to close other user's account.","Permissions error. You do not have sufficient permissions to access this data.":"Permissions error. You do not have sufficient permissions to access this data.","Phone number has been successfully removed.":"Phone number has been successfully removed.","Phone number has been successfully set, you will receive a verification text message shortly.":"Phone number has been successfully set, you will receive a verification text message shortly.","Profile successfully updated.":"Profile successfully updated.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Affiliation` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DkimFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcSummaries` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Domain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `FullPassTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `GuidanceTag` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Organization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `SpfFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `User` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedDomain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedOrganization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIMResults` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIM` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DMARC` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `HTTPS` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SPF` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SSL` connection exceeds the `",["argSet"],"` limit of 100 records."],"Successfully closed account.":"Successfully closed account.","Successfully dispatched one time scan.":"Successfully dispatched one time scan.","Successfully email verified account, and set TFA send method to email.":"Successfully email verified account, and set TFA send method to email.","Successfully invited user to organization, and sent notification email.":"Successfully invited user to organization, and sent notification email.","Successfully left organization: {0}":["Successfully left organization: ",["0"]],"Successfully removed domain: {0} from {1}.":["Successfully removed domain: ",["0"]," from ",["1"],"."],"Successfully removed organization: {0}.":["Successfully removed organization: ",["0"],"."],"Successfully removed user from organization.":"Successfully removed user from organization.","Successfully sent invitation to service, and organization email.":"Successfully sent invitation to service, and organization email.","Successfully signed out.":"Successfully signed out.","Successfully transferred org: {0} ownership to user: {1}":["Successfully transferred org: ",["0"]," ownership to user: ",["1"]],"Successfully verified organization: {0}.":["Successfully verified organization: ",["0"],"."],"Successfully verified phone number, and set TFA send method to text.":"Successfully verified phone number, and set TFA send method to text.","Token value incorrect, please sign in again.":"Token value incorrect, please sign in again.","Too many failed login attempts, please reset your password, and try again.":"Too many failed login attempts, please reset your password, and try again.","Two factor code is incorrect. Please try again.":"Two factor code is incorrect. Please try again.","Two factor code length is incorrect. Please try again.":"Two factor code length is incorrect. Please try again.","Unable leave organization. Please try again.":"Unable leave organization. Please try again.","Unable to authenticate. Please try again.":"Unable to authenticate. Please try again.","Unable to check permission. Please try again.":"Unable to check permission. Please try again.","Unable to close account of an undefined user.":"Unable to close account of an undefined user.","Unable to close account. Please try again.":"Unable to close account. Please try again.","Unable to create domain in unknown organization.":"Unable to create domain in unknown organization.","Unable to create domain, organization has already claimed it.":"Unable to create domain, organization has already claimed it.","Unable to create domain. Please try again.":"Unable to create domain. Please try again.","Unable to create organization. Please try again.":"Unable to create organization. Please try again.","Unable to dispatch one time scan. Please try again.":"Unable to dispatch one time scan. Please try again.","Unable to find Aggregate guidance tag(s). Please try again.":"Unable to find Aggregate guidance tag(s). Please try again.","Unable to find DKIM guidance tag(s). Please try again.":"Unable to find DKIM guidance tag(s). Please try again.","Unable to find DKIM result(s). Please try again.":"Unable to find DKIM result(s). Please try again.","Unable to find DKIM scan(s). Please try again.":"Unable to find DKIM scan(s). Please try again.","Unable to find DMARC guidance tag(s). Please try again.":"Unable to find DMARC guidance tag(s). Please try again.","Unable to find DMARC scan(s). Please try again.":"Unable to find DMARC scan(s). Please try again.","Unable to find DMARC summary data. Please try again.":"Unable to find DMARC summary data. Please try again.","Unable to find DNS scan(s). Please try again.":"Unable to find DNS scan(s). Please try again.","Unable to find HTTPS guidance tag(s). Please try again.":"Unable to find HTTPS guidance tag(s). Please try again.","Unable to find HTTPS scan(s). Please try again.":"Unable to find HTTPS scan(s). Please try again.","Unable to find SPF guidance tag(s). Please try again.":"Unable to find SPF guidance tag(s). Please try again.","Unable to find SPF scan(s). Please try again.":"Unable to find SPF scan(s). Please try again.","Unable to find SSL guidance tag(s). Please try again.":"Unable to find SSL guidance tag(s). Please try again.","Unable to find SSL scan(s). Please try again.":"Unable to find SSL scan(s). Please try again.","Unable to find the requested domain.":"Unable to find the requested domain.","Unable to find user affiliation(s). Please try again.":"Unable to find user affiliation(s). Please try again.","Unable to find verified organization(s). Please try again.":"Unable to find verified organization(s). Please try again.","Unable to invite user to unknown organization.":"Unable to invite user to unknown organization.","Unable to invite user. Please try again.":"Unable to invite user. Please try again.","Unable to invite yourself to an org.":"Unable to invite yourself to an org.","Unable to leave organization. Please try again.":"Unable to leave organization. Please try again.","Unable to leave undefined organization.":"Unable to leave undefined organization.","Unable to load Aggregate guidance tag(s). Please try again.":"Unable to load Aggregate guidance tag(s). Please try again.","Unable to load DKIM failure data. Please try again.":"Unable to load DKIM failure data. Please try again.","Unable to load DKIM guidance tag(s). Please try again.":"Unable to load DKIM guidance tag(s). Please try again.","Unable to load DKIM result(s). Please try again.":"Unable to load DKIM result(s). Please try again.","Unable to load DKIM scan(s). Please try again.":"Unable to load DKIM scan(s). Please try again.","Unable to load DMARC failure data. Please try again.":"Unable to load DMARC failure data. Please try again.","Unable to load DMARC guidance tag(s). Please try again.":"Unable to load DMARC guidance tag(s). Please try again.","Unable to load DMARC phase summary. Please try again.":"Unable to load DMARC phase summary. Please try again.","Unable to load DMARC scan(s). Please try again.":"Unable to load DMARC scan(s). Please try again.","Unable to load DMARC summary data. Please try again.":"Unable to load DMARC summary data. Please try again.","Unable to load DNS scan(s). Please try again.":"Unable to load DNS scan(s). Please try again.","Unable to load HTTPS guidance tag(s). Please try again.":"Unable to load HTTPS guidance tag(s). Please try again.","Unable to load HTTPS scan(s). Please try again.":"Unable to load HTTPS scan(s). Please try again.","Unable to load HTTPS summary. Please try again.":"Unable to load HTTPS summary. Please try again.","Unable to load SPF failure data. Please try again.":"Unable to load SPF failure data. Please try again.","Unable to load SPF guidance tag(s). Please try again.":"Unable to load SPF guidance tag(s). Please try again.","Unable to load SPF scan(s). Please try again.":"Unable to load SPF scan(s). Please try again.","Unable to load SSL guidance tag(s). Please try again.":"Unable to load SSL guidance tag(s). Please try again.","Unable to load SSL scan(s). Please try again.":"Unable to load SSL scan(s). Please try again.","Unable to load affiliation information. Please try again.":"Unable to load affiliation information. Please try again.","Unable to load affiliation(s). Please try again.":"Unable to load affiliation(s). Please try again.","Unable to load domain(s). Please try again.":"Unable to load domain(s). Please try again.","Unable to load domain. Please try again.":"Unable to load domain. Please try again.","Unable to load full pass data. Please try again.":"Unable to load full pass data. Please try again.","Unable to load mail summary. Please try again.":"Unable to load mail summary. Please try again.","Unable to load organization domain statuses. Please try again.":"Unable to load organization domain statuses. Please try again.","Unable to load organization(s). Please try again.":"Unable to load organization(s). Please try again.","Unable to load owner information. Please try again.":"Unable to load owner information. Please try again.","Unable to load summary. Please try again.":"Unable to load summary. Please try again.","Unable to load tags(s). Please try again.":"Unable to load tags(s). Please try again.","Unable to load user(s). Please try again.":"Unable to load user(s). Please try again.","Unable to load verified domain(s). Please try again.":"Unable to load verified domain(s). Please try again.","Unable to load verified organization(s). Please try again.":"Unable to load verified organization(s). Please try again.","Unable to load web scan(s). Please try again.":"Unable to load web scan(s). Please try again.","Unable to load web summary. Please try again.":"Unable to load web summary. Please try again.","Unable to query affiliation(s). Please try again.":"Unable to query affiliation(s). Please try again.","Unable to query domain(s). Please try again.":"Unable to query domain(s). Please try again.","Unable to query user(s). Please try again.":"Unable to query user(s). Please try again.","Unable to refresh tokens, please sign in.":"Unable to refresh tokens, please sign in.","Unable to remove a user that already does not belong to this organization.":"Unable to remove a user that already does not belong to this organization.","Unable to remove domain from unknown organization.":"Unable to remove domain from unknown organization.","Unable to remove domain. Please try again.":"Unable to remove domain. Please try again.","Unable to remove organization. Please try again.":"Unable to remove organization. Please try again.","Unable to remove phone number. Please try again.":"Unable to remove phone number. Please try again.","Unable to remove unknown domain.":"Unable to remove unknown domain.","Unable to remove unknown organization.":"Unable to remove unknown organization.","Unable to remove unknown user from organization.":"Unable to remove unknown user from organization.","Unable to remove user from organization.":"Unable to remove user from organization.","Unable to remove user from this organization. Please try again.":"Unable to remove user from this organization. Please try again.","Unable to remove user from unknown organization.":"Unable to remove user from unknown organization.","Unable to request a one time scan on an unknown domain.":"Unable to request a one time scan on an unknown domain.","Unable to reset password. Please request a new email.":"Unable to reset password. Please request a new email.","Unable to reset password. Please try again.":"Unable to reset password. Please try again.","Unable to retrieve DMARC report information for: {domain}":["Unable to retrieve DMARC report information for: ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Unable to select DMARC report(s) for this period and year.","Unable to send authentication email. Please try again.":"Unable to send authentication email. Please try again.","Unable to send authentication text message. Please try again.":"Unable to send authentication text message. Please try again.","Unable to send org invite email. Please try again.":"Unable to send org invite email. Please try again.","Unable to send password reset email. Please try again.":"Unable to send password reset email. Please try again.","Unable to send two factor authentication message. Please try again.":"Unable to send two factor authentication message. Please try again.","Unable to send verification email. Please try again.":"Unable to send verification email. Please try again.","Unable to set phone number, please try again.":"Unable to set phone number, please try again.","Unable to sign in, please try again.":"Unable to sign in, please try again.","Unable to sign up, please contact org admin for a new invite.":"Unable to sign up, please contact org admin for a new invite.","Unable to sign up. Please try again.":"Unable to sign up. Please try again.","Unable to transfer organization ownership. Please try again.":"Unable to transfer organization ownership. Please try again.","Unable to transfer ownership of a verified organization.":"Unable to transfer ownership of a verified organization.","Unable to transfer ownership of an org to an undefined user.":"Unable to transfer ownership of an org to an undefined user.","Unable to transfer ownership of undefined organization.":"Unable to transfer ownership of undefined organization.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Unable to transfer ownership to a user outside the org. Please invite the user and try again.","Unable to two factor authenticate. Please try again.":"Unable to two factor authenticate. Please try again.","Unable to update domain in an unknown org.":"Unable to update domain in an unknown org.","Unable to update domain that does not belong to the given organization.":"Unable to update domain that does not belong to the given organization.","Unable to update domain. Please try again.":"Unable to update domain. Please try again.","Unable to update organization. Please try again.":"Unable to update organization. Please try again.","Unable to update password, current password does not match. Please try again.":"Unable to update password, current password does not match. Please try again.","Unable to update password, new passwords do not match. Please try again.":"Unable to update password, new passwords do not match. Please try again.","Unable to update password, passwords do not match requirements. Please try again.":"Unable to update password, passwords do not match requirements. Please try again.","Unable to update password. Please try again.":"Unable to update password. Please try again.","Unable to update profile. Please try again.":"Unable to update profile. Please try again.","Unable to update role: organization unknown.":"Unable to update role: organization unknown.","Unable to update role: user does not belong to organization.":"Unable to update role: user does not belong to organization.","Unable to update role: user unknown.":"Unable to update role: user unknown.","Unable to update unknown domain.":"Unable to update unknown domain.","Unable to update unknown organization.":"Unable to update unknown organization.","Unable to update user's role. Please try again.":"Unable to update user's role. Please try again.","Unable to update your own role.":"Unable to update your own role.","Unable to verify account. Please request a new email.":"Unable to verify account. Please request a new email.","Unable to verify account. Please try again.":"Unable to verify account. Please try again.","Unable to verify if user is a super admin, please try again.":"Unable to verify if user is a super admin, please try again.","Unable to verify if user is an admin, please try again.":"Unable to verify if user is an admin, please try again.","Unable to verify organization. Please try again.":"Unable to verify organization. Please try again.","Unable to verify unknown organization.":"Unable to verify unknown organization.","User could not be queried.":"User could not be queried.","User role was updated successfully.":"User role was updated successfully.","Username not available, please try another.":"Username not available, please try another.","Verification error. Please activate multi-factor authentication to access content.":"Verification error. Please activate multi-factor authentication to access content.","Verification error. Please verify your account via email to access content.":"Verification error. Please verify your account via email to access content.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIM` connection.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"You must provide a `first` or `last` value to properly paginate the `DMARC` connection.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"You must provide a `first` or `last` value to properly paginate the `Domain` connection.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"You must provide a `first` or `last` value to properly paginate the `Organization` connection.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"You must provide a `first` or `last` value to properly paginate the `SPF` connection.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"You must provide a `first` or `last` value to properly paginate the `SSL` connection.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"You must provide a `first` or `last` value to properly paginate the `User` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.","You must provide a `limit` value to properly paginate the `DNS` connection.":"You must provide a `limit` value to properly paginate the `DNS` connection.","You must provide a `limit` value to properly paginate the `web` connection.":"You must provide a `limit` value to properly paginate the `web` connection.","You must provide a `period` value to access the `DmarcSummaries` connection.":"You must provide a `period` value to access the `DmarcSummaries` connection.","You must provide a `year` value to access the `DmarcSummaries` connection.":"You must provide a `year` value to access the `DmarcSummaries` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` must be of type `number` not `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` on the `Affiliation` connection cannot be less than zero."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIMResults` connection cannot be less than zero."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIM` connection cannot be less than zero."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` on the `DMARC` connection cannot be less than zero."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DkimFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcSummaries` connection cannot be less than zero."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` on the `Domain` connection cannot be less than zero."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` on the `FullPassTable` connection cannot be less than zero."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` on the `GuidanceTag` connection cannot be less than zero."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` on the `HTTPS` connection cannot be less than zero."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` on the `Organization` connection cannot be less than zero."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` on the `SPF` connection cannot be less than zero."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` on the `SSL` connection cannot be less than zero."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `SpfFailureTable` connection cannot be less than zero."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` on the `User` connection cannot be less than zero."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedDomain` connection cannot be less than zero."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedOrganization` connection cannot be less than zero."]}}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Authentication error. Please sign in.","Cannot query affiliations on organization without admin permission or higher.":"Cannot query affiliations on organization without admin permission or higher.","Cannot query audit logs on organization without admin permission or higher.":"Cannot query audit logs on organization without admin permission or higher.","Email already in use.":"Email already in use.","If an account with this username is found, a password reset link will be found in your inbox.":"If an account with this username is found, a password reset link will be found in your inbox.","If an account with this username is found, an email verification link will be found in your inbox.":"If an account with this username is found, an email verification link will be found in your inbox.","Incorrect TFA code. Please sign in again.":"Incorrect TFA code. Please sign in again.","Incorrect token value. Please request a new email.":"Incorrect token value. Please request a new email.","Incorrect username or password. Please try again.":"Incorrect username or password. Please try again.","Invalid token, please sign in.":"Invalid token, please sign in.","New passwords do not match.":"New passwords do not match.","No organization with the provided slug could be found.":"No organization with the provided slug could be found.","No verified domain with the provided domain could be found.":"No verified domain with the provided domain could be found.","No verified organization with the provided slug could be found.":"No verified organization with the provided slug could be found.","Organization has already been verified.":"Organization has already been verified.","Organization name already in use, please choose another and try again.":"Organization name already in use, please choose another and try again.","Organization name already in use. Please try again with a different name.":"Organization name already in use. Please try again with a different name.","Ownership check error. Unable to request domain information.":"Ownership check error. Unable to request domain information.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIM` connection is not supported.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passing both `first` and `last` to paginate the `DMARC` connection is not supported.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passing both `first` and `last` to paginate the `Domain` connection is not supported.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passing both `first` and `last` to paginate the `Log` connection is not supported.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passing both `first` and `last` to paginate the `Organization` connection is not supported.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passing both `first` and `last` to paginate the `SPF` connection is not supported.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passing both `first` and `last` to paginate the `SSL` connection is not supported.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passing both `first` and `last` to paginate the `User` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.","Password does not meet requirements.":"Password does not meet requirements.","Password was successfully reset.":"Password was successfully reset.","Password was successfully updated.":"Password was successfully updated.","Passwords do not match.":"Passwords do not match.","Permission Denied: Could not retrieve specified organization.":"Permission Denied: Could not retrieve specified organization.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission Denied: Multi-factor authentication is required for admin accounts","Permission Denied: Please contact org owner to transfer ownership.":"Permission Denied: Please contact org owner to transfer ownership.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission Denied: Please contact organization admin for help with archiving domains.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission Denied: Please contact organization admin for help with removing domain.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission Denied: Please contact organization admin for help with removing domains.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission Denied: Please contact organization admin for help with removing organization.","Permission Denied: Please contact organization admin for help with removing users.":"Permission Denied: Please contact organization admin for help with removing users.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission Denied: Please contact organization admin for help with updating organization.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission Denied: Please contact organization admin for help with updating user roles.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission Denied: Please contact organization admin for help with user invitations.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission Denied: Please contact organization admin for help with user role changes.","Permission Denied: Please contact organization user for help with creating domain.":"Permission Denied: Please contact organization user for help with creating domain.","Permission Denied: Please contact organization user for help with creating domains.":"Permission Denied: Please contact organization user for help with creating domains.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission Denied: Please contact organization user for help with retrieving this domain.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission Denied: Please contact organization user for help with scanning this domain.","Permission Denied: Please contact organization user for help with updating this domain.":"Permission Denied: Please contact organization user for help with updating this domain.","Permission Denied: Please contact super admin for help with removing domain.":"Permission Denied: Please contact super admin for help with removing domain.","Permission Denied: Please contact super admin for help with removing organization.":"Permission Denied: Please contact super admin for help with removing organization.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission Denied: Please contact super admin for help with scanning this domain.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission Denied: Please contact super admin for help with verifying this organization.","Permission check error. Unable to request domain information.":"Permission check error. Unable to request domain information.","Permission error, not an admin for this user.":"Permission error, not an admin for this user.","Permission error: Unable to close other user's account.":"Permission error: Unable to close other user's account.","Permissions error. You do not have sufficient permissions to access this data.":"Permissions error. You do not have sufficient permissions to access this data.","Phone number has been successfully removed.":"Phone number has been successfully removed.","Phone number has been successfully set, you will receive a verification text message shortly.":"Phone number has been successfully set, you will receive a verification text message shortly.","Profile successfully updated.":"Profile successfully updated.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Affiliation` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DkimFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcSummaries` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Domain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `FullPassTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `GuidanceTag` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Log` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Organization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `SpfFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `User` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedDomain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedOrganization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIMResults` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIM` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DMARC` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `HTTPS` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SPF` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SSL` connection exceeds the `",["argSet"],"` limit of 100 records."],"Successfully added {domainCount} domain(s) to {0}.":["Successfully added ",["domainCount"]," domain(s) to ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Successfully added ",["domainCount"]," domains to ",["0"],"."],"Successfully closed account.":"Successfully closed account.","Successfully dispatched one time scan.":"Successfully dispatched one time scan.","Successfully email verified account, and set TFA send method to email.":"Successfully email verified account, and set TFA send method to email.","Successfully invited user to organization, and sent notification email.":"Successfully invited user to organization, and sent notification email.","Successfully left organization: {0}":["Successfully left organization: ",["0"]],"Successfully removed domain: {0} from favourites.":["Successfully removed domain: ",["0"]," from favourites."],"Successfully removed domain: {0} from {1}.":["Successfully removed domain: ",["0"]," from ",["1"],"."],"Successfully removed organization: {0}.":["Successfully removed organization: ",["0"],"."],"Successfully removed user from organization.":"Successfully removed user from organization.","Successfully removed {domainCount} domain(s) from {0}.":["Successfully removed ",["domainCount"]," domain(s) from ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Successfully removed ",["domainCount"]," domains from ",["0"],"."],"Successfully sent invitation to service, and organization email.":"Successfully sent invitation to service, and organization email.","Successfully signed out.":"Successfully signed out.","Successfully transferred org: {0} ownership to user: {1}":["Successfully transferred org: ",["0"]," ownership to user: ",["1"]],"Successfully verified organization: {0}.":["Successfully verified organization: ",["0"],"."],"Successfully verified phone number, and set TFA send method to text.":"Successfully verified phone number, and set TFA send method to text.","Token value incorrect, please sign in again.":"Token value incorrect, please sign in again.","Too many failed login attempts, please reset your password, and try again.":"Too many failed login attempts, please reset your password, and try again.","Two factor code is incorrect. Please try again.":"Two factor code is incorrect. Please try again.","Two factor code length is incorrect. Please try again.":"Two factor code length is incorrect. Please try again.","Unable leave organization. Please try again.":"Unable leave organization. Please try again.","Unable to add domains in unknown organization.":"Unable to add domains in unknown organization.","Unable to authenticate. Please try again.":"Unable to authenticate. Please try again.","Unable to check permission. Please try again.":"Unable to check permission. Please try again.","Unable to close account of an undefined user.":"Unable to close account of an undefined user.","Unable to close account. Please try again.":"Unable to close account. Please try again.","Unable to create domain in unknown organization.":"Unable to create domain in unknown organization.","Unable to create domain, organization has already claimed it.":"Unable to create domain, organization has already claimed it.","Unable to create domain. Please try again.":"Unable to create domain. Please try again.","Unable to create domains. Please try again.":"Unable to create domains. Please try again.","Unable to create organization. Please try again.":"Unable to create organization. Please try again.","Unable to dispatch one time scan. Please try again.":"Unable to dispatch one time scan. Please try again.","Unable to favourite domain, user has already favourited it.":"Unable to favourite domain, user has already favourited it.","Unable to favourite domain. Please try again.":"Unable to favourite domain. Please try again.","Unable to favourite unknown domain.":"Unable to favourite unknown domain.","Unable to find Aggregate guidance tag(s). Please try again.":"Unable to find Aggregate guidance tag(s). Please try again.","Unable to find DKIM guidance tag(s). Please try again.":"Unable to find DKIM guidance tag(s). Please try again.","Unable to find DKIM result(s). Please try again.":"Unable to find DKIM result(s). Please try again.","Unable to find DKIM scan(s). Please try again.":"Unable to find DKIM scan(s). Please try again.","Unable to find DMARC guidance tag(s). Please try again.":"Unable to find DMARC guidance tag(s). Please try again.","Unable to find DMARC scan(s). Please try again.":"Unable to find DMARC scan(s). Please try again.","Unable to find DMARC summary data. Please try again.":"Unable to find DMARC summary data. Please try again.","Unable to find DNS scan(s). Please try again.":"Unable to find DNS scan(s). Please try again.","Unable to find HTTPS guidance tag(s). Please try again.":"Unable to find HTTPS guidance tag(s). Please try again.","Unable to find HTTPS scan(s). Please try again.":"Unable to find HTTPS scan(s). Please try again.","Unable to find SPF guidance tag(s). Please try again.":"Unable to find SPF guidance tag(s). Please try again.","Unable to find SPF scan(s). Please try again.":"Unable to find SPF scan(s). Please try again.","Unable to find SSL guidance tag(s). Please try again.":"Unable to find SSL guidance tag(s). Please try again.","Unable to find SSL scan(s). Please try again.":"Unable to find SSL scan(s). Please try again.","Unable to find the requested domain.":"Unable to find the requested domain.","Unable to find user affiliation(s). Please try again.":"Unable to find user affiliation(s). Please try again.","Unable to find verified organization(s). Please try again.":"Unable to find verified organization(s). Please try again.","Unable to invite user to organization. Please try again.":"Unable to invite user to organization. Please try again.","Unable to invite user to organization. User is already affiliated with organization.":"Unable to invite user to organization. User is already affiliated with organization.","Unable to invite user to unknown organization.":"Unable to invite user to unknown organization.","Unable to invite user. Please try again.":"Unable to invite user. Please try again.","Unable to invite yourself to an org.":"Unable to invite yourself to an org.","Unable to leave organization. Please try again.":"Unable to leave organization. Please try again.","Unable to leave undefined organization.":"Unable to leave undefined organization.","Unable to load Aggregate guidance tag(s). Please try again.":"Unable to load Aggregate guidance tag(s). Please try again.","Unable to load DKIM failure data. Please try again.":"Unable to load DKIM failure data. Please try again.","Unable to load DKIM guidance tag(s). Please try again.":"Unable to load DKIM guidance tag(s). Please try again.","Unable to load DKIM result(s). Please try again.":"Unable to load DKIM result(s). Please try again.","Unable to load DKIM scan(s). Please try again.":"Unable to load DKIM scan(s). Please try again.","Unable to load DMARC failure data. Please try again.":"Unable to load DMARC failure data. Please try again.","Unable to load DMARC guidance tag(s). Please try again.":"Unable to load DMARC guidance tag(s). Please try again.","Unable to load DMARC phase summary. Please try again.":"Unable to load DMARC phase summary. Please try again.","Unable to load DMARC scan(s). Please try again.":"Unable to load DMARC scan(s). Please try again.","Unable to load DMARC summary data. Please try again.":"Unable to load DMARC summary data. Please try again.","Unable to load DNS scan(s). Please try again.":"Unable to load DNS scan(s). Please try again.","Unable to load HTTPS guidance tag(s). Please try again.":"Unable to load HTTPS guidance tag(s). Please try again.","Unable to load HTTPS scan(s). Please try again.":"Unable to load HTTPS scan(s). Please try again.","Unable to load HTTPS summary. Please try again.":"Unable to load HTTPS summary. Please try again.","Unable to load SPF failure data. Please try again.":"Unable to load SPF failure data. Please try again.","Unable to load SPF guidance tag(s). Please try again.":"Unable to load SPF guidance tag(s). Please try again.","Unable to load SPF scan(s). Please try again.":"Unable to load SPF scan(s). Please try again.","Unable to load SSL guidance tag(s). Please try again.":"Unable to load SSL guidance tag(s). Please try again.","Unable to load SSL scan(s). Please try again.":"Unable to load SSL scan(s). Please try again.","Unable to load affiliation information. Please try again.":"Unable to load affiliation information. Please try again.","Unable to load affiliation(s). Please try again.":"Unable to load affiliation(s). Please try again.","Unable to load all organization domain statuses. Please try again.":"Unable to load all organization domain statuses. Please try again.","Unable to load domain(s). Please try again.":"Unable to load domain(s). Please try again.","Unable to load domain. Please try again.":"Unable to load domain. Please try again.","Unable to load full pass data. Please try again.":"Unable to load full pass data. Please try again.","Unable to load log(s). Please try again.":"Unable to load log(s). Please try again.","Unable to load log. Please try again.":"Unable to load log. Please try again.","Unable to load mail summary. Please try again.":"Unable to load mail summary. Please try again.","Unable to load organization domain statuses. Please try again.":"Unable to load organization domain statuses. Please try again.","Unable to load organization(s). Please try again.":"Unable to load organization(s). Please try again.","Unable to load owner information. Please try again.":"Unable to load owner information. Please try again.","Unable to load summary. Please try again.":"Unable to load summary. Please try again.","Unable to load tags(s). Please try again.":"Unable to load tags(s). Please try again.","Unable to load user(s). Please try again.":"Unable to load user(s). Please try again.","Unable to load verified domain(s). Please try again.":"Unable to load verified domain(s). Please try again.","Unable to load verified organization(s). Please try again.":"Unable to load verified organization(s). Please try again.","Unable to load web scan(s). Please try again.":"Unable to load web scan(s). Please try again.","Unable to load web summary. Please try again.":"Unable to load web summary. Please try again.","Unable to query affiliation(s). Please try again.":"Unable to query affiliation(s). Please try again.","Unable to query domain(s). Please try again.":"Unable to query domain(s). Please try again.","Unable to query log(s). Please try again.":"Unable to query log(s). Please try again.","Unable to query user(s). Please try again.":"Unable to query user(s). Please try again.","Unable to refresh tokens, please sign in.":"Unable to refresh tokens, please sign in.","Unable to remove a user that already does not belong to this organization.":"Unable to remove a user that already does not belong to this organization.","Unable to remove domain from unknown organization.":"Unable to remove domain from unknown organization.","Unable to remove domain. Domain is not part of organization.":"Unable to remove domain. Domain is not part of organization.","Unable to remove domain. Please try again.":"Unable to remove domain. Please try again.","Unable to remove domains from unknown organization.":"Unable to remove domains from unknown organization.","Unable to remove organization. Please try again.":"Unable to remove organization. Please try again.","Unable to remove phone number. Please try again.":"Unable to remove phone number. Please try again.","Unable to remove unknown domain.":"Unable to remove unknown domain.","Unable to remove unknown organization.":"Unable to remove unknown organization.","Unable to remove unknown user from organization.":"Unable to remove unknown user from organization.","Unable to remove user from organization.":"Unable to remove user from organization.","Unable to remove user from this organization. Please try again.":"Unable to remove user from this organization. Please try again.","Unable to remove user from unknown organization.":"Unable to remove user from unknown organization.","Unable to request a one time scan on a domain that already has a pending scan.":"Unable to request a one time scan on a domain that already has a pending scan.","Unable to request a one time scan on an unknown domain.":"Unable to request a one time scan on an unknown domain.","Unable to request a one time scan. Please try again.":"Unable to request a one time scan. Please try again.","Unable to reset password. Please request a new email.":"Unable to reset password. Please request a new email.","Unable to reset password. Please try again.":"Unable to reset password. Please try again.","Unable to retrieve DMARC report information for: {domain}":["Unable to retrieve DMARC report information for: ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Unable to select DMARC report(s) for this period and year.","Unable to send authentication email. Please try again.":"Unable to send authentication email. Please try again.","Unable to send authentication text message. Please try again.":"Unable to send authentication text message. Please try again.","Unable to send org invite email. Please try again.":"Unable to send org invite email. Please try again.","Unable to send password reset email. Please try again.":"Unable to send password reset email. Please try again.","Unable to send two factor authentication message. Please try again.":"Unable to send two factor authentication message. Please try again.","Unable to send verification email. Please try again.":"Unable to send verification email. Please try again.","Unable to set phone number, please try again.":"Unable to set phone number, please try again.","Unable to sign in, please try again.":"Unable to sign in, please try again.","Unable to sign up, please contact org admin for a new invite.":"Unable to sign up, please contact org admin for a new invite.","Unable to sign up. Please try again.":"Unable to sign up. Please try again.","Unable to transfer organization ownership. Please try again.":"Unable to transfer organization ownership. Please try again.","Unable to transfer ownership of a verified organization.":"Unable to transfer ownership of a verified organization.","Unable to transfer ownership of an org to an undefined user.":"Unable to transfer ownership of an org to an undefined user.","Unable to transfer ownership of undefined organization.":"Unable to transfer ownership of undefined organization.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Unable to transfer ownership to a user outside the org. Please invite the user and try again.","Unable to two factor authenticate. Please try again.":"Unable to two factor authenticate. Please try again.","Unable to unfavourite domain, domain is not favourited.":"Unable to unfavourite domain, domain is not favourited.","Unable to unfavourite domain. Please try again.":"Unable to unfavourite domain. Please try again.","Unable to unfavourite unknown domain.":"Unable to unfavourite unknown domain.","Unable to update domain edge. Please try again.":"Unable to update domain edge. Please try again.","Unable to update domain in an unknown org.":"Unable to update domain in an unknown org.","Unable to update domain that does not belong to the given organization.":"Unable to update domain that does not belong to the given organization.","Unable to update domain. Please try again.":"Unable to update domain. Please try again.","Unable to update organization. Please try again.":"Unable to update organization. Please try again.","Unable to update password, current password does not match. Please try again.":"Unable to update password, current password does not match. Please try again.","Unable to update password, new passwords do not match. Please try again.":"Unable to update password, new passwords do not match. Please try again.","Unable to update password, passwords do not match requirements. Please try again.":"Unable to update password, passwords do not match requirements. Please try again.","Unable to update password. Please try again.":"Unable to update password. Please try again.","Unable to update profile. Please try again.":"Unable to update profile. Please try again.","Unable to update role: organization unknown.":"Unable to update role: organization unknown.","Unable to update role: user does not belong to organization.":"Unable to update role: user does not belong to organization.","Unable to update role: user unknown.":"Unable to update role: user unknown.","Unable to update unknown domain.":"Unable to update unknown domain.","Unable to update unknown organization.":"Unable to update unknown organization.","Unable to update user's role. Please try again.":"Unable to update user's role. Please try again.","Unable to update your own role.":"Unable to update your own role.","Unable to verify account. Please request a new email.":"Unable to verify account. Please request a new email.","Unable to verify account. Please try again.":"Unable to verify account. Please try again.","Unable to verify if user is a super admin, please try again.":"Unable to verify if user is a super admin, please try again.","Unable to verify if user is an admin, please try again.":"Unable to verify if user is an admin, please try again.","Unable to verify organization. Please try again.":"Unable to verify organization. Please try again.","Unable to verify unknown organization.":"Unable to verify unknown organization.","User could not be queried.":"User could not be queried.","User role was updated successfully.":"User role was updated successfully.","Username not available, please try another.":"Username not available, please try another.","Verification error. Please activate multi-factor authentication to access content.":"Verification error. Please activate multi-factor authentication to access content.","Verification error. Please verify your account via email to access content.":"Verification error. Please verify your account via email to access content.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIM` connection.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"You must provide a `first` or `last` value to properly paginate the `DMARC` connection.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"You must provide a `first` or `last` value to properly paginate the `Domain` connection.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"You must provide a `first` or `last` value to properly paginate the `Log` connection.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"You must provide a `first` or `last` value to properly paginate the `Organization` connection.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"You must provide a `first` or `last` value to properly paginate the `SPF` connection.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"You must provide a `first` or `last` value to properly paginate the `SSL` connection.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"You must provide a `first` or `last` value to properly paginate the `User` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.","You must provide a `limit` value to properly paginate the `DNS` connection.":"You must provide a `limit` value to properly paginate the `DNS` connection.","You must provide a `limit` value to properly paginate the `web` connection.":"You must provide a `limit` value to properly paginate the `web` connection.","You must provide a `period` value to access the `DmarcSummaries` connection.":"You must provide a `period` value to access the `DmarcSummaries` connection.","You must provide a `year` value to access the `DmarcSummaries` connection.":"You must provide a `year` value to access the `DmarcSummaries` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` must be of type `number` not `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` on the `Affiliation` connection cannot be less than zero."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIMResults` connection cannot be less than zero."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIM` connection cannot be less than zero."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` on the `DMARC` connection cannot be less than zero."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DkimFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcSummaries` connection cannot be less than zero."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` on the `Domain` connection cannot be less than zero."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` on the `FullPassTable` connection cannot be less than zero."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` on the `GuidanceTag` connection cannot be less than zero."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` on the `HTTPS` connection cannot be less than zero."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` on the `Log` connection cannot be less than zero."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` on the `Organization` connection cannot be less than zero."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` on the `SPF` connection cannot be less than zero."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` on the `SSL` connection cannot be less than zero."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `SpfFailureTable` connection cannot be less than zero."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` on the `User` connection cannot be less than zero."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedDomain` connection cannot be less than zero."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedOrganization` connection cannot be less than zero."]}}; \ No newline at end of file diff --git a/api/src/locale/en/messages.po b/api/src/locale/en/messages.po index 110903c079..3ec44d61fc 100644 --- a/api/src/locale/en/messages.po +++ b/api/src/locale/en/messages.po @@ -28,7 +28,7 @@ msgstr "Cannot query affiliations on organization without admin permission or hi msgid "Cannot query audit logs on organization without admin permission or higher." msgstr "Cannot query audit logs on organization without admin permission or higher." -#: src/user/mutations/sign-up.js:117 +#: src/user/mutations/sign-up.js:106 msgid "Email already in use." msgstr "Email already in use." @@ -86,10 +86,10 @@ msgstr "Organization name already in use, please choose another and try again." msgid "Organization name already in use. Please try again with a different name." msgstr "Organization name already in use. Please try again with a different name." -#: src/auth/check-domain-ownership.js:30 -#: src/auth/check-domain-ownership.js:42 -#: src/auth/check-domain-ownership.js:64 -#: src/auth/check-domain-ownership.js:75 +#: src/auth/check-domain-ownership.js:33 +#: src/auth/check-domain-ownership.js:45 +#: src/auth/check-domain-ownership.js:67 +#: src/auth/check-domain-ownership.js:78 msgid "Ownership check error. Unable to request domain information." msgstr "Ownership check error. Unable to request domain information." @@ -123,8 +123,8 @@ msgstr "Passing both `first` and `last` to paginate the `DmarcFailureTable` conn msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:163 -#: src/domain/loaders/load-domain-connections-by-user-id.js:175 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 +#: src/domain/loaders/load-domain-connections-by-user-id.js:168 msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `Domain` connection is not supported." @@ -182,7 +182,7 @@ msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` co msgstr "Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported." #: src/user/mutations/reset-password.js:120 -#: src/user/mutations/sign-up.js:91 +#: src/user/mutations/sign-up.js:84 msgid "Password does not meet requirements." msgstr "Password does not meet requirements." @@ -194,7 +194,7 @@ msgstr "Password was successfully reset." msgid "Password was successfully updated." msgstr "Password was successfully updated." -#: src/user/mutations/sign-up.js:103 +#: src/user/mutations/sign-up.js:94 msgid "Passwords do not match." msgstr "Passwords do not match." @@ -211,15 +211,15 @@ msgstr "Permission Denied: Multi-factor authentication is required for admin acc msgid "Permission Denied: Please contact org owner to transfer ownership." msgstr "Permission Denied: Please contact org owner to transfer ownership." -#: src/domain/mutations/remove-organizations-domains.js:135 +#: src/domain/mutations/remove-organizations-domains.js:127 msgid "Permission Denied: Please contact organization admin for help with archiving domains." msgstr "Permission Denied: Please contact organization admin for help with archiving domains." -#: src/domain/mutations/remove-domain.js:117 +#: src/domain/mutations/remove-domain.js:106 msgid "Permission Denied: Please contact organization admin for help with removing domain." msgstr "Permission Denied: Please contact organization admin for help with removing domain." -#: src/domain/mutations/remove-organizations-domains.js:122 +#: src/domain/mutations/remove-organizations-domains.js:116 msgid "Permission Denied: Please contact organization admin for help with removing domains." msgstr "Permission Denied: Please contact organization admin for help with removing domains." @@ -241,7 +241,7 @@ msgstr "Permission Denied: Please contact organization admin for help with updat msgid "Permission Denied: Please contact organization admin for help with updating user roles." msgstr "Permission Denied: Please contact organization admin for help with updating user roles." -#: src/affiliation/mutations/invite-user-to-org.js:114 +#: src/affiliation/mutations/invite-user-to-org.js:105 msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission Denied: Please contact organization admin for help with user invitations." @@ -249,16 +249,19 @@ msgstr "Permission Denied: Please contact organization admin for help with user msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission Denied: Please contact organization admin for help with user role changes." -#: src/domain/mutations/add-organizations-domains.js:131 -#: src/domain/mutations/create-domain.js:140 +#: src/domain/mutations/create-domain.js:121 msgid "Permission Denied: Please contact organization user for help with creating domain." msgstr "Permission Denied: Please contact organization user for help with creating domain." +#: src/domain/mutations/add-organizations-domains.js:130 +msgid "Permission Denied: Please contact organization user for help with creating domains." +msgstr "Permission Denied: Please contact organization user for help with creating domains." + #: src/domain/queries/find-domain-by-domain.js:57 msgid "Permission Denied: Please contact organization user for help with retrieving this domain." msgstr "Permission Denied: Please contact organization user for help with retrieving this domain." -#: src/domain/mutations/request-scan.js:71 +#: src/domain/mutations/request-scan.js:60 msgid "Permission Denied: Please contact organization user for help with scanning this domain." msgstr "Permission Denied: Please contact organization user for help with scanning this domain." @@ -266,8 +269,8 @@ msgstr "Permission Denied: Please contact organization user for help with scanni msgid "Permission Denied: Please contact organization user for help with updating this domain." msgstr "Permission Denied: Please contact organization user for help with updating this domain." -#: src/domain/mutations/remove-domain.js:104 -#: src/domain/mutations/remove-organizations-domains.js:109 +#: src/domain/mutations/remove-domain.js:95 +#: src/domain/mutations/remove-organizations-domains.js:105 msgid "Permission Denied: Please contact super admin for help with removing domain." msgstr "Permission Denied: Please contact super admin for help with removing domain." @@ -296,7 +299,7 @@ msgstr "Permission check error. Unable to request domain information." msgid "Permission error, not an admin for this user." msgstr "Permission error, not an admin for this user." -#: src/user/mutations/close-account.js:56 +#: src/user/mutations/close-account.js:54 msgid "Permission error: Unable to close other user's account." msgstr "Permission error: Unable to close other user's account." @@ -334,8 +337,8 @@ msgstr "Requesting `{amount}` records on the `DmarcFailureTable` connection exce msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:186 -#: src/domain/loaders/load-domain-connections-by-user-id.js:198 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 +#: src/domain/loaders/load-domain-connections-by-user-id.js:186 msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." @@ -404,15 +407,19 @@ msgstr "Requesting `{amount}` records on the `VerifiedOrganization` connection e #~ msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." #~ msgstr "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." +#: src/domain/mutations/add-organizations-domains.js:353 +msgid "Successfully added {domainCount} domain(s) to {0}." +msgstr "Successfully added {domainCount} domain(s) to {0}." + #: src/domain/mutations/add-organizations-domains.js:351 -msgid "Successfully added {domainCount} domains to {0}." -msgstr "Successfully added {domainCount} domains to {0}." +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Successfully added {domainCount} domains to {0}." -#: src/user/mutations/close-account.js:502 +#: src/user/mutations/close-account.js:405 msgid "Successfully closed account." msgstr "Successfully closed account." -#: src/domain/mutations/request-scan.js:94 +#: src/domain/mutations/request-scan.js:108 msgid "Successfully dispatched one time scan." msgstr "Successfully dispatched one time scan." @@ -420,11 +427,11 @@ msgstr "Successfully dispatched one time scan." msgid "Successfully email verified account, and set TFA send method to email." msgstr "Successfully email verified account, and set TFA send method to email." -#: src/affiliation/mutations/invite-user-to-org.js:241 +#: src/affiliation/mutations/invite-user-to-org.js:259 msgid "Successfully invited user to organization, and sent notification email." msgstr "Successfully invited user to organization, and sent notification email." -#: src/affiliation/mutations/leave-organization.js:319 +#: src/affiliation/mutations/leave-organization.js:308 msgid "Successfully left organization: {0}" msgstr "Successfully left organization: {0}" @@ -432,11 +439,11 @@ msgstr "Successfully left organization: {0}" msgid "Successfully removed domain: {0} from favourites." msgstr "Successfully removed domain: {0} from favourites." -#: src/domain/mutations/remove-domain.js:395 +#: src/domain/mutations/remove-domain.js:325 msgid "Successfully removed domain: {0} from {1}." msgstr "Successfully removed domain: {0} from {1}." -#: src/organization/mutations/remove-organization.js:502 +#: src/organization/mutations/remove-organization.js:384 msgid "Successfully removed organization: {0}." msgstr "Successfully removed organization: {0}." @@ -444,11 +451,15 @@ msgstr "Successfully removed organization: {0}." msgid "Successfully removed user from organization." msgstr "Successfully removed user from organization." +#: src/domain/mutations/remove-organizations-domains.js:459 +msgid "Successfully removed {domainCount} domain(s) from {0}." +msgstr "Successfully removed {domainCount} domain(s) from {0}." + #: src/domain/mutations/remove-organizations-domains.js:530 -msgid "Successfully removed {domainCount} domains from {0}." -msgstr "Successfully removed {domainCount} domains from {0}." +#~ msgid "Successfully removed {domainCount} domains from {0}." +#~ msgstr "Successfully removed {domainCount} domains from {0}." -#: src/affiliation/mutations/invite-user-to-org.js:167 +#: src/affiliation/mutations/invite-user-to-org.js:150 msgid "Successfully sent invitation to service, and organization email." msgstr "Successfully sent invitation to service, and organization email." @@ -484,19 +495,23 @@ msgstr "Two factor code is incorrect. Please try again." msgid "Two factor code length is incorrect. Please try again." msgstr "Two factor code length is incorrect. Please try again." -#: src/affiliation/mutations/leave-organization.js:74 -#: src/affiliation/mutations/leave-organization.js:84 -#: src/affiliation/mutations/leave-organization.js:115 -#: src/affiliation/mutations/leave-organization.js:132 -#: src/affiliation/mutations/leave-organization.js:163 -#: src/affiliation/mutations/leave-organization.js:173 -#: src/affiliation/mutations/leave-organization.js:247 -#: src/affiliation/mutations/leave-organization.js:284 -#: src/affiliation/mutations/leave-organization.js:302 -#: src/affiliation/mutations/leave-organization.js:312 +#: src/affiliation/mutations/leave-organization.js:71 +#: src/affiliation/mutations/leave-organization.js:81 +#: src/affiliation/mutations/leave-organization.js:111 +#: src/affiliation/mutations/leave-organization.js:126 +#: src/affiliation/mutations/leave-organization.js:156 +#: src/affiliation/mutations/leave-organization.js:166 +#: src/affiliation/mutations/leave-organization.js:239 +#: src/affiliation/mutations/leave-organization.js:275 +#: src/affiliation/mutations/leave-organization.js:293 +#: src/affiliation/mutations/leave-organization.js:301 msgid "Unable leave organization. Please try again." msgstr "Unable leave organization. Please try again." +#: src/domain/mutations/add-organizations-domains.js:115 +msgid "Unable to add domains in unknown organization." +msgstr "Unable to add domains in unknown organization." + #: src/user/mutations/authenticate.js:77 #: src/user/mutations/authenticate.js:117 #: src/user/mutations/authenticate.js:126 @@ -510,46 +525,45 @@ msgstr "Unable to authenticate. Please try again." msgid "Unable to check permission. Please try again." msgstr "Unable to check permission. Please try again." -#: src/user/mutations/close-account.js:69 +#: src/user/mutations/close-account.js:66 msgid "Unable to close account of an undefined user." msgstr "Unable to close account of an undefined user." -#: src/user/mutations/close-account.js:92 -#: src/user/mutations/close-account.js:102 -#: src/user/mutations/close-account.js:121 -#: src/user/mutations/close-account.js:131 -#: src/user/mutations/close-account.js:162 -#: src/user/mutations/close-account.js:177 -#: src/user/mutations/close-account.js:206 -#: src/user/mutations/close-account.js:216 -#: src/user/mutations/close-account.js:252 -#: src/user/mutations/close-account.js:364 -#: src/user/mutations/close-account.js:395 -#: src/user/mutations/close-account.js:420 -#: src/user/mutations/close-account.js:456 -#: src/user/mutations/close-account.js:473 -#: src/user/mutations/close-account.js:488 -#: src/user/mutations/close-account.js:497 +#: src/user/mutations/close-account.js:89 +#: src/user/mutations/close-account.js:99 +#: src/user/mutations/close-account.js:118 +#: src/user/mutations/close-account.js:128 +#: src/user/mutations/close-account.js:159 +#: src/user/mutations/close-account.js:174 +#: src/user/mutations/close-account.js:203 +#: src/user/mutations/close-account.js:213 +#: src/user/mutations/close-account.js:239 +#: src/user/mutations/close-account.js:257 +#: src/user/mutations/close-account.js:286 +#: src/user/mutations/close-account.js:309 +#: src/user/mutations/close-account.js:344 +#: src/user/mutations/close-account.js:361 +#: src/user/mutations/close-account.js:376 +#: src/user/mutations/close-account.js:383 msgid "Unable to close account. Please try again." msgstr "Unable to close account. Please try again." -#: src/domain/mutations/add-organizations-domains.js:115 -#: src/domain/mutations/create-domain.js:120 +#: src/domain/mutations/create-domain.js:107 msgid "Unable to create domain in unknown organization." msgstr "Unable to create domain in unknown organization." -#: src/domain/mutations/create-domain.js:198 +#: src/domain/mutations/create-domain.js:174 msgid "Unable to create domain, organization has already claimed it." msgstr "Unable to create domain, organization has already claimed it." -#: src/domain/mutations/create-domain.js:177 -#: src/domain/mutations/create-domain.js:187 -#: src/domain/mutations/create-domain.js:230 -#: src/domain/mutations/create-domain.js:240 +#: src/domain/mutations/create-domain.js:156 +#: src/domain/mutations/create-domain.js:164 +#: src/domain/mutations/create-domain.js:203 +#: src/domain/mutations/create-domain.js:213 +#: src/domain/mutations/create-domain.js:231 #: src/domain/mutations/create-domain.js:260 -#: src/domain/mutations/create-domain.js:291 -#: src/domain/mutations/create-domain.js:311 -#: src/domain/mutations/create-domain.js:321 +#: src/domain/mutations/create-domain.js:278 +#: src/domain/mutations/create-domain.js:286 msgid "Unable to create domain. Please try again." msgstr "Unable to create domain. Please try again." @@ -671,25 +685,33 @@ msgstr "Unable to find user affiliation(s). Please try again." msgid "Unable to find verified organization(s). Please try again." msgstr "Unable to find verified organization(s). Please try again." -#: src/affiliation/mutations/invite-user-to-org.js:95 +#: src/affiliation/mutations/invite-user-to-org.js:170 +msgid "Unable to invite user to organization. Please try again." +msgstr "Unable to invite user to organization. Please try again." + +#: src/affiliation/mutations/invite-user-to-org.js:182 +msgid "Unable to invite user to organization. User is already affiliated with organization." +msgstr "Unable to invite user to organization. User is already affiliated with organization." + +#: src/affiliation/mutations/invite-user-to-org.js:87 msgid "Unable to invite user to unknown organization." msgstr "Unable to invite user to unknown organization." -#: src/affiliation/mutations/invite-user-to-org.js:194 -#: src/affiliation/mutations/invite-user-to-org.js:209 +#: src/affiliation/mutations/invite-user-to-org.js:212 +#: src/affiliation/mutations/invite-user-to-org.js:231 msgid "Unable to invite user. Please try again." msgstr "Unable to invite user. Please try again." -#: src/affiliation/mutations/invite-user-to-org.js:81 +#: src/affiliation/mutations/invite-user-to-org.js:73 msgid "Unable to invite yourself to an org." msgstr "Unable to invite yourself to an org." -#: src/affiliation/mutations/leave-organization.js:197 -#: src/affiliation/mutations/leave-organization.js:215 +#: src/affiliation/mutations/leave-organization.js:190 +#: src/affiliation/mutations/leave-organization.js:208 msgid "Unable to leave organization. Please try again." msgstr "Unable to leave organization. Please try again." -#: src/affiliation/mutations/leave-organization.js:51 +#: src/affiliation/mutations/leave-organization.js:48 msgid "Unable to leave undefined organization." msgstr "Unable to leave undefined organization." @@ -748,8 +770,8 @@ msgstr "Unable to load DMARC phase summary. Please try again." msgid "Unable to load DMARC summary data. Please try again." msgstr "Unable to load DMARC summary data. Please try again." -#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:173 -#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:185 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 msgid "Unable to load DNS scan(s). Please try again." msgstr "Unable to load DNS scan(s). Please try again." @@ -803,13 +825,13 @@ msgstr "Unable to load affiliation information. Please try again." msgid "Unable to load affiliation(s). Please try again." msgstr "Unable to load affiliation(s). Please try again." -#: src/organization/loaders/load-all-organization-domain-statuses.js:36 +#: src/organization/loaders/load-all-organization-domain-statuses.js:32 msgid "Unable to load all organization domain statuses. Please try again." msgstr "Unable to load all organization domain statuses. Please try again." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:547 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:557 -#: src/domain/loaders/load-domain-connections-by-user-id.js:471 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:534 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:544 +#: src/domain/loaders/load-domain-connections-by-user-id.js:462 #: src/domain/loaders/load-domain-tags-by-org-id.js:27 #: src/user/loaders/load-my-tracker-by-user-id.js:43 msgid "Unable to load domain(s). Please try again." @@ -898,11 +920,11 @@ msgstr "Unable to load verified domain(s). Please try again." msgid "Unable to load verified organization(s). Please try again." msgstr "Unable to load verified organization(s). Please try again." -#: src/web-scan/loaders/load-web-connections-by-domain-id.js:173 -#: src/web-scan/loaders/load-web-connections-by-domain-id.js:185 -#: src/web-scan/loaders/load-web-scans-by-web-id.js:15 -#: src/web-scan/loaders/load-web-scans-by-web-id.js:33 -#: src/web-scan/loaders/load-web-scans-by-web-id.js:45 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:9 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:25 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:35 msgid "Unable to load web scan(s). Please try again." msgstr "Unable to load web scan(s). Please try again." @@ -915,7 +937,7 @@ msgstr "Unable to load web summary. Please try again." msgid "Unable to query affiliation(s). Please try again." msgstr "Unable to query affiliation(s). Please try again." -#: src/domain/loaders/load-domain-connections-by-user-id.js:461 +#: src/domain/loaders/load-domain-connections-by-user-id.js:452 #: src/user/loaders/load-my-tracker-by-user-id.js:33 msgid "Unable to query domain(s). Please try again." msgstr "Unable to query domain(s). Please try again." @@ -942,30 +964,27 @@ msgstr "Unable to refresh tokens, please sign in." msgid "Unable to remove a user that already does not belong to this organization." msgstr "Unable to remove a user that already does not belong to this organization." -#: src/domain/mutations/remove-domain.js:87 +#: src/domain/mutations/remove-domain.js:80 msgid "Unable to remove domain from unknown organization." msgstr "Unable to remove domain from unknown organization." -#: src/domain/mutations/remove-domain.js:148 +#: src/domain/mutations/remove-domain.js:135 msgid "Unable to remove domain. Domain is not part of organization." msgstr "Unable to remove domain. Domain is not part of organization." -#: src/domain/mutations/remove-domain.js:134 -#: src/domain/mutations/remove-domain.js:165 -#: src/domain/mutations/remove-domain.js:198 -#: src/domain/mutations/remove-domain.js:217 -#: src/domain/mutations/remove-domain.js:243 -#: src/domain/mutations/remove-domain.js:260 -#: src/domain/mutations/remove-domain.js:278 -#: src/domain/mutations/remove-domain.js:296 -#: src/domain/mutations/remove-domain.js:314 -#: src/domain/mutations/remove-domain.js:331 -#: src/domain/mutations/remove-domain.js:354 -#: src/domain/mutations/remove-domain.js:365 +#: src/domain/mutations/remove-domain.js:122 +#: src/domain/mutations/remove-domain.js:151 +#: src/domain/mutations/remove-domain.js:184 +#: src/domain/mutations/remove-domain.js:203 +#: src/domain/mutations/remove-domain.js:229 +#: src/domain/mutations/remove-domain.js:247 +#: src/domain/mutations/remove-domain.js:264 +#: src/domain/mutations/remove-domain.js:287 +#: src/domain/mutations/remove-domain.js:298 msgid "Unable to remove domain. Please try again." msgstr "Unable to remove domain. Please try again." -#: src/domain/mutations/remove-organizations-domains.js:92 +#: src/domain/mutations/remove-organizations-domains.js:90 msgid "Unable to remove domains from unknown organization." msgstr "Unable to remove domains from unknown organization." @@ -975,11 +994,11 @@ msgstr "Unable to remove domains from unknown organization." #: src/organization/mutations/remove-organization.js:168 #: src/organization/mutations/remove-organization.js:200 #: src/organization/mutations/remove-organization.js:212 -#: src/organization/mutations/remove-organization.js:251 -#: src/organization/mutations/remove-organization.js:373 -#: src/organization/mutations/remove-organization.js:406 -#: src/organization/mutations/remove-organization.js:462 -#: src/organization/mutations/remove-organization.js:473 +#: src/organization/mutations/remove-organization.js:237 +#: src/organization/mutations/remove-organization.js:255 +#: src/organization/mutations/remove-organization.js:288 +#: src/organization/mutations/remove-organization.js:344 +#: src/organization/mutations/remove-organization.js:355 msgid "Unable to remove organization. Please try again." msgstr "Unable to remove organization. Please try again." @@ -988,7 +1007,7 @@ msgstr "Unable to remove organization. Please try again." msgid "Unable to remove phone number. Please try again." msgstr "Unable to remove phone number. Please try again." -#: src/domain/mutations/remove-domain.js:71 +#: src/domain/mutations/remove-domain.js:65 msgid "Unable to remove unknown domain." msgstr "Unable to remove unknown domain." @@ -1015,10 +1034,18 @@ msgstr "Unable to remove user from this organization. Please try again." msgid "Unable to remove user from unknown organization." msgstr "Unable to remove user from unknown organization." -#: src/domain/mutations/request-scan.js:57 +#: src/domain/mutations/request-scan.js:82 +msgid "Unable to request a one time scan on a domain that already has a pending scan." +msgstr "Unable to request a one time scan on a domain that already has a pending scan." + +#: src/domain/mutations/request-scan.js:48 msgid "Unable to request a one time scan on an unknown domain." msgstr "Unable to request a one time scan on an unknown domain." +#: src/domain/mutations/request-scan.js:90 +msgid "Unable to request a one time scan. Please try again." +msgstr "Unable to request a one time scan. Please try again." + #: src/user/mutations/reset-password.js:95 msgid "Unable to reset password. Please request a new email." msgstr "Unable to reset password. Please request a new email." @@ -1029,8 +1056,8 @@ msgstr "Unable to reset password. Please request a new email." msgid "Unable to reset password. Please try again." msgstr "Unable to reset password. Please try again." -#: src/domain/objects/domain.js:167 -#: src/domain/objects/domain.js:213 +#: src/domain/objects/domain.js:206 +#: src/domain/objects/domain.js:248 msgid "Unable to retrieve DMARC report information for: {domain}" msgstr "Unable to retrieve DMARC report information for: {domain}" @@ -1079,15 +1106,15 @@ msgstr "Unable to set phone number, please try again." msgid "Unable to sign in, please try again." msgstr "Unable to sign in, please try again." -#: src/user/mutations/sign-up.js:200 -#: src/user/mutations/sign-up.js:214 +#: src/user/mutations/sign-up.js:182 +#: src/user/mutations/sign-up.js:192 msgid "Unable to sign up, please contact org admin for a new invite." msgstr "Unable to sign up, please contact org admin for a new invite." -#: src/user/mutations/sign-up.js:168 -#: src/user/mutations/sign-up.js:178 -#: src/user/mutations/sign-up.js:236 -#: src/user/mutations/sign-up.js:246 +#: src/user/mutations/sign-up.js:155 +#: src/user/mutations/sign-up.js:163 +#: src/user/mutations/sign-up.js:213 +#: src/user/mutations/sign-up.js:221 msgid "Unable to sign up. Please try again." msgstr "Unable to sign up. Please try again." @@ -1289,8 +1316,8 @@ msgstr "You must provide a `first` or `last` value to properly paginate the `Dma msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:154 -#: src/domain/loaders/load-domain-connections-by-user-id.js:166 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 +#: src/domain/loaders/load-domain-connections-by-user-id.js:161 msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `Domain` connection." @@ -1347,19 +1374,19 @@ msgstr "You must provide a `first` or `last` value to properly paginate the `Ver msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." -#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:33 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:16 msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." msgstr "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." -#: src/web-scan/loaders/load-web-connections-by-domain-id.js:33 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:16 msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." msgstr "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." -#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:22 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:9 msgid "You must provide a `limit` value to properly paginate the `DNS` connection." msgstr "You must provide a `limit` value to properly paginate the `DNS` connection." -#: src/web-scan/loaders/load-web-connections-by-domain-id.js:22 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:9 msgid "You must provide a `limit` value to properly paginate the `web` connection." msgstr "You must provide a `limit` value to properly paginate the `web` connection." @@ -1371,11 +1398,11 @@ msgstr "You must provide a `period` value to access the `DmarcSummaries` connect msgid "You must provide a `year` value to access the `DmarcSummaries` connection." msgstr "You must provide a `year` value to access the `DmarcSummaries` connection." -#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:49 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:30 msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." -#: src/web-scan/loaders/load-web-connections-by-domain-id.js:49 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:30 msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." @@ -1387,12 +1414,8 @@ msgstr "You must provide at most one pagination method (`before`, `after`, `offs #: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:236 #: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:201 -#: src/domain/loaders/load-domain-connections-by-user-id.js:213 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:170 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:136 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:186 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:180 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 +#: src/domain/loaders/load-domain-connections-by-user-id.js:200 #: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 #: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 @@ -1439,8 +1462,8 @@ msgstr "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zer msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." msgstr "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:175 -#: src/domain/loaders/load-domain-connections-by-user-id.js:187 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 +#: src/domain/loaders/load-domain-connections-by-user-id.js:177 msgid "`{argSet}` on the `Domain` connection cannot be less than zero." msgstr "`{argSet}` on the `Domain` connection cannot be less than zero." diff --git a/api/src/locale/fr/messages.js b/api/src/locale/fr/messages.js index adc866c9e2..b59e68a72e 100644 --- a/api/src/locale/fr/messages.js +++ b/api/src/locale/fr/messages.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Erreur d'authentification. Veuillez vous connecter.","Cannot query affiliations on organization without admin permission or higher.":"Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.","Email already in use.":"Courriel déjà utilisé.","If an account with this username is found, a password reset link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.","If an account with this username is found, an email verification link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.","Incorrect TFA code. Please sign in again.":"Code TFA incorrect. Veuillez vous reconnecter.","Incorrect token value. Please request a new email.":"La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.","Incorrect username or password. Please try again.":"Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.","Invalid token, please sign in.":"Jeton invalide, veuillez vous connecter.","New passwords do not match.":"Les nouveaux mots de passe ne correspondent pas.","No organization with the provided slug could be found.":"Aucune organisation avec le slug fourni n'a pu être trouvée.","No verified domain with the provided domain could be found.":"Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.","No verified organization with the provided slug could be found.":"Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.","Organization has already been verified.":"L'organisation a déjà été vérifiée.","Organization name already in use, please choose another and try again.":"Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.","Organization name already in use. Please try again with a different name.":"Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.","Ownership check error. Unable to request domain information.":"Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.","Password does not meet requirements.":"Le mot de passe ne répond pas aux exigences.","Password was successfully reset.":"Le mot de passe a été réinitialisé avec succès.","Password was successfully updated.":"Le mot de passe a été mis à jour avec succès.","Passwords do not match.":"Les mots de passe ne correspondent pas.","Permission Denied: Could not retrieve specified organization.":"Permission refusée : Impossible de récupérer l'organisation spécifiée.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission refusée : L'authentification multifactorielle est requise pour les comptes admin.","Permission Denied: Please contact org owner to transfer ownership.":"Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.","Permission Denied: Please contact organization admin for help with removing users.":"Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.","Permission Denied: Please contact organization user for help with creating domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact organization user for help with updating this domain.":"Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.","Permission check error. Unable to request domain information.":"Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.","Permission error, not an admin for this user.":"Erreur de permission, pas d'administrateur pour cet utilisateur.","Permission error: Unable to close other user's account.":"Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.","Permissions error. You do not have sufficient permissions to access this data.":"Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données.","Phone number has been successfully removed.":"Le numéro de téléphone a été supprimé avec succès.","Phone number has been successfully set, you will receive a verification text message shortly.":"Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.","Profile successfully updated.":"Le profil a été mis à jour avec succès.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Affiliation` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DmarcSummaries` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Domain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `FullPassTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `GuidanceTag` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Organization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `SpfFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `User` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedDomain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedOrganization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIMResults` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIM` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DMARC` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `HTTPS` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SPF` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SSL` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Successfully closed account.":"Le compte a été fermé avec succès.","Successfully dispatched one time scan.":"Un seul balayage a été effectué avec succès.","Successfully email verified account, and set TFA send method to email.":"Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.","Successfully invited user to organization, and sent notification email.":"L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.","Successfully left organization: {0}":["L'organisation a été quittée avec succès: ",["0"]],"Successfully removed domain: {0} from {1}.":["A réussi à supprimer le domaine : ",["0"]," de ",["1"],"."],"Successfully removed organization: {0}.":["A réussi à supprimer l'organisation : ",["0"],"."],"Successfully removed user from organization.":"L'utilisateur a été retiré de l'organisation avec succès.","Successfully sent invitation to service, and organization email.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully signed out.":"J'ai réussi à me déconnecter.","Successfully transferred org: {0} ownership to user: {1}":["A réussi à transférer la propriété de org: ",["0"]," à l'utilisateur: ",["1"]],"Successfully verified organization: {0}.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully verified phone number, and set TFA send method to text.":"Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.","Token value incorrect, please sign in again.":"La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.","Too many failed login attempts, please reset your password, and try again.":"Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.","Two factor code is incorrect. Please try again.":"Le code à deux facteurs est incorrect. Veuillez réessayer.","Two factor code length is incorrect. Please try again.":"La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.","Unable leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to authenticate. Please try again.":"Impossible de s'authentifier. Veuillez réessayer.","Unable to check permission. Please try again.":"Impossible de vérifier l'autorisation. Veuillez réessayer.","Unable to close account of an undefined user.":"Impossible de fermer le compte d'un utilisateur non défini.","Unable to close account. Please try again.":"Impossible de fermer le compte. Veuillez réessayer.","Unable to create domain in unknown organization.":"Impossible de créer un domaine dans une organisation inconnue.","Unable to create domain, organization has already claimed it.":"Impossible de créer le domaine, l'organisation l'a déjà réclamé.","Unable to create domain. Please try again.":"Impossible de créer un domaine. Veuillez réessayer.","Unable to create organization. Please try again.":"Impossible de créer une organisation. Veuillez réessayer.","Unable to dispatch one time scan. Please try again.":"Impossible d'envoyer un scan unique. Veuillez réessayer.","Unable to find Aggregate guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to find DKIM guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to find DKIM result(s). Please try again.":"Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to find DKIM scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.","Unable to find DMARC guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to find DMARC scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.","Unable to find DMARC summary data. Please try again.":"Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.","Unable to find DNS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer.","Unable to find HTTPS guidance tag(s). Please try again.":"Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to find HTTPS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to find SPF guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to find SPF scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.","Unable to find SSL guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to find SSL scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.","Unable to find the requested domain.":"Impossible de trouver le domaine demandé.","Unable to find user affiliation(s). Please try again.":"Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.","Unable to find verified organization(s). Please try again.":"Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.","Unable to invite user to unknown organization.":"Impossible d'inviter un utilisateur à une organisation inconnue.","Unable to invite user. Please try again.":"Impossible d'inviter un utilisateur. Veuillez réessayer.","Unable to invite yourself to an org.":"Impossible de s'inviter à un org.","Unable to leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to leave undefined organization.":"Impossible de quitter une organisation non définie.","Unable to load Aggregate guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to load DKIM failure data. Please try again.":"Impossible de charger les données d'échec DKIM. Veuillez réessayer.","Unable to load DKIM guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to load DKIM result(s). Please try again.":"Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to load DKIM scan(s). Please try again.":"Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.","Unable to load DMARC failure data. Please try again.":"Impossible de charger les données d'échec DMARC. Veuillez réessayer.","Unable to load DMARC guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to load DMARC phase summary. Please try again.":"Impossible de charger le résumé DMARC. Veuillez réessayer.","Unable to load DMARC scan(s). Please try again.":"Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.","Unable to load DMARC summary data. Please try again.":"Impossible de charger les données de synthèse DMARC. Veuillez réessayer.","Unable to load DNS scan(s). Please try again.":"Impossible de charger le(s) scan(s) DNS. Veuillez réessayer.","Unable to load HTTPS guidance tag(s). Please try again.":"Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to load HTTPS scan(s). Please try again.":"Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to load HTTPS summary. Please try again.":"Impossible de charger le résumé HTTPS. Veuillez réessayer.","Unable to load SPF failure data. Please try again.":"Impossible de charger les données d'échec SPF. Veuillez réessayer.","Unable to load SPF guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to load SPF scan(s). Please try again.":"Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.","Unable to load SSL guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to load SSL scan(s). Please try again.":"Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.","Unable to load affiliation information. Please try again.":"Impossible de charger les informations d'affiliation. Veuillez réessayer.","Unable to load affiliation(s). Please try again.":"Impossible de charger l'affiliation (s). Veuillez réessayer.","Unable to load domain(s). Please try again.":"Impossible de charger le(s) domaine(s). Veuillez réessayer.","Unable to load domain. Please try again.":"Impossible de charger le domaine. Veuillez réessayer.","Unable to load full pass data. Please try again.":"Impossible de charger les données complètes de la passe. Veuillez réessayer.","Unable to load mail summary. Please try again.":"Impossible de charger le résumé du courrier. Veuillez réessayer.","Unable to load organization domain statuses. Please try again.":"Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer.","Unable to load organization(s). Please try again.":"Impossible de charger l'organisation (s). Veuillez réessayer.","Unable to load owner information. Please try again.":"Impossible de charger les informations sur le propriétaire. Veuillez réessayer.","Unable to load summary. Please try again.":"Impossible de charger le résumé. Veuillez réessayer.","Unable to load tags(s). Please try again.":"Impossible de charger les balises. Veuillez réessayer.","Unable to load user(s). Please try again.":"Impossible de charger le(s) utilisateur(s). Veuillez réessayer.","Unable to load verified domain(s). Please try again.":"Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.","Unable to load verified organization(s). Please try again.":"Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.","Unable to load web scan(s). Please try again.":"Impossible de charger le(s) scan(s) web. Veuillez réessayer.","Unable to load web summary. Please try again.":"Impossible de charger le résumé web. Veuillez réessayer.","Unable to query affiliation(s). Please try again.":"Impossible de demander l'affiliation (s). Veuillez réessayer.","Unable to query domain(s). Please try again.":"Impossible d'interroger le(s) domaine(s). Veuillez réessayer.","Unable to query user(s). Please try again.":"Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer.","Unable to refresh tokens, please sign in.":"Impossible de rafraîchir les jetons, veuillez vous connecter.","Unable to remove a user that already does not belong to this organization.":"Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.","Unable to remove domain from unknown organization.":"Impossible de supprimer le domaine d'une organisation inconnue.","Unable to remove domain. Please try again.":"Impossible de supprimer le domaine. Veuillez réessayer.","Unable to remove organization. Please try again.":"Impossible de supprimer l'organisation. Veuillez réessayer.","Unable to remove phone number. Please try again.":"Impossible de supprimer le numéro de téléphone. Veuillez réessayer.","Unable to remove unknown domain.":"Impossible de supprimer un domaine inconnu.","Unable to remove unknown organization.":"Impossible de supprimer une organisation inconnue.","Unable to remove unknown user from organization.":"Impossible de supprimer un utilisateur inconnu de l'organisation.","Unable to remove user from organization.":"Impossible de supprimer un utilisateur de l'organisation.","Unable to remove user from this organization. Please try again.":"Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.","Unable to remove user from unknown organization.":"Impossible de supprimer un utilisateur d'une organisation inconnue.","Unable to request a one time scan on an unknown domain.":"Impossible de demander un scan unique sur un domaine inconnu.","Unable to reset password. Please request a new email.":"Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.","Unable to reset password. Please try again.":"Impossible de réinitialiser le mot de passe. Veuillez réessayer.","Unable to retrieve DMARC report information for: {domain}":["Impossible de récupérer les informations du rapport DMARC pour : ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.","Unable to send authentication email. Please try again.":"Impossible d'envoyer l'email d'authentification. Veuillez réessayer.","Unable to send authentication text message. Please try again.":"Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.","Unable to send org invite email. Please try again.":"Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.","Unable to send password reset email. Please try again.":"Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.","Unable to send two factor authentication message. Please try again.":"Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.","Unable to send verification email. Please try again.":"Impossible d'envoyer l'email de vérification. Veuillez réessayer.","Unable to set phone number, please try again.":"Impossible de définir le numéro de téléphone, veuillez réessayer.","Unable to sign in, please try again.":"Impossible de se connecter, veuillez réessayer.","Unable to sign up, please contact org admin for a new invite.":"Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.","Unable to sign up. Please try again.":"Impossible de s'inscrire. Veuillez réessayer.","Unable to transfer organization ownership. Please try again.":"Impossible de transférer la propriété de l'organisation. Veuillez réessayer.","Unable to transfer ownership of a verified organization.":"Impossible de transférer la propriété d'une organisation vérifiée.","Unable to transfer ownership of an org to an undefined user.":"Impossible de transférer la propriété d'un org à un utilisateur non défini.","Unable to transfer ownership of undefined organization.":"Impossible de transférer la propriété d'une organisation non définie.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.","Unable to two factor authenticate. Please try again.":"Impossible de s'authentifier par deux facteurs. Veuillez réessayer.","Unable to update domain in an unknown org.":"Impossible de mettre à jour le domaine dans un org inconnu.","Unable to update domain that does not belong to the given organization.":"Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.","Unable to update domain. Please try again.":"Impossible de mettre à jour le domaine. Veuillez réessayer.","Unable to update organization. Please try again.":"Impossible de mettre à jour l'organisation. Veuillez réessayer.","Unable to update password, current password does not match. Please try again.":"Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.","Unable to update password, new passwords do not match. Please try again.":"Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.","Unable to update password, passwords do not match requirements. Please try again.":"Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.","Unable to update password. Please try again.":"Impossible de mettre à jour le mot de passe. Veuillez réessayer.","Unable to update profile. Please try again.":"Impossible de mettre à jour le profil. Veuillez réessayer.","Unable to update role: organization unknown.":"Impossible de mettre à jour le rôle : organisation inconnue.","Unable to update role: user does not belong to organization.":"Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.","Unable to update role: user unknown.":"Impossible de mettre à jour le rôle : utilisateur inconnu.","Unable to update unknown domain.":"Impossible de mettre à jour un domaine inconnu.","Unable to update unknown organization.":"Impossible de mettre à jour une organisation inconnue.","Unable to update user's role. Please try again.":"Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.","Unable to update your own role.":"Impossible de mettre à jour votre propre rôle.","Unable to verify account. Please request a new email.":"Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.","Unable to verify account. Please try again.":"Impossible de vérifier le compte. Veuillez réessayer.","Unable to verify if user is a super admin, please try again.":"Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.","Unable to verify if user is an admin, please try again.":"Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.","Unable to verify organization. Please try again.":"Impossible de vérifier l'organisation. Veuillez réessayer.","Unable to verify unknown organization.":"Impossible de vérifier une organisation inconnue.","User could not be queried.":"L'utilisateur n'a pas pu être interrogé.","User role was updated successfully.":"Le rôle de l'utilisateur a été mis à jour avec succès.","Username not available, please try another.":"Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.","Verification error. Please activate multi-factor authentication to access content.":"Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.","Verification error. Please verify your account via email to access content.":"Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` dans la gamme de 1-100 pour paginer correctement la connexion `DNS`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` dans la gamme de 1-100 pour paginer correctement la connexion `web`.","You must provide a `limit` value to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`.","You must provide a `limit` value to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`.","You must provide a `period` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.","You must provide a `year` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` doit être de type `number` et non `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Affiliation` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIMResults` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIM` ne peut être inférieur à zéro."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DMARC` ne peut être inférieur à zéro."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Domain` ne peut être inférieur à zéro."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `FullPassTable` ne peut être inférieur à zéro."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `GuidanceTag` ne peut être inférieure à zéro."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `HTTPS` ne peut être inférieur à zéro."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Organization` ne peut être inférieure à zéro."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SPF` ne peut être inférieure à zéro."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SSL` ne peut être inférieur à zéro."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `User` ne peut être inférieure à zéro."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro."]}}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Erreur d'authentification. Veuillez vous connecter.","Cannot query affiliations on organization without admin permission or higher.":"Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.","Cannot query audit logs on organization without admin permission or higher.":"Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus.","Email already in use.":"Courriel déjà utilisé.","If an account with this username is found, a password reset link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.","If an account with this username is found, an email verification link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.","Incorrect TFA code. Please sign in again.":"Code TFA incorrect. Veuillez vous reconnecter.","Incorrect token value. Please request a new email.":"La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.","Incorrect username or password. Please try again.":"Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.","Invalid token, please sign in.":"Jeton invalide, veuillez vous connecter.","New passwords do not match.":"Les nouveaux mots de passe ne correspondent pas.","No organization with the provided slug could be found.":"Aucune organisation avec le slug fourni n'a pu être trouvée.","No verified domain with the provided domain could be found.":"Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.","No verified organization with the provided slug could be found.":"Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.","Organization has already been verified.":"L'organisation a déjà été vérifiée.","Organization name already in use, please choose another and try again.":"Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.","Organization name already in use. Please try again with a different name.":"Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.","Ownership check error. Unable to request domain information.":"Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.","Password does not meet requirements.":"Le mot de passe ne répond pas aux exigences.","Password was successfully reset.":"Le mot de passe a été réinitialisé avec succès.","Password was successfully updated.":"Le mot de passe a été mis à jour avec succès.","Passwords do not match.":"Les mots de passe ne correspondent pas.","Permission Denied: Could not retrieve specified organization.":"Permission refusée : Impossible de récupérer l'organisation spécifiée.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission refusée : L'authentification multifactorielle est requise pour les comptes admin.","Permission Denied: Please contact org owner to transfer ownership.":"Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.","Permission Denied: Please contact organization admin for help with removing users.":"Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.","Permission Denied: Please contact organization user for help with creating domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.","Permission Denied: Please contact organization user for help with creating domains.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact organization user for help with updating this domain.":"Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.","Permission check error. Unable to request domain information.":"Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.","Permission error, not an admin for this user.":"Erreur de permission, pas d'administrateur pour cet utilisateur.","Permission error: Unable to close other user's account.":"Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.","Permissions error. You do not have sufficient permissions to access this data.":"Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données.","Phone number has been successfully removed.":"Le numéro de téléphone a été supprimé avec succès.","Phone number has been successfully set, you will receive a verification text message shortly.":"Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.","Profile successfully updated.":"Le profil a été mis à jour avec succès.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Affiliation` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DmarcSummaries` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Domain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `FullPassTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `GuidanceTag` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Log` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Organization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `SpfFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `User` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedDomain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedOrganization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIMResults` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIM` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DMARC` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `HTTPS` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SPF` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SSL` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Successfully added {domainCount} domain(s) to {0}.":["Ajouté avec succès le(s) domaine(s) ",["domainCount"]," à ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Ajouté avec succès les domaines ",["domainCount"]," à ",["0"],"."],"Successfully closed account.":"Le compte a été fermé avec succès.","Successfully dispatched one time scan.":"Un seul balayage a été effectué avec succès.","Successfully email verified account, and set TFA send method to email.":"Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.","Successfully invited user to organization, and sent notification email.":"L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.","Successfully left organization: {0}":["L'organisation a été quittée avec succès: ",["0"]],"Successfully removed domain: {0} from favourites.":["A réussi à supprimer le domaine : ",["0"]," des favoris."],"Successfully removed domain: {0} from {1}.":["A réussi à supprimer le domaine : ",["0"]," de ",["1"],"."],"Successfully removed organization: {0}.":["A réussi à supprimer l'organisation : ",["0"],"."],"Successfully removed user from organization.":"L'utilisateur a été retiré de l'organisation avec succès.","Successfully removed {domainCount} domain(s) from {0}.":["Supprimé avec succès le(s) domaine(s) ",["domainCount"]," de ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Suppression réussie des domaines ",["domainCount"]," de ",["0"],"."],"Successfully sent invitation to service, and organization email.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully signed out.":"J'ai réussi à me déconnecter.","Successfully transferred org: {0} ownership to user: {1}":["A réussi à transférer la propriété de org: ",["0"]," à l'utilisateur: ",["1"]],"Successfully verified organization: {0}.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully verified phone number, and set TFA send method to text.":"Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.","Token value incorrect, please sign in again.":"La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.","Too many failed login attempts, please reset your password, and try again.":"Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.","Two factor code is incorrect. Please try again.":"Le code à deux facteurs est incorrect. Veuillez réessayer.","Two factor code length is incorrect. Please try again.":"La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.","Unable leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to add domains in unknown organization.":"Impossible d'ajouter des domaines dans une organisation inconnue.","Unable to authenticate. Please try again.":"Impossible de s'authentifier. Veuillez réessayer.","Unable to check permission. Please try again.":"Impossible de vérifier l'autorisation. Veuillez réessayer.","Unable to close account of an undefined user.":"Impossible de fermer le compte d'un utilisateur non défini.","Unable to close account. Please try again.":"Impossible de fermer le compte. Veuillez réessayer.","Unable to create domain in unknown organization.":"Impossible de créer un domaine dans une organisation inconnue.","Unable to create domain, organization has already claimed it.":"Impossible de créer le domaine, l'organisation l'a déjà réclamé.","Unable to create domain. Please try again.":"Impossible de créer un domaine. Veuillez réessayer.","Unable to create domains. Please try again.":"Impossible de créer des domaines. Veuillez réessayer.","Unable to create organization. Please try again.":"Impossible de créer une organisation. Veuillez réessayer.","Unable to dispatch one time scan. Please try again.":"Impossible d'envoyer un scan unique. Veuillez réessayer.","Unable to favourite domain, user has already favourited it.":"Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé.","Unable to favourite domain. Please try again.":"Impossible d'accéder au domaine favori. Veuillez réessayer.","Unable to favourite unknown domain.":"Impossible de favoriser le domaine inconnu.","Unable to find Aggregate guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to find DKIM guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to find DKIM result(s). Please try again.":"Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to find DKIM scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.","Unable to find DMARC guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to find DMARC scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.","Unable to find DMARC summary data. Please try again.":"Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.","Unable to find DNS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer.","Unable to find HTTPS guidance tag(s). Please try again.":"Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to find HTTPS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to find SPF guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to find SPF scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.","Unable to find SSL guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to find SSL scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.","Unable to find the requested domain.":"Impossible de trouver le domaine demandé.","Unable to find user affiliation(s). Please try again.":"Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.","Unable to find verified organization(s). Please try again.":"Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.","Unable to invite user to organization. Please try again.":"Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer.","Unable to invite user to organization. User is already affiliated with organization.":"Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation.","Unable to invite user to unknown organization.":"Impossible d'inviter un utilisateur à une organisation inconnue.","Unable to invite user. Please try again.":"Impossible d'inviter un utilisateur. Veuillez réessayer.","Unable to invite yourself to an org.":"Impossible de s'inviter à un org.","Unable to leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to leave undefined organization.":"Impossible de quitter une organisation non définie.","Unable to load Aggregate guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to load DKIM failure data. Please try again.":"Impossible de charger les données d'échec DKIM. Veuillez réessayer.","Unable to load DKIM guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to load DKIM result(s). Please try again.":"Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to load DKIM scan(s). Please try again.":"Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.","Unable to load DMARC failure data. Please try again.":"Impossible de charger les données d'échec DMARC. Veuillez réessayer.","Unable to load DMARC guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to load DMARC phase summary. Please try again.":"Impossible de charger le résumé DMARC. Veuillez réessayer.","Unable to load DMARC scan(s). Please try again.":"Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.","Unable to load DMARC summary data. Please try again.":"Impossible de charger les données de synthèse DMARC. Veuillez réessayer.","Unable to load DNS scan(s). Please try again.":"Impossible de charger le(s) scan(s) DNS. Veuillez réessayer.","Unable to load HTTPS guidance tag(s). Please try again.":"Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to load HTTPS scan(s). Please try again.":"Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to load HTTPS summary. Please try again.":"Impossible de charger le résumé HTTPS. Veuillez réessayer.","Unable to load SPF failure data. Please try again.":"Impossible de charger les données d'échec SPF. Veuillez réessayer.","Unable to load SPF guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to load SPF scan(s). Please try again.":"Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.","Unable to load SSL guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to load SSL scan(s). Please try again.":"Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.","Unable to load affiliation information. Please try again.":"Impossible de charger les informations d'affiliation. Veuillez réessayer.","Unable to load affiliation(s). Please try again.":"Impossible de charger l'affiliation (s). Veuillez réessayer.","Unable to load all organization domain statuses. Please try again.":"Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer.","Unable to load domain(s). Please try again.":"Impossible de charger le(s) domaine(s). Veuillez réessayer.","Unable to load domain. Please try again.":"Impossible de charger le domaine. Veuillez réessayer.","Unable to load full pass data. Please try again.":"Impossible de charger les données complètes de la passe. Veuillez réessayer.","Unable to load log(s). Please try again.":"Impossible de charger le(s) journal(s). Veuillez réessayer.","Unable to load log. Please try again.":"Impossible de charger le journal. Veuillez réessayer.","Unable to load mail summary. Please try again.":"Impossible de charger le résumé du courrier. Veuillez réessayer.","Unable to load organization domain statuses. Please try again.":"Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer.","Unable to load organization(s). Please try again.":"Impossible de charger l'organisation (s). Veuillez réessayer.","Unable to load owner information. Please try again.":"Impossible de charger les informations sur le propriétaire. Veuillez réessayer.","Unable to load summary. Please try again.":"Impossible de charger le résumé. Veuillez réessayer.","Unable to load tags(s). Please try again.":"Impossible de charger les balises. Veuillez réessayer.","Unable to load user(s). Please try again.":"Impossible de charger le(s) utilisateur(s). Veuillez réessayer.","Unable to load verified domain(s). Please try again.":"Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.","Unable to load verified organization(s). Please try again.":"Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.","Unable to load web scan(s). Please try again.":"Impossible de charger le(s) scan(s) web. Veuillez réessayer.","Unable to load web summary. Please try again.":"Impossible de charger le résumé web. Veuillez réessayer.","Unable to query affiliation(s). Please try again.":"Impossible de demander l'affiliation (s). Veuillez réessayer.","Unable to query domain(s). Please try again.":"Impossible d'interroger le(s) domaine(s). Veuillez réessayer.","Unable to query log(s). Please try again.":"Impossible d'interroger le(s) journal(s). Veuillez réessayer.","Unable to query user(s). Please try again.":"Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer.","Unable to refresh tokens, please sign in.":"Impossible de rafraîchir les jetons, veuillez vous connecter.","Unable to remove a user that already does not belong to this organization.":"Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.","Unable to remove domain from unknown organization.":"Impossible de supprimer le domaine d'une organisation inconnue.","Unable to remove domain. Domain is not part of organization.":"Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation.","Unable to remove domain. Please try again.":"Impossible de supprimer le domaine. Veuillez réessayer.","Unable to remove domains from unknown organization.":"Impossible de supprimer les domaines d'une organisation inconnue.","Unable to remove organization. Please try again.":"Impossible de supprimer l'organisation. Veuillez réessayer.","Unable to remove phone number. Please try again.":"Impossible de supprimer le numéro de téléphone. Veuillez réessayer.","Unable to remove unknown domain.":"Impossible de supprimer un domaine inconnu.","Unable to remove unknown organization.":"Impossible de supprimer une organisation inconnue.","Unable to remove unknown user from organization.":"Impossible de supprimer un utilisateur inconnu de l'organisation.","Unable to remove user from organization.":"Impossible de supprimer un utilisateur de l'organisation.","Unable to remove user from this organization. Please try again.":"Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.","Unable to remove user from unknown organization.":"Impossible de supprimer un utilisateur d'une organisation inconnue.","Unable to request a one time scan on a domain that already has a pending scan.":"Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours.","Unable to request a one time scan on an unknown domain.":"Impossible de demander un scan unique sur un domaine inconnu.","Unable to request a one time scan. Please try again.":"Impossible de demander une analyse unique. Veuillez réessayer.","Unable to reset password. Please request a new email.":"Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.","Unable to reset password. Please try again.":"Impossible de réinitialiser le mot de passe. Veuillez réessayer.","Unable to retrieve DMARC report information for: {domain}":["Impossible de récupérer les informations du rapport DMARC pour : ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.","Unable to send authentication email. Please try again.":"Impossible d'envoyer l'email d'authentification. Veuillez réessayer.","Unable to send authentication text message. Please try again.":"Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.","Unable to send org invite email. Please try again.":"Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.","Unable to send password reset email. Please try again.":"Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.","Unable to send two factor authentication message. Please try again.":"Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.","Unable to send verification email. Please try again.":"Impossible d'envoyer l'email de vérification. Veuillez réessayer.","Unable to set phone number, please try again.":"Impossible de définir le numéro de téléphone, veuillez réessayer.","Unable to sign in, please try again.":"Impossible de se connecter, veuillez réessayer.","Unable to sign up, please contact org admin for a new invite.":"Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.","Unable to sign up. Please try again.":"Impossible de s'inscrire. Veuillez réessayer.","Unable to transfer organization ownership. Please try again.":"Impossible de transférer la propriété de l'organisation. Veuillez réessayer.","Unable to transfer ownership of a verified organization.":"Impossible de transférer la propriété d'une organisation vérifiée.","Unable to transfer ownership of an org to an undefined user.":"Impossible de transférer la propriété d'un org à un utilisateur non défini.","Unable to transfer ownership of undefined organization.":"Impossible de transférer la propriété d'une organisation non définie.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.","Unable to two factor authenticate. Please try again.":"Impossible de s'authentifier par deux facteurs. Veuillez réessayer.","Unable to unfavourite domain, domain is not favourited.":"Impossible de désactiver le domaine, le domaine n'est pas favorisé.","Unable to unfavourite domain. Please try again.":"Impossible de défavoriser le domaine. Veuillez réessayer.","Unable to unfavourite unknown domain.":"Impossible de défavoriser un domaine inconnu.","Unable to update domain edge. Please try again.":"Impossible de mettre à jour le bord du domaine. Veuillez réessayer.","Unable to update domain in an unknown org.":"Impossible de mettre à jour le domaine dans un org inconnu.","Unable to update domain that does not belong to the given organization.":"Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.","Unable to update domain. Please try again.":"Impossible de mettre à jour le domaine. Veuillez réessayer.","Unable to update organization. Please try again.":"Impossible de mettre à jour l'organisation. Veuillez réessayer.","Unable to update password, current password does not match. Please try again.":"Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.","Unable to update password, new passwords do not match. Please try again.":"Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.","Unable to update password, passwords do not match requirements. Please try again.":"Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.","Unable to update password. Please try again.":"Impossible de mettre à jour le mot de passe. Veuillez réessayer.","Unable to update profile. Please try again.":"Impossible de mettre à jour le profil. Veuillez réessayer.","Unable to update role: organization unknown.":"Impossible de mettre à jour le rôle : organisation inconnue.","Unable to update role: user does not belong to organization.":"Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.","Unable to update role: user unknown.":"Impossible de mettre à jour le rôle : utilisateur inconnu.","Unable to update unknown domain.":"Impossible de mettre à jour un domaine inconnu.","Unable to update unknown organization.":"Impossible de mettre à jour une organisation inconnue.","Unable to update user's role. Please try again.":"Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.","Unable to update your own role.":"Impossible de mettre à jour votre propre rôle.","Unable to verify account. Please request a new email.":"Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.","Unable to verify account. Please try again.":"Impossible de vérifier le compte. Veuillez réessayer.","Unable to verify if user is a super admin, please try again.":"Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.","Unable to verify if user is an admin, please try again.":"Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.","Unable to verify organization. Please try again.":"Impossible de vérifier l'organisation. Veuillez réessayer.","Unable to verify unknown organization.":"Impossible de vérifier une organisation inconnue.","User could not be queried.":"L'utilisateur n'a pas pu être interrogé.","User role was updated successfully.":"Le rôle de l'utilisateur a été mis à jour avec succès.","Username not available, please try another.":"Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.","Verification error. Please activate multi-factor authentication to access content.":"Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.","Verification error. Please verify your account via email to access content.":"Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`.","You must provide a `limit` value to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`.","You must provide a `limit` value to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`.","You must provide a `period` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.","You must provide a `year` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` doit être de type `number` et non `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Affiliation` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIMResults` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIM` ne peut être inférieur à zéro."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DMARC` ne peut être inférieur à zéro."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Domain` ne peut être inférieur à zéro."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `FullPassTable` ne peut être inférieur à zéro."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `GuidanceTag` ne peut être inférieure à zéro."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `HTTPS` ne peut être inférieur à zéro."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Log` ne peut être inférieur à zéro."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Organization` ne peut être inférieure à zéro."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SPF` ne peut être inférieure à zéro."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SSL` ne peut être inférieur à zéro."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `User` ne peut être inférieure à zéro."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro."]}}; \ No newline at end of file diff --git a/api/src/locale/fr/messages.po b/api/src/locale/fr/messages.po index be7c260aed..6c5ec1fde5 100644 --- a/api/src/locale/fr/messages.po +++ b/api/src/locale/fr/messages.po @@ -28,7 +28,7 @@ msgstr "Impossible d'interroger les affiliations sur l'organisation sans l'autor msgid "Cannot query audit logs on organization without admin permission or higher." msgstr "Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus." -#: src/user/mutations/sign-up.js:117 +#: src/user/mutations/sign-up.js:106 msgid "Email already in use." msgstr "Courriel déjà utilisé." @@ -86,10 +86,10 @@ msgstr "Le nom de l'organisation est déjà utilisé, veuillez en choisir un aut msgid "Organization name already in use. Please try again with a different name." msgstr "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent." -#: src/auth/check-domain-ownership.js:30 -#: src/auth/check-domain-ownership.js:42 -#: src/auth/check-domain-ownership.js:64 -#: src/auth/check-domain-ownership.js:75 +#: src/auth/check-domain-ownership.js:33 +#: src/auth/check-domain-ownership.js:45 +#: src/auth/check-domain-ownership.js:67 +#: src/auth/check-domain-ownership.js:78 msgid "Ownership check error. Unable to request domain information." msgstr "Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine." @@ -99,17 +99,17 @@ msgid "Passing both `first` and `last` to paginate the `Affiliation` connection msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté." #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:98 -msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:123 #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:159 -msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `DKIM` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté." #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:147 -msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `DMARC` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté." #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:45 msgid "Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported." @@ -123,8 +123,8 @@ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcFail msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:163 -#: src/domain/loaders/load-domain-connections-by-user-id.js:175 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 +#: src/domain/loaders/load-domain-connections-by-user-id.js:168 msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté." @@ -142,8 +142,8 @@ msgid "Passing both `first` and `last` to paginate the `GuidanceTag` connection msgstr "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté." #: src/web-scan/loaders/load-https-connections-by-domain-id.js:156 -msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté." #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:110 msgid "Passing both `first` and `last` to paginate the `Log` connection is not supported." @@ -156,12 +156,12 @@ msgid "Passing both `first` and `last` to paginate the `Organization` connection msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté." #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:142 -msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `SPF` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté." #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:180 -msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." -msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté." +#~ msgid "Passing both `first` and `last` to paginate the `SSL` connection is not supported." +#~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté." #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:45 msgid "Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported." @@ -182,7 +182,7 @@ msgid "Passing both `first` and `last` to paginate the `VerifiedOrganization` co msgstr "Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté." #: src/user/mutations/reset-password.js:120 -#: src/user/mutations/sign-up.js:91 +#: src/user/mutations/sign-up.js:84 msgid "Password does not meet requirements." msgstr "Le mot de passe ne répond pas aux exigences." @@ -194,7 +194,7 @@ msgstr "Le mot de passe a été réinitialisé avec succès." msgid "Password was successfully updated." msgstr "Le mot de passe a été mis à jour avec succès." -#: src/user/mutations/sign-up.js:103 +#: src/user/mutations/sign-up.js:94 msgid "Passwords do not match." msgstr "Les mots de passe ne correspondent pas." @@ -211,15 +211,15 @@ msgstr "Permission refusée : L'authentification multifactorielle est requise po msgid "Permission Denied: Please contact org owner to transfer ownership." msgstr "Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété." -#: src/domain/mutations/remove-organizations-domains.js:135 +#: src/domain/mutations/remove-organizations-domains.js:127 msgid "Permission Denied: Please contact organization admin for help with archiving domains." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines." -#: src/domain/mutations/remove-domain.js:117 +#: src/domain/mutations/remove-domain.js:106 msgid "Permission Denied: Please contact organization admin for help with removing domain." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine." -#: src/domain/mutations/remove-organizations-domains.js:122 +#: src/domain/mutations/remove-organizations-domains.js:116 msgid "Permission Denied: Please contact organization admin for help with removing domains." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines." @@ -241,7 +241,7 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat msgid "Permission Denied: Please contact organization admin for help with updating user roles." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." -#: src/affiliation/mutations/invite-user-to-org.js:114 +#: src/affiliation/mutations/invite-user-to-org.js:105 msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs." @@ -249,16 +249,19 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs." -#: src/domain/mutations/add-organizations-domains.js:131 -#: src/domain/mutations/create-domain.js:140 +#: src/domain/mutations/create-domain.js:121 msgid "Permission Denied: Please contact organization user for help with creating domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine." +#: src/domain/mutations/add-organizations-domains.js:130 +msgid "Permission Denied: Please contact organization user for help with creating domains." +msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines." + #: src/domain/queries/find-domain-by-domain.js:57 msgid "Permission Denied: Please contact organization user for help with retrieving this domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine." -#: src/domain/mutations/request-scan.js:71 +#: src/domain/mutations/request-scan.js:60 msgid "Permission Denied: Please contact organization user for help with scanning this domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine." @@ -266,8 +269,8 @@ msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation msgid "Permission Denied: Please contact organization user for help with updating this domain." msgstr "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." -#: src/domain/mutations/remove-domain.js:104 -#: src/domain/mutations/remove-organizations-domains.js:109 +#: src/domain/mutations/remove-domain.js:95 +#: src/domain/mutations/remove-organizations-domains.js:105 msgid "Permission Denied: Please contact super admin for help with removing domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." @@ -296,7 +299,7 @@ msgstr "Erreur de vérification des permissions. Impossible de demander des info msgid "Permission error, not an admin for this user." msgstr "Erreur de permission, pas d'administrateur pour cet utilisateur." -#: src/user/mutations/close-account.js:56 +#: src/user/mutations/close-account.js:54 msgid "Permission error: Unable to close other user's account." msgstr "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur." @@ -334,8 +337,8 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTab msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `DmarcSummaries` dépasse la limite `{argSet}` de 100 enregistrements." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:186 -#: src/domain/loaders/load-domain-connections-by-user-id.js:198 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 +#: src/domain/loaders/load-domain-connections-by-user-id.js:186 msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `Domain` dépasse la limite `{argSet}` de 100 enregistrements." @@ -381,38 +384,42 @@ msgid "Requesting `{amount}` records on the `VerifiedOrganization` connection ex msgstr "La demande d'enregistrements `{amount}` sur la connexion `VerifiedOrganization` dépasse la limite `{argSet}` de 100 enregistrements." #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:121 -msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DKIMResults` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DKIMResults` dépasse la limite `{argSet}` de 100 enregistrements." #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:146 -msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DKIM` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DKIM` dépasse la limite `{argSet}` de 100 enregistrements." #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:171 -msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `DMARC` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `DMARC` dépasse la limite `{argSet}` de 100 enregistrements." #: src/web-scan/loaders/load-https-connections-by-domain-id.js:179 -msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `HTTPS` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `HTTPS` dépasse la limite `{argSet}` de 100 enregistrements." #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:165 -msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `SPF` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `SPF` dépasse la limite `{argSet}` de 100 enregistrements." #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:203 -msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." -msgstr "La demande de {amount} enregistrements sur la connexion `SSL` dépasse la limite `{argSet}` de 100 enregistrements." +#~ msgid "Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records." +#~ msgstr "La demande de {amount} enregistrements sur la connexion `SSL` dépasse la limite `{argSet}` de 100 enregistrements." + +#: src/domain/mutations/add-organizations-domains.js:353 +msgid "Successfully added {domainCount} domain(s) to {0}." +msgstr "Ajouté avec succès le(s) domaine(s) {domainCount} à {0}." #: src/domain/mutations/add-organizations-domains.js:351 -msgid "Successfully added {domainCount} domains to {0}." -msgstr "Ajouté avec succès les domaines {domainCount} à {0}." +#~ msgid "Successfully added {domainCount} domains to {0}." +#~ msgstr "Ajouté avec succès les domaines {domainCount} à {0}." -#: src/user/mutations/close-account.js:521 +#: src/user/mutations/close-account.js:405 msgid "Successfully closed account." msgstr "Le compte a été fermé avec succès." -#: src/domain/mutations/request-scan.js:94 +#: src/domain/mutations/request-scan.js:108 msgid "Successfully dispatched one time scan." msgstr "Un seul balayage a été effectué avec succès." @@ -420,11 +427,11 @@ msgstr "Un seul balayage a été effectué avec succès." msgid "Successfully email verified account, and set TFA send method to email." msgstr "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email." -#: src/affiliation/mutations/invite-user-to-org.js:241 +#: src/affiliation/mutations/invite-user-to-org.js:259 msgid "Successfully invited user to organization, and sent notification email." msgstr "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé." -#: src/affiliation/mutations/leave-organization.js:438 +#: src/affiliation/mutations/leave-organization.js:308 msgid "Successfully left organization: {0}" msgstr "L'organisation a été quittée avec succès: {0}" @@ -432,11 +439,11 @@ msgstr "L'organisation a été quittée avec succès: {0}" msgid "Successfully removed domain: {0} from favourites." msgstr "A réussi à supprimer le domaine : {0} des favoris." -#: src/domain/mutations/remove-domain.js:395 +#: src/domain/mutations/remove-domain.js:325 msgid "Successfully removed domain: {0} from {1}." msgstr "A réussi à supprimer le domaine : {0} de {1}." -#: src/organization/mutations/remove-organization.js:502 +#: src/organization/mutations/remove-organization.js:384 msgid "Successfully removed organization: {0}." msgstr "A réussi à supprimer l'organisation : {0}." @@ -444,11 +451,15 @@ msgstr "A réussi à supprimer l'organisation : {0}." msgid "Successfully removed user from organization." msgstr "L'utilisateur a été retiré de l'organisation avec succès." +#: src/domain/mutations/remove-organizations-domains.js:459 +msgid "Successfully removed {domainCount} domain(s) from {0}." +msgstr "Supprimé avec succès le(s) domaine(s) {domainCount} de {0}." + #: src/domain/mutations/remove-organizations-domains.js:530 -msgid "Successfully removed {domainCount} domains from {0}." -msgstr "Suppression réussie des domaines {domainCount} de {0}." +#~ msgid "Successfully removed {domainCount} domains from {0}." +#~ msgstr "Suppression réussie des domaines {domainCount} de {0}." -#: src/affiliation/mutations/invite-user-to-org.js:167 +#: src/affiliation/mutations/invite-user-to-org.js:150 msgid "Successfully sent invitation to service, and organization email." msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." @@ -484,21 +495,23 @@ msgstr "Le code à deux facteurs est incorrect. Veuillez réessayer." msgid "Two factor code length is incorrect. Please try again." msgstr "La longueur du code à deux facteurs est incorrecte. Veuillez réessayer." -#: src/affiliation/mutations/leave-organization.js:74 -#: src/affiliation/mutations/leave-organization.js:84 -#: src/affiliation/mutations/leave-organization.js:115 -#: src/affiliation/mutations/leave-organization.js:132 -#: src/affiliation/mutations/leave-organization.js:163 -#: src/affiliation/mutations/leave-organization.js:173 -#: src/affiliation/mutations/leave-organization.js:211 -#: src/affiliation/mutations/leave-organization.js:333 -#: src/affiliation/mutations/leave-organization.js:366 -#: src/affiliation/mutations/leave-organization.js:403 -#: src/affiliation/mutations/leave-organization.js:421 -#: src/affiliation/mutations/leave-organization.js:431 +#: src/affiliation/mutations/leave-organization.js:71 +#: src/affiliation/mutations/leave-organization.js:81 +#: src/affiliation/mutations/leave-organization.js:111 +#: src/affiliation/mutations/leave-organization.js:126 +#: src/affiliation/mutations/leave-organization.js:156 +#: src/affiliation/mutations/leave-organization.js:166 +#: src/affiliation/mutations/leave-organization.js:239 +#: src/affiliation/mutations/leave-organization.js:275 +#: src/affiliation/mutations/leave-organization.js:293 +#: src/affiliation/mutations/leave-organization.js:301 msgid "Unable leave organization. Please try again." msgstr "Impossible de quitter l'organisation. Veuillez réessayer." +#: src/domain/mutations/add-organizations-domains.js:115 +msgid "Unable to add domains in unknown organization." +msgstr "Impossible d'ajouter des domaines dans une organisation inconnue." + #: src/user/mutations/authenticate.js:77 #: src/user/mutations/authenticate.js:117 #: src/user/mutations/authenticate.js:126 @@ -512,46 +525,45 @@ msgstr "Impossible de s'authentifier. Veuillez réessayer." msgid "Unable to check permission. Please try again." msgstr "Impossible de vérifier l'autorisation. Veuillez réessayer." -#: src/user/mutations/close-account.js:69 +#: src/user/mutations/close-account.js:66 msgid "Unable to close account of an undefined user." msgstr "Impossible de fermer le compte d'un utilisateur non défini." -#: src/user/mutations/close-account.js:92 -#: src/user/mutations/close-account.js:102 -#: src/user/mutations/close-account.js:121 -#: src/user/mutations/close-account.js:131 -#: src/user/mutations/close-account.js:162 -#: src/user/mutations/close-account.js:177 -#: src/user/mutations/close-account.js:206 -#: src/user/mutations/close-account.js:216 -#: src/user/mutations/close-account.js:252 -#: src/user/mutations/close-account.js:364 -#: src/user/mutations/close-account.js:395 -#: src/user/mutations/close-account.js:420 -#: src/user/mutations/close-account.js:456 -#: src/user/mutations/close-account.js:473 -#: src/user/mutations/close-account.js:488 -#: src/user/mutations/close-account.js:497 +#: src/user/mutations/close-account.js:89 +#: src/user/mutations/close-account.js:99 +#: src/user/mutations/close-account.js:118 +#: src/user/mutations/close-account.js:128 +#: src/user/mutations/close-account.js:159 +#: src/user/mutations/close-account.js:174 +#: src/user/mutations/close-account.js:203 +#: src/user/mutations/close-account.js:213 +#: src/user/mutations/close-account.js:239 +#: src/user/mutations/close-account.js:257 +#: src/user/mutations/close-account.js:286 +#: src/user/mutations/close-account.js:309 +#: src/user/mutations/close-account.js:344 +#: src/user/mutations/close-account.js:361 +#: src/user/mutations/close-account.js:376 +#: src/user/mutations/close-account.js:383 msgid "Unable to close account. Please try again." msgstr "Impossible de fermer le compte. Veuillez réessayer." -#: src/domain/mutations/add-organizations-domains.js:115 -#: src/domain/mutations/create-domain.js:120 +#: src/domain/mutations/create-domain.js:107 msgid "Unable to create domain in unknown organization." msgstr "Impossible de créer un domaine dans une organisation inconnue." -#: src/domain/mutations/create-domain.js:198 +#: src/domain/mutations/create-domain.js:174 msgid "Unable to create domain, organization has already claimed it." msgstr "Impossible de créer le domaine, l'organisation l'a déjà réclamé." -#: src/domain/mutations/create-domain.js:177 -#: src/domain/mutations/create-domain.js:187 -#: src/domain/mutations/create-domain.js:230 -#: src/domain/mutations/create-domain.js:240 +#: src/domain/mutations/create-domain.js:156 +#: src/domain/mutations/create-domain.js:164 +#: src/domain/mutations/create-domain.js:203 +#: src/domain/mutations/create-domain.js:213 +#: src/domain/mutations/create-domain.js:231 #: src/domain/mutations/create-domain.js:260 -#: src/domain/mutations/create-domain.js:291 -#: src/domain/mutations/create-domain.js:311 -#: src/domain/mutations/create-domain.js:321 +#: src/domain/mutations/create-domain.js:278 +#: src/domain/mutations/create-domain.js:286 msgid "Unable to create domain. Please try again." msgstr "Impossible de créer un domaine. Veuillez réessayer." @@ -600,13 +612,13 @@ msgstr "Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessay #: src/email-scan/loaders/load-dkim-result-by-key.js:20 #: src/email-scan/loaders/load-dkim-result-by-key.js:34 -msgid "Unable to find DKIM result(s). Please try again." -msgstr "Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer." +#~ msgid "Unable to find DKIM result(s). Please try again." +#~ msgstr "Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer." #: src/email-scan/loaders/load-dkim-by-key.js:19 #: src/email-scan/loaders/load-dkim-by-key.js:31 -msgid "Unable to find DKIM scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer." +#~ msgid "Unable to find DKIM scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer." #: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:34 #: src/guidance-tag/loaders/load-dmarc-guidance-tags.js:48 @@ -615,42 +627,47 @@ msgstr "Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessa #: src/email-scan/loaders/load-dmarc-by-key.js:20 #: src/email-scan/loaders/load-dmarc-by-key.js:34 -msgid "Unable to find DMARC scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer." +#~ msgid "Unable to find DMARC scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer." #: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:37 #: src/dmarc-summaries/loaders/load-dmarc-summary-by-key.js:51 msgid "Unable to find DMARC summary data. Please try again." msgstr "Impossible de trouver les données de synthèse DMARC. Veuillez réessayer." -#: src/guidance-tag/loaders/load-https-guidance-tags.js:34 -#: src/guidance-tag/loaders/load-https-guidance-tags.js:48 +#: src/dns-scan/loaders/load-dns-by-key.js:20 +#: src/dns-scan/loaders/load-dns-by-key.js:34 +msgid "Unable to find DNS scan(s). Please try again." +msgstr "Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer." + +#: src/guidance-tag/loaders/load-https-guidance-tags.js:33 +#: src/guidance-tag/loaders/load-https-guidance-tags.js:47 msgid "Unable to find HTTPS guidance tag(s). Please try again." msgstr "Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer." #: src/web-scan/loaders/load-https-by-key.js:19 -msgid "Unable to find HTTPS scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer." +#~ msgid "Unable to find HTTPS scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer." -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-spf-guidance-tags.js:43 +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-spf-guidance-tags.js:42 msgid "Unable to find SPF guidance tag(s). Please try again." msgstr "Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer." #: src/email-scan/loaders/load-spf-by-key.js:19 #: src/email-scan/loaders/load-spf-by-key.js:31 -msgid "Unable to find SPF scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer." +#~ msgid "Unable to find SPF scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer." -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:29 -#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:43 +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:28 +#: src/guidance-tag/loaders/load-ssl-guidance-tags.js:42 msgid "Unable to find SSL guidance tag(s). Please try again." msgstr "Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer." #: src/web-scan/loaders/load-ssl-by-key.js:18 #: src/web-scan/loaders/load-ssl-by-key.js:30 -msgid "Unable to find SSL scan(s). Please try again." -msgstr "Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer." +#~ msgid "Unable to find SSL scan(s). Please try again." +#~ msgstr "Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer." #: src/domain/queries/find-domain-by-domain.js:46 msgid "Unable to find the requested domain." @@ -668,20 +685,33 @@ msgstr "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez rées msgid "Unable to find verified organization(s). Please try again." msgstr "Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer." -#: src/affiliation/mutations/invite-user-to-org.js:95 +#: src/affiliation/mutations/invite-user-to-org.js:170 +msgid "Unable to invite user to organization. Please try again." +msgstr "Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer." + +#: src/affiliation/mutations/invite-user-to-org.js:182 +msgid "Unable to invite user to organization. User is already affiliated with organization." +msgstr "Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation." + +#: src/affiliation/mutations/invite-user-to-org.js:87 msgid "Unable to invite user to unknown organization." msgstr "Impossible d'inviter un utilisateur à une organisation inconnue." -#: src/affiliation/mutations/invite-user-to-org.js:194 -#: src/affiliation/mutations/invite-user-to-org.js:209 +#: src/affiliation/mutations/invite-user-to-org.js:212 +#: src/affiliation/mutations/invite-user-to-org.js:231 msgid "Unable to invite user. Please try again." msgstr "Impossible d'inviter un utilisateur. Veuillez réessayer." -#: src/affiliation/mutations/invite-user-to-org.js:81 +#: src/affiliation/mutations/invite-user-to-org.js:73 msgid "Unable to invite yourself to an org." msgstr "Impossible de s'inviter à un org." -#: src/affiliation/mutations/leave-organization.js:51 +#: src/affiliation/mutations/leave-organization.js:190 +#: src/affiliation/mutations/leave-organization.js:208 +msgid "Unable to leave organization. Please try again." +msgstr "Impossible de quitter l'organisation. Veuillez réessayer." + +#: src/affiliation/mutations/leave-organization.js:48 msgid "Unable to leave undefined organization." msgstr "Impossible de quitter une organisation non définie." @@ -703,13 +733,13 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessay #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:258 #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:270 -msgid "Unable to load DKIM result(s). Please try again." -msgstr "Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer." +#~ msgid "Unable to load DKIM result(s). Please try again." +#~ msgstr "Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer." #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:279 #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:289 -msgid "Unable to load DKIM scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer." +#~ msgid "Unable to load DKIM scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer." #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 @@ -728,8 +758,8 @@ msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:319 #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:331 -msgid "Unable to load DMARC scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." +#~ msgid "Unable to load DMARC scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." #: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:477 #: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:489 @@ -740,6 +770,11 @@ msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." msgid "Unable to load DMARC summary data. Please try again." msgstr "Impossible de charger les données de synthèse DMARC. Veuillez réessayer." +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 +msgid "Unable to load DNS scan(s). Please try again." +msgstr "Impossible de charger le(s) scan(s) DNS. Veuillez réessayer." + #: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:260 #: src/guidance-tag/loaders/load-https-guidance-tags-connections.js:272 msgid "Unable to load HTTPS guidance tag(s). Please try again." @@ -748,8 +783,8 @@ msgstr "Impossible de charger la ou les balises d'orientation HTTPS. Veuillez r #: src/web-scan/loaders/load-https-by-key.js:33 #: src/web-scan/loaders/load-https-connections-by-domain-id.js:333 #: src/web-scan/loaders/load-https-connections-by-domain-id.js:345 -msgid "Unable to load HTTPS scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer." +#~ msgid "Unable to load HTTPS scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer." #: src/summaries/queries/https-summary.js:13 msgid "Unable to load HTTPS summary. Please try again." @@ -768,8 +803,8 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessaye #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:306 #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:316 -msgid "Unable to load SPF scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) SPF. Veuillez réessayer." +#~ msgid "Unable to load SPF scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) SPF. Veuillez réessayer." #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 @@ -778,8 +813,8 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessaye #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:380 #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:390 -msgid "Unable to load SSL scan(s). Please try again." -msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." +#~ msgid "Unable to load SSL scan(s). Please try again." +#~ msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." #: src/auth/check-user-belongs-to-org.js:22 msgid "Unable to load affiliation information. Please try again." @@ -790,13 +825,13 @@ msgstr "Impossible de charger les informations d'affiliation. Veuillez réessaye msgid "Unable to load affiliation(s). Please try again." msgstr "Impossible de charger l'affiliation (s). Veuillez réessayer." -#: src/organization/loaders/load-all-organization-domain-statuses.js:36 +#: src/organization/loaders/load-all-organization-domain-statuses.js:32 msgid "Unable to load all organization domain statuses. Please try again." msgstr "Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:547 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:557 -#: src/domain/loaders/load-domain-connections-by-user-id.js:471 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:534 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:544 +#: src/domain/loaders/load-domain-connections-by-user-id.js:462 #: src/domain/loaders/load-domain-tags-by-org-id.js:27 #: src/user/loaders/load-my-tracker-by-user-id.js:43 msgid "Unable to load domain(s). Please try again." @@ -885,6 +920,14 @@ msgstr "Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer msgid "Unable to load verified organization(s). Please try again." msgstr "Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer." +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:9 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:25 +#: src/web-scan/loaders/load-web-scans-by-web-id.js:35 +msgid "Unable to load web scan(s). Please try again." +msgstr "Impossible de charger le(s) scan(s) web. Veuillez réessayer." + #: src/summaries/queries/web-summary.js:13 msgid "Unable to load web summary. Please try again." msgstr "Impossible de charger le résumé web. Veuillez réessayer." @@ -894,7 +937,7 @@ msgstr "Impossible de charger le résumé web. Veuillez réessayer." msgid "Unable to query affiliation(s). Please try again." msgstr "Impossible de demander l'affiliation (s). Veuillez réessayer." -#: src/domain/loaders/load-domain-connections-by-user-id.js:461 +#: src/domain/loaders/load-domain-connections-by-user-id.js:452 #: src/user/loaders/load-my-tracker-by-user-id.js:33 msgid "Unable to query domain(s). Please try again." msgstr "Impossible d'interroger le(s) domaine(s). Veuillez réessayer." @@ -921,30 +964,27 @@ msgstr "Impossible de rafraîchir les jetons, veuillez vous connecter." msgid "Unable to remove a user that already does not belong to this organization." msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation." -#: src/domain/mutations/remove-domain.js:87 +#: src/domain/mutations/remove-domain.js:80 msgid "Unable to remove domain from unknown organization." msgstr "Impossible de supprimer le domaine d'une organisation inconnue." -#: src/domain/mutations/remove-domain.js:148 +#: src/domain/mutations/remove-domain.js:135 msgid "Unable to remove domain. Domain is not part of organization." msgstr "Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation." -#: src/domain/mutations/remove-domain.js:134 -#: src/domain/mutations/remove-domain.js:165 -#: src/domain/mutations/remove-domain.js:198 -#: src/domain/mutations/remove-domain.js:217 -#: src/domain/mutations/remove-domain.js:243 -#: src/domain/mutations/remove-domain.js:260 -#: src/domain/mutations/remove-domain.js:278 -#: src/domain/mutations/remove-domain.js:296 -#: src/domain/mutations/remove-domain.js:314 -#: src/domain/mutations/remove-domain.js:331 -#: src/domain/mutations/remove-domain.js:354 -#: src/domain/mutations/remove-domain.js:365 +#: src/domain/mutations/remove-domain.js:122 +#: src/domain/mutations/remove-domain.js:151 +#: src/domain/mutations/remove-domain.js:184 +#: src/domain/mutations/remove-domain.js:203 +#: src/domain/mutations/remove-domain.js:229 +#: src/domain/mutations/remove-domain.js:247 +#: src/domain/mutations/remove-domain.js:264 +#: src/domain/mutations/remove-domain.js:287 +#: src/domain/mutations/remove-domain.js:298 msgid "Unable to remove domain. Please try again." msgstr "Impossible de supprimer le domaine. Veuillez réessayer." -#: src/domain/mutations/remove-organizations-domains.js:92 +#: src/domain/mutations/remove-organizations-domains.js:90 msgid "Unable to remove domains from unknown organization." msgstr "Impossible de supprimer les domaines d'une organisation inconnue." @@ -954,11 +994,11 @@ msgstr "Impossible de supprimer les domaines d'une organisation inconnue." #: src/organization/mutations/remove-organization.js:168 #: src/organization/mutations/remove-organization.js:200 #: src/organization/mutations/remove-organization.js:212 -#: src/organization/mutations/remove-organization.js:251 -#: src/organization/mutations/remove-organization.js:373 -#: src/organization/mutations/remove-organization.js:406 -#: src/organization/mutations/remove-organization.js:462 -#: src/organization/mutations/remove-organization.js:473 +#: src/organization/mutations/remove-organization.js:237 +#: src/organization/mutations/remove-organization.js:255 +#: src/organization/mutations/remove-organization.js:288 +#: src/organization/mutations/remove-organization.js:344 +#: src/organization/mutations/remove-organization.js:355 msgid "Unable to remove organization. Please try again." msgstr "Impossible de supprimer l'organisation. Veuillez réessayer." @@ -967,7 +1007,7 @@ msgstr "Impossible de supprimer l'organisation. Veuillez réessayer." msgid "Unable to remove phone number. Please try again." msgstr "Impossible de supprimer le numéro de téléphone. Veuillez réessayer." -#: src/domain/mutations/remove-domain.js:71 +#: src/domain/mutations/remove-domain.js:65 msgid "Unable to remove unknown domain." msgstr "Impossible de supprimer un domaine inconnu." @@ -994,10 +1034,18 @@ msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez r msgid "Unable to remove user from unknown organization." msgstr "Impossible de supprimer un utilisateur d'une organisation inconnue." -#: src/domain/mutations/request-scan.js:57 +#: src/domain/mutations/request-scan.js:82 +msgid "Unable to request a one time scan on a domain that already has a pending scan." +msgstr "Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours." + +#: src/domain/mutations/request-scan.js:48 msgid "Unable to request a one time scan on an unknown domain." msgstr "Impossible de demander un scan unique sur un domaine inconnu." +#: src/domain/mutations/request-scan.js:90 +msgid "Unable to request a one time scan. Please try again." +msgstr "Impossible de demander une analyse unique. Veuillez réessayer." + #: src/user/mutations/reset-password.js:95 msgid "Unable to reset password. Please request a new email." msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail." @@ -1008,8 +1056,8 @@ msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouve msgid "Unable to reset password. Please try again." msgstr "Impossible de réinitialiser le mot de passe. Veuillez réessayer." -#: src/domain/objects/domain.js:167 -#: src/domain/objects/domain.js:213 +#: src/domain/objects/domain.js:206 +#: src/domain/objects/domain.js:248 msgid "Unable to retrieve DMARC report information for: {domain}" msgstr "Impossible de récupérer les informations du rapport DMARC pour : {domain}" @@ -1058,15 +1106,15 @@ msgstr "Impossible de définir le numéro de téléphone, veuillez réessayer." msgid "Unable to sign in, please try again." msgstr "Impossible de se connecter, veuillez réessayer." -#: src/user/mutations/sign-up.js:200 -#: src/user/mutations/sign-up.js:214 +#: src/user/mutations/sign-up.js:182 +#: src/user/mutations/sign-up.js:192 msgid "Unable to sign up, please contact org admin for a new invite." msgstr "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation." -#: src/user/mutations/sign-up.js:168 -#: src/user/mutations/sign-up.js:178 -#: src/user/mutations/sign-up.js:236 -#: src/user/mutations/sign-up.js:246 +#: src/user/mutations/sign-up.js:155 +#: src/user/mutations/sign-up.js:163 +#: src/user/mutations/sign-up.js:213 +#: src/user/mutations/sign-up.js:221 msgid "Unable to sign up. Please try again." msgstr "Impossible de s'inscrire. Veuillez réessayer." @@ -1245,16 +1293,16 @@ msgid "You must provide a `first` or `last` value to properly paginate the `Affi msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`." #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:89 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`." #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:114 -msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DKIM` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`." #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:138 -msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `DMARC` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`." #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:36 msgid "You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection." @@ -1268,8 +1316,8 @@ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctemen msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:154 -#: src/domain/loaders/load-domain-connections-by-user-id.js:166 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 +#: src/domain/loaders/load-domain-connections-by-user-id.js:161 msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`." @@ -1287,8 +1335,8 @@ msgid "You must provide a `first` or `last` value to properly paginate the `Guid msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`." #: src/web-scan/loaders/load-https-connections-by-domain-id.js:147 -msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`." #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:101 msgid "You must provide a `first` or `last` value to properly paginate the `Log` connection." @@ -1301,12 +1349,12 @@ msgid "You must provide a `first` or `last` value to properly paginate the `Orga msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`." #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:133 -msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SPF` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`." #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:171 -msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." -msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`." +#~ msgid "You must provide a `first` or `last` value to properly paginate the `SSL` connection." +#~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`." #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:36 msgid "You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection." @@ -1326,6 +1374,22 @@ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctemen msgid "You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`." +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection." +msgstr "Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:16 +msgid "You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection." +msgstr "Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`." + +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `DNS` connection." +msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:9 +msgid "You must provide a `limit` value to properly paginate the `web` connection." +msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`." + #: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:31 msgid "You must provide a `period` value to access the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`." @@ -1334,6 +1398,14 @@ msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `Dm msgid "You must provide a `year` value to access the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`." +#: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection." +msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`." + +#: src/web-scan/loaders/load-web-connections-by-domain-id.js:30 +msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." +msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`." + #: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:118 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:148 @@ -1342,12 +1414,8 @@ msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `Dmar #: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:236 #: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:201 -#: src/domain/loaders/load-domain-connections-by-user-id.js:213 -#: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:170 -#: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:136 -#: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:186 -#: src/email-scan/loaders/load-spf-connections-by-domain-id.js:180 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 +#: src/domain/loaders/load-domain-connections-by-user-id.js:200 #: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 #: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 @@ -1362,8 +1430,6 @@ msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `Dmar #: src/verified-domains/loaders/load-verified-domain-connections.js:164 #: src/verified-organizations/loaders/load-verified-organization-connections-by-domain-id.js:214 #: src/verified-organizations/loaders/load-verified-organizations-connections.js:212 -#: src/web-scan/loaders/load-https-connections-by-domain-id.js:194 -#: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:218 msgid "`{argSet}` must be of type `number` not `{typeSet}`." msgstr "`{argSet}` doit être de type `number` et non `{typeSet}`." @@ -1373,16 +1439,16 @@ msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Affiliation` ne peut être inférieur à zéro." #: src/email-scan/loaders/load-dkim-results-connections-by-dkim-id.js:110 -msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DKIMResults` ne peut être inférieur à zéro." +#~ msgid "`{argSet}` on the `DKIMResults` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DKIMResults` ne peut être inférieur à zéro." #: src/email-scan/loaders/load-dkim-connections-by-domain-id.js:135 -msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DKIM` ne peut être inférieur à zéro." +#~ msgid "`{argSet}` on the `DKIM` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DKIM` ne peut être inférieur à zéro." #: src/email-scan/loaders/load-dmarc-connections-by-domain-id.js:160 -msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `DMARC` ne peut être inférieur à zéro." +#~ msgid "`{argSet}` on the `DMARC` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `DMARC` ne peut être inférieur à zéro." #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:57 msgid "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero." @@ -1396,8 +1462,8 @@ msgstr "`{argSet}` sur la connexion `DmarcFailureTable` ne peut être inférieur msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:175 -#: src/domain/loaders/load-domain-connections-by-user-id.js:187 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 +#: src/domain/loaders/load-domain-connections-by-user-id.js:177 msgid "`{argSet}` on the `Domain` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Domain` ne peut être inférieur à zéro." @@ -1415,8 +1481,8 @@ msgid "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `GuidanceTag` ne peut être inférieure à zéro." #: src/web-scan/loaders/load-https-connections-by-domain-id.js:168 -msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `HTTPS` ne peut être inférieur à zéro." +#~ msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `HTTPS` ne peut être inférieur à zéro." #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:122 msgid "`{argSet}` on the `Log` connection cannot be less than zero." @@ -1429,12 +1495,12 @@ msgid "`{argSet}` on the `Organization` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Organization` ne peut être inférieure à zéro." #: src/email-scan/loaders/load-spf-connections-by-domain-id.js:154 -msgid "`{argSet}` on the `SPF` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `SPF` ne peut être inférieure à zéro." +#~ msgid "`{argSet}` on the `SPF` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `SPF` ne peut être inférieure à zéro." #: src/web-scan/loaders/load-ssl-connections-by-domain-id.js:192 -msgid "`{argSet}` on the `SSL` connection cannot be less than zero." -msgstr "`{argSet}` sur la connexion `SSL` ne peut être inférieur à zéro." +#~ msgid "`{argSet}` on the `SSL` connection cannot be less than zero." +#~ msgstr "`{argSet}` sur la connexion `SSL` ne peut être inférieur à zéro." #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:57 msgid "`{argSet}` on the `SpfFailureTable` connection cannot be less than zero." From 181f41095e74de2e57ae474b6329b702c6e21afe Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 4 May 2023 15:07:56 +0000 Subject: [PATCH 015/113] [ci skip] gcr.io/track-compliance/api-js:master-6915258-1683212804 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 346c346093..cc8f2efdb5 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-53f65de-1682605811 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6915258-1683212804 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index c9ab37100b..56b8972f69 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-53f65de-1682605811 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6915258-1683212804 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 7264f487f4273445b7895a1ca18002213b035f5b Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Fri, 5 May 2023 09:31:29 -0300 Subject: [PATCH 016/113] wrap called function within function (#4502) --- frontend/src/utilities/useDebouncedFunction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/utilities/useDebouncedFunction.js b/frontend/src/utilities/useDebouncedFunction.js index d2aa52fbf5..08c9ca866c 100644 --- a/frontend/src/utilities/useDebouncedFunction.js +++ b/frontend/src/utilities/useDebouncedFunction.js @@ -2,7 +2,9 @@ import { useEffect } from 'react' export const useDebouncedFunction = (functionToCall, delay) => { useEffect(() => { - const timeoutID = setTimeout(functionToCall, delay) + const timeoutID = setTimeout(function () { + functionToCall() + }, delay) return () => { clearTimeout(timeoutID) From 9e115b049b91de47894e88324373ce123e272109 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 5 May 2023 12:39:32 +0000 Subject: [PATCH 017/113] [ci skip] gcr.io/track-compliance/frontend:master-7264f48-1683290184 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 3e172350b3..1788a1d015 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-53f65de-1682605929 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-7264f48-1683290184 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 04e63babab..52b1d8b7cb 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-53f65de-1682605929 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-7264f48-1683290184 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 76a06c877248c1be151e67914feae4abb144aede Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Mon, 8 May 2023 15:21:08 -0400 Subject: [PATCH 018/113] Send bilingual org invite/create account email (#4513) * Send bilingual org invite/create account email * Fix tests --- .../__tests__/invite-user-to-org.test.js | 30 +++++++---- .../mutations/invite-user-to-org.js | 29 +++++++++- ...ify-send-org-invite-create-account.test.js | 53 ++++++++----------- .../notify-send-org-invite-create-account.js | 49 +++++++---------- 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js index a6d3496867..c2f43964fb 100644 --- a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js +++ b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js @@ -501,7 +501,8 @@ describe('invite user to org', () => { userName: 'test@email.gc.ca', preferredLang: 'english', }, - orgName: 'Treasury Board of Canada Secretariat', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -597,7 +598,8 @@ describe('invite user to org', () => { userName: 'test@email.gc.ca', preferredLang: 'english', }, - orgName: 'Treasury Board of Canada Secretariat', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -693,7 +695,8 @@ describe('invite user to org', () => { userName: 'test@email.gc.ca', preferredLang: 'english', }, - orgName: 'Treasury Board of Canada Secretariat', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -994,7 +997,8 @@ describe('invite user to org', () => { userName: 'test@email.gc.ca', preferredLang: 'english', }, - orgName: 'Treasury Board of Canada Secretariat', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -1090,7 +1094,8 @@ describe('invite user to org', () => { userName: 'test@email.gc.ca', preferredLang: 'english', }, - orgName: 'Treasury Board of Canada Secretariat', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -1535,7 +1540,8 @@ describe('invite user to org', () => { ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -1628,7 +1634,8 @@ describe('invite user to org', () => { ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -1721,7 +1728,8 @@ describe('invite user to org', () => { ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -2021,7 +2029,8 @@ describe('invite user to org', () => { ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) @@ -2114,7 +2123,8 @@ describe('invite user to org', () => { ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, - orgName: 'Secrétariat du Conseil Trésor du Canada', + orgNameEN: 'Treasury Board of Canada Secretariat', + orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, }) }) diff --git a/api/src/affiliation/mutations/invite-user-to-org.js b/api/src/affiliation/mutations/invite-user-to-org.js index 4c281608b0..f08c0a1a2b 100644 --- a/api/src/affiliation/mutations/invite-user-to-org.js +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -117,9 +117,36 @@ able to sign-up and be assigned to that organization in one mutation.`, }) const createAccountLink = `https://${request.get('host')}/create-user/${token}` + let orgNamesCursor + try { + orgNamesCursor = await query` + LET org = DOCUMENT(organizations, ${org._id}) + RETURN { + "orgNameEN": org.orgDetails.en.name, + "orgNameFR": org.orgDetails.fr.name, + } + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while creating cursor for retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) + } + + let orgNames + try { + orgNames = await orgNamesCursor.next() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) + } + await sendOrgInviteCreateAccount({ user: { userName: userName, preferredLang }, - orgName: org.name, + orgNameEN: orgNames.orgNameEN, + orgNameFR: orgNames.orgNameFR, createAccountLink, }) diff --git a/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js b/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js index a0aafaffd5..f76dea6b5e 100644 --- a/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js +++ b/api/src/notify/__tests__/notify-send-org-invite-create-account.test.js @@ -1,13 +1,10 @@ -import {setupI18n} from '@lingui/core' +import { setupI18n } from '@lingui/core' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' -import {sendOrgInviteCreateAccount} from '../index' +import { sendOrgInviteCreateAccount } from '../index' -const { - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, -} = process.env +const { NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL } = process.env describe('given the sendOrgInviteCreateAccount function', () => { let i18n @@ -23,8 +20,8 @@ describe('given the sendOrgInviteCreateAccount function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -38,8 +35,8 @@ describe('given the sendOrgInviteCreateAccount function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -67,18 +64,20 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) await mockedSendOrgInviteCreateAccount({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', createAccountLink: 'TestLink.ca', }) expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, + NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { personalisation: { create_account_link: 'TestLink.ca', display_name: user.userName, - organization_name: 'Test Org', + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', }, }, ) @@ -86,9 +85,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -110,9 +107,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { createAccountLink: 'TestLink.ca', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to send org invite email. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to send org invite email. Please try again.')) } expect(consoleOutput).toEqual([ @@ -126,8 +121,8 @@ describe('given the sendOrgInviteCreateAccount function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -155,18 +150,20 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) await mockedSendOrgInviteCreateAccount({ user, - orgName: 'Test Org', + orgNameEN: 'Test Org EN', + orgNameFR: 'Test Org FR', createAccountLink: 'TestLink.ca', }) expect(notifyClient.sendEmail).toHaveBeenCalledWith( - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, + NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { personalisation: { create_account_link: 'TestLink.ca', display_name: user.userName, - organization_name: 'Test Org', + organization_name_en: 'Test Org EN', + organization_name_fr: 'Test Org FR', }, }, ) @@ -174,9 +171,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { }) describe('an error occurs while sending email', () => { it('throws an error message', async () => { - const sendEmail = jest - .fn() - .mockRejectedValue(new Error('Notification error occurred.')) + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) const notifyClient = { sendEmail, } @@ -198,9 +193,7 @@ describe('given the sendOrgInviteCreateAccount function', () => { createAccountLink: 'TestLink.ca', }) } catch (err) { - expect(err).toEqual( - new Error('Unable to send org invite email. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to send org invite email. Please try again.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/notify/notify-send-org-invite-create-account.js b/api/src/notify/notify-send-org-invite-create-account.js index ff44900fe8..9fb2e9aff3 100644 --- a/api/src/notify/notify-send-org-invite-create-account.js +++ b/api/src/notify/notify-send-org-invite-create-account.js @@ -1,34 +1,21 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' -const { - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN, - NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR, -} = process.env +const { NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL } = process.env -export const sendOrgInviteCreateAccount = ({notifyClient, i18n}) => async ({ - user, - orgName, - createAccountLink, - }) => { - let templateId = NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_EN - if (user.preferredLang === 'french') { - templateId = NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_FR +export const sendOrgInviteCreateAccount = + ({ notifyClient, i18n }) => + async ({ user, orgNameEN, orgNameFR, createAccountLink }) => { + try { + await notifyClient.sendEmail(NOTIFICATION_ORG_INVITE_CREATE_ACCOUNT_BILINGUAL, user.userName, { + personalisation: { + create_account_link: createAccountLink, + display_name: user.userName, + organization_name_en: orgNameEN, + organization_name_fr: orgNameFR, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org create account invite email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send org invite email. Please try again.`)) + } } - - try { - await notifyClient.sendEmail(templateId, user.userName, { - personalisation: { - create_account_link: createAccountLink, - display_name: user.userName, - organization_name: orgName, - }, - }) - } catch (err) { - console.error( - `Error occurred when sending org create account invite email for ${user._key}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to send org invite email. Please try again.`), - ) - } -} From 80aaf9ed8c7689331a74af9f463494279c6ba0ad Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 8 May 2023 19:26:52 +0000 Subject: [PATCH 019/113] [ci skip] gcr.io/track-compliance/api-js:master-76a06c8-1683573884 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index cc8f2efdb5..6f9f87ceab 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6915258-1683212804 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-76a06c8-1683573884 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 56b8972f69..5bd6fc22f3 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6915258-1683212804 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-76a06c8-1683573884 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From fd784bb2ddc305cbff0f61a7f29f046db6809046 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Thu, 11 May 2023 09:55:30 -0400 Subject: [PATCH 020/113] Add script to downgrade list of affiliations to the "USER" level (#4508) * Add start of script * Include example downgrade list csv * Add example users to example csv * Add logic for downgradeUsers script --- scripts/downgrade-users/README.md | 10 +++ scripts/downgrade-users/downgradeUsers.js | 102 ++++++++++++++++++++++ scripts/downgrade-users/example.csv | 3 + 3 files changed, 115 insertions(+) create mode 100644 scripts/downgrade-users/README.md create mode 100644 scripts/downgrade-users/downgradeUsers.js create mode 100644 scripts/downgrade-users/example.csv diff --git a/scripts/downgrade-users/README.md b/scripts/downgrade-users/README.md new file mode 100644 index 0000000000..d3a5560b33 --- /dev/null +++ b/scripts/downgrade-users/README.md @@ -0,0 +1,10 @@ +Use in Chromium-based browsers (Chrome, Edge, etc.): +1. Open the console (F12) +2. Copy the code from the file `downgradeUsers.js` and paste it into the console +3. Enter auth token into `AUTH_TOKEN`. The token can be obtained from a Tracker browser request. Log into Tracker, open the console (F12), go to the Network tab, refresh the page, find the latest request with the name `graphql`, go to the Headers tab, copy the value of the `Authorization` header. +4. Enter the URI for the Tracker instance into `TRACKER_URI` +5. Press Enter +6. Wait for the script to finish +7. Enjoy + +CSV file format given in the example file `example.csv`. diff --git a/scripts/downgrade-users/downgradeUsers.js b/scripts/downgrade-users/downgradeUsers.js new file mode 100644 index 0000000000..fb733649ec --- /dev/null +++ b/scripts/downgrade-users/downgradeUsers.js @@ -0,0 +1,102 @@ +const AUTH_TOKEN = ""; +const TRACKER_GRAPHQL_URI = ""; + +if (!AUTH_TOKEN) { + console.error("You must enter your auth token!"); +} + +if (!TRACKER_GRAPHQL_URI) { + console.error("You must enter the Tracker graphql URI!"); +} + +const csv2json = (str, delimiter = ",") => { + const titles = str + .slice(0, str.indexOf("\n")) + .replace(/["']/g, "") + .split(delimiter) + .map((str) => str.trim()); + const rows = str.slice(str.indexOf("\n") + 1).split("\n"); + return rows.map((row) => { + const values = row.split(delimiter); + return titles.reduce((object, curr, i) => { + object[curr] = values[i].replace(/["']/g, "").trim(); + return object; + }, {}); + }); +}; + +const changeUserRole = async ({ email, role = "USER", orgId }) => { + const res = await fetch(TRACKER_GRAPHQL_URI, { + body: JSON.stringify({ + query: `mutation { + updateUserRole(input: { + userName: "${email}", + role: ${role}, + orgId: "${orgId}", + }) { + result { + ...on UpdateUserRoleResult { + status + } + ...on AffiliationError { + code + description + } + __typename + } + } + }`, + }), + headers: { + Accept: "application/json", + Authorization: AUTH_TOKEN, + "Content-Type": "application/json", + }, + method: "POST", + }); + + return await res.json(); +}; + +const [fileHandle] = await window.showOpenFilePicker(); +const file = await fileHandle.getFile(); +const content = (await file.text()).trim(); +const downgradeUserList = csv2json(content, ","); + +for await (const [key, val] of downgradeUserList.entries()) { + try { + const updateRoleRes = await changeUserRole({ email: val.userName, orgId: val.organizationId, role: "USER" }); + if (updateRoleRes.data.updateUserRole.result["__typename"] === "AffiliationError") { + console.error( + `Error while updating "${val.email}" permission for organization "${val.organizationId}" to "USER": `, + val + ); + downgradeUserList[key].success = false; + downgradeUserList[key].error = updateRoleRes.data.updateUserRole.result.description; + continue; + } + } catch (e) { + console.error( + `Error while updating "${val.email}" permission for organization "${val.organizationId}" to "USER": `, + val + ); + downgradeUserList[key].success = false; + downgradeUserList[key].error = e; + } + downgradeUserList[key].success = true; + console.log( + `Successfully changed "${val.email}" permission for organization "${val.organizationId}" to "USER": `, + val + ); +} + +for (const val of downgradeUserList) { + if (val.error) { + console.error( + `Error while updating "${val.email}" permission for organization "${val.organizationId}" to "USER": `, + val + ); + } +} + +console.table(downgradeUserList.filter((val) => val.success === false)); diff --git a/scripts/downgrade-users/example.csv b/scripts/downgrade-users/example.csv new file mode 100644 index 0000000000..f1ec6cf5a1 --- /dev/null +++ b/scripts/downgrade-users/example.csv @@ -0,0 +1,3 @@ +userId,displayName,userName,totalCount,affiliationId,permission,organizationId,acronym,name,slug +exampleIdqd2a=,example name ,example1@example.com,2,e21e12eaw,ADMIN,2e21e12e,EO,Example Org,example-org +exampleIdqd2a=,example name ,example1@example.com,2,e21e12eaw,ADMIN,anrk30a=,AEO,Another Example,another-example From b5a7d6ec45bec502290df3f40c056065cf12ebc3 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Thu, 11 May 2023 09:55:43 -0400 Subject: [PATCH 021/113] Add script for mass inviting users (#4506) * Add script for mass inviting users * Remove static uri for requests --- scripts/mass-invite-users/README.md | 10 ++ scripts/mass-invite-users/example.csv | 3 + scripts/mass-invite-users/massInviteUsers.js | 116 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 scripts/mass-invite-users/README.md create mode 100644 scripts/mass-invite-users/example.csv create mode 100644 scripts/mass-invite-users/massInviteUsers.js diff --git a/scripts/mass-invite-users/README.md b/scripts/mass-invite-users/README.md new file mode 100644 index 0000000000..75ed428ffa --- /dev/null +++ b/scripts/mass-invite-users/README.md @@ -0,0 +1,10 @@ +Use in Chromium-based browsers (Chrome, Edge, etc.): +1. Open the console (F12) +2. Copy the code from the file `mass-invite-users.js` and paste it into the console +3. Enter auth token into `AUTH_TOKEN`. The token can be obtained from a Tracker browser request. Log into Tracker, open the console (F12), go to the Network tab, refresh the page, find the latest request with the name `graphql`, go to the Headers tab, copy the value of the `Authorization` header. +4. Enter the URI for the Tracker instance into `TRACKER_URI` +5. Press Enter +6. Wait for the script to finish +7. Enjoy + +CSV file format given in the example file `example.csv`. diff --git a/scripts/mass-invite-users/example.csv b/scripts/mass-invite-users/example.csv new file mode 100644 index 0000000000..f231fd63d1 --- /dev/null +++ b/scripts/mass-invite-users/example.csv @@ -0,0 +1,3 @@ +email,role,orgSlug +email@example.com,USER,example-slug +adminEmail@example.com,ADMIN,another-example-slug diff --git a/scripts/mass-invite-users/massInviteUsers.js b/scripts/mass-invite-users/massInviteUsers.js new file mode 100644 index 0000000000..f770aace68 --- /dev/null +++ b/scripts/mass-invite-users/massInviteUsers.js @@ -0,0 +1,116 @@ +const AUTH_TOKEN = ""; +const TRACKER_GRAPHQL_URI = ""; + +if (!AUTH_TOKEN) { + console.error("You must enter your auth token!"); +} + +if (!TRACKER_GRAPHQL_URI) { + console.error("You must enter the Tracker graphql URI!"); +} + +const csv2json = (str, delimiter = ",") => { + const titles = str + .slice(0, str.indexOf("\n")) + .replace(/["']/g, "") + .split(delimiter) + .map((str) => str.trim()); + const rows = str.slice(str.indexOf("\n") + 1).split("\n"); + return rows.map((row) => { + const values = row.split(delimiter); + return titles.reduce((object, curr, i) => { + object[curr] = values[i].replace(/["']/g, "").trim(); + return object; + }, {}); + }); +}; + +const getOrg = async (orgSlug) => { + const res = await fetch(TRACKER_GRAPHQL_URI, { + body: JSON.stringify({ + query: `query { + findOrganizationBySlug(orgSlug: "${orgSlug}") { + id + name + slug + } + }`, + }), + headers: { + Accept: "application/json", + Authorization: AUTH_TOKEN, + "Content-Type": "application/json", + }, + method: "POST", + }); + return (await res.json()).data; +}; + +const inviteUser = async ({ email, role, orgId }) => { + const res = await fetch(TRACKER_GRAPHQL_URI, { + body: JSON.stringify({ + query: `mutation { + inviteUserToOrg(input: { + userName: "${email}", + requestedRole: ${role}, + orgId: "${orgId}", + preferredLang: ENGLISH + }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + __typename + } + } + }`, + }), + headers: { + Accept: "application/json", + Authorization: AUTH_TOKEN, + "Content-Type": "application/json", + }, + method: "POST", + }); + + const json = await res.json(); + + return json; +}; + +const [fileHandle] = await window.showOpenFilePicker(); +const file = await fileHandle.getFile(); +const content = (await file.text()).trim(); +const inviteList = csv2json(content, ","); + +for await (const [key, inv] of inviteList.entries()) { + try { + const data = await getOrg(inv.orgSlug); + inviteList[key].orgId = data.findOrganizationBySlug.id; + const inviteRes = await inviteUser({ email: inv.email, orgId: inv.orgId, role: inv.role }); + if (inviteRes.data.inviteUserToOrg.result["__typename"] === "AffiliationError") { + console.error(`Error while inviting ${inv.email} to ${inv.orgSlug}: `, inv); + inviteList[key].success = false; + inviteList[key].error = inviteRes.data.inviteUserToOrg.result.description; + continue; + } + } catch (e) { + console.error(`Error while inviting ${inv.email} to ${inv.orgSlug}: `, inv); + inviteList[key].success = false; + inviteList[key].error = e; + } + inviteList[key].success = true; + console.log(`Successfully invited ${inv.email} to ${inv.orgSlug}: `, inv); +} + +for (const inv of inviteList) { + if (inv.error) { + console.error(`Error while inviting ${inv.email} to ${inv.orgSlug}: `, inv); + } +} + +console.table(inviteList.filter((inv) => inv.success === false)); From 81738303f43b33a926f5bda30e00788f944e660b Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Thu, 11 May 2023 09:55:55 -0400 Subject: [PATCH 022/113] Use the invited user's preferred language in email (#4516) * Use the invited user's preferred language in email * Fix failing tests * Remove unused "preferredLang" from org invite mutation --- .../__tests__/invite-user-to-org.test.js | 49 +--- .../mutations/invite-user-to-org.js | 64 +++-- frontend/mocking/faked_schema.js | 2 - frontend/src/admin/UserListModal.js | 249 ++++++++---------- .../src/admin/__tests__/UserListModal.test.js | 237 ++++------------- frontend/src/graphql/mutations.js | 85 +----- 6 files changed, 210 insertions(+), 476 deletions(-) diff --git a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js index c2f43964fb..db734a9620 100644 --- a/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js +++ b/api/src/affiliation/mutations/__tests__/invite-user-to-org.test.js @@ -143,7 +143,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -239,7 +238,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -335,7 +333,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -424,7 +421,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -499,7 +495,6 @@ describe('invite user to org', () => { expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', - preferredLang: 'english', }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', @@ -520,7 +515,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -596,7 +590,6 @@ describe('invite user to org', () => { expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', - preferredLang: 'english', }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', @@ -617,7 +610,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -693,7 +685,6 @@ describe('invite user to org', () => { expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', - preferredLang: 'english', }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', @@ -733,7 +724,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -829,7 +819,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -919,7 +908,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -995,7 +983,6 @@ describe('invite user to org', () => { expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', - preferredLang: 'english', }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', @@ -1016,7 +1003,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: ENGLISH } ) { result { @@ -1092,7 +1078,6 @@ describe('invite user to org', () => { expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ user: { userName: 'test@email.gc.ca', - preferredLang: 'english', }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', @@ -1180,7 +1165,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1277,7 +1261,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1374,7 +1357,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1465,7 +1447,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1539,7 +1520,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, + user: { userName: 'test@email.gc.ca' }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, @@ -1559,7 +1540,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1633,7 +1613,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, + user: { userName: 'test@email.gc.ca' }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, @@ -1653,7 +1633,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1727,7 +1706,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, + user: { userName: 'test@email.gc.ca' }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, @@ -1766,7 +1745,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1863,7 +1841,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -1954,7 +1931,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: ADMIN orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -2028,7 +2004,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, + user: { userName: 'test@email.gc.ca' }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, @@ -2048,7 +2024,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -2122,7 +2097,7 @@ describe('invite user to org', () => { `User: ${user._key} successfully invited user: test@email.gc.ca to the service, and org: secretariat-conseil-tresor.`, ]) expect(sendOrgInviteCreateAccount).toHaveBeenCalledWith({ - user: { userName: 'test@email.gc.ca', preferredLang: 'french' }, + user: { userName: 'test@email.gc.ca' }, orgNameEN: 'Treasury Board of Canada Secretariat', orgNameFR: 'Secrétariat du Conseil Trésor du Canada', createAccountLink, @@ -2231,7 +2206,6 @@ describe('invite user to org', () => { userName: "${user.userName}" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -2303,7 +2277,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH } ) { result { @@ -2383,7 +2356,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -2464,7 +2436,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -2545,7 +2516,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -2627,7 +2597,6 @@ describe('invite user to org', () => { userName: "${userToInvite.userName}" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -2703,7 +2672,6 @@ describe('invite user to org', () => { userName: "${userToInvite.userName}" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -2800,7 +2768,6 @@ describe('invite user to org', () => { userName: "test.account@istio.actually.exists" requestedRole: USER orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH } ) { result { @@ -2880,7 +2847,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 1)}" - preferredLang: FRENCH } ) { result { @@ -2960,7 +2926,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -3040,7 +3005,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: USER orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -3120,7 +3084,6 @@ describe('invite user to org', () => { userName: "test@email.gc.ca" requestedRole: SUPER_ADMIN orgId: "${toGlobalId('organizations', 123)}" - preferredLang: FRENCH } ) { result { @@ -3201,7 +3164,6 @@ describe('invite user to org', () => { userName: "${userToInvite.userName}" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { @@ -3277,7 +3239,6 @@ describe('invite user to org', () => { userName: "${userToInvite.userName}" requestedRole: USER orgId: "${toGlobalId('organizations', org._key)}" - preferredLang: FRENCH } ) { result { diff --git a/api/src/affiliation/mutations/invite-user-to-org.js b/api/src/affiliation/mutations/invite-user-to-org.js index f08c0a1a2b..525d37f7a0 100644 --- a/api/src/affiliation/mutations/invite-user-to-org.js +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -4,7 +4,7 @@ import { GraphQLEmailAddress } from 'graphql-scalars' import { t } from '@lingui/macro' import { inviteUserToOrgUnion } from '../unions' -import { LanguageEnums, RoleEnums } from '../../enums' +import { RoleEnums } from '../../enums' import { logActivity } from '../../audit-logs/mutations/log-activity' export const inviteUserToOrg = new mutationWithClientMutationId({ @@ -25,10 +25,6 @@ able to sign-up and be assigned to that organization in one mutation.`, type: GraphQLNonNull(GraphQLID), description: 'The organization you wish to invite the user to.', }, - preferredLang: { - type: GraphQLNonNull(LanguageEnums), - description: 'The language in which the email will be sent in.', - }, }), outputFields: () => ({ result: { @@ -56,7 +52,6 @@ able to sign-up and be assigned to that organization in one mutation.`, const userName = cleanseInput(args.userName).toLowerCase() const requestedRole = cleanseInput(args.requestedRole) const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) - const preferredLang = cleanseInput(args.preferredLang) // Get requesting user const user = await userRequired() @@ -106,6 +101,33 @@ able to sign-up and be assigned to that organization in one mutation.`, } } + // Get org names to use in email + let orgNamesCursor + try { + orgNamesCursor = await query` + LET org = DOCUMENT(organizations, ${org._id}) + RETURN { + "orgNameEN": org.orgDetails.en.name, + "orgNameFR": org.orgDetails.fr.name, + } + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while creating cursor for retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) + } + + let orgNames + try { + orgNames = await orgNamesCursor.next() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) + } + // Check to see if requested user exists const requestedUser = await loadUserByUserName.load(userName) @@ -117,34 +139,8 @@ able to sign-up and be assigned to that organization in one mutation.`, }) const createAccountLink = `https://${request.get('host')}/create-user/${token}` - let orgNamesCursor - try { - orgNamesCursor = await query` - LET org = DOCUMENT(organizations, ${org._id}) - RETURN { - "orgNameEN": org.orgDetails.en.name, - "orgNameFR": org.orgDetails.fr.name, - } - ` - } catch (err) { - console.error( - `Database error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while creating cursor for retrieving organization names. error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) - } - - let orgNames - try { - orgNames = await orgNamesCursor.next() - } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} attempted to invite user: ${userName} to org: ${org._key}. Error while retrieving organization names. error: ${err}`, - ) - throw new Error(i18n._(t`Unable to invite user to organization. Please try again.`)) - } - await sendOrgInviteCreateAccount({ - user: { userName: userName, preferredLang }, + user: { userName: userName }, orgNameEN: orgNames.orgNameEN, orgNameFR: orgNames.orgNameFR, createAccountLink, @@ -242,7 +238,7 @@ able to sign-up and be assigned to that organization in one mutation.`, await sendOrgInviteEmail({ user: requestedUser, - orgName: org.name, + orgName: requestedUser.preferredLang === 'english' ? orgNames.orgNameEN : orgNames.orgNameFR, }) // Commit affiliation diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 279560e685..872a472583 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -2747,8 +2747,6 @@ export const getTypeNames = () => gql` # The organization you wish to invite the user to. orgId: ID! - # The language in which the email will be sent in. - preferredLang: LanguageEnums! clientMutationId: String } diff --git a/frontend/src/admin/UserListModal.js b/frontend/src/admin/UserListModal.js index 972505509b..af2c5ad963 100644 --- a/frontend/src/admin/UserListModal.js +++ b/frontend/src/admin/UserListModal.js @@ -20,11 +20,7 @@ import { useMutation } from '@apollo/client' import { bool, func, string } from 'prop-types' import { EmailField } from '../components/fields/EmailField' -import { - UPDATE_USER_ROLE, - INVITE_USER_TO_ORG, - REMOVE_USER_FROM_ORG, -} from '../graphql/mutations' +import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG, REMOVE_USER_FROM_ORG } from '../graphql/mutations' import { createValidationSchema } from '../utilities/fieldRequirements' export function UserListModal({ @@ -42,152 +38,140 @@ export function UserListModal({ const toast = useToast() const initialFocusRef = useRef() - const [addUser, { loading: _addUserLoading }] = useMutation( - INVITE_USER_TO_ORG, - { - refetchQueries: ['PaginatedOrgAffiliations', 'FindAuditLogs'], - awaitRefetchQueries: true, + const [addUser, { loading: _addUserLoading }] = useMutation(INVITE_USER_TO_ORG, { + refetchQueries: ['PaginatedOrgAffiliations', 'FindAuditLogs'], + awaitRefetchQueries: true, - onError(error) { + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ inviteUserToOrg }) { + if (inviteUserToOrg.result.__typename === 'InviteUserToOrgResult') { + toast({ + title: t`User invited`, + description: t`Email invitation sent`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + onClose() + } else if (inviteUserToOrg.result.__typename === 'AffiliationError') { + toast({ + title: t`Unable to invite user.`, + description: inviteUserToOrg.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { toast({ - title: t`An error occurred.`, - description: error.message, + title: t`Incorrect send method received.`, + description: t`Incorrect inviteUserToOrg.result typename.`, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ inviteUserToOrg }) { - if (inviteUserToOrg.result.__typename === 'InviteUserToOrgResult') { - toast({ - title: t`User invited`, - description: t`Email invitation sent`, - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - onClose() - } else if (inviteUserToOrg.result.__typename === 'AffiliationError') { - toast({ - title: t`Unable to invite user.`, - description: inviteUserToOrg.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: t`Incorrect send method received.`, - description: t`Incorrect inviteUserToOrg.result typename.`, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } - }, + } }, - ) + }) - const [updateUserRole, { loading: _updateLoading, error: _updateError }] = - useMutation(UPDATE_USER_ROLE, { - refetchQueries: ['FindMyUsers', 'FindAuditLogs'], - awaitRefetchQueries: true, + const [updateUserRole, { loading: _updateLoading, error: _updateError }] = useMutation(UPDATE_USER_ROLE, { + refetchQueries: ['FindMyUsers', 'FindAuditLogs'], + awaitRefetchQueries: true, - onError(updateError) { + onError(updateError) { + toast({ + title: updateError.message, + description: t`Unable to change user role, please try again.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ updateUserRole }) { + if (updateUserRole.result.__typename === 'UpdateUserRoleResult') { toast({ - title: updateError.message, - description: t`Unable to change user role, please try again.`, + title: t`Role updated`, + description: t`The user's role has been successfully updated`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + onClose() + } else if (updateUserRole.result.__typename === 'AffiliationError') { + toast({ + title: t`Unable to update user role.`, + description: updateUserRole.result.description, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ updateUserRole }) { - if (updateUserRole.result.__typename === 'UpdateUserRoleResult') { - toast({ - title: t`Role updated`, - description: t`The user's role has been successfully updated`, - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - onClose() - } else if (updateUserRole.result.__typename === 'AffiliationError') { - toast({ - title: t`Unable to update user role.`, - description: updateUserRole.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else { - toast({ - title: t`Incorrect send method received.`, - description: t`Incorrect updateUserRole.result typename.`, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } - }, - }) + } else { + toast({ + title: t`Incorrect send method received.`, + description: t`Incorrect updateUserRole.result typename.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } + }, + }) - const [removeUser, { loading: _removeUserLoading }] = useMutation( - REMOVE_USER_FROM_ORG, - { - refetchQueries: ['FindMyUsers', 'FindAuditLogs'], - awaitRefetchQueries: true, + const [removeUser, { loading: _removeUserLoading }] = useMutation(REMOVE_USER_FROM_ORG, { + refetchQueries: ['FindMyUsers', 'FindAuditLogs'], + awaitRefetchQueries: true, - onError(error) { + onError(error) { + toast({ + title: t`An error occurred.`, + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ removeUserFromOrg }) { + if (removeUserFromOrg.result.__typename === 'RemoveUserFromOrgResult') { + onClose() + toast({ + title: t`User removed.`, + description: t`Successfully removed user ${removeUserFromOrg.result.user.userName}.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else if (removeUserFromOrg.result.__typename === 'AffiliationError') { toast({ - title: t`An error occurred.`, - description: error.message, + title: t`Unable to remove user.`, + description: removeUserFromOrg.result.description, status: 'error', duration: 9000, isClosable: true, position: 'top-left', }) - }, - onCompleted({ removeUserFromOrg }) { - if (removeUserFromOrg.result.__typename === 'RemoveUserFromOrgResult') { - onClose() - toast({ - title: t`User removed.`, - description: t`Successfully removed user ${removeUserFromOrg.result.user.userName}.`, - status: 'success', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } else if (removeUserFromOrg.result.__typename === 'AffiliationError') { - toast({ - title: t`Unable to remove user.`, - description: removeUserFromOrg.result.description, - status: 'error', - duration: 9000, - isClosable: true, - position: 'top-left', - }) - } - }, + } }, - ) + }) return ( - + {(editingUserRole === 'USER' || - (permission === 'SUPER_ADMIN' && - editingUserRole === 'ADMIN')) && ( + (permission === 'SUPER_ADMIN' && editingUserRole === 'ADMIN')) && ( )} - {(editingUserRole === 'USER' || - editingUserRole === 'ADMIN') && ( + {(editingUserRole === 'USER' || editingUserRole === 'ADMIN') && ( )} {(editingUserRole === 'SUPER_ADMIN' || - (permission === 'SUPER_ADMIN' && - ['super-admin', 'sa'].includes(orgSlug))) && ( + (permission === 'SUPER_ADMIN' && ['super-admin', 'sa'].includes(orgSlug))) && ( )} @@ -290,12 +270,7 @@ export function UserListModal({ - diff --git a/frontend/src/admin/__tests__/UserListModal.test.js b/frontend/src/admin/__tests__/UserListModal.test.js index e876adb17d..3301a429bb 100644 --- a/frontend/src/admin/__tests__/UserListModal.test.js +++ b/frontend/src/admin/__tests__/UserListModal.test.js @@ -10,11 +10,7 @@ import { en } from 'make-plural/plurals' import { UserVarProvider } from '../../utilities/userState' import { createCache } from '../../client' -import { - UPDATE_USER_ROLE, - INVITE_USER_TO_ORG, - REMOVE_USER_FROM_ORG, -} from '../../graphql/mutations' +import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG, REMOVE_USER_FROM_ORG } from '../../graphql/mutations' import { UserListModal } from '../UserListModal' import userEvent from '@testing-library/user-event' import canada from '../../theme/canada' @@ -60,17 +56,11 @@ describe('', () => { it('can be opened and closed', async () => { const { getByRole, queryByText } = render( - + - + @@ -93,9 +83,7 @@ describe('', () => { // modal closed userEvent.click(closeModalButton) - await waitFor(() => - expect(queryByText(/test-username/)).not.toBeInTheDocument(), - ) + await waitFor(() => expect(queryByText(/test-username/)).not.toBeInTheDocument()) }) describe('admin is updating a user', () => { @@ -129,11 +117,7 @@ describe('', () => { - + @@ -153,12 +137,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -169,9 +149,7 @@ describe('', () => { // check for "error" toast await waitFor(() => { - expect( - getAllByText(/Unable to change user role, please try again./)[0], - ) + expect(getAllByText(/Unable to change user role, please try again./)[0]) }) }) it('a client-side error occurs', async () => { @@ -211,11 +189,7 @@ describe('', () => { - + @@ -235,12 +209,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -251,9 +221,7 @@ describe('', () => { // check for "error" toast await waitFor(() => { - expect( - getAllByText(/Unable to update user role./)[0], - ).toBeInTheDocument() + expect(getAllByText(/Unable to update user role./)[0]).toBeInTheDocument() }) }) it('a type error occurs', async () => { @@ -293,11 +261,7 @@ describe('', () => { - + @@ -317,12 +281,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -333,9 +293,7 @@ describe('', () => { // check for "error" toast await waitFor(() => { - expect( - getAllByText(/Incorrect send method received./)[0], - ).toBeInTheDocument() + expect(getAllByText(/Incorrect send method received./)[0]).toBeInTheDocument() }) }) }) @@ -378,11 +336,7 @@ describe('', () => { - + @@ -402,12 +356,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -418,9 +368,7 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect( - getAllByText(/The user's role has been successfully updated/)[0], - ).toBeInTheDocument() + expect(getAllByText(/The user's role has been successfully updated/)[0]).toBeInTheDocument() }) }) it('admin can not change user role to "SUPER_ADMIN"', async () => { @@ -436,11 +384,7 @@ describe('', () => { - + @@ -460,12 +404,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) }) }) @@ -483,11 +423,7 @@ describe('', () => { - + @@ -507,9 +443,7 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(1) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/ADMIN/) expect(roleChangeSelect).not.toHaveTextContent(/USER/) }) }) @@ -553,11 +487,7 @@ describe('', () => { - + @@ -577,12 +507,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) // select new role and update userEvent.selectOptions(roleChangeSelect, 'ADMIN') @@ -594,9 +520,7 @@ describe('', () => { // check for "success" toast await waitFor(() => { - expect( - getAllByText(/The user's role has been successfully updated/)[0], - ).toBeInTheDocument() + expect(getAllByText(/The user's role has been successfully updated/)[0]).toBeInTheDocument() }) }) it('admin can not change user role to "SUPER_ADMIN"', async () => { @@ -612,11 +536,7 @@ describe('', () => { - + @@ -636,12 +556,8 @@ describe('', () => { name: /Role:/, }) expect(roleChangeSelect.options.length).toEqual(2) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) expect(roleChangeSelect).not.toHaveTextContent(/SUPER_ADMIN/) }) }) @@ -678,10 +594,7 @@ describe('', () => { - + @@ -742,10 +655,7 @@ describe('', () => { - + @@ -784,7 +694,6 @@ describe('', () => { orgId: orgId, requestedRole: 'USER', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -807,11 +716,7 @@ describe('', () => { - + @@ -835,12 +740,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) const confirmUserInviteButton = getByRole('button', { name: /Confirm/i, @@ -861,7 +762,6 @@ describe('', () => { orgId: orgId, requestedRole: 'USER', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -890,11 +790,7 @@ describe('', () => { - + @@ -918,12 +814,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) const confirmUserInviteButton = getByRole('button', { name: /Confirm/i, @@ -944,7 +836,6 @@ describe('', () => { orgId: orgId, requestedRole: 'USER', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -973,11 +864,7 @@ describe('', () => { - + @@ -1001,12 +888,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) const confirmUserInviteButton = getByRole('button', { name: /Confirm/i, @@ -1015,9 +898,7 @@ describe('', () => { // check for "success" toast and modal to close await waitFor(() => { - expect( - getAllByText(/Incorrect send method received./)[0], - ).toBeVisible() + expect(getAllByText(/Incorrect send method received./)[0]).toBeVisible() }) }) }) @@ -1032,7 +913,6 @@ describe('', () => { orgId: orgId, requestedRole: 'USER', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -1061,11 +941,7 @@ describe('', () => { - + @@ -1089,12 +965,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) const confirmUserInviteButton = getByRole('button', { name: /Confirm/i, @@ -1124,7 +996,6 @@ describe('', () => { orgId: orgId, requestedRole: 'ADMIN', userName: editingUserName, - preferredLang: 'ENGLISH', }, }, result: { @@ -1153,11 +1024,7 @@ describe('', () => { - + @@ -1178,12 +1045,8 @@ describe('', () => { }) expect(newUserRoleSelect.options.length).toEqual(2) - expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent( - /USER/, - ) - expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent( - /ADMIN/, - ) + expect(Object.values(newUserRoleSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(newUserRoleSelect.options)[1]).toHaveTextContent(/ADMIN/) userEvent.selectOptions(newUserRoleSelect, 'ADMIN') const confirmUserInviteButton = getByRole('button', { diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 2acb76ccf6..ba677ea24f 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -35,18 +35,8 @@ export const SIGN_UP = gql` ` export const SIGN_IN = gql` - mutation signIn( - $userName: EmailAddress! - $password: String! - $rememberMe: Boolean - ) { - signIn( - input: { - userName: $userName - password: $password - rememberMe: $rememberMe - } - ) { + mutation signIn($userName: EmailAddress!, $password: String!, $rememberMe: Boolean) { + signIn(input: { userName: $userName, password: $password, rememberMe: $rememberMe }) { result { ... on TFASignInResult { authenticateToken @@ -66,16 +56,8 @@ export const SIGN_IN = gql` ` export const AUTHENTICATE = gql` - mutation authenticate( - $authenticationCode: Int! - $authenticateToken: String! - ) { - authenticate( - input: { - authenticationCode: $authenticationCode - authenticateToken: $authenticateToken - } - ) { + mutation authenticate($authenticationCode: Int!, $authenticateToken: String!) { + authenticate(input: { authenticationCode: $authenticationCode, authenticateToken: $authenticateToken }) { result { ... on AuthResult { ...RequiredAuthResultFields @@ -91,18 +73,8 @@ export const AUTHENTICATE = gql` ` export const RESET_PASSWORD = gql` - mutation ResetPassword( - $password: String! - $confirmPassword: String! - $resetToken: String! - ) { - resetPassword( - input: { - password: $password - confirmPassword: $confirmPassword - resetToken: $resetToken - } - ) { + mutation ResetPassword($password: String!, $confirmPassword: String!, $resetToken: String!) { + resetPassword(input: { password: $password, confirmPassword: $confirmPassword, resetToken: $resetToken }) { result { ... on ResetPasswordError { code @@ -125,11 +97,7 @@ export const SEND_PASSWORD_RESET_LINK = gql` ` export const UPDATE_USER_ROLE = gql` - mutation UpdateUserRole( - $userName: EmailAddress! - $orgId: ID! - $role: RoleEnums! - ) { + mutation UpdateUserRole($userName: EmailAddress!, $orgId: ID!, $role: RoleEnums!) { updateUserRole(input: { userName: $userName, orgId: $orgId, role: $role }) { result { ... on UpdateUserRoleResult { @@ -183,11 +151,7 @@ export const UPDATE_USER_PROFILE = gql` ` export const UPDATE_USER_PASSWORD = gql` - mutation UpdateUserPassword( - $currentPassword: String! - $updatedPassword: String! - $updatedPasswordConfirm: String! - ) { + mutation UpdateUserPassword($currentPassword: String!, $updatedPassword: String!, $updatedPasswordConfirm: String!) { updateUserPassword( input: { currentPassword: $currentPassword @@ -241,14 +205,8 @@ export const CREATE_DOMAIN = gql` ` export const REMOVE_DOMAIN = gql` - mutation RemoveDomain( - $domainId: ID! - $orgId: ID! - $reason: DomainRemovalReasonEnum! - ) { - removeDomain( - input: { domainId: $domainId, orgId: $orgId, reason: $reason } - ) { + mutation RemoveDomain($domainId: ID!, $orgId: ID!, $reason: DomainRemovalReasonEnum!) { + removeDomain(input: { domainId: $domainId, orgId: $orgId, reason: $reason }) { result { ... on DomainResult { status @@ -332,20 +290,8 @@ export const UPDATE_DOMAIN = gql` ` export const INVITE_USER_TO_ORG = gql` - mutation InviteUserToOrg( - $userName: EmailAddress! - $requestedRole: RoleEnums! - $orgId: ID! - $preferredLang: LanguageEnums! - ) { - inviteUserToOrg( - input: { - userName: $userName - requestedRole: $requestedRole - orgId: $orgId - preferredLang: $preferredLang - } - ) { + mutation InviteUserToOrg($userName: EmailAddress!, $requestedRole: RoleEnums!, $orgId: ID!) { + inviteUserToOrg(input: { userName: $userName, requestedRole: $requestedRole, orgId: $orgId }) { result { ... on InviteUserToOrgResult { status @@ -671,12 +617,7 @@ export const REMOVE_ORGANIZATIONS_DOMAINS = gql` $audit: Boolean ) { removeOrganizationsDomains( - input: { - orgId: $orgId - domains: $domains - archiveDomains: $archiveDomains - audit: $audit - } + input: { orgId: $orgId, domains: $domains, archiveDomains: $archiveDomains, audit: $audit } ) { result { ... on DomainBulkResult { From e81baa92a2bceef93903b8f5daf657c320114470 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 11 May 2023 14:00:24 +0000 Subject: [PATCH 023/113] [ci skip] gcr.io/track-compliance/api-js:master-8173830-1683813552 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 6f9f87ceab..ac4b5e7220 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-76a06c8-1683573884 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-8173830-1683813552 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 5bd6fc22f3..dad9bdc6d1 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-76a06c8-1683573884 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-8173830-1683813552 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 1ab8fc8d0cd14c4ebc2d2fa9459d93e8a949ef37 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 11 May 2023 14:03:34 +0000 Subject: [PATCH 024/113] [ci skip] gcr.io/track-compliance/frontend:master-8173830-1683813647 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 1788a1d015..a60ef8abc3 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-7264f48-1683290184 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-8173830-1683813647 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 52b1d8b7cb..8d04b44e8f 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-7264f48-1683290184 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-8173830-1683813647 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 6ff56deb438cabd6b3c18be90991f95e9e662396 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Mon, 15 May 2023 09:26:04 -0300 Subject: [PATCH 025/113] Bring B features over to A stream (#4432) * move domain tags and hiding/archiving to A stream * allow hidden domains to be counted in summaries * allow org admins to see hidden domains * don't show domain filters on myTracker page * move myTracker to the A stream * move activity logs to A stream * fix AdminDomains test * add dismissable alert banner * put ABTestVariant inside Wrapper file * put hidden and archive features back into B stream * rehide hidden domains in landing page summaries * comment out alert banner for domain consolidation * cleanup * cert status info box --- ...-domain-connections-by-organizations-id.js | 2 +- frontend/src/admin/AdminDomainCard.js | 65 +- frontend/src/admin/AdminDomainModal.js | 154 ++-- frontend/src/admin/AdminPanel.js | 22 +- .../src/admin/__tests__/AdminDomains.test.js | 2 +- frontend/src/app/ABTestVariant.js | 10 - frontend/src/app/ABTestWrapper.js | 15 +- frontend/src/app/App.js | 27 +- frontend/src/app/TopBanner.js | 24 +- frontend/src/domains/DomainCard.js | 81 +-- frontend/src/locales/en.po | 666 +++++++++--------- frontend/src/locales/fr.po | 666 +++++++++--------- .../OrganizationDomains.js | 347 ++++----- 13 files changed, 1002 insertions(+), 1079 deletions(-) delete mode 100644 frontend/src/app/ABTestVariant.js diff --git a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js index 2e4d83311e..8c49af94c8 100644 --- a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js @@ -415,7 +415,7 @@ export const loadDomainConnectionsByOrgId = showArchivedDomains = aql`` } let showHiddenDomains = aql`FILTER e.hidden != true` - if (permission === 'super_admin') { + if (['super_admin'].includes(permission)) { showHiddenDomains = aql`` } diff --git a/frontend/src/admin/AdminDomainCard.js b/frontend/src/admin/AdminDomainCard.js index 7fbd63533c..c472c353b7 100644 --- a/frontend/src/admin/AdminDomainCard.js +++ b/frontend/src/admin/AdminDomainCard.js @@ -1,29 +1,16 @@ import React from 'react' import { Trans } from '@lingui/macro' import { array, bool, string } from 'prop-types' -import { - Flex, - Grid, - Link, - ListItem, - Stack, - Tag, - TagLabel, - Text, -} from '@chakra-ui/react' +import { Flex, Grid, Link, ListItem, Stack, Tag, TagLabel, Text } from '@chakra-ui/react' import { ExternalLinkIcon } from '@chakra-ui/icons' +import { ABTestVariant, ABTestingWrapper } from '../app/ABTestWrapper' import { sanitizeUrl } from '../utilities/sanitizeUrl' -import { ABTestingWrapper } from '../app/ABTestWrapper' -import { ABTestVariant } from '../app/ABTestVariant' export function AdminDomainCard({ url, tags, isHidden, isArchived, ...rest }) { return ( - + Domain: @@ -41,49 +28,33 @@ export function AdminDomainCard({ url, tags, isHidden, isArchived, ...rest }) { - - - - {tags?.map((tag, idx) => { - return ( - - {tag} - - ) - })} + + {tags?.map((tag, idx) => { + return ( + + {tag} + + ) + })} + + {isHidden && ( - + Hidden )} {isArchived && ( - + Archived )} - - - + + + ) diff --git a/frontend/src/admin/AdminDomainModal.js b/frontend/src/admin/AdminDomainModal.js index 47ed65deb0..ed16e8d7a9 100644 --- a/frontend/src/admin/AdminDomainModal.js +++ b/frontend/src/admin/AdminDomainModal.js @@ -30,28 +30,16 @@ import { Tooltip, useToast, } from '@chakra-ui/react' -import { - AddIcon, - MinusIcon, - QuestionOutlineIcon, - SmallAddIcon, -} from '@chakra-ui/icons' +import { AddIcon, MinusIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons' import { array, bool, func, number, object, string } from 'prop-types' import { Field, FieldArray, Formik } from 'formik' import { useMutation } from '@apollo/client' import { DomainField } from '../components/fields/DomainField' import { CREATE_DOMAIN, UPDATE_DOMAIN } from '../graphql/mutations' -import { ABTestingWrapper } from '../app/ABTestWrapper' -import { ABTestVariant } from '../app/ABTestVariant' +import { ABTestVariant, ABTestingWrapper } from '../app/ABTestWrapper' -export function AdminDomainModal({ - isOpen, - onClose, - validationSchema, - orgId, - ...props -}) { +export function AdminDomainModal({ isOpen, onClose, validationSchema, orgId, ...props }) { const { editingDomainId, editingDomainUrl, @@ -84,9 +72,7 @@ export function AdminDomainModal({ if (createDomain.result.__typename === 'Domain') { toast({ title: i18n._(t`Domain added`), - description: i18n._( - t`${createDomain.result.domain} was added to ${orgSlug}`, - ), + description: i18n._(t`${createDomain.result.domain} was added to ${orgSlug}`), status: 'success', duration: 9000, isClosable: true, @@ -178,9 +164,7 @@ export function AdminDomainModal({ const stringValues = values?.map((label) => { return label[i18n.locale] }) - const difference = tagOptions.filter( - (label) => !stringValues?.includes(label[i18n.locale]), - ) + const difference = tagOptions.filter((label) => !stringValues?.includes(label[i18n.locale])) return difference?.map((label, idx) => { return ( + - {({ - handleSubmit, - handleChange, - isSubmitting, - values, - errors, - touched, - }) => ( + {({ handleSubmit, handleChange, isSubmitting, values, errors, touched }) => (
- {mutation === 'update' ? ( - Edit Domain Details - ) : ( - Add Domain Details - )} + {mutation === 'update' ? Edit Domain Details : Add Domain Details} - + - + } @@ -312,11 +271,7 @@ export function AdminDomainModal({ onClick={() => arrayHelpers.remove(index)} aria-label="remove-dkim-selector" /> - + {({ field }) => ( - {errors && - errors.selectors && - errors.selectors[index]} + {errors && errors.selectors && errors.selectors[index]} @@ -348,41 +301,36 @@ export function AdminDomainModal({ )} /> + ( + + Tags: + + {values.tags?.map((label, idx) => { + return ( + + {label[i18n.locale]} + arrayHelpers.remove(idx)} + aria-label={`remove-tag-${label[i18n.locale]}`} + /> + + ) + })} + + + + {addableTags(values.tags, arrayHelpers)} + + + )} + /> - ( - - Tags: - - {values.tags?.map((label, idx) => { - return ( - - {label[i18n.locale]} - arrayHelpers.remove(idx)} - aria-label={`remove-tag-${ - label[i18n.locale] - }`} - /> - - ) - })} - - - - {addableTags(values.tags, arrayHelpers)} - - - )} - /> - - + - {permission === 'SUPER_ADMIN' && ( @@ -424,25 +371,15 @@ export function AdminDomainModal({ {orgCount > 0 ? ( - - Note: This will affect results for {orgCount}{' '} - organizations - + Note: This will affect results for {orgCount} organizations ) : ( - - Note: This could affect results for multiple - organizations - + Note: This could affect results for multiple organizations )} )} - - - Please allow up to 24 hours for summaries to reflect - any changes. - + Please allow up to 24 hours for summaries to reflect any changes. @@ -450,12 +387,7 @@ export function AdminDomainModal({ - diff --git a/frontend/src/admin/AdminPanel.js b/frontend/src/admin/AdminPanel.js index b445913d39..ce2337721a 100644 --- a/frontend/src/admin/AdminPanel.js +++ b/frontend/src/admin/AdminPanel.js @@ -16,8 +16,6 @@ import { UserList } from './UserList' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { AuditLogTable } from './AuditLogTable' -import { ABTestingWrapper } from '../app/ABTestWrapper' -import { ABTestVariant } from '../app/ABTestVariant' export function AdminPanel({ activeMenu, orgSlug, permission, orgId }) { return ( @@ -30,13 +28,9 @@ export function AdminPanel({ activeMenu, orgSlug, permission, orgId }) { Users - - - - Activity - - - + + Activity + @@ -62,13 +56,9 @@ export function AdminPanel({ activeMenu, orgSlug, permission, orgId }) { - - - - - - - + + + diff --git a/frontend/src/admin/__tests__/AdminDomains.test.js b/frontend/src/admin/__tests__/AdminDomains.test.js index 437523adf6..e035332e1c 100644 --- a/frontend/src/admin/__tests__/AdminDomains.test.js +++ b/frontend/src/admin/__tests__/AdminDomains.test.js @@ -279,7 +279,7 @@ describe('', () => { userEvent.click(confirmButton) await waitFor(() => - expect(getByText(/Unable to create new domain./i)).toBeVisible(), + expect(getByText(/Unable to create new domain./i)).toBeInTheDocument(), ) }) diff --git a/frontend/src/app/ABTestVariant.js b/frontend/src/app/ABTestVariant.js deleted file mode 100644 index 03a2d305c8..0000000000 --- a/frontend/src/app/ABTestVariant.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { any } from 'prop-types' - -export function ABTestVariant({ children }) { - return <>{children} -} - -ABTestVariant.propTypes = { - children: any, -} diff --git a/frontend/src/app/ABTestWrapper.js b/frontend/src/app/ABTestWrapper.js index 25660a0be3..8b5e8dcd9f 100644 --- a/frontend/src/app/ABTestWrapper.js +++ b/frontend/src/app/ABTestWrapper.js @@ -6,6 +6,10 @@ const isInsiderUser = ({ userName, insideUser }) => { return userName.endsWith('@tbs-sct.gc.ca') || insideUser } +export function ABTestVariant({ children }) { + return <>{children} +} + export function ABTestingWrapper({ children, insiderVariantName = 'B' }) { const { currentUser } = useUserVar() let childIndex = 0 @@ -32,17 +36,16 @@ export function ABTestingWrapper({ children, insiderVariantName = 'B' }) { insideUser: currentUser?.insideUser || false, }) ) { - childIndex = children.findIndex( - (variant) => variant.props.name === insiderVariantName, - ) + childIndex = children.findIndex((variant) => variant.props.name === insiderVariantName) } else { - childIndex = children.findIndex( - (variant) => variant.props.name !== insiderVariantName, - ) + childIndex = children.findIndex((variant) => variant.props.name !== insiderVariantName) } return <>{children[childIndex]} } +ABTestVariant.propTypes = { + children: any, +} ABTestingWrapper.propTypes = { insiderVariantName: string, children: any, diff --git a/frontend/src/app/App.js b/frontend/src/app/App.js index b222061978..4242ec5632 100644 --- a/frontend/src/app/App.js +++ b/frontend/src/app/App.js @@ -1,10 +1,5 @@ import React, { Suspense, useEffect } from 'react' -import { - Switch, - Link as RouteLink, - Redirect, - useLocation, -} from 'react-router-dom' +import { Switch, Link as RouteLink, Redirect, useLocation } from 'react-router-dom' import { CSSReset, Flex, Link, Text } from '@chakra-ui/react' import { t, Trans } from '@lingui/macro' import { ErrorBoundary } from 'react-error-boundary' @@ -29,8 +24,6 @@ import { LandingPage } from '../landing/LandingPage' import { NotificationBanner } from './NotificationBanner' import { IS_LOGIN_REQUIRED } from '../graphql/queries' import { useLingui } from '@lingui/react' -import { ABTestingWrapper } from './ABTestWrapper' -import { ABTestVariant } from './ABTestVariant' const GuidancePage = lazyWithRetry(() => import('../guidance/GuidancePage')) const PageNotFound = lazyWithRetry(() => import('./PageNotFound')) @@ -58,6 +51,7 @@ export function App() { const { currentUser, isLoggedIn, isEmailValidated, currentTFAMethod } = useUserVar() const { i18n } = useLingui() const { data } = useQuery(IS_LOGIN_REQUIRED, {}) + // const { isOpen: isVisible, onClose } = useDisclosure({ defaultIsOpen: true }) const location = useLocation() // Close websocket on user jwt change (refresh/logout) @@ -99,13 +93,10 @@ export function App() { {isLoggedIn() && ( <> - - - - myTracker - - - + + myTracker + + Account Settings @@ -283,11 +274,7 @@ export function App() { {isLoggedIn() ? ( - - - - - + ) : ( { const { isLoggedIn, logout } = useUserVar() @@ -53,12 +52,7 @@ export const TopBanner = (props) => { - + { - + {t`Tracker - + {t`Tracker diff --git a/frontend/src/domains/DomainCard.js b/frontend/src/domains/DomainCard.js index 6d06ab8327..b4fd1639af 100644 --- a/frontend/src/domains/DomainCard.js +++ b/frontend/src/domains/DomainCard.js @@ -16,15 +16,13 @@ import { } from '@chakra-ui/react' import { Link as RouteLink, useLocation } from 'react-router-dom' import { array, bool, object, string } from 'prop-types' - +import { ABTestingWrapper, ABTestVariant } from '../app/ABTestWrapper' import { StatusBadge } from './StatusBadge' import { ScanDomainButton } from './ScanDomainButton' import { StarIcon } from '@chakra-ui/icons' import { FAVOURITE_DOMAIN, UNFAVOURITE_DOMAIN } from '../graphql/mutations' import { useMutation } from '@apollo/client' import { useUserVar } from '../utilities/userState' -import { ABTestingWrapper } from '../app/ABTestWrapper' -import { ABTestVariant } from '../app/ABTestVariant' export function DomainCard({ id, @@ -145,18 +143,19 @@ export function DomainCard({ )} {url} - - - - {tags?.map((tag, idx) => { - return ( - - - {tag} - - - ) - })} + + + {tags?.map((tag, idx) => { + return ( + + + {tag} + + + ) + })} + + {isHidden && ( @@ -170,10 +169,10 @@ export function DomainCard({ ARCHIVED - )} - - - + )}{' '} + + + @@ -237,30 +236,26 @@ export function DomainCard({ {isLoggedIn() && isEmailValidated() && } - - - {isLoggedIn() && - (location.pathname.match('my-tracker') ? ( - { - await unfavouriteDomain({ variables: { domainId: id } }) - }} - variant="primary" - aria-label={`unfavourite ${url}`} - icon={} - /> - ) : ( - { - await favouriteDomain({ variables: { domainId: id } }) - }} - variant="primary" - aria-label={`favourite ${url}`} - icon={} - /> - ))} - - + {isLoggedIn() && + (location.pathname.match('my-tracker') ? ( + { + await unfavouriteDomain({ variables: { domainId: id } }) + }} + variant="primary" + aria-label={`unfavourite ${url}`} + icon={} + /> + ) : ( + { + await favouriteDomain({ variables: { domainId: id } }) + }} + variant="primary" + aria-label={`favourite ${url}`} + icon={} + /> + ))} diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index 14488d8fcf..d460c017c9 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -57,7 +57,7 @@ msgstr "404 - Page Not Found" #~ msgid "6.2.3 All remaining websites and web services must be accessible through a secure connection, as outlined in Section 6.1, by December 31, 2019." #~ msgstr "6.2.3 All remaining websites and web services must be accessible through a secure connection, as outlined in Section 6.1, by December 31, 2019." -#: src/guidance/GuidancePage.js:68 +#: src/guidance/GuidancePage.js:70 msgid "A DNS request for this service has resulted in the following error code:" msgstr "A DNS request for this service has resulted in the following error code:" @@ -77,11 +77,11 @@ msgstr "A more detailed breakdown of each domain can be found by clicking on its msgid "A verification link has been sent to your email account" msgstr "A verification link has been sent to your email account" -#: src/admin/UserListModal.js:280 +#: src/admin/UserListModal.js:261 msgid "ADMIN" msgstr "ADMIN" -#: src/domains/DomainCard.js:170 +#: src/domains/DomainCard.js:169 msgid "ARCHIVED" msgstr "ARCHIVED" @@ -110,7 +110,7 @@ msgstr "Account" msgid "Account Closed Successfully" msgstr "Account Closed Successfully" -#: src/app/App.js:110 +#: src/app/App.js:101 #: src/app/FloatingMenu.js:177 #: src/user/UserPage.js:150 msgid "Account Settings" @@ -120,7 +120,7 @@ msgstr "Account Settings" msgid "Account created." msgstr "Account created." -#: src/admin/WebCheckPage.js:68 +#: src/admin/WebCheckPage.js:61 #: src/createOrganization/CreateOrganizationPage.js:184 #: src/createOrganization/CreateOrganizationPage.js:189 #: src/organizations/Organizations.js:61 @@ -155,7 +155,7 @@ msgstr "Action" msgid "Action:" msgstr "Action:" -#: src/admin/AdminPanel.js:36 +#: src/admin/AdminPanel.js:32 msgid "Activity" msgstr "Activity" @@ -167,15 +167,15 @@ msgstr "Add" msgid "Add Domain" msgstr "Add Domain" -#: src/admin/AdminDomainModal.js:271 +#: src/admin/AdminDomainModal.js:240 msgid "Add Domain Details" msgstr "Add Domain Details" -#: src/admin/UserListModal.js:237 +#: src/admin/UserListModal.js:220 msgid "Add User" msgstr "Add User" -#: src/app/App.js:216 +#: src/app/App.js:207 msgid "Admin" msgstr "Admin" @@ -183,7 +183,7 @@ msgstr "Admin" msgid "Admin Portal" msgstr "Admin Portal" -#: src/app/App.js:118 +#: src/app/App.js:109 msgid "Admin Profile" msgstr "Admin Profile" @@ -224,11 +224,11 @@ msgid "An error occured when you attempted to download all domain statuses." msgstr "An error occured when you attempted to download all domain statuses." #: src/app/FloatingMenu.js:38 -#: src/app/TopBanner.js:31 +#: src/app/TopBanner.js:30 msgid "An error occured when you attempted to sign out" msgstr "An error occured when you attempted to sign out" -#: src/domains/DomainCard.js:49 +#: src/domains/DomainCard.js:47 msgid "An error occurred while favouriting a domain." msgstr "An error occurred while favouriting a domain." @@ -240,7 +240,7 @@ msgstr "An error occurred while removing this organization." msgid "An error occurred while requesting a scan." msgstr "An error occurred while requesting a scan." -#: src/domains/DomainCard.js:75 +#: src/domains/DomainCard.js:73 msgid "An error occurred while unfavouriting a domain." msgstr "An error occurred while unfavouriting a domain." @@ -280,11 +280,11 @@ msgstr "An error occurred while updating your phone number." msgid "An error occurred while verifying your phone number." msgstr "An error occurred while verifying your phone number." -#: src/admin/AdminDomainModal.js:75 -#: src/admin/AdminDomainModal.js:124 +#: src/admin/AdminDomainModal.js:63 +#: src/admin/AdminDomainModal.js:110 #: src/admin/AdminDomains.js:103 -#: src/admin/UserListModal.js:53 -#: src/admin/UserListModal.js:151 +#: src/admin/UserListModal.js:47 +#: src/admin/UserListModal.js:141 #: src/auth/TwoFactorAuthenticatePage.js:29 #: src/createOrganization/CreateOrganizationPage.js:56 #: src/user/UserPage.js:83 @@ -307,7 +307,7 @@ msgstr "Any products or related services provided to you by TBS are and will rem #~ msgid "Application Portfolio Management (APM) systems; and" #~ msgstr "Application Portfolio Management (APM) systems; and" -#: src/organizationDetails/OrganizationDomains.js:237 +#: src/organizationDetails/OrganizationDomains.js:233 msgid "Apply" msgstr "Apply" @@ -316,11 +316,11 @@ msgstr "Apply" msgid "April" msgstr "April" -#: src/admin/AdminDomainModal.js:421 +#: src/admin/AdminDomainModal.js:368 msgid "Archive domain" msgstr "Archive domain" -#: src/admin/AdminDomainCard.js:80 +#: src/admin/AdminDomainCard.js:51 #: src/organizationDetails/OrganizationDomains.js:103 msgid "Archived" msgstr "Archived" @@ -352,11 +352,11 @@ msgstr "Audit Logs" msgid "August" msgstr "August" -#: src/app/App.js:182 +#: src/app/App.js:173 msgid "Authenticate" msgstr "Authenticate" -#: src/app/TopBanner.js:98 +#: src/app/TopBanner.js:82 msgid "BETA" msgstr "BETA" @@ -381,7 +381,7 @@ msgstr "Below are steps on how government organizations can leverage the Tracker msgid "Blank fields will not be included when updating the organization." msgstr "Blank fields will not be included when updating the organization." -#: src/domains/DomainCard.js:138 +#: src/domains/DomainCard.js:136 #: src/guidance/WebGuidance.js:83 msgid "Blocked" msgstr "Blocked" @@ -416,18 +416,24 @@ msgstr "Canadians rely on the Government of Canada to provide secure digital ser msgid "Cancel" msgstr "Cancel" -#: src/guidance/WebTLSResults.js:227 +#: src/guidance/WebTLSResults.js:268 msgid "Certificate Chain" msgstr "Certificate Chain" -#: src/guidance/WebTLSResults.js:235 +#: src/guidance/WebTLSResults.js:276 msgid "Certificate chain info could not be found during the scan." msgstr "Certificate chain info could not be found during the scan." -#: src/domains/DomainCard.js:187 +#: src/domains/DomainCard.js:186 +#: src/organizationDetails/OrganizationDomains.js:279 msgid "Certificates" msgstr "Certificates" +#: src/domains/DomainsPage.js:71 +#: src/organizationDetails/OrganizationDomains.js:83 +msgid "Certificates Status" +msgstr "Certificates Status" + #: src/auth/ResetPasswordPage.js:126 #: src/user/EditableUserPassword.js:153 msgid "Change Password" @@ -469,15 +475,15 @@ msgstr "Changes Required for ITPIN Compliance" msgid "Check your associated Tracker email for the verification link" msgstr "Check your associated Tracker email for the verification link" -#: src/domains/DomainCard.js:189 -#: src/domains/DomainsPage.js:154 +#: src/domains/DomainCard.js:188 +#: src/domains/DomainsPage.js:155 #: src/guidance/WebTLSResults.js:101 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:280 msgid "Ciphers" msgstr "Ciphers" -#: src/domains/DomainsPage.js:71 -#: src/organizationDetails/OrganizationDomains.js:87 +#: src/domains/DomainsPage.js:72 +#: src/organizationDetails/OrganizationDomains.js:84 msgid "Ciphers Status" msgstr "Ciphers Status" @@ -523,7 +529,7 @@ msgstr "Code field must not be empty" msgid "Collect and analyze DMARC reports." msgstr "Collect and analyze DMARC reports." -#: src/organizationDetails/OrganizationDomains.js:188 +#: src/organizationDetails/OrganizationDomains.js:175 msgid "Comparison" msgstr "Comparison" @@ -531,12 +537,12 @@ msgstr "Comparison" msgid "Compliant" msgstr "Compliant" -#: src/admin/AdminDomainModal.js:459 +#: src/admin/AdminDomainModal.js:391 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 #: src/admin/SuperAdminUserList.js:441 -#: src/admin/UserListModal.js:299 +#: src/admin/UserListModal.js:274 #: src/user/EditableUserDisplayName.js:168 #: src/user/EditableUserEmail.js:168 #: src/user/EditableUserPassword.js:182 @@ -562,20 +568,20 @@ msgstr "Confirm removal of domain:" #~ msgid "Confirm removal of user:" #~ msgstr "Confirm removal of user:" -#: src/app/ReadGuidancePage.js:216 -msgid "Consider prioritizing websites and web services that exchange Protected data." -msgstr "Consider prioritizing websites and web services that exchange Protected data." - #: src/guidance/WebConnectionResults.js:99 msgid "Connection Results" msgstr "Connection Results" +#: src/app/ReadGuidancePage.js:216 +msgid "Consider prioritizing websites and web services that exchange Protected data." +msgstr "Consider prioritizing websites and web services that exchange Protected data." + #: src/app/FloatingMenu.js:238 msgid "Contact" msgstr "Contact" -#: src/app/App.js:191 -#: src/app/App.js:346 +#: src/app/App.js:182 +#: src/app/App.js:333 #: src/app/ContactUsPage.js:39 #: src/app/SlideMessage.js:103 msgid "Contact Us" @@ -621,18 +627,18 @@ msgid "Create" msgstr "Create" #: src/app/FloatingMenu.js:200 -#: src/app/TopBanner.js:143 +#: src/app/TopBanner.js:127 #: src/auth/CreateUserPage.js:243 msgid "Create Account" msgstr "Create Account" #: src/admin/AdminPage.js:130 -#: src/app/App.js:305 +#: src/app/App.js:292 #: src/createOrganization/CreateOrganizationPage.js:237 msgid "Create Organization" msgstr "Create Organization" -#: src/app/App.js:159 +#: src/app/App.js:150 msgid "Create an Account" msgstr "Create an Account" @@ -660,21 +666,21 @@ msgstr "Current Password:" msgid "Current Phone Number:" msgstr "Current Phone Number:" -#: src/domains/DomainCard.js:190 -#: src/domains/DomainsPage.js:155 +#: src/domains/DomainCard.js:189 +#: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 -#: src/organizationDetails/OrganizationDomains.js:282 -#: src/organizationDetails/OrganizationDomains.js:328 +#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:325 msgid "Curves" msgstr "Curves" -#: src/domains/DomainsPage.js:72 -#: src/organizationDetails/OrganizationDomains.js:88 +#: src/domains/DomainsPage.js:73 +#: src/organizationDetails/OrganizationDomains.js:85 msgid "Curves Status" msgstr "Curves Status" -#: src/domains/DomainsPage.js:164 -#: src/organizationDetails/OrganizationDomains.js:291 +#: src/domains/DomainsPage.js:165 +#: src/organizationDetails/OrganizationDomains.js:290 msgid "DKIM" msgstr "DKIM" @@ -700,7 +706,7 @@ msgstr "DKIM Failures by IP Address" msgid "DKIM Results" msgstr "DKIM Results" -#: src/admin/AdminDomainModal.js:325 +#: src/admin/AdminDomainModal.js:280 msgid "DKIM Selector" msgstr "DKIM Selector" @@ -708,12 +714,12 @@ msgstr "DKIM Selector" msgid "DKIM Selectors" msgstr "DKIM Selectors" -#: src/admin/AdminDomainModal.js:288 +#: src/admin/AdminDomainModal.js:252 msgid "DKIM Selectors:" msgstr "DKIM Selectors:" -#: src/domains/DomainsPage.js:75 -#: src/organizationDetails/OrganizationDomains.js:91 +#: src/domains/DomainsPage.js:76 +#: src/organizationDetails/OrganizationDomains.js:88 msgid "DKIM Status" msgstr "DKIM Status" @@ -721,8 +727,8 @@ msgstr "DKIM Status" #~ msgid "DKIM record could not be found for this selector." #~ msgstr "DKIM record could not be found for this selector." -#: src/domains/DomainsPage.js:168 -#: src/organizationDetails/OrganizationDomains.js:295 +#: src/domains/DomainsPage.js:169 +#: src/organizationDetails/OrganizationDomains.js:294 msgid "DMARC" msgstr "DMARC" @@ -758,9 +764,9 @@ msgstr "DMARC Implementation Phase: {0}" msgid "DMARC Phases" msgstr "DMARC Phases" -#: src/dmarc/DmarcReportPage.js:92 -#: src/domains/DomainCard.js:233 -#: src/guidance/GuidancePage.js:150 +#: src/dmarc/DmarcReportPage.js:95 +#: src/domains/DomainCard.js:232 +#: src/guidance/GuidancePage.js:152 msgid "DMARC Report" msgstr "DMARC Report" @@ -768,13 +774,13 @@ msgstr "DMARC Report" msgid "DMARC Report for {domainSlug}" msgstr "DMARC Report for {domainSlug}" -#: src/domains/DomainsPage.js:76 -#: src/organizationDetails/OrganizationDomains.js:92 +#: src/domains/DomainsPage.js:77 +#: src/organizationDetails/OrganizationDomains.js:89 msgid "DMARC Status" msgstr "DMARC Status" -#: src/app/App.js:95 -#: src/app/App.js:263 +#: src/app/App.js:89 +#: src/app/App.js:254 #: src/app/FloatingMenu.js:131 #: src/dmarc/DmarcByDomainPage.js:181 #: src/dmarc/DmarcByDomainPage.js:241 @@ -790,7 +796,7 @@ msgstr "DMARC Summaries" #~ msgid "DMARC record could not be found during the scan." #~ msgstr "DMARC record could not be found during the scan." -#: src/dmarc/DmarcReportPage.js:182 +#: src/dmarc/DmarcReportPage.js:185 msgid "DNS Host" msgstr "DNS Host" @@ -806,7 +812,7 @@ msgstr "DNS Scan Complete" msgid "DNS scan for domain \"{0}\" has completed." msgstr "DNS scan for domain \"{0}\" has completed." -#: src/organizationDetails/OrganizationDomains.js:194 +#: src/organizationDetails/OrganizationDomains.js:181 msgid "DOES NOT EQUAL" msgstr "DOES NOT EQUAL" @@ -894,11 +900,11 @@ msgstr "Disposition" #: src/admin/AuditLogTable.js:76 #: src/dmarc/DmarcByDomainPage.js:124 -#: src/dmarc/DmarcByDomainPage.js:312 +#: src/dmarc/DmarcByDomainPage.js:324 #: src/domains/DomainsPage.js:68 -#: src/domains/DomainsPage.js:153 -#: src/organizationDetails/OrganizationDomains.js:280 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/domains/DomainsPage.js:154 +#: src/organizationDetails/OrganizationDomains.js:278 +#: src/organizationDetails/OrganizationDomains.js:312 msgid "Domain" msgstr "Domain" @@ -919,7 +925,7 @@ msgstr "Domain URL" msgid "Domain URL:" msgstr "Domain URL:" -#: src/admin/AdminDomainModal.js:86 +#: src/admin/AdminDomainModal.js:74 msgid "Domain added" msgstr "Domain added" @@ -935,7 +941,7 @@ msgstr "Domain removed" msgid "Domain removed from {orgSlug}" msgstr "Domain removed from {orgSlug}" -#: src/admin/AdminDomainModal.js:135 +#: src/admin/AdminDomainModal.js:121 msgid "Domain updated" msgstr "Domain updated" @@ -943,19 +949,19 @@ msgstr "Domain updated" msgid "Domain url field must not be empty" msgstr "Domain url field must not be empty" -#: src/admin/AdminDomainCard.js:29 -#: src/admin/WebCheckPage.js:147 -#: src/domains/DomainCard.js:129 +#: src/admin/AdminDomainCard.js:16 +#: src/admin/WebCheckPage.js:129 +#: src/domains/DomainCard.js:127 #: src/domains/ScanDomain.js:211 msgid "Domain:" msgstr "Domain:" -#: src/admin/AdminPanel.js:28 -#: src/app/App.js:92 -#: src/app/App.js:229 +#: src/admin/AdminPanel.js:26 +#: src/app/App.js:86 +#: src/app/App.js:220 #: src/app/FloatingMenu.js:116 -#: src/domains/DomainsPage.js:81 -#: src/domains/DomainsPage.js:115 +#: src/domains/DomainsPage.js:82 +#: src/domains/DomainsPage.js:116 #: src/organizationDetails/OrganizationDetails.js:139 #: src/organizationDetails/OrganizationDomains.js:108 #: src/summaries/Doughnut.js:50 @@ -980,7 +986,7 @@ msgstr "Don't have an account? <0>Sign up" #~ msgid "Dploy DKIM records and keys for all domains and senders; and" #~ msgstr "Dploy DKIM records and keys for all domains and senders; and" -#: src/organizationDetails/OrganizationDomains.js:191 +#: src/organizationDetails/OrganizationDomains.js:178 msgid "EQUALS" msgstr "EQUALS" @@ -1003,7 +1009,7 @@ msgstr "Edit" msgid "Edit Display Name" msgstr "Edit Display Name" -#: src/admin/AdminDomainModal.js:269 +#: src/admin/AdminDomainModal.js:240 msgid "Edit Domain Details" msgstr "Edit Domain Details" @@ -1019,19 +1025,19 @@ msgstr "Edit Organization" msgid "Edit Phone Number" msgstr "Edit Phone Number" -#: src/admin/UserListModal.js:233 +#: src/admin/UserListModal.js:216 msgid "Edit User" msgstr "Edit User" #: src/admin/SuperAdminUserList.js:148 #: src/components/fields/EmailField.js:15 -#: src/domains/DomainCard.js:195 +#: src/domains/DomainCard.js:194 #: src/user/EditableUserTFAMethod.js:166 msgid "Email" msgstr "Email" #: src/domains/ScanDomain.js:245 -#: src/guidance/GuidancePage.js:103 +#: src/guidance/GuidancePage.js:105 msgid "Email Guidance" msgstr "Email Guidance" @@ -1055,7 +1061,7 @@ msgstr "Email Sent" msgid "Email Validated" msgstr "Email Validated" -#: src/app/App.js:301 +#: src/app/App.js:288 msgid "Email Verification" msgstr "Email Verification" @@ -1065,7 +1071,7 @@ msgstr "Email Verification" msgid "Email cannot be empty" msgstr "Email cannot be empty" -#: src/admin/UserListModal.js:65 +#: src/admin/UserListModal.js:59 msgid "Email invitation sent" msgstr "Email invitation sent" @@ -1152,7 +1158,7 @@ msgstr "Envelope From" msgid "Eventually" msgstr "Eventually" -#: src/guidance/WebTLSResults.js:380 +#: src/guidance/WebTLSResults.js:421 msgid "Expired:" msgstr "Expired:" @@ -1164,9 +1170,9 @@ msgstr "Export to CSV" #~ msgid "FAQ" #~ msgstr "FAQ" -#: src/dmarc/DmarcReportPage.js:126 -#: src/dmarc/DmarcReportPage.js:127 -#: src/organizationDetails/OrganizationDomains.js:227 +#: src/dmarc/DmarcReportPage.js:129 +#: src/dmarc/DmarcReportPage.js:130 +#: src/organizationDetails/OrganizationDomains.js:223 msgid "Fail" msgstr "Fail" @@ -1194,7 +1200,7 @@ msgstr "Fail SPF %" msgid "Fake email domain blocks (reject + quarantine):" msgstr "Fake email domain blocks (reject + quarantine):" -#: src/domains/DomainCard.js:59 +#: src/domains/DomainCard.js:57 msgid "Favourited Domain" msgstr "Favourited Domain" @@ -1215,7 +1221,7 @@ msgstr "February" #~ msgid "Filters" #~ msgstr "Filters" -#: src/organizationDetails/OrganizationDomains.js:145 +#: src/organizationDetails/OrganizationDomains.js:138 msgid "Filters:" msgstr "Filters:" @@ -1255,7 +1261,7 @@ msgstr "For details related to terms pertaining to privacy, please refer to" msgid "For users interested in using new features that are still in progress." msgstr "For users interested in using new features that are still in progress." -#: src/app/App.js:185 +#: src/app/App.js:176 #: src/auth/ForgotPasswordPage.js:75 msgid "Forgot Password" msgstr "Forgot Password" @@ -1316,11 +1322,11 @@ msgstr "Getting Started" msgid "Getting an Account:" msgstr "Getting an Account:" -#: src/domains/DomainsPage.js:150 +#: src/domains/DomainsPage.js:126 msgid "Getting domain statuses" msgstr "Getting domain statuses" -#: src/components/InfoPanel.js:32 +#: src/components/InfoPanel.js:27 msgid "Glossary" msgstr "Glossary" @@ -1340,10 +1346,9 @@ msgstr "Government of Canada Employees" #~ msgid "Graph direction:" #~ msgstr "Graph direction:" -#: src/app/App.js:350 -#: src/app/ReadGuidancePage.js:28 -#: src/dmarc/DmarcReportPage.js:193 -#: src/dmarc/DmarcReportPage.js:606 +#: src/app/App.js:337 +#: src/dmarc/DmarcReportPage.js:196 +#: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" msgstr "Guidance" @@ -1351,7 +1356,7 @@ msgstr "Guidance" #~ msgid "Guidance Tags" #~ msgstr "Guidance Tags" -#: src/guidance/GuidancePage.js:43 +#: src/guidance/GuidancePage.js:45 msgid "Guidance results" msgstr "Guidance results" @@ -1360,13 +1365,13 @@ msgstr "Guidance results" #~ msgid "Guidance:" #~ msgstr "Guidance:" -#: src/domains/DomainCard.js:163 +#: src/domains/DomainCard.js:162 msgid "HIDDEN" msgstr "HIDDEN" -#: src/domains/DomainCard.js:186 -#: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:283 +#: src/domains/DomainCard.js:185 +#: src/domains/DomainsPage.js:157 +#: src/organizationDetails/OrganizationDomains.js:282 msgid "HSTS" msgstr "HSTS" @@ -1391,7 +1396,7 @@ msgid "HSTS Preloaded" msgstr "HSTS Preloaded" #: src/domains/DomainsPage.js:70 -#: src/organizationDetails/OrganizationDomains.js:86 +#: src/organizationDetails/OrganizationDomains.js:82 msgid "HSTS Status" msgstr "HSTS Status" @@ -1411,9 +1416,9 @@ msgstr "HTTP Live" msgid "HTTP Upgrades" msgstr "HTTP Upgrades" -#: src/domains/DomainCard.js:185 -#: src/domains/DomainsPage.js:158 -#: src/organizationDetails/OrganizationDomains.js:285 +#: src/domains/DomainCard.js:184 +#: src/domains/DomainsPage.js:159 +#: src/organizationDetails/OrganizationDomains.js:284 msgid "HTTPS" msgstr "HTTPS" @@ -1443,7 +1448,7 @@ msgid "HTTPS Scan Complete" msgstr "HTTPS Scan Complete" #: src/domains/DomainsPage.js:69 -#: src/organizationDetails/OrganizationDomains.js:85 +#: src/organizationDetails/OrganizationDomains.js:81 msgid "HTTPS Status" msgstr "HTTPS Status" @@ -1459,11 +1464,11 @@ msgstr "HTTPS is configured and HTTP connections redirect to HTTPS" msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "HTTPS scan for domain \"{0}\" has completed." -#: src/guidance/WebTLSResults.js:395 +#: src/guidance/WebTLSResults.js:436 msgid "Hash Algorithm:" msgstr "Hash Algorithm:" -#: src/dmarc/DmarcReportPage.js:188 +#: src/dmarc/DmarcReportPage.js:191 msgid "Header From" msgstr "Header From" @@ -1471,21 +1476,25 @@ msgstr "Header From" #~ msgid "Heartbleed Vulnerability:" #~ msgstr "Heartbleed Vulnerability:" +#: src/guidance/WebTLSResults.js:229 +msgid "Heartbleed Vulnerable" +msgstr "Heartbleed Vulnerable" + #: src/app/ReadGuidancePage.js:23 #~ msgid "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." #~ msgstr "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." -#: src/admin/AdminDomainCard.js:68 +#: src/admin/AdminDomainCard.js:44 #: src/organizationDetails/OrganizationDomains.js:102 msgid "Hidden" msgstr "Hidden" -#: src/admin/AdminDomainModal.js:398 +#: src/admin/AdminDomainModal.js:346 msgid "Hide domain" msgstr "Hide domain" -#: src/app/App.js:83 -#: src/app/App.js:155 +#: src/app/App.js:77 +#: src/app/App.js:146 #: src/app/FloatingMenu.js:175 msgid "Home" msgstr "Home" @@ -1498,8 +1507,7 @@ msgstr "Home" msgid "Host from reverse DNS of source IP address." msgstr "Host from reverse DNS of source IP address." -#: src/app/ReadGuidancePage.js:396 -#: src/guidance/WebTLSResults.js:247 +#: src/guidance/WebTLSResults.js:288 msgid "Hostname Matches" msgstr "Hostname Matches" @@ -1507,7 +1515,7 @@ msgstr "Hostname Matches" #~ msgid "Hostname Validated" #~ msgstr "Hostname Validated" -#: src/app/ReadGuidancePage.js:239 +#: src/app/ReadGuidancePage.js:396 msgid "How can I edit my domain list?" msgstr "How can I edit my domain list?" @@ -1515,7 +1523,7 @@ msgstr "How can I edit my domain list?" msgid "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" msgstr "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" -#: src/organizationDetails/OrganizationDomains.js:101 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "INACTIVE" msgstr "INACTIVE" @@ -1568,7 +1576,7 @@ msgstr "If at any time you or your representatives wish to adjust or cancel thes #~ msgid "If at any time you or your representatives wish to adjust or cancel these services, please contact us at" #~ msgstr "If at any time you or your representatives wish to adjust or cancel these services, please contact us at" -#: src/guidance/GuidancePage.js:73 +#: src/guidance/GuidancePage.js:75 msgid "If you believe this could be the result of an issue with the scan, rescan the service using the refresh button. If you believe this is because the service no longer exists (NXDOMAIN), this domain should be removed from all affiliated organizations." msgstr "If you believe this could be the result of an issue with the scan, rescan the service using the refresh button. If you believe this is because the service no longer exists (NXDOMAIN), this domain should be removed from all affiliated organizations." @@ -1580,15 +1588,15 @@ msgstr "If you believe this was caused by a problem with Tracker, please <0>Repo #~ msgid "If you believe this was caused by a problem with Tracker, please use the \"Report an Issue\" link below" #~ msgstr "If you believe this was caused by a problem with Tracker, please use the \"Report an Issue\" link below" +#: src/app/ReadGuidancePage.js:88 +msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." +msgstr "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." + #: src/guidance/WebConnectionResults.js:138 #: src/guidance/WebConnectionResults.js:178 msgid "Immediately" msgstr "Immediately" -#: src/app/ReadGuidancePage.js:88 -msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." -msgstr "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." - #: src/guidance/WebGuidance.js:19 #~ msgid "Implementation" #~ msgstr "Implementation" @@ -1613,7 +1621,7 @@ msgstr "Implementation: <0>Implementation guidance: email domain protection (ITS msgid "Implemented" msgstr "Implemented" -#: src/organizationDetails/OrganizationDomains.js:101 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "Inactive" msgstr "Inactive" @@ -1626,7 +1634,7 @@ msgstr "Incorrect authenticate.result typename." msgid "Incorrect closeAccount.result typename." msgstr "Incorrect closeAccount.result typename." -#: src/admin/AdminDomainModal.js:108 +#: src/admin/AdminDomainModal.js:94 msgid "Incorrect createDomain.result typename." msgstr "Incorrect createDomain.result typename." @@ -1634,7 +1642,7 @@ msgstr "Incorrect createDomain.result typename." msgid "Incorrect createOrganization.result typename." msgstr "Incorrect createOrganization.result typename." -#: src/admin/UserListModal.js:84 +#: src/admin/UserListModal.js:78 msgid "Incorrect inviteUserToOrg.result typename." msgstr "Incorrect inviteUserToOrg.result typename." @@ -1655,12 +1663,12 @@ msgstr "Incorrect removeOrganization.result typename." msgid "Incorrect resetPassword.result typename." msgstr "Incorrect resetPassword.result typename." -#: src/admin/AdminDomainModal.js:107 -#: src/admin/AdminDomainModal.js:156 +#: src/admin/AdminDomainModal.js:93 +#: src/admin/AdminDomainModal.js:142 #: src/admin/AdminDomains.js:133 #: src/admin/SuperAdminUserList.js:110 -#: src/admin/UserListModal.js:83 -#: src/admin/UserListModal.js:132 +#: src/admin/UserListModal.js:77 +#: src/admin/UserListModal.js:124 #: src/auth/CreateUserPage.js:83 #: src/auth/ResetPasswordPage.js:60 #: src/auth/SignInPage.js:100 @@ -1698,7 +1706,7 @@ msgstr "Incorrect typename received." msgid "Incorrect update method received." msgstr "Incorrect update method received." -#: src/admin/AdminDomainModal.js:157 +#: src/admin/AdminDomainModal.js:143 msgid "Incorrect updateDomain.result typename." msgstr "Incorrect updateDomain.result typename." @@ -1718,7 +1726,7 @@ msgstr "Incorrect updateUserPassword.result typename." msgid "Incorrect updateUserProfile.result typename." msgstr "Incorrect updateUserProfile.result typename." -#: src/admin/UserListModal.js:133 +#: src/admin/UserListModal.js:125 msgid "Incorrect updateUserRole.result typename." msgstr "Incorrect updateUserRole.result typename." @@ -1742,7 +1750,7 @@ msgstr "Individuals from a departmental information technology group may contact #~ msgid "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." #~ msgstr "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." -#: src/organizationDetails/OrganizationDomains.js:224 +#: src/organizationDetails/OrganizationDomains.js:220 msgid "Info" msgstr "Info" @@ -1814,12 +1822,11 @@ msgstr "Is DKIM aligned. Can be true or false." msgid "Is SPF aligned. Can be true or false." msgstr "Is SPF aligned. Can be true or false." -#: src/app/ReadGuidancePage.js:378 -#: src/guidance/WebTLSResults.js:383 +#: src/guidance/WebTLSResults.js:424 msgid "Issuer:" msgstr "Issuer:" -#: src/app/ReadGuidancePage.js:221 +#: src/app/ReadGuidancePage.js:378 msgid "It is not clear to me why a domain has failed?" msgstr "It is not clear to me why a domain has failed?" @@ -1881,11 +1888,11 @@ msgstr "Last 30 Days" #~ msgid "Last Scanned" #~ msgstr "Last Scanned" -#: src/admin/WebCheckPage.js:154 +#: src/admin/WebCheckPage.js:136 msgid "Last Scanned:" msgstr "Last Scanned:" -#: src/guidance/WebTLSResults.js:267 +#: src/guidance/WebTLSResults.js:308 msgid "Leaf Certificate is EV" msgstr "Leaf Certificate is EV" @@ -1928,14 +1935,14 @@ msgstr "Login" msgid "Login to your account" msgstr "Login to your account" -#: src/app/ReadGuidancePage.js:117 -msgid "Managing Your Domains:" -msgstr "Managing Your Domains:" - #: src/guidance/EmailGuidance.js:178 msgid "Lookups:" msgstr "Lookups:" +#: src/app/ReadGuidancePage.js:117 +msgid "Managing Your Domains:" +msgstr "Managing Your Domains:" + #: src/components/MonthSelect.js:19 #: src/utilities/months.js:6 msgid "March" @@ -1965,15 +1972,15 @@ msgstr "Monitor DMARC reports and correct misconfigurations." msgid "Monitor DMARC reports;" msgstr "Monitor DMARC reports;" +#: src/guidance/WebTLSResults.js:402 +msgid "More details" +msgstr "More details" + #: src/app/ReadGuidancePage.js:604 msgid "Mozilla SSL Configuration Generator" msgstr "Mozilla SSL Configuration Generator" -#: src/guidance/WebTLSResults.js:361 -msgid "More details" -msgstr "More details" - -#: src/guidance/WebTLSResults.js:258 +#: src/guidance/WebTLSResults.js:299 msgid "Must Staple" msgstr "Must Staple" @@ -1981,11 +1988,11 @@ msgstr "Must Staple" #~ msgid "My Tracker" #~ msgstr "My Tracker" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:93 msgid "NEW" msgstr "NEW" -#: src/admin/WebCheckPage.js:67 +#: src/admin/WebCheckPage.js:60 #: src/createOrganization/CreateOrganizationPage.js:173 #: src/createOrganization/CreateOrganizationPage.js:178 #: src/organizations/Organizations.js:60 @@ -2004,7 +2011,7 @@ msgstr "Name (FR)" msgid "Name:" msgstr "Name:" -#: src/guidance/WebTLSResults.js:365 +#: src/guidance/WebTLSResults.js:406 msgid "Names:" msgstr "Names:" @@ -2030,7 +2037,7 @@ msgstr "Negative" msgid "Never" msgstr "Never" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:93 msgid "New" msgstr "New" @@ -2038,11 +2045,11 @@ msgstr "New" msgid "New Display Name:" msgstr "New Display Name:" -#: src/admin/AdminDomainModal.js:280 +#: src/admin/AdminDomainModal.js:245 msgid "New Domain URL" msgstr "New Domain URL" -#: src/admin/AdminDomainModal.js:279 +#: src/admin/AdminDomainModal.js:245 msgid "New Domain URL:" msgstr "New Domain URL:" @@ -2071,16 +2078,17 @@ msgstr "New Value:" #: src/guidance/WebConnectionResults.js:188 #: src/guidance/WebConnectionResults.js:206 #: src/guidance/WebConnectionResults.js:215 -#: src/guidance/WebTLSResults.js:250 -#: src/guidance/WebTLSResults.js:261 -#: src/guidance/WebTLSResults.js:270 -#: src/guidance/WebTLSResults.js:281 -#: src/guidance/WebTLSResults.js:292 -#: src/guidance/WebTLSResults.js:303 -#: src/guidance/WebTLSResults.js:314 -#: src/guidance/WebTLSResults.js:380 -#: src/guidance/WebTLSResults.js:389 -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:291 +#: src/guidance/WebTLSResults.js:302 +#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:322 +#: src/guidance/WebTLSResults.js:333 +#: src/guidance/WebTLSResults.js:344 +#: src/guidance/WebTLSResults.js:355 +#: src/guidance/WebTLSResults.js:421 +#: src/guidance/WebTLSResults.js:430 +#: src/guidance/WebTLSResults.js:433 msgid "No" msgstr "No" @@ -2093,8 +2101,8 @@ msgid "No DMARC phase information available for this organization." msgstr "No DMARC phase information available for this organization." #: src/admin/AdminDomains.js:156 -#: src/domains/DomainsPage.js:88 -#: src/organizationDetails/OrganizationDomains.js:251 +#: src/domains/DomainsPage.js:89 +#: src/organizationDetails/OrganizationDomains.js:246 msgid "No Domains" msgstr "No Domains" @@ -2102,7 +2110,7 @@ msgstr "No Domains" msgid "No HTTPS configuration information available for this organization." msgstr "No HTTPS configuration information available for this organization." -#: src/admin/WebCheckPage.js:101 +#: src/admin/WebCheckPage.js:94 #: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "No Organizations" @@ -2139,12 +2147,12 @@ msgstr "No data for the Fully Aligned by IP Address table" msgid "No data for the SPF Failures by IP Address table" msgstr "No data for the SPF Failures by IP Address table" -#: src/domains/DomainsPage.js:135 -#: src/domains/DomainsPage.js:143 +#: src/domains/DomainsPage.js:136 +#: src/domains/DomainsPage.js:144 msgid "No data found" msgstr "No data found" -#: src/domains/DomainsPage.js:136 +#: src/domains/DomainsPage.js:137 msgid "No data found when retrieving all domain statuses." msgstr "No data found when retrieving all domain statuses." @@ -2168,7 +2176,7 @@ msgstr "No known weak protocols used." msgid "No scan data for this organization." msgstr "No scan data for this organization." -#: src/guidance/GuidancePage.js:88 +#: src/guidance/GuidancePage.js:90 msgid "No scan data is currently available for this service. You may request a scan using the refresh button, or wait up to 24 hours for data to refresh." msgstr "No scan data is currently available for this service. You may request a scan using the refresh button, or wait up to 24 hours for data to refresh." @@ -2189,12 +2197,12 @@ msgstr "Non-compliant" msgid "None" msgstr "None" -#: src/guidance/WebTLSResults.js:352 -#: src/guidance/WebTLSResults.js:377 +#: src/guidance/WebTLSResults.js:393 +#: src/guidance/WebTLSResults.js:418 msgid "Not After:" msgstr "Not After:" -#: src/guidance/WebTLSResults.js:374 +#: src/guidance/WebTLSResults.js:415 msgid "Not Before:" msgstr "Not Before:" @@ -2206,11 +2214,11 @@ msgstr "Not Implemented" msgid "Note that compliance data does not automatically refresh. Modifications to domains could take 24 hours to update." msgstr "Note that compliance data does not automatically refresh. Modifications to domains could take 24 hours to update." -#: src/admin/AdminDomainModal.js:432 +#: src/admin/AdminDomainModal.js:376 msgid "Note: This could affect results for multiple organizations" msgstr "Note: This could affect results for multiple organizations" -#: src/admin/AdminDomainModal.js:427 +#: src/admin/AdminDomainModal.js:374 msgid "Note: This will affect results for {orgCount} organizations" msgstr "Note: This will affect results for {orgCount} organizations" @@ -2299,26 +2307,26 @@ msgstr "Organization name does not match." msgid "Organization not updated" msgstr "Organization not updated" -#: src/guidance/GuidancePage.js:157 +#: src/guidance/GuidancePage.js:159 msgid "Organization(s):" msgstr "Organization(s):" #: src/admin/AdminPage.js:77 #: src/admin/AdminPage.js:93 -#: src/admin/UserListModal.js:255 +#: src/admin/UserListModal.js:238 msgid "Organization:" msgstr "Organization:" #: src/admin/AdminPage.js:189 -#: src/app/App.js:89 -#: src/app/App.js:195 +#: src/app/App.js:83 +#: src/app/App.js:186 #: src/app/FloatingMenu.js:103 #: src/organizations/Organizations.js:72 #: src/organizations/Organizations.js:109 msgid "Organizations" msgstr "Organizations" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "PROD" msgstr "PROD" @@ -2326,9 +2334,9 @@ msgstr "PROD" msgid "Page {0} of {1}" msgstr "Page {0} of {1}" -#: src/dmarc/DmarcReportPage.js:117 -#: src/dmarc/DmarcReportPage.js:118 -#: src/organizationDetails/OrganizationDomains.js:221 +#: src/dmarc/DmarcReportPage.js:120 +#: src/dmarc/DmarcReportPage.js:121 +#: src/organizationDetails/OrganizationDomains.js:217 msgid "Pass" msgstr "Pass" @@ -2410,7 +2418,7 @@ msgstr "Phone number field must not be empty" msgid "Phone number must be a valid phone number that is 10-15 digits long" msgstr "Phone number must be a valid phone number that is 10-15 digits long" -#: src/admin/AdminDomainModal.js:442 +#: src/admin/AdminDomainModal.js:382 msgid "Please allow up to 24 hours for summaries to reflect any changes." msgstr "Please allow up to 24 hours for summaries to reflect any changes." @@ -2465,7 +2473,7 @@ msgstr "Positive" #~ msgid "Preloaded Status:" #~ msgstr "Preloaded Status:" -#: src/admin/AdminDomainModal.js:384 +#: src/admin/AdminDomainModal.js:333 msgid "Prevent this domain from being counted in your organization's summaries." msgstr "Prevent this domain from being counted in your organization's summaries." @@ -2473,7 +2481,7 @@ msgstr "Prevent this domain from being counted in your organization's summaries. #~ msgid "Prevent this domain from being scanned and being counted in any summaries." #~ msgstr "Prevent this domain from being scanned and being counted in any summaries." -#: src/admin/AdminDomainModal.js:406 +#: src/admin/AdminDomainModal.js:353 msgid "Prevent this domain from being visible, scanned, and being counted in any summaries." msgstr "Prevent this domain from being visible, scanned, and being counted in any summaries." @@ -2481,7 +2489,7 @@ msgstr "Prevent this domain from being visible, scanned, and being counted in an #~ msgid "Previous" #~ msgstr "Previous" -#: src/app/App.js:334 +#: src/app/App.js:321 #: src/app/FloatingMenu.js:219 #: src/app/SlideMessage.js:88 #: src/termsConditions/TermsConditionsPage.js:41 @@ -2496,7 +2504,7 @@ msgstr "Privacy Act." msgid "Privacy Notice Statement" msgstr "Privacy Notice Statement" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "Prod" msgstr "Prod" @@ -2504,16 +2512,16 @@ msgstr "Prod" msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" msgstr "Protect domains that do not send email - GOV.UK (www.gov.uk)" -#: src/domains/DomainCard.js:188 -#: src/domains/DomainsPage.js:161 +#: src/domains/DomainCard.js:187 +#: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 -#: src/organizationDetails/OrganizationDomains.js:288 -#: src/organizationDetails/OrganizationDomains.js:329 +#: src/organizationDetails/OrganizationDomains.js:287 +#: src/organizationDetails/OrganizationDomains.js:326 msgid "Protocols" msgstr "Protocols" -#: src/domains/DomainsPage.js:73 -#: src/organizationDetails/OrganizationDomains.js:89 +#: src/domains/DomainsPage.js:74 +#: src/organizationDetails/OrganizationDomains.js:86 msgid "Protocols Status" msgstr "Protocols Status" @@ -2546,11 +2554,15 @@ msgstr "Province (FR)" msgid "Province:" msgstr "Province:" +#: src/guidance/WebTLSResults.js:253 +msgid "ROBOT Vulnerable" +msgstr "ROBOT Vulnerable" + #: src/app/ReadGuidancePage.js:259 #~ msgid "Read Guidance" #~ msgstr "Read Guidance" -#: src/app/App.js:193 +#: src/app/App.js:184 msgid "Read guidance" msgstr "Read guidance" @@ -2559,11 +2571,11 @@ msgstr "Read guidance" msgid "Reason" msgstr "Reason" -#: src/guidance/WebTLSResults.js:278 +#: src/guidance/WebTLSResults.js:319 msgid "Received Chain Contains Anchor Certificate" msgstr "Received Chain Contains Anchor Certificate" -#: src/guidance/WebTLSResults.js:289 +#: src/guidance/WebTLSResults.js:330 msgid "Received Chain Has Valid Order" msgstr "Received Chain Has Valid Order" @@ -2573,7 +2585,7 @@ msgstr "Received Chain Has Valid Order" msgid "Record:" msgstr "Record:" -#: src/app/ReadGuidancePage.js:355 +#: src/app/ReadGuidancePage.js:555 msgid "References:" msgstr "References:" @@ -2603,7 +2615,7 @@ msgstr "Remove Domain" msgid "Remove Organization" msgstr "Remove Organization" -#: src/admin/UserListModal.js:235 +#: src/admin/UserListModal.js:218 msgid "Remove User" msgstr "Remove User" @@ -2611,7 +2623,7 @@ msgstr "Remove User" msgid "Removed Organization" msgstr "Removed Organization" -#: src/app/App.js:342 +#: src/app/App.js:329 #: src/app/FloatingMenu.js:230 #: src/app/SlideMessage.js:99 msgid "Report an Issue" @@ -2621,7 +2633,7 @@ msgstr "Report an Issue" msgid "Request a domain to be scanned:" msgstr "Request a domain to be scanned:" -#: src/domains/DomainsPage.js:126 +#: src/domains/DomainsPage.js:127 msgid "Request successfully sent to get all domain statuses - this may take a minute." msgstr "Request successfully sent to get all domain statuses - this may take a minute." @@ -2641,7 +2653,7 @@ msgstr "Requirements: <0>Email Management Services Configuration RequirementsWeb Sites and Services Management Configuration Requirements" msgstr "Requirements: <0>Web Sites and Services Management Configuration Requirements" -#: src/app/App.js:220 +#: src/app/App.js:178 msgid "Reset Password" msgstr "Reset Password" @@ -2680,15 +2692,15 @@ msgstr "Results for scans of email technologies (DMARC, SPF, DKIM)." msgid "Results for scans of web technologies (TLS, HTTPS)." msgstr "Results for scans of web technologies (TLS, HTTPS)." -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:433 msgid "Revoked:" msgstr "Revoked:" -#: src/admin/UserListModal.js:113 +#: src/admin/UserListModal.js:105 msgid "Role updated" msgstr "Role updated" -#: src/admin/UserListModal.js:263 +#: src/admin/UserListModal.js:246 msgid "Role:" msgstr "Role:" @@ -2697,12 +2709,12 @@ msgstr "Role:" msgid "Rotate DKIM keys annually." msgstr "Rotate DKIM keys annually." -#: src/guidance/WebTLSResults.js:399 +#: src/guidance/WebTLSResults.js:440 msgid "SAN List:" msgstr "SAN List:" -#: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:289 +#: src/domains/DomainsPage.js:163 +#: src/organizationDetails/OrganizationDomains.js:288 msgid "SPF" msgstr "SPF" @@ -2728,8 +2740,8 @@ msgstr "SPF Failures by IP Address" msgid "SPF Results" msgstr "SPF Results" -#: src/domains/DomainsPage.js:74 -#: src/organizationDetails/OrganizationDomains.js:90 +#: src/domains/DomainsPage.js:75 +#: src/organizationDetails/OrganizationDomains.js:87 msgid "SPF Status" msgstr "SPF Status" @@ -2750,11 +2762,11 @@ msgstr "SPF Status" #~ msgid "SSL scan for domain \"{0}\" has completed." #~ msgstr "SSL scan for domain \"{0}\" has completed." -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "STAGING" msgstr "STAGING" -#: src/admin/UserListModal.js:285 +#: src/admin/UserListModal.js:265 msgid "SUPER_ADMIN" msgstr "SUPER_ADMIN" @@ -2771,8 +2783,8 @@ msgstr "Save" msgid "Scan Domain" msgstr "Scan Domain" -#: src/domains/DomainCard.js:143 -#: src/guidance/GuidancePage.js:138 +#: src/domains/DomainCard.js:141 +#: src/guidance/GuidancePage.js:140 msgid "Scan Pending" msgstr "Scan Pending" @@ -2810,12 +2822,12 @@ msgstr "Search by initiated by, resource name" #: src/dmarc/DmarcByDomainPage.js:221 #: src/dmarc/DmarcByDomainPage.js:292 -#: src/domains/DomainsPage.js:228 -#: src/organizationDetails/OrganizationDomains.js:351 +#: src/domains/DomainsPage.js:189 +#: src/organizationDetails/OrganizationDomains.js:313 msgid "Search for a domain" msgstr "Search for a domain" -#: src/admin/WebCheckPage.js:192 +#: src/admin/WebCheckPage.js:174 msgid "Search for a tagged organization" msgstr "Search for a tagged organization" @@ -2834,7 +2846,7 @@ msgstr "Search for an organization" #: src/admin/AdminDomains.js:252 #: src/admin/UserList.js:149 #: src/components/ReactTableGlobalFilter.js:36 -#: src/components/SearchBox.js:67 +#: src/components/SearchBox.js:44 msgid "Search:" msgstr "Search:" @@ -2879,7 +2891,7 @@ msgstr "Selector must be either a string containing alphanumeric characters and #~ msgid "Selector must be string ending in '._domainkey'" #~ msgstr "Selector must be string ending in '._domainkey'" -#: src/guidance/WebTLSResults.js:389 +#: src/guidance/WebTLSResults.js:430 msgid "Self-signed:" msgstr "Self-signed:" @@ -2888,7 +2900,7 @@ msgstr "Self-signed:" msgid "September" msgstr "September" -#: src/guidance/WebTLSResults.js:371 +#: src/guidance/WebTLSResults.js:412 msgid "Serial:" msgstr "Serial:" @@ -2910,7 +2922,7 @@ msgstr "Show {pageSize}" msgid "Showing data for period:" msgstr "Showing data for period:" -#: src/guidance/WebTLSResults.js:285 +#: src/guidance/WebTLSResults.js:326 msgid "Shows if all the certificates in the bundle provided by the server were sent in the correct order." msgstr "Shows if all the certificates in the bundle provided by the server were sent in the correct order." @@ -2942,10 +2954,14 @@ msgstr "Shows if the HTTPS connection is live." msgid "Shows if the HTTPS endpoint downgrades to unsecured HTTP immediately, eventually, or never." msgstr "Shows if the HTTPS endpoint downgrades to unsecured HTTP immediately, eventually, or never." -#: src/guidance/WebTLSResults.js:274 +#: src/guidance/WebTLSResults.js:315 msgid "Shows if the certificate bundle provided from the server included the root certificate." msgstr "Shows if the certificate bundle provided from the server included the root certificate." +#: src/organizationDetails/OrganizationDomains.js:279 +msgid "Shows if the domain has a valid SSL certificate." +msgstr "Shows if the domain has a valid SSL certificate." + #: src/domains/DomainsPage.js:185 #: src/organizationDetails/OrganizationDomains.js:126 #~ msgid "Shows if the domain is compliant with" @@ -2961,66 +2977,74 @@ msgstr "Shows if the certificate bundle provided from the server included the ro #~ msgid "Shows if the domain is policy compliant." #~ msgstr "Shows if the domain is policy compliant." -#: src/domains/DomainsPage.js:165 -#: src/organizationDetails/OrganizationDomains.js:292 +#: src/domains/DomainsPage.js:166 +#: src/organizationDetails/OrganizationDomains.js:291 msgid "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." msgstr "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." -#: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:283 +#: src/domains/DomainsPage.js:157 +#: src/organizationDetails/OrganizationDomains.js:282 msgid "Shows if the domain meets the HSTS requirements." msgstr "Shows if the domain meets the HSTS requirements." -#: src/domains/DomainsPage.js:159 -#: src/organizationDetails/OrganizationDomains.js:286 +#: src/domains/DomainsPage.js:160 +#: src/organizationDetails/OrganizationDomains.js:285 msgid "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." msgstr "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." -#: src/domains/DomainsPage.js:169 -#: src/organizationDetails/OrganizationDomains.js:296 +#: src/domains/DomainsPage.js:170 +#: src/organizationDetails/OrganizationDomains.js:295 msgid "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." msgstr "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." -#: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:289 +#: src/domains/DomainsPage.js:163 +#: src/organizationDetails/OrganizationDomains.js:288 msgid "Shows if the domain meets the Sender Policy Framework (SPF) requirements." msgstr "Shows if the domain meets the Sender Policy Framework (SPF) requirements." -#: src/domains/DomainsPage.js:161 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/domains/DomainsPage.js:162 +#: src/organizationDetails/OrganizationDomains.js:287 msgid "Shows if the domain uses acceptable protocols." msgstr "Shows if the domain uses acceptable protocols." -#: src/domains/DomainsPage.js:154 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/domains/DomainsPage.js:155 +#: src/organizationDetails/OrganizationDomains.js:280 msgid "Shows if the domain uses only ciphers that are strong or acceptable." msgstr "Shows if the domain uses only ciphers that are strong or acceptable." -#: src/domains/DomainsPage.js:155 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/domains/DomainsPage.js:156 +#: src/organizationDetails/OrganizationDomains.js:281 msgid "Shows if the domain uses only curves that are strong or acceptable." msgstr "Shows if the domain uses only curves that are strong or acceptable." -#: src/guidance/WebTLSResults.js:243 +#: src/guidance/WebTLSResults.js:284 msgid "Shows if the hostname on the server certificate matches the the hostname from the HTTP request." msgstr "Shows if the hostname on the server certificate matches the the hostname from the HTTP request." -#: src/guidance/WebTLSResults.js:254 +#: src/guidance/WebTLSResults.js:295 msgid "Shows if the leaf certificate includes the \"OCSP Must-Staple\" extension." msgstr "Shows if the leaf certificate includes the \"OCSP Must-Staple\" extension." -#: src/guidance/WebTLSResults.js:264 +#: src/guidance/WebTLSResults.js:305 msgid "Shows if the leaf certificate is an Extended Validation Certificate." msgstr "Shows if the leaf certificate is an Extended Validation Certificate." -#: src/guidance/WebTLSResults.js:296 +#: src/guidance/WebTLSResults.js:337 msgid "Shows if the received certificates are free from the use of the deprecated SHA-1 algorithm." msgstr "Shows if the received certificates are free from the use of the deprecated SHA-1 algorithm." -#: src/guidance/WebTLSResults.js:307 +#: src/guidance/WebTLSResults.js:348 msgid "Shows if the received certificates are not relying on a distrusted Symantec root certificate." msgstr "Shows if the received certificates are not relying on a distrusted Symantec root certificate." +#: src/guidance/WebTLSResults.js:224 +msgid "Shows if the server was found to be vulnerable to the Heartbleed vulnerability." +msgstr "Shows if the server was found to be vulnerable to the Heartbleed vulnerability." + +#: src/guidance/WebTLSResults.js:237 +msgid "Shows if the server was found to be vulnerable to the ROBOT vulnerability." +msgstr "Shows if the server was found to be vulnerable to the ROBOT vulnerability." + #: src/guidance/WebConnectionResults.js:191 msgid "Shows the duration of time, in seconds, that the HSTS header is valid." msgstr "Shows the duration of time, in seconds, that the HSTS header is valid." @@ -3065,9 +3089,9 @@ msgstr "Shows the total number of emails that have been sent by this domain duri #~ msgid "Siganture Hash:" #~ msgstr "Siganture Hash:" -#: src/app/App.js:165 +#: src/app/App.js:156 #: src/app/FloatingMenu.js:197 -#: src/app/TopBanner.js:134 +#: src/app/TopBanner.js:118 #: src/auth/SignInPage.js:189 msgid "Sign In" msgstr "Sign In" @@ -3078,12 +3102,12 @@ msgid "Sign In." msgstr "Sign In." #: src/app/FloatingMenu.js:192 -#: src/app/TopBanner.js:122 +#: src/app/TopBanner.js:106 msgid "Sign Out" msgstr "Sign Out" #: src/app/FloatingMenu.js:48 -#: src/app/TopBanner.js:41 +#: src/app/TopBanner.js:40 msgid "Sign Out." msgstr "Sign Out." @@ -3091,11 +3115,11 @@ msgstr "Sign Out." #~ msgid "Sign in with your username and password." #~ msgstr "Sign in with your username and password." -#: src/guidance/WebTLSResults.js:357 +#: src/guidance/WebTLSResults.js:398 msgid "Signature Hash:" msgstr "Signature Hash:" -#: src/app/App.js:77 +#: src/app/App.js:71 msgid "Skip to main content" msgstr "Skip to main content" @@ -3103,7 +3127,7 @@ msgstr "Skip to main content" msgid "Slug:" msgstr "Slug:" -#: src/components/SearchBox.js:95 +#: src/components/SearchBox.js:66 msgid "Sort by:" msgstr "Sort by:" @@ -3111,7 +3135,7 @@ msgstr "Sort by:" msgid "Source IP Address" msgstr "Source IP Address" -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "Staging" msgstr "Staging" @@ -3119,7 +3143,7 @@ msgstr "Staging" #~ msgid "Status" #~ msgstr "Status" -#: src/organizationDetails/OrganizationDomains.js:208 +#: src/organizationDetails/OrganizationDomains.js:191 msgid "Status or tag" msgstr "Status or tag" @@ -3139,7 +3163,7 @@ msgstr "Status:" #~ msgid "Strong Curves:" #~ msgstr "Strong Curves:" -#: src/guidance/WebTLSResults.js:368 +#: src/guidance/WebTLSResults.js:409 msgid "Subject:" msgstr "Subject:" @@ -3148,7 +3172,7 @@ msgstr "Subject:" msgid "Submit" msgstr "Submit" -#: src/admin/UserListModal.js:164 +#: src/admin/UserListModal.js:154 msgid "Successfully removed user {0}." msgstr "Successfully removed user {0}." @@ -3165,7 +3189,7 @@ msgstr "Super Admin Menu:" #~ msgid "Supports ECDH Key Exchange:" #~ msgstr "Supports ECDH Key Exchange:" -#: src/app/TopBanner.js:67 +#: src/app/TopBanner.js:61 msgid "Symbol of the Government of Canada" msgstr "Symbol of the Government of Canada" @@ -3185,7 +3209,7 @@ msgstr "TBS be identified as the source; and" msgid "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." msgstr "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." -#: src/organizationDetails/OrganizationDomains.js:99 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "TEST" msgstr "TEST" @@ -3193,7 +3217,7 @@ msgstr "TEST" #~ msgid "TLS" #~ msgstr "TLS" -#: src/guidance/WebTLSResults.js:208 +#: src/guidance/WebTLSResults.js:211 msgid "TLS Results" msgstr "TLS Results" @@ -3205,7 +3229,7 @@ msgstr "TLS Scan Complete" msgid "TLS scan for domain \"{0}\" has completed." msgstr "TLS scan for domain \"{0}\" has completed." -#: src/organizationDetails/OrganizationDomains.js:174 +#: src/organizationDetails/OrganizationDomains.js:165 msgid "Tag" msgstr "Tag" @@ -3217,11 +3241,11 @@ msgstr "Technical implementation guidance:" msgid "Termination" msgstr "Termination" -#: src/app/App.js:189 +#: src/app/App.js:180 msgid "Terms & Conditions" msgstr "Terms & Conditions" -#: src/app/App.js:338 +#: src/app/App.js:325 #: src/app/FloatingMenu.js:225 #: src/app/SlideMessage.js:92 msgid "Terms & conditions" @@ -3235,7 +3259,7 @@ msgstr "Terms and Conditions" msgid "Terms of Use" msgstr "Terms of Use" -#: src/organizationDetails/OrganizationDomains.js:99 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "Test" msgstr "Test" @@ -3267,16 +3291,16 @@ msgstr "The address/domain used in the \"From\" field." msgid "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." msgstr "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." +#: src/dmarc/DmarcByDomainPage.js:324 +#: src/domains/DomainsPage.js:154 +#: src/organizationDetails/OrganizationDomains.js:278 +msgid "The domain address." +msgstr "The domain address." + #: src/dmarc/DmarcReportPage.js:231 msgid "The domains used for DKIM validation." msgstr "The domains used for DKIM validation." -#: src/dmarc/DmarcByDomainPage.js:312 -#: src/domains/DomainsPage.js:153 -#: src/organizationDetails/OrganizationDomains.js:280 -msgid "The domain address." -msgstr "The domain address." - #: src/guidance/WebTLSResults.js:60 msgid "The following ciphers are from known weak protocols and must be disabled:" msgstr "The following ciphers are from known weak protocols and must be disabled:" @@ -3317,7 +3341,7 @@ msgstr "The results of DKIM verification of the message. Can be pass, fail, neut msgid "The summary cards show two metrics that Tracker scans:" msgstr "The summary cards show two metrics that Tracker scans:" -#: src/admin/UserListModal.js:114 +#: src/admin/UserListModal.js:106 msgid "The user's role has been successfully updated" msgstr "The user's role has been successfully updated" @@ -3363,7 +3387,7 @@ msgstr "This domain no longer exists" msgid "This field cannot be empty" msgstr "This field cannot be empty" -#: src/app/TopBanner.js:106 +#: src/app/TopBanner.js:90 msgid "This is a new service, we are constantly improving." msgstr "This is a new service, we are constantly improving." @@ -3387,11 +3411,11 @@ msgstr "Time Generated (UTC)" #~ msgid "Timestamp" #~ msgstr "Timestamp" -#: src/app/App.js:127 +#: src/app/App.js:118 msgid "To enable full app functionality and maximize your account's security, <0>please verify your account." msgstr "To enable full app functionality and maximize your account's security, <0>please verify your account." -#: src/app/App.js:141 +#: src/app/App.js:132 msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "To maximize your account's security, <0>please activate a multi-factor authentication option." @@ -3435,11 +3459,11 @@ msgstr "Tracker does not automatically add selectors, so it is likely that they #~ msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." #~ msgstr "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." -#: src/app/TopBanner.js:81 +#: src/app/TopBanner.js:70 msgid "Tracker logo outline" msgstr "Tracker logo outline" -#: src/app/TopBanner.js:89 +#: src/app/TopBanner.js:73 msgid "Tracker logo text" msgstr "Tracker logo text" @@ -3468,11 +3492,11 @@ msgstr "Two-Factor Authentication:" msgid "URL:" msgstr "URL:" -#: src/admin/UserListModal.js:276 +#: src/admin/UserListModal.js:258 msgid "USER" msgstr "USER" -#: src/admin/UserListModal.js:103 +#: src/admin/UserListModal.js:95 msgid "Unable to change user role, please try again." msgstr "Unable to change user role, please try again." @@ -3489,7 +3513,7 @@ msgstr "Unable to close this account." msgid "Unable to create account, please try again." msgstr "Unable to create account, please try again." -#: src/admin/AdminDomainModal.js:98 +#: src/admin/AdminDomainModal.js:84 msgid "Unable to create new domain." msgstr "Unable to create new domain." @@ -3501,7 +3525,7 @@ msgstr "Unable to create new organization." msgid "Unable to create your account, please try again." msgstr "Unable to create your account, please try again." -#: src/admin/UserListModal.js:74 +#: src/admin/UserListModal.js:68 msgid "Unable to invite user." msgstr "Unable to invite user." @@ -3518,7 +3542,7 @@ msgstr "Unable to remove domain." msgid "Unable to remove this organization." msgstr "Unable to remove this organization." -#: src/admin/UserListModal.js:172 +#: src/admin/UserListModal.js:162 msgid "Unable to remove user." msgstr "Unable to remove user." @@ -3545,7 +3569,7 @@ msgstr "Unable to send verification email" msgid "Unable to sign in to your account, please try again." msgstr "Unable to sign in to your account, please try again." -#: src/admin/AdminDomainModal.js:147 +#: src/admin/AdminDomainModal.js:133 msgid "Unable to update domain." msgstr "Unable to update domain." @@ -3577,7 +3601,7 @@ msgstr "Unable to update to your preferred language, please try again." msgid "Unable to update to your username, please try again." msgstr "Unable to update to your username, please try again." -#: src/admin/UserListModal.js:123 +#: src/admin/UserListModal.js:115 msgid "Unable to update user role." msgstr "Unable to update user role." @@ -3597,10 +3621,15 @@ msgstr "Unable to verify your phone number, please try again." msgid "Understanding Scan Metrics:" msgstr "Understanding Scan Metrics:" -#: src/domains/DomainCard.js:84 +#: src/domains/DomainCard.js:83 msgid "Unfavourited Domain" msgstr "Unfavourited Domain" +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:256 +msgid "Unknown" +msgstr "Unknown" + #: src/summaries/RadialBarChart.js:43 #: src/summaries/SummaryGroup.js:28 #: src/summaries/SummaryGroup.js:54 @@ -3669,20 +3698,20 @@ msgstr "User List" msgid "User email does not match" msgstr "User email does not match" -#: src/admin/UserListModal.js:64 +#: src/admin/UserListModal.js:58 msgid "User invited" msgstr "User invited" -#: src/admin/UserListModal.js:163 +#: src/admin/UserListModal.js:153 msgid "User removed." msgstr "User removed." -#: src/admin/UserListModal.js:247 +#: src/admin/UserListModal.js:230 msgid "User:" msgstr "User:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:31 +#: src/admin/AdminPanel.js:29 #: src/organizationDetails/OrganizationDetails.js:143 msgid "Users" msgstr "Users" @@ -3691,7 +3720,7 @@ msgstr "Users" msgid "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" msgstr "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" -#: src/organizationDetails/OrganizationDomains.js:164 +#: src/organizationDetails/OrganizationDomains.js:155 msgid "Value" msgstr "Value" @@ -3705,11 +3734,11 @@ msgstr "Verification code must only contains numbers" msgid "Verified" msgstr "Verified" -#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:352 msgid "Verified Chain Free of Legacy Symantec Anchor" msgstr "Verified Chain Free of Legacy Symantec Anchor" -#: src/guidance/WebTLSResults.js:300 +#: src/guidance/WebTLSResults.js:341 msgid "Verified Chain Free of SHA1 Signature" msgstr "Verified Chain Free of SHA1 Signature" @@ -3729,11 +3758,11 @@ msgstr "Verify Account" msgid "View Details" msgstr "View Details" -#: src/domains/DomainCard.js:222 +#: src/domains/DomainCard.js:221 msgid "View Results" msgstr "View Results" -#: src/dmarc/DmarcReportPage.js:577 +#: src/dmarc/DmarcReportPage.js:661 msgid "Volume of messages spoofing domain (reject + quarantine + none):" msgstr "Volume of messages spoofing domain (reject + quarantine + none):" @@ -3741,11 +3770,11 @@ msgstr "Volume of messages spoofing domain (reject + quarantine + none):" #~ msgid "Volume of messages spoofing {domainSlug} (reject + quarantine + none):" #~ msgstr "Volume of messages spoofing {domainSlug} (reject + quarantine + none):" -#: src/admin/WebCheckPage.js:176 +#: src/admin/WebCheckPage.js:158 msgid "Vulnerability Scan Dashboard" msgstr "Vulnerability Scan Dashboard" -#: src/organizationDetails/OrganizationDomains.js:100 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "WEB" msgstr "WEB" @@ -3781,20 +3810,20 @@ msgstr "We've sent you an email with an authentication code to sign into Tracker #~ msgid "Weak Curves:" #~ msgstr "Weak Curves:" -#: src/organizationDetails/OrganizationDomains.js:100 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "Web" msgstr "Web" -#: src/domains/DomainCard.js:182 +#: src/domains/DomainCard.js:181 msgid "Web (HTTPS/TLS)" msgstr "Web (HTTPS/TLS)" -#: src/admin/WebCheckPage.js:173 +#: src/admin/WebCheckPage.js:155 msgid "Web Check" msgstr "Web Check" #: src/domains/ScanDomain.js:242 -#: src/guidance/GuidancePage.js:100 +#: src/guidance/GuidancePage.js:102 msgid "Web Guidance" msgstr "Web Guidance" @@ -3880,16 +3909,17 @@ msgstr "Wiki" #: src/guidance/WebConnectionResults.js:188 #: src/guidance/WebConnectionResults.js:206 #: src/guidance/WebConnectionResults.js:215 -#: src/guidance/WebTLSResults.js:250 -#: src/guidance/WebTLSResults.js:261 -#: src/guidance/WebTLSResults.js:270 -#: src/guidance/WebTLSResults.js:281 -#: src/guidance/WebTLSResults.js:292 -#: src/guidance/WebTLSResults.js:303 -#: src/guidance/WebTLSResults.js:314 -#: src/guidance/WebTLSResults.js:380 -#: src/guidance/WebTLSResults.js:389 -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:291 +#: src/guidance/WebTLSResults.js:302 +#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:322 +#: src/guidance/WebTLSResults.js:333 +#: src/guidance/WebTLSResults.js:344 +#: src/guidance/WebTLSResults.js:355 +#: src/guidance/WebTLSResults.js:421 +#: src/guidance/WebTLSResults.js:430 +#: src/guidance/WebTLSResults.js:433 msgid "Yes" msgstr "Yes" @@ -3909,12 +3939,12 @@ msgstr "You agree to protect any information disclosed to you by TBS in accordan msgid "You agree to use our website, products and services only for lawful purposes and in a manner that does not infringe the rights of, or restrict or inhibit the use and enjoyment of, the website, products or services by any third party. Additionally, you must not misuse, compromise or interfere with our services, or introduce material to our services that is malicious or technologically harmful. You must not attempt to gain unauthorized access to, tamper with, reverse engineer, or modify our website, products or services, the server(s) on which they are stored, or any server, computer or database connected to our website, products or services. We may suspend or stop providing our products or services to you if you do not comply with our terms or policies or if we are investigating suspected misconduct. Any suspected illegal use of our website, products or services may be reported to the relevant law enforcement authorities and where necessary we will co-operate with those authorities by disclosing your identity to them." msgstr "You agree to use our website, products and services only for lawful purposes and in a manner that does not infringe the rights of, or restrict or inhibit the use and enjoyment of, the website, products or services by any third party. Additionally, you must not misuse, compromise or interfere with our services, or introduce material to our services that is malicious or technologically harmful. You must not attempt to gain unauthorized access to, tamper with, reverse engineer, or modify our website, products or services, the server(s) on which they are stored, or any server, computer or database connected to our website, products or services. We may suspend or stop providing our products or services to you if you do not comply with our terms or policies or if we are investigating suspected misconduct. Any suspected illegal use of our website, products or services may be reported to the relevant law enforcement authorities and where necessary we will co-operate with those authorities by disclosing your identity to them." -#: src/domains/DomainCard.js:60 +#: src/domains/DomainCard.js:58 msgid "You have successfully added {url} to myTracker." msgstr "You have successfully added {url} to myTracker." #: src/app/FloatingMenu.js:49 -#: src/app/TopBanner.js:42 +#: src/app/TopBanner.js:41 msgid "You have successfully been signed out." msgstr "You have successfully been signed out." @@ -3927,7 +3957,7 @@ msgstr "You have successfully been signed out." msgid "You have successfully removed {0}." msgstr "You have successfully removed {0}." -#: src/domains/DomainCard.js:86 +#: src/domains/DomainCard.js:84 msgid "You have successfully removed {url} from myTracker." msgstr "You have successfully removed {url} from myTracker." @@ -3975,7 +4005,7 @@ msgstr "You may now sign in with your new password" msgid "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." msgstr "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." -#: src/app/App.js:271 +#: src/app/App.js:262 msgid "Your Account" msgstr "Your Account" @@ -4011,8 +4041,8 @@ msgstr "contact us" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:105 -#: src/app/App.js:284 +#: src/app/App.js:97 +#: src/app/App.js:275 #: src/user/MyTrackerPage.js:43 #: src/user/MyTrackerPage.js:74 msgid "myTracker" @@ -4054,7 +4084,7 @@ msgstr "user email" msgid "weak" msgstr "weak" -#: src/admin/AdminDomainModal.js:88 +#: src/admin/AdminDomainModal.js:75 msgid "{0} was added to {orgSlug}" msgstr "{0} was added to {orgSlug}" @@ -4074,11 +4104,11 @@ msgstr "{count} records..." msgid "{domainSlug} does not support aggregate data" msgstr "{domainSlug} does not support aggregate data" -#: src/admin/AdminDomainModal.js:137 +#: src/admin/AdminDomainModal.js:123 msgid "{editingDomainUrl} from {orgSlug} successfully updated to {0}" msgstr "{editingDomainUrl} from {orgSlug} successfully updated to {0}" -#: src/components/InfoPanel.js:52 +#: src/components/InfoPanel.js:47 msgid "{info}" msgstr "{info}" @@ -4086,7 +4116,7 @@ msgstr "{info}" #~ msgid "{label}" #~ msgstr "{label}" -#: src/components/InfoPanel.js:49 +#: src/components/InfoPanel.js:44 msgid "{title}" msgstr "{title}" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 317a455afd..20c9d5dda7 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -57,7 +57,7 @@ msgstr "404 - Page non trouvée" #~ msgid "6.2.3 All remaining websites and web services must be accessible through a secure connection, as outlined in Section 6.1, by December 31, 2019." #~ msgstr "6.2.3 Tous les sites web et services web restants doivent être accessibles par une connexion sécurisée, comme indiqué à la section 6.1, d'ici le 31 décembre 2019." -#: src/guidance/GuidancePage.js:68 +#: src/guidance/GuidancePage.js:70 msgid "A DNS request for this service has resulted in the following error code:" msgstr "Une requête DNS pour ce service a donné lieu au code d'erreur suivant :" @@ -77,11 +77,11 @@ msgstr "Une ventilation plus détaillée de chaque domaine peut être trouvée e msgid "A verification link has been sent to your email account" msgstr "Un lien de vérification a été envoyé à votre compte de messagerie." -#: src/admin/UserListModal.js:280 +#: src/admin/UserListModal.js:261 msgid "ADMIN" msgstr "ADMIN" -#: src/domains/DomainCard.js:170 +#: src/domains/DomainCard.js:169 msgid "ARCHIVED" msgstr "ARCHIVES" @@ -110,7 +110,7 @@ msgstr "Compte" msgid "Account Closed Successfully" msgstr "Compte clôturé avec succès" -#: src/app/App.js:110 +#: src/app/App.js:101 #: src/app/FloatingMenu.js:177 #: src/user/UserPage.js:150 msgid "Account Settings" @@ -120,7 +120,7 @@ msgstr "Paramètres du compte" msgid "Account created." msgstr "Compte créé" -#: src/admin/WebCheckPage.js:68 +#: src/admin/WebCheckPage.js:61 #: src/createOrganization/CreateOrganizationPage.js:184 #: src/createOrganization/CreateOrganizationPage.js:189 #: src/organizations/Organizations.js:61 @@ -155,7 +155,7 @@ msgstr "Action" msgid "Action:" msgstr "Action :" -#: src/admin/AdminPanel.js:36 +#: src/admin/AdminPanel.js:32 msgid "Activity" msgstr "Activité" @@ -167,15 +167,15 @@ msgstr "Ajouter" msgid "Add Domain" msgstr "Ajouter un domaine" -#: src/admin/AdminDomainModal.js:271 +#: src/admin/AdminDomainModal.js:240 msgid "Add Domain Details" msgstr "Ajouter les détails du domaine" -#: src/admin/UserListModal.js:237 +#: src/admin/UserListModal.js:220 msgid "Add User" msgstr "Ajouter un utilisateur" -#: src/app/App.js:216 +#: src/app/App.js:207 msgid "Admin" msgstr "Administrateur" @@ -183,7 +183,7 @@ msgstr "Administrateur" msgid "Admin Portal" msgstr "Portail Admin" -#: src/app/App.js:118 +#: src/app/App.js:109 msgid "Admin Profile" msgstr "Profil de l'administrateur" @@ -224,11 +224,11 @@ msgid "An error occured when you attempted to download all domain statuses." msgstr "Une erreur s'est produite lorsque vous avez tenté de télécharger tous les statuts de domaine." #: src/app/FloatingMenu.js:38 -#: src/app/TopBanner.js:31 +#: src/app/TopBanner.js:30 msgid "An error occured when you attempted to sign out" msgstr "Une erreur s'est produite lorsque vous avez tenté de vous déconnecter." -#: src/domains/DomainCard.js:49 +#: src/domains/DomainCard.js:47 msgid "An error occurred while favouriting a domain." msgstr "Une erreur s'est produite lors de la mise en favori d'un domaine." @@ -240,7 +240,7 @@ msgstr "Une erreur s'est produite lors de la suppression de cette organisation." msgid "An error occurred while requesting a scan." msgstr "Une erreur s'est produite lors de la demande d'un scan." -#: src/domains/DomainCard.js:75 +#: src/domains/DomainCard.js:73 msgid "An error occurred while unfavouriting a domain." msgstr "Une erreur s'est produite lors du dé-favorisage d'un domaine." @@ -280,11 +280,11 @@ msgstr "Une erreur s'est produite lors de la mise à jour de votre numéro de t msgid "An error occurred while verifying your phone number." msgstr "Une erreur s'est produite lors de la mise à jour de votre numéro de téléphone." -#: src/admin/AdminDomainModal.js:75 -#: src/admin/AdminDomainModal.js:124 +#: src/admin/AdminDomainModal.js:63 +#: src/admin/AdminDomainModal.js:110 #: src/admin/AdminDomains.js:103 -#: src/admin/UserListModal.js:53 -#: src/admin/UserListModal.js:151 +#: src/admin/UserListModal.js:47 +#: src/admin/UserListModal.js:141 #: src/auth/TwoFactorAuthenticatePage.js:29 #: src/createOrganization/CreateOrganizationPage.js:56 #: src/user/UserPage.js:83 @@ -307,7 +307,7 @@ msgstr "Tous les produits ou services connexes qui vous sont fournis par le SCT #~ msgid "Application Portfolio Management (APM) systems; and" #~ msgstr "les systèmes de gestion du portefeuille d’applications (GPA);" -#: src/organizationDetails/OrganizationDomains.js:237 +#: src/organizationDetails/OrganizationDomains.js:233 msgid "Apply" msgstr "Appliquer" @@ -316,11 +316,11 @@ msgstr "Appliquer" msgid "April" msgstr "Avril" -#: src/admin/AdminDomainModal.js:421 +#: src/admin/AdminDomainModal.js:368 msgid "Archive domain" msgstr "Archiver ce domaine" -#: src/admin/AdminDomainCard.js:80 +#: src/admin/AdminDomainCard.js:51 #: src/organizationDetails/OrganizationDomains.js:103 msgid "Archived" msgstr "Archivé" @@ -352,11 +352,11 @@ msgstr "Journaux d'audit" msgid "August" msgstr "Août" -#: src/app/App.js:182 +#: src/app/App.js:173 msgid "Authenticate" msgstr "Authentifier" -#: src/app/TopBanner.js:98 +#: src/app/TopBanner.js:82 msgid "BETA" msgstr "BETA" @@ -381,7 +381,7 @@ msgstr "Voici la façon dont les organisations gouvernementales peuvent tirer pa msgid "Blank fields will not be included when updating the organization." msgstr "Les champs vides ne seront pas pris en compte lors de la mise à jour de l'organisation." -#: src/domains/DomainCard.js:138 +#: src/domains/DomainCard.js:136 #: src/guidance/WebGuidance.js:83 msgid "Blocked" msgstr "Bloqué" @@ -416,18 +416,24 @@ msgstr "Les Canadiens comptent sur le gouvernement du Canada pour fournir des se msgid "Cancel" msgstr "Annuler" -#: src/guidance/WebTLSResults.js:227 +#: src/guidance/WebTLSResults.js:268 msgid "Certificate Chain" msgstr "Chaîne de certificats" -#: src/guidance/WebTLSResults.js:235 +#: src/guidance/WebTLSResults.js:276 msgid "Certificate chain info could not be found during the scan." msgstr "Les informations sur la chaîne de certificats n'ont pas pu être trouvées pendant l'analyse." -#: src/domains/DomainCard.js:187 +#: src/domains/DomainCard.js:186 +#: src/organizationDetails/OrganizationDomains.js:279 msgid "Certificates" msgstr "Certificats" +#: src/domains/DomainsPage.js:71 +#: src/organizationDetails/OrganizationDomains.js:83 +msgid "Certificates Status" +msgstr "" + #: src/auth/ResetPasswordPage.js:126 #: src/user/EditableUserPassword.js:153 msgid "Change Password" @@ -469,15 +475,15 @@ msgstr "Changements requis pour la mise en conformité ITPIN" msgid "Check your associated Tracker email for the verification link" msgstr "Vérifiez le lien de vérification dans votre courriel de suivi associé." -#: src/domains/DomainCard.js:189 -#: src/domains/DomainsPage.js:154 +#: src/domains/DomainCard.js:188 +#: src/domains/DomainsPage.js:155 #: src/guidance/WebTLSResults.js:101 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:280 msgid "Ciphers" msgstr "Ciphers" -#: src/domains/DomainsPage.js:71 -#: src/organizationDetails/OrganizationDomains.js:87 +#: src/domains/DomainsPage.js:72 +#: src/organizationDetails/OrganizationDomains.js:84 msgid "Ciphers Status" msgstr "État du chiffrement" @@ -523,7 +529,7 @@ msgstr "Le champ de code ne doit pas être vide" msgid "Collect and analyze DMARC reports." msgstr "Recueillir et analyser les rapports DMARC." -#: src/organizationDetails/OrganizationDomains.js:188 +#: src/organizationDetails/OrganizationDomains.js:175 msgid "Comparison" msgstr "Comparaison" @@ -531,12 +537,12 @@ msgstr "Comparaison" msgid "Compliant" msgstr "Conforme" -#: src/admin/AdminDomainModal.js:459 +#: src/admin/AdminDomainModal.js:391 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 #: src/admin/SuperAdminUserList.js:441 -#: src/admin/UserListModal.js:299 +#: src/admin/UserListModal.js:274 #: src/user/EditableUserDisplayName.js:168 #: src/user/EditableUserEmail.js:168 #: src/user/EditableUserPassword.js:182 @@ -562,20 +568,20 @@ msgstr "Confirmer la suppression du domaine:" #~ msgid "Confirm removal of user:" #~ msgstr "Confirmer le retrait de l'utilisateur:" -#: src/app/ReadGuidancePage.js:216 -msgid "Consider prioritizing websites and web services that exchange Protected data." -msgstr "Envisagez de donner la priorité aux sites web et aux services web qui échangent des données protégées." - #: src/guidance/WebConnectionResults.js:99 msgid "Connection Results" msgstr "Résultats de la connexion" +#: src/app/ReadGuidancePage.js:216 +msgid "Consider prioritizing websites and web services that exchange Protected data." +msgstr "Envisagez de donner la priorité aux sites web et aux services web qui échangent des données protégées." + #: src/app/FloatingMenu.js:238 msgid "Contact" msgstr "Contact" -#: src/app/App.js:191 -#: src/app/App.js:346 +#: src/app/App.js:182 +#: src/app/App.js:333 #: src/app/ContactUsPage.js:39 #: src/app/SlideMessage.js:103 msgid "Contact Us" @@ -621,18 +627,18 @@ msgid "Create" msgstr "Créer" #: src/app/FloatingMenu.js:200 -#: src/app/TopBanner.js:143 +#: src/app/TopBanner.js:127 #: src/auth/CreateUserPage.js:243 msgid "Create Account" msgstr "Créer un compte" #: src/admin/AdminPage.js:130 -#: src/app/App.js:305 +#: src/app/App.js:292 #: src/createOrganization/CreateOrganizationPage.js:237 msgid "Create Organization" msgstr "Créer une organisation" -#: src/app/App.js:159 +#: src/app/App.js:150 msgid "Create an Account" msgstr "Créer un compte" @@ -660,21 +666,21 @@ msgstr "Mot de passe actuel:" msgid "Current Phone Number:" msgstr "Numéro de téléphone actuel:" -#: src/domains/DomainCard.js:190 -#: src/domains/DomainsPage.js:155 +#: src/domains/DomainCard.js:189 +#: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 -#: src/organizationDetails/OrganizationDomains.js:282 -#: src/organizationDetails/OrganizationDomains.js:328 +#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:325 msgid "Curves" msgstr "Courbes" -#: src/domains/DomainsPage.js:72 -#: src/organizationDetails/OrganizationDomains.js:88 +#: src/domains/DomainsPage.js:73 +#: src/organizationDetails/OrganizationDomains.js:85 msgid "Curves Status" msgstr "État des courbes" -#: src/domains/DomainsPage.js:164 -#: src/organizationDetails/OrganizationDomains.js:291 +#: src/domains/DomainsPage.js:165 +#: src/organizationDetails/OrganizationDomains.js:290 msgid "DKIM" msgstr "DKIM" @@ -700,7 +706,7 @@ msgstr "Défaillances DKIM par adresse IP" msgid "DKIM Results" msgstr "Résultats DKIM" -#: src/admin/AdminDomainModal.js:325 +#: src/admin/AdminDomainModal.js:280 msgid "DKIM Selector" msgstr "Sélecteur DKIM" @@ -708,12 +714,12 @@ msgstr "Sélecteur DKIM" msgid "DKIM Selectors" msgstr "Sélecteurs DKIM" -#: src/admin/AdminDomainModal.js:288 +#: src/admin/AdminDomainModal.js:252 msgid "DKIM Selectors:" msgstr "Sélecteurs DKIM:" -#: src/domains/DomainsPage.js:75 -#: src/organizationDetails/OrganizationDomains.js:91 +#: src/domains/DomainsPage.js:76 +#: src/organizationDetails/OrganizationDomains.js:88 msgid "DKIM Status" msgstr "Statut DKIM" @@ -721,8 +727,8 @@ msgstr "Statut DKIM" #~ msgid "DKIM record could not be found for this selector." #~ msgstr "Un enregistrement DKIM n'a pas pu être trouvé pour ce sélecteur." -#: src/domains/DomainsPage.js:168 -#: src/organizationDetails/OrganizationDomains.js:295 +#: src/domains/DomainsPage.js:169 +#: src/organizationDetails/OrganizationDomains.js:294 msgid "DMARC" msgstr "DMARC" @@ -758,9 +764,9 @@ msgstr "Phase de mise en œuvre de DMARC: {0}" msgid "DMARC Phases" msgstr "Phases DMARC" -#: src/dmarc/DmarcReportPage.js:92 -#: src/domains/DomainCard.js:233 -#: src/guidance/GuidancePage.js:150 +#: src/dmarc/DmarcReportPage.js:95 +#: src/domains/DomainCard.js:232 +#: src/guidance/GuidancePage.js:152 msgid "DMARC Report" msgstr "Rapport DMARC " @@ -768,13 +774,13 @@ msgstr "Rapport DMARC " msgid "DMARC Report for {domainSlug}" msgstr "Rapport DMARC pour {domainSlug}" -#: src/domains/DomainsPage.js:76 -#: src/organizationDetails/OrganizationDomains.js:92 +#: src/domains/DomainsPage.js:77 +#: src/organizationDetails/OrganizationDomains.js:89 msgid "DMARC Status" msgstr "Statut DMARC" -#: src/app/App.js:95 -#: src/app/App.js:263 +#: src/app/App.js:89 +#: src/app/App.js:254 #: src/app/FloatingMenu.js:131 #: src/dmarc/DmarcByDomainPage.js:181 #: src/dmarc/DmarcByDomainPage.js:241 @@ -790,7 +796,7 @@ msgstr "Résumés DMARC" #~ msgid "DMARC record could not be found during the scan." #~ msgstr "L'enregistrement DMARC n'a pas pu être trouvé pendant le scan." -#: src/dmarc/DmarcReportPage.js:182 +#: src/dmarc/DmarcReportPage.js:185 msgid "DNS Host" msgstr "Hôte DNS" @@ -806,7 +812,7 @@ msgstr "Scan DNS terminé" msgid "DNS scan for domain \"{0}\" has completed." msgstr "Le scan DNS du domaine \"{0}\" est terminé." -#: src/organizationDetails/OrganizationDomains.js:194 +#: src/organizationDetails/OrganizationDomains.js:181 msgid "DOES NOT EQUAL" msgstr "N'EST PAS ÉGAL" @@ -894,11 +900,11 @@ msgstr "Disposition" #: src/admin/AuditLogTable.js:76 #: src/dmarc/DmarcByDomainPage.js:124 -#: src/dmarc/DmarcByDomainPage.js:312 +#: src/dmarc/DmarcByDomainPage.js:324 #: src/domains/DomainsPage.js:68 -#: src/domains/DomainsPage.js:153 -#: src/organizationDetails/OrganizationDomains.js:280 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/domains/DomainsPage.js:154 +#: src/organizationDetails/OrganizationDomains.js:278 +#: src/organizationDetails/OrganizationDomains.js:312 msgid "Domain" msgstr "Domaine" @@ -919,7 +925,7 @@ msgstr "URL du domaine" msgid "Domain URL:" msgstr "URL du domaine:" -#: src/admin/AdminDomainModal.js:86 +#: src/admin/AdminDomainModal.js:74 msgid "Domain added" msgstr "Domaine ajouté" @@ -935,7 +941,7 @@ msgstr "Domaine supprimé" msgid "Domain removed from {orgSlug}" msgstr "Domaine supprimé de {orgSlug}" -#: src/admin/AdminDomainModal.js:135 +#: src/admin/AdminDomainModal.js:121 msgid "Domain updated" msgstr "Domaine mis à jour" @@ -943,19 +949,19 @@ msgstr "Domaine mis à jour" msgid "Domain url field must not be empty" msgstr "Le champ de l'url du domaine ne doit pas être vide" -#: src/admin/AdminDomainCard.js:29 -#: src/admin/WebCheckPage.js:147 -#: src/domains/DomainCard.js:129 +#: src/admin/AdminDomainCard.js:16 +#: src/admin/WebCheckPage.js:129 +#: src/domains/DomainCard.js:127 #: src/domains/ScanDomain.js:211 msgid "Domain:" msgstr "Domaine:" -#: src/admin/AdminPanel.js:28 -#: src/app/App.js:92 -#: src/app/App.js:229 +#: src/admin/AdminPanel.js:26 +#: src/app/App.js:86 +#: src/app/App.js:220 #: src/app/FloatingMenu.js:116 -#: src/domains/DomainsPage.js:81 -#: src/domains/DomainsPage.js:115 +#: src/domains/DomainsPage.js:82 +#: src/domains/DomainsPage.js:116 #: src/organizationDetails/OrganizationDetails.js:139 #: src/organizationDetails/OrganizationDomains.js:108 #: src/summaries/Doughnut.js:50 @@ -976,7 +982,7 @@ msgstr "Domaines utilisés pour la validation SPF." msgid "Don't have an account? <0>Sign up" msgstr "Vous n'avez pas de compte ? <0>S'inscrire" -#: src/organizationDetails/OrganizationDomains.js:191 +#: src/organizationDetails/OrganizationDomains.js:178 msgid "EQUALS" msgstr "ÉGAUX" @@ -995,7 +1001,7 @@ msgstr "Edit" msgid "Edit Display Name" msgstr "Modifier le nom d'affichage" -#: src/admin/AdminDomainModal.js:269 +#: src/admin/AdminDomainModal.js:240 msgid "Edit Domain Details" msgstr "Modifier les détails d'un domaine" @@ -1011,19 +1017,19 @@ msgstr "Organisation d'édition" msgid "Edit Phone Number" msgstr "Modifier le numéro de téléphone" -#: src/admin/UserListModal.js:233 +#: src/admin/UserListModal.js:216 msgid "Edit User" msgstr "Modifier l'utilisateur" #: src/admin/SuperAdminUserList.js:148 #: src/components/fields/EmailField.js:15 -#: src/domains/DomainCard.js:195 +#: src/domains/DomainCard.js:194 #: src/user/EditableUserTFAMethod.js:166 msgid "Email" msgstr "Courriel" #: src/domains/ScanDomain.js:245 -#: src/guidance/GuidancePage.js:103 +#: src/guidance/GuidancePage.js:105 msgid "Email Guidance" msgstr "Conseils par courriel" @@ -1047,7 +1053,7 @@ msgstr "Courriel envoyé" msgid "Email Validated" msgstr "Courriel validé" -#: src/app/App.js:301 +#: src/app/App.js:288 msgid "Email Verification" msgstr "Vérification de l'e-mail" @@ -1057,7 +1063,7 @@ msgstr "Vérification de l'e-mail" msgid "Email cannot be empty" msgstr "Le courriel ne peut être vide" -#: src/admin/UserListModal.js:65 +#: src/admin/UserListModal.js:59 msgid "Email invitation sent" msgstr "Envoi d'une invitation par courriel" @@ -1136,7 +1142,7 @@ msgstr "Enveloppe De" msgid "Eventually" msgstr "Éventuellement" -#: src/guidance/WebTLSResults.js:380 +#: src/guidance/WebTLSResults.js:421 msgid "Expired:" msgstr "Expiré :" @@ -1148,9 +1154,9 @@ msgstr "Exportation vers CSV" #~ msgid "FAQ" #~ msgstr "FAQ" -#: src/dmarc/DmarcReportPage.js:126 -#: src/dmarc/DmarcReportPage.js:127 -#: src/organizationDetails/OrganizationDomains.js:227 +#: src/dmarc/DmarcReportPage.js:129 +#: src/dmarc/DmarcReportPage.js:130 +#: src/organizationDetails/OrganizationDomains.js:223 msgid "Fail" msgstr "Échec" @@ -1178,7 +1184,7 @@ msgstr "Échec du SPF %" msgid "Fake email domain blocks (reject + quarantine):" msgstr "Blocs de domaines de faux e-mails (rejet + quarantaine) :" -#: src/domains/DomainCard.js:59 +#: src/domains/DomainCard.js:57 msgid "Favourited Domain" msgstr "Domaine favori" @@ -1195,7 +1201,7 @@ msgstr "Février" #~ msgid "Filters" #~ msgstr "Filtres" -#: src/organizationDetails/OrganizationDomains.js:145 +#: src/organizationDetails/OrganizationDomains.js:138 msgid "Filters:" msgstr "Filtres :" @@ -1227,7 +1233,7 @@ msgstr "Pour plus de détails concernant les termes relatifs à la vie privée, msgid "For users interested in using new features that are still in progress." msgstr "Pour les utilisateurs intéressés par l'utilisation de nouvelles fonctionnalités qui sont encore en cours de développement." -#: src/app/App.js:185 +#: src/app/App.js:176 #: src/auth/ForgotPasswordPage.js:75 msgid "Forgot Password" msgstr "Mot de passe oublié" @@ -1288,11 +1294,11 @@ msgstr "Pour commencer" msgid "Getting an Account:" msgstr "Ouverture d'un compte :" -#: src/domains/DomainsPage.js:150 +#: src/domains/DomainsPage.js:126 msgid "Getting domain statuses" msgstr "Obtenir les statuts des domaines" -#: src/components/InfoPanel.js:32 +#: src/components/InfoPanel.js:27 msgid "Glossary" msgstr "Glossaire" @@ -1312,10 +1318,9 @@ msgstr "Employés du gouvernement du Canada" #~ msgid "Graph direction:" #~ msgstr "Direction du graphique :" -#: src/app/App.js:350 -#: src/app/ReadGuidancePage.js:28 -#: src/dmarc/DmarcReportPage.js:193 -#: src/dmarc/DmarcReportPage.js:606 +#: src/app/App.js:337 +#: src/dmarc/DmarcReportPage.js:196 +#: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" msgstr "Orientation" @@ -1323,7 +1328,7 @@ msgstr "Orientation" #~ msgid "Guidance Tags" #~ msgstr "Étiquettes d'orientation" -#: src/guidance/GuidancePage.js:43 +#: src/guidance/GuidancePage.js:45 msgid "Guidance results" msgstr "Résultats de l'orientation" @@ -1332,13 +1337,13 @@ msgstr "Résultats de l'orientation" #~ msgid "Guidance:" #~ msgstr "Orientation:" -#: src/domains/DomainCard.js:163 +#: src/domains/DomainCard.js:162 msgid "HIDDEN" msgstr "CACHÉ" -#: src/domains/DomainCard.js:186 -#: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:283 +#: src/domains/DomainCard.js:185 +#: src/domains/DomainsPage.js:157 +#: src/organizationDetails/OrganizationDomains.js:282 msgid "HSTS" msgstr "HSTS" @@ -1363,7 +1368,7 @@ msgid "HSTS Preloaded" msgstr "HSTS préchargé" #: src/domains/DomainsPage.js:70 -#: src/organizationDetails/OrganizationDomains.js:86 +#: src/organizationDetails/OrganizationDomains.js:82 msgid "HSTS Status" msgstr "Statut HSTS" @@ -1383,9 +1388,9 @@ msgstr "HTTP Live" msgid "HTTP Upgrades" msgstr "Mises à jour HTTP" -#: src/domains/DomainCard.js:185 -#: src/domains/DomainsPage.js:158 -#: src/organizationDetails/OrganizationDomains.js:285 +#: src/domains/DomainCard.js:184 +#: src/domains/DomainsPage.js:159 +#: src/organizationDetails/OrganizationDomains.js:284 msgid "HTTPS" msgstr "HTTPS" @@ -1415,7 +1420,7 @@ msgid "HTTPS Scan Complete" msgstr "Scan HTTPS terminé" #: src/domains/DomainsPage.js:69 -#: src/organizationDetails/OrganizationDomains.js:85 +#: src/organizationDetails/OrganizationDomains.js:81 msgid "HTTPS Status" msgstr "Statut HTTPS" @@ -1431,11 +1436,11 @@ msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS. msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "L'analyse HTTPS du domaine \"{0}\" est terminée." -#: src/guidance/WebTLSResults.js:395 +#: src/guidance/WebTLSResults.js:436 msgid "Hash Algorithm:" msgstr "Algorithme de hachage :" -#: src/dmarc/DmarcReportPage.js:188 +#: src/dmarc/DmarcReportPage.js:191 msgid "Header From" msgstr "En-tête De" @@ -1443,21 +1448,25 @@ msgstr "En-tête De" #~ msgid "Heartbleed Vulnerability:" #~ msgstr "Vulnérabilité Heartbleed:" +#: src/guidance/WebTLSResults.js:229 +msgid "Heartbleed Vulnerable" +msgstr "" + #: src/app/ReadGuidancePage.js:23 #~ msgid "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." #~ msgstr "Aidez-nous à rendre les sites Web du gouvernement plus sûrs. Veuillez suivre les étapes suivantes pour vous conformer aux normes de sécurité Web du gouvernement du Canada. Si vous avez des questions sur ce processus, veuillez <0>nous contacter." -#: src/admin/AdminDomainCard.js:68 +#: src/admin/AdminDomainCard.js:44 #: src/organizationDetails/OrganizationDomains.js:102 msgid "Hidden" msgstr "Caché" -#: src/admin/AdminDomainModal.js:398 +#: src/admin/AdminDomainModal.js:346 msgid "Hide domain" msgstr "Cacher ce domaine" -#: src/app/App.js:83 -#: src/app/App.js:155 +#: src/app/App.js:77 +#: src/app/App.js:146 #: src/app/FloatingMenu.js:175 msgid "Home" msgstr "Accueil" @@ -1470,8 +1479,7 @@ msgstr "Accueil" msgid "Host from reverse DNS of source IP address." msgstr "Hôte du DNS inversé de l'adresse IP source." -#: src/app/ReadGuidancePage.js:396 -#: src/guidance/WebTLSResults.js:247 +#: src/guidance/WebTLSResults.js:288 msgid "Hostname Matches" msgstr "Correspondance des noms d'hôtes" @@ -1479,7 +1487,7 @@ msgstr "Correspondance des noms d'hôtes" #~ msgid "Hostname Validated" #~ msgstr "Nom d'hôte validé" -#: src/app/ReadGuidancePage.js:239 +#: src/app/ReadGuidancePage.js:396 msgid "How can I edit my domain list?" msgstr "Comment puis-je modifier ma liste de domaines?" @@ -1487,7 +1495,7 @@ msgstr "Comment puis-je modifier ma liste de domaines?" msgid "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" msgstr "J'accepte toutes les <0>Conditions générales, la politique de confidentialité et les directives du code de conduite<1/>." -#: src/organizationDetails/OrganizationDomains.js:101 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "INACTIVE" msgstr "INACTIF" @@ -1540,7 +1548,7 @@ msgstr "Si, à tout moment, vous ou vos représentants souhaitez ajuster ou annu #~ msgid "If at any time you or your representatives wish to adjust or cancel these services, please contact us at" #~ msgstr "Si, à tout moment, vous ou vos représentants souhaitez adapter ou annuler ces services, veuillez nous contacter à l'adresse suivante" -#: src/guidance/GuidancePage.js:73 +#: src/guidance/GuidancePage.js:75 msgid "If you believe this could be the result of an issue with the scan, rescan the service using the refresh button. If you believe this is because the service no longer exists (NXDOMAIN), this domain should be removed from all affiliated organizations." msgstr "Si vous pensez que cela peut être le résultat d'un problème avec l'analyse, réanalysez le service en utilisant le bouton d'actualisation. Si vous pensez que c'est parce que le service n'existe plus (NXDOMAIN), ce domaine doit être supprimé de toutes les organisations affiliées." @@ -1552,15 +1560,15 @@ msgstr "Si vous pensez que cela a été causé par un problème avec Tracker, ve #~ msgid "If you believe this was caused by a problem with Tracker, please use the \"Report an Issue\" link below" #~ msgstr "Si vous pensez que cela est dû à un problème avec Suivi, veuillez utiliser le lien \"Signaler un problème\" ci-dessous" +#: src/app/ReadGuidancePage.js:88 +msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." +msgstr "Si votre organisation n'a pas d'utilisateurs affiliés à Suivi, contactez l’<0>équipe responsable de la cybersécurité du SCT pour vous aider à l'intégrer." + #: src/guidance/WebConnectionResults.js:138 #: src/guidance/WebConnectionResults.js:178 msgid "Immediately" msgstr "Immédiatement" -#: src/app/ReadGuidancePage.js:88 -msgid "If your organization has no affiliated users within Tracker, contact the <0>TBS Cyber Security to assist in onboarding." -msgstr "Si votre organisation n'a pas d'utilisateurs affiliés à Suivi, contactez l’<0>équipe responsable de la cybersécurité du SCT pour vous aider à l'intégrer." - #: src/guidance/WebGuidance.js:19 #~ msgid "Implementation" #~ msgstr "Mise en œuvre" @@ -1585,7 +1593,7 @@ msgstr "Mise en œuvre : <0>Conseils de mise en œuvre : protection du domaine d msgid "Implemented" msgstr "Mis en œuvre" -#: src/organizationDetails/OrganizationDomains.js:101 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "Inactive" msgstr "Inactif" @@ -1598,7 +1606,7 @@ msgstr "Incorrect authenticate.result typename." msgid "Incorrect closeAccount.result typename." msgstr "Incorrect closeAccount.result typename." -#: src/admin/AdminDomainModal.js:108 +#: src/admin/AdminDomainModal.js:94 msgid "Incorrect createDomain.result typename." msgstr "Incorrect createDomain.result typename." @@ -1606,7 +1614,7 @@ msgstr "Incorrect createDomain.result typename." msgid "Incorrect createOrganization.result typename." msgstr "createOrganization.result incorrecte typename." -#: src/admin/UserListModal.js:84 +#: src/admin/UserListModal.js:78 msgid "Incorrect inviteUserToOrg.result typename." msgstr "Incorrect inviteUserToOrg.result typename." @@ -1627,12 +1635,12 @@ msgstr "Incorrect removeOrganization.result typename." msgid "Incorrect resetPassword.result typename." msgstr "Incorrect resetPassword.result typename." -#: src/admin/AdminDomainModal.js:107 -#: src/admin/AdminDomainModal.js:156 +#: src/admin/AdminDomainModal.js:93 +#: src/admin/AdminDomainModal.js:142 #: src/admin/AdminDomains.js:133 #: src/admin/SuperAdminUserList.js:110 -#: src/admin/UserListModal.js:83 -#: src/admin/UserListModal.js:132 +#: src/admin/UserListModal.js:77 +#: src/admin/UserListModal.js:124 #: src/auth/CreateUserPage.js:83 #: src/auth/ResetPasswordPage.js:60 #: src/auth/SignInPage.js:100 @@ -1670,7 +1678,7 @@ msgstr "Incorrect typename received." msgid "Incorrect update method received." msgstr "Méthode de mise à jour incorrecte reçue." -#: src/admin/AdminDomainModal.js:157 +#: src/admin/AdminDomainModal.js:143 msgid "Incorrect updateDomain.result typename." msgstr "Incorrect updateDomain.result typename." @@ -1690,7 +1698,7 @@ msgstr "Incorrect updateUserPassword.result typename." msgid "Incorrect updateUserProfile.result typename." msgstr "Incorrect updateUserProfile.result typename." -#: src/admin/UserListModal.js:133 +#: src/admin/UserListModal.js:125 msgid "Incorrect updateUserRole.result typename." msgstr "Incorrect updateUserRole.result typename." @@ -1714,7 +1722,7 @@ msgstr "Les personnes d'un groupe ministériel de technologie de l'information p #~ msgid "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." #~ msgstr "Les personnes ayant des questions sur l'exactitude des données de conformité de leur domaine peuvent contacter la boîte aux lettres de la cybersécurité du SCT." -#: src/organizationDetails/OrganizationDomains.js:224 +#: src/organizationDetails/OrganizationDomains.js:220 msgid "Info" msgstr "Info" @@ -1786,12 +1794,11 @@ msgstr "Est aligné sur la norme DKIM. Peut être vrai ou faux." msgid "Is SPF aligned. Can be true or false." msgstr "Est aligné sur le SPF. Peut être vrai ou faux." -#: src/app/ReadGuidancePage.js:378 -#: src/guidance/WebTLSResults.js:383 +#: src/guidance/WebTLSResults.js:424 msgid "Issuer:" msgstr "Émetteur :" -#: src/app/ReadGuidancePage.js:221 +#: src/app/ReadGuidancePage.js:378 msgid "It is not clear to me why a domain has failed?" msgstr "Je ne comprends pas pourquoi un domaine a échoué." @@ -1853,11 +1860,11 @@ msgstr "Les 30 derniers jours" #~ msgid "Last Scanned" #~ msgstr "Dernière numérisation" -#: src/admin/WebCheckPage.js:154 +#: src/admin/WebCheckPage.js:136 msgid "Last Scanned:" msgstr "Dernier balayage :" -#: src/guidance/WebTLSResults.js:267 +#: src/guidance/WebTLSResults.js:308 msgid "Leaf Certificate is EV" msgstr "Le certificat Leaf est EV" @@ -1900,14 +1907,14 @@ msgstr "Connexion" msgid "Login to your account" msgstr "Connectez-vous à votre compte" -#: src/app/ReadGuidancePage.js:117 -msgid "Managing Your Domains:" -msgstr "Gérer vos domaines :" - #: src/guidance/EmailGuidance.js:178 msgid "Lookups:" msgstr "Les recherches :" +#: src/app/ReadGuidancePage.js:117 +msgid "Managing Your Domains:" +msgstr "Gérer vos domaines :" + #: src/components/MonthSelect.js:19 #: src/utilities/months.js:6 msgid "March" @@ -1937,23 +1944,23 @@ msgstr "Surveiller les rapports DMARC et corriger les erreurs de configuration." msgid "Monitor DMARC reports;" msgstr "Surveiller les rapports DMARC." +#: src/guidance/WebTLSResults.js:402 +msgid "More details" +msgstr "Plus de détails" + #: src/app/ReadGuidancePage.js:604 msgid "Mozilla SSL Configuration Generator" msgstr "Générateur de configuration SSL de Mozilla" -#: src/guidance/WebTLSResults.js:361 -msgid "More details" -msgstr "Plus de détails" - -#: src/guidance/WebTLSResults.js:258 +#: src/guidance/WebTLSResults.js:299 msgid "Must Staple" msgstr "Agrafe obligatoire" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:93 msgid "NEW" msgstr "NOUVEAU" -#: src/admin/WebCheckPage.js:67 +#: src/admin/WebCheckPage.js:60 #: src/createOrganization/CreateOrganizationPage.js:173 #: src/createOrganization/CreateOrganizationPage.js:178 #: src/organizations/Organizations.js:60 @@ -1972,7 +1979,7 @@ msgstr "Nom (FR)" msgid "Name:" msgstr "Nom:" -#: src/guidance/WebTLSResults.js:365 +#: src/guidance/WebTLSResults.js:406 msgid "Names:" msgstr "Noms :" @@ -1998,7 +2005,7 @@ msgstr "Négatif" msgid "Never" msgstr "Jamais" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:93 msgid "New" msgstr "Nouveau" @@ -2006,11 +2013,11 @@ msgstr "Nouveau" msgid "New Display Name:" msgstr "Nouveau nom d'affichage:" -#: src/admin/AdminDomainModal.js:280 +#: src/admin/AdminDomainModal.js:245 msgid "New Domain URL" msgstr "Nouvelle URL de domaine" -#: src/admin/AdminDomainModal.js:279 +#: src/admin/AdminDomainModal.js:245 msgid "New Domain URL:" msgstr "Nouvelle URL de domaine:" @@ -2039,16 +2046,17 @@ msgstr "Nouvelle valeur :" #: src/guidance/WebConnectionResults.js:188 #: src/guidance/WebConnectionResults.js:206 #: src/guidance/WebConnectionResults.js:215 -#: src/guidance/WebTLSResults.js:250 -#: src/guidance/WebTLSResults.js:261 -#: src/guidance/WebTLSResults.js:270 -#: src/guidance/WebTLSResults.js:281 -#: src/guidance/WebTLSResults.js:292 -#: src/guidance/WebTLSResults.js:303 -#: src/guidance/WebTLSResults.js:314 -#: src/guidance/WebTLSResults.js:380 -#: src/guidance/WebTLSResults.js:389 -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:291 +#: src/guidance/WebTLSResults.js:302 +#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:322 +#: src/guidance/WebTLSResults.js:333 +#: src/guidance/WebTLSResults.js:344 +#: src/guidance/WebTLSResults.js:355 +#: src/guidance/WebTLSResults.js:421 +#: src/guidance/WebTLSResults.js:430 +#: src/guidance/WebTLSResults.js:433 msgid "No" msgstr "Non" @@ -2061,8 +2069,8 @@ msgid "No DMARC phase information available for this organization." msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organisation." #: src/admin/AdminDomains.js:156 -#: src/domains/DomainsPage.js:88 -#: src/organizationDetails/OrganizationDomains.js:251 +#: src/domains/DomainsPage.js:89 +#: src/organizationDetails/OrganizationDomains.js:246 msgid "No Domains" msgstr "Aucun domaine" @@ -2070,7 +2078,7 @@ msgstr "Aucun domaine" msgid "No HTTPS configuration information available for this organization." msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." -#: src/admin/WebCheckPage.js:101 +#: src/admin/WebCheckPage.js:94 #: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "Aucune organisation" @@ -2107,12 +2115,12 @@ msgstr "Pas de données pour le tableau Entièrement aligné par adresse IP" msgid "No data for the SPF Failures by IP Address table" msgstr "Aucune donnée pour le tableau des défaillances du SPF par adresse IP" -#: src/domains/DomainsPage.js:135 -#: src/domains/DomainsPage.js:143 +#: src/domains/DomainsPage.js:136 +#: src/domains/DomainsPage.js:144 msgid "No data found" msgstr "Aucune donnée trouvée" -#: src/domains/DomainsPage.js:136 +#: src/domains/DomainsPage.js:137 msgid "No data found when retrieving all domain statuses." msgstr "Aucune donnée n'a été trouvée lors de la récupération de tous les statuts de domaine." @@ -2136,7 +2144,7 @@ msgstr "Aucun protocole faible connu n'a été utilisé." msgid "No scan data for this organization." msgstr "Aucune donnée d'analyse pour cette organisation." -#: src/guidance/GuidancePage.js:88 +#: src/guidance/GuidancePage.js:90 msgid "No scan data is currently available for this service. You may request a scan using the refresh button, or wait up to 24 hours for data to refresh." msgstr "Aucune donnée de balayage n'est actuellement disponible pour ce service. Vous pouvez demander un scan en utilisant le bouton d'actualisation, ou attendre jusqu'à 24 heures pour que les données soient actualisées." @@ -2157,12 +2165,12 @@ msgstr "Non conforme" msgid "None" msgstr "Aucun" -#: src/guidance/WebTLSResults.js:352 -#: src/guidance/WebTLSResults.js:377 +#: src/guidance/WebTLSResults.js:393 +#: src/guidance/WebTLSResults.js:418 msgid "Not After:" msgstr "Pas après :" -#: src/guidance/WebTLSResults.js:374 +#: src/guidance/WebTLSResults.js:415 msgid "Not Before:" msgstr "Pas avant :" @@ -2174,11 +2182,11 @@ msgstr "Non mis en œuvre" msgid "Note that compliance data does not automatically refresh. Modifications to domains could take 24 hours to update." msgstr "Notez que les données de conformité ne sont pas automatiquement actualisées. La mise à jour des modifications apportées aux domaines peut prendre 24 heures." -#: src/admin/AdminDomainModal.js:432 +#: src/admin/AdminDomainModal.js:376 msgid "Note: This could affect results for multiple organizations" msgstr "Note : Cela pourrait affecter les résultats de plusieurs organisations" -#: src/admin/AdminDomainModal.js:427 +#: src/admin/AdminDomainModal.js:374 msgid "Note: This will affect results for {orgCount} organizations" msgstr "Note : Ceci affectera les résultats pour les organisations {orgCount}." @@ -2267,26 +2275,26 @@ msgstr "Le nom de l'organisation ne correspond pas." msgid "Organization not updated" msgstr "Organisation non mise à jour" -#: src/guidance/GuidancePage.js:157 +#: src/guidance/GuidancePage.js:159 msgid "Organization(s):" msgstr "Organisation(s) :" #: src/admin/AdminPage.js:77 #: src/admin/AdminPage.js:93 -#: src/admin/UserListModal.js:255 +#: src/admin/UserListModal.js:238 msgid "Organization:" msgstr "Organisation:" #: src/admin/AdminPage.js:189 -#: src/app/App.js:89 -#: src/app/App.js:195 +#: src/app/App.js:83 +#: src/app/App.js:186 #: src/app/FloatingMenu.js:103 #: src/organizations/Organizations.js:72 #: src/organizations/Organizations.js:109 msgid "Organizations" msgstr "Organisations" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "PROD" msgstr "PROD" @@ -2294,9 +2302,9 @@ msgstr "PROD" msgid "Page {0} of {1}" msgstr "Page {0} de {1}" -#: src/dmarc/DmarcReportPage.js:117 -#: src/dmarc/DmarcReportPage.js:118 -#: src/organizationDetails/OrganizationDomains.js:221 +#: src/dmarc/DmarcReportPage.js:120 +#: src/dmarc/DmarcReportPage.js:121 +#: src/organizationDetails/OrganizationDomains.js:217 msgid "Pass" msgstr "Passez" @@ -2378,7 +2386,7 @@ msgstr "Le champ du numéro de téléphone ne doit pas être vide" msgid "Phone number must be a valid phone number that is 10-15 digits long" msgstr "Le numéro de téléphone doit être un numéro de téléphone valide de 10 à 15 chiffres." -#: src/admin/AdminDomainModal.js:442 +#: src/admin/AdminDomainModal.js:382 msgid "Please allow up to 24 hours for summaries to reflect any changes." msgstr "Veuillez prévoir jusqu'à 24 heures pour que les résumés reflètent les changements éventuels." @@ -2433,7 +2441,7 @@ msgstr "Positif" #~ msgid "Preloaded Status:" #~ msgstr "Statut préchargé:" -#: src/admin/AdminDomainModal.js:384 +#: src/admin/AdminDomainModal.js:333 msgid "Prevent this domain from being counted in your organization's summaries." msgstr "Empêchez ce domaine d'être comptabilisé dans les résumés de votre organisation." @@ -2441,7 +2449,7 @@ msgstr "Empêchez ce domaine d'être comptabilisé dans les résumés de votre o #~ msgid "Prevent this domain from being scanned and being counted in any summaries." #~ msgstr "Empêchez ce domaine d'être scanné et d'être compté dans les résumés." -#: src/admin/AdminDomainModal.js:406 +#: src/admin/AdminDomainModal.js:353 msgid "Prevent this domain from being visible, scanned, and being counted in any summaries." msgstr "Empêchez ce domaine d'être visible, d'être scanné et d'être compté dans les résumés." @@ -2449,7 +2457,7 @@ msgstr "Empêchez ce domaine d'être visible, d'être scanné et d'être compté #~ msgid "Previous" #~ msgstr "Précédent" -#: src/app/App.js:334 +#: src/app/App.js:321 #: src/app/FloatingMenu.js:219 #: src/app/SlideMessage.js:88 #: src/termsConditions/TermsConditionsPage.js:41 @@ -2464,7 +2472,7 @@ msgstr "Loi sur la protection de la vie privée." msgid "Privacy Notice Statement" msgstr "Déclaration de confidentialité" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "Prod" msgstr "Prod" @@ -2472,16 +2480,16 @@ msgstr "Prod" msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" msgstr "Protéger les domaines qui n'envoient pas de courrier électronique - GOV.UK (www.gov.uk)" -#: src/domains/DomainCard.js:188 -#: src/domains/DomainsPage.js:161 +#: src/domains/DomainCard.js:187 +#: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 -#: src/organizationDetails/OrganizationDomains.js:288 -#: src/organizationDetails/OrganizationDomains.js:329 +#: src/organizationDetails/OrganizationDomains.js:287 +#: src/organizationDetails/OrganizationDomains.js:326 msgid "Protocols" msgstr "Protocoles" -#: src/domains/DomainsPage.js:73 -#: src/organizationDetails/OrganizationDomains.js:89 +#: src/domains/DomainsPage.js:74 +#: src/organizationDetails/OrganizationDomains.js:86 msgid "Protocols Status" msgstr "Statut des protocoles" @@ -2510,11 +2518,15 @@ msgstr "Province (FR)" msgid "Province:" msgstr "Province:" +#: src/guidance/WebTLSResults.js:253 +msgid "ROBOT Vulnerable" +msgstr "" + #: src/app/ReadGuidancePage.js:259 #~ msgid "Read Guidance" #~ msgstr "Conseils de lecture" -#: src/app/App.js:193 +#: src/app/App.js:184 msgid "Read guidance" msgstr "Conseils de lecture" @@ -2523,11 +2535,11 @@ msgstr "Conseils de lecture" msgid "Reason" msgstr "Raison" -#: src/guidance/WebTLSResults.js:278 +#: src/guidance/WebTLSResults.js:319 msgid "Received Chain Contains Anchor Certificate" msgstr "La chaîne reçue contient le certificat d'ancrage" -#: src/guidance/WebTLSResults.js:289 +#: src/guidance/WebTLSResults.js:330 msgid "Received Chain Has Valid Order" msgstr "La chaîne reçue a un ordre valide" @@ -2537,7 +2549,7 @@ msgstr "La chaîne reçue a un ordre valide" msgid "Record:" msgstr "Record :" -#: src/app/ReadGuidancePage.js:355 +#: src/app/ReadGuidancePage.js:555 msgid "References:" msgstr "Références :" @@ -2567,7 +2579,7 @@ msgstr "Supprimer un domaine" msgid "Remove Organization" msgstr "Supprimer l'organisation" -#: src/admin/UserListModal.js:235 +#: src/admin/UserListModal.js:218 msgid "Remove User" msgstr "Supprimer l'utilisateur" @@ -2575,7 +2587,7 @@ msgstr "Supprimer l'utilisateur" msgid "Removed Organization" msgstr "Organisation supprimée" -#: src/app/App.js:342 +#: src/app/App.js:329 #: src/app/FloatingMenu.js:230 #: src/app/SlideMessage.js:99 msgid "Report an Issue" @@ -2585,7 +2597,7 @@ msgstr "Signaler un problème" msgid "Request a domain to be scanned:" msgstr "Demander qu'un domaine soit scanné:" -#: src/domains/DomainsPage.js:126 +#: src/domains/DomainsPage.js:127 msgid "Request successfully sent to get all domain statuses - this may take a minute." msgstr "La requête a été envoyée avec succès pour obtenir les statuts de tous les domaines - cela peut prendre une minute." @@ -2605,7 +2617,7 @@ msgstr "Exigences : <0>Configuration requise pour les services de gestion du cou msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" msgstr "Exigences : <0>Exigences de configuration de la gestion des sites et services web" -#: src/app/App.js:220 +#: src/app/App.js:178 msgid "Reset Password" msgstr "Réinitialiser le mot de passe" @@ -2644,15 +2656,15 @@ msgstr "Résultats des analyses des technologies du courrier électronique (DMAR msgid "Results for scans of web technologies (TLS, HTTPS)." msgstr "Résultats pour les analyses des technologies web (TLS, HTTPS)." -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:433 msgid "Revoked:" msgstr "Révoqué :" -#: src/admin/UserListModal.js:113 +#: src/admin/UserListModal.js:105 msgid "Role updated" msgstr "Rôle mis à jour" -#: src/admin/UserListModal.js:263 +#: src/admin/UserListModal.js:246 msgid "Role:" msgstr "Fonction:" @@ -2661,12 +2673,12 @@ msgstr "Fonction:" msgid "Rotate DKIM keys annually." msgstr "Effectuer la rotation des clés DKIM annuellement." -#: src/guidance/WebTLSResults.js:399 +#: src/guidance/WebTLSResults.js:440 msgid "SAN List:" msgstr "Liste des SAN :" -#: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:289 +#: src/domains/DomainsPage.js:163 +#: src/organizationDetails/OrganizationDomains.js:288 msgid "SPF" msgstr "SPF" @@ -2692,8 +2704,8 @@ msgstr "Défaillances du SPF par adresse IP" msgid "SPF Results" msgstr "Résultats du SPF" -#: src/domains/DomainsPage.js:74 -#: src/organizationDetails/OrganizationDomains.js:90 +#: src/domains/DomainsPage.js:75 +#: src/organizationDetails/OrganizationDomains.js:87 msgid "SPF Status" msgstr "Statut SPF" @@ -2714,11 +2726,11 @@ msgstr "Statut SPF" #~ msgid "SSL scan for domain \"{0}\" has completed." #~ msgstr "Le scan SSL pour le domaine \"{0}\" est terminé." -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "STAGING" msgstr "DEV" -#: src/admin/UserListModal.js:285 +#: src/admin/UserListModal.js:265 msgid "SUPER_ADMIN" msgstr "SUPER_ADMIN" @@ -2735,8 +2747,8 @@ msgstr "Sauvez" msgid "Scan Domain" msgstr "Domaine de balayage" -#: src/domains/DomainCard.js:143 -#: src/guidance/GuidancePage.js:138 +#: src/domains/DomainCard.js:141 +#: src/guidance/GuidancePage.js:140 msgid "Scan Pending" msgstr "Scan en attente" @@ -2774,12 +2786,12 @@ msgstr "Recherche par initié par, nom de la ressource" #: src/dmarc/DmarcByDomainPage.js:221 #: src/dmarc/DmarcByDomainPage.js:292 -#: src/domains/DomainsPage.js:228 -#: src/organizationDetails/OrganizationDomains.js:351 +#: src/domains/DomainsPage.js:189 +#: src/organizationDetails/OrganizationDomains.js:313 msgid "Search for a domain" msgstr "Rechercher un domaine" -#: src/admin/WebCheckPage.js:192 +#: src/admin/WebCheckPage.js:174 msgid "Search for a tagged organization" msgstr "Recherche d'une organisation étiquetée" @@ -2798,7 +2810,7 @@ msgstr "Rechercher une organisation" #: src/admin/AdminDomains.js:252 #: src/admin/UserList.js:149 #: src/components/ReactTableGlobalFilter.js:36 -#: src/components/SearchBox.js:67 +#: src/components/SearchBox.js:44 msgid "Search:" msgstr "Recherche:" @@ -2843,7 +2855,7 @@ msgstr "Le sélecteur doit être soit une chaîne contenant des caractères alph #~ msgid "Selector must be string ending in '._domainkey'" #~ msgstr "Le sélecteur doit être une chaîne se terminant par '._domainkey'" -#: src/guidance/WebTLSResults.js:389 +#: src/guidance/WebTLSResults.js:430 msgid "Self-signed:" msgstr "Auto-signé :" @@ -2852,7 +2864,7 @@ msgstr "Auto-signé :" msgid "September" msgstr "Septembre" -#: src/guidance/WebTLSResults.js:371 +#: src/guidance/WebTLSResults.js:412 msgid "Serial:" msgstr "En série :" @@ -2874,7 +2886,7 @@ msgstr "Voir {pageSize}" msgid "Showing data for period:" msgstr "Affichage des données pour la période:" -#: src/guidance/WebTLSResults.js:285 +#: src/guidance/WebTLSResults.js:326 msgid "Shows if all the certificates in the bundle provided by the server were sent in the correct order." msgstr "Indique si tous les certificats du paquet fourni par le serveur ont été envoyés dans le bon ordre." @@ -2906,10 +2918,14 @@ msgstr "Indique si la connexion HTTPS est active." msgid "Shows if the HTTPS endpoint downgrades to unsecured HTTP immediately, eventually, or never." msgstr "Indique si le point de terminaison HTTPS passe en HTTP non sécurisé immédiatement, éventuellement ou jamais." -#: src/guidance/WebTLSResults.js:274 +#: src/guidance/WebTLSResults.js:315 msgid "Shows if the certificate bundle provided from the server included the root certificate." msgstr "Indique si le paquet de certificats fourni par le serveur comprend le certificat racine." +#: src/organizationDetails/OrganizationDomains.js:279 +msgid "Shows if the domain has a valid SSL certificate." +msgstr "" + #: src/domains/DomainsPage.js:185 #: src/organizationDetails/OrganizationDomains.js:126 #~ msgid "Shows if the domain is compliant with" @@ -2925,18 +2941,18 @@ msgstr "Indique si le paquet de certificats fourni par le serveur comprend le ce #~ msgid "Shows if the domain is policy compliant." #~ msgstr "Indique si le domaine est conforme à la politique." -#: src/domains/DomainsPage.js:165 -#: src/organizationDetails/OrganizationDomains.js:292 +#: src/domains/DomainsPage.js:166 +#: src/organizationDetails/OrganizationDomains.js:291 msgid "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." msgstr "Indique si le domaine répond aux exigences de DomainKeys Identified Mail (DKIM)." -#: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:283 +#: src/domains/DomainsPage.js:157 +#: src/organizationDetails/OrganizationDomains.js:282 msgid "Shows if the domain meets the HSTS requirements." msgstr "Indique si le domaine répond aux exigences du HSTS." -#: src/domains/DomainsPage.js:159 -#: src/organizationDetails/OrganizationDomains.js:286 +#: src/domains/DomainsPage.js:160 +#: src/organizationDetails/OrganizationDomains.js:285 msgid "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." msgstr "Indique si le domaine répond aux exigences du protocole de transfert hypertexte sécurisé (HTTPS)." @@ -2946,51 +2962,59 @@ msgstr "Indique si le domaine répond aux exigences du protocole de transfert hy #~ msgid "Shows if the domain meets the Hypertext Transfer ol Secure (HTTPS) requirements." #~ msgstr "Indique si le domaine répond aux exigences de Hypertext Transfer ol Secure (HTTPS)." -#: src/domains/DomainsPage.js:169 -#: src/organizationDetails/OrganizationDomains.js:296 +#: src/domains/DomainsPage.js:170 +#: src/organizationDetails/OrganizationDomains.js:295 msgid "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." msgstr "Indique si le domaine répond aux exigences de Message Authentication, Reporting, and Conformance (DMARC)." -#: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:289 +#: src/domains/DomainsPage.js:163 +#: src/organizationDetails/OrganizationDomains.js:288 msgid "Shows if the domain meets the Sender Policy Framework (SPF) requirements." msgstr "Indique si le domaine répond aux exigences du Sender Policy Framework (SPF)." -#: src/domains/DomainsPage.js:161 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/domains/DomainsPage.js:162 +#: src/organizationDetails/OrganizationDomains.js:287 msgid "Shows if the domain uses acceptable protocols." msgstr "Indique si le domaine utilise des protocoles acceptables." -#: src/domains/DomainsPage.js:154 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/domains/DomainsPage.js:155 +#: src/organizationDetails/OrganizationDomains.js:280 msgid "Shows if the domain uses only ciphers that are strong or acceptable." msgstr "Indique si le domaine utilise uniquement des ciphers forts ou acceptables." -#: src/domains/DomainsPage.js:155 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/domains/DomainsPage.js:156 +#: src/organizationDetails/OrganizationDomains.js:281 msgid "Shows if the domain uses only curves that are strong or acceptable." msgstr "Indique si le domaine utilise uniquement des courbes fortes ou acceptables" -#: src/guidance/WebTLSResults.js:243 +#: src/guidance/WebTLSResults.js:284 msgid "Shows if the hostname on the server certificate matches the the hostname from the HTTP request." msgstr "Indique si le nom d'hôte figurant sur le certificat du serveur correspond au nom d'hôte figurant dans la requête HTTP." -#: src/guidance/WebTLSResults.js:254 +#: src/guidance/WebTLSResults.js:295 msgid "Shows if the leaf certificate includes the \"OCSP Must-Staple\" extension." msgstr "Indique si le certificat feuille comprend l'extension \"OCSP Must-Staple\"." -#: src/guidance/WebTLSResults.js:264 +#: src/guidance/WebTLSResults.js:305 msgid "Shows if the leaf certificate is an Extended Validation Certificate." msgstr "Indique si le certificat de la feuille est un certificat de validation étendue." -#: src/guidance/WebTLSResults.js:296 +#: src/guidance/WebTLSResults.js:337 msgid "Shows if the received certificates are free from the use of the deprecated SHA-1 algorithm." msgstr "Indique si les certificats reçus sont exempts de l'utilisation de l'algorithme SHA-1 déprécié." -#: src/guidance/WebTLSResults.js:307 +#: src/guidance/WebTLSResults.js:348 msgid "Shows if the received certificates are not relying on a distrusted Symantec root certificate." msgstr "Indique si les certificats reçus ne reposent pas sur un certificat racine Symantec douteux." +#: src/guidance/WebTLSResults.js:224 +msgid "Shows if the server was found to be vulnerable to the Heartbleed vulnerability." +msgstr "" + +#: src/guidance/WebTLSResults.js:237 +msgid "Shows if the server was found to be vulnerable to the ROBOT vulnerability." +msgstr "" + #: src/guidance/WebConnectionResults.js:191 msgid "Shows the duration of time, in seconds, that the HSTS header is valid." msgstr "Indique la durée, en secondes, pendant laquelle l'en-tête HSTS est valide." @@ -3035,9 +3059,9 @@ msgstr "Indique le nombre total d'e-mails qui ont été envoyés par ce domaine #~ msgid "Siganture Hash:" #~ msgstr "Siganture Hash :" -#: src/app/App.js:165 +#: src/app/App.js:156 #: src/app/FloatingMenu.js:197 -#: src/app/TopBanner.js:134 +#: src/app/TopBanner.js:118 #: src/auth/SignInPage.js:189 msgid "Sign In" msgstr "Se connecter" @@ -3048,12 +3072,12 @@ msgid "Sign In." msgstr "Se connecter." #: src/app/FloatingMenu.js:192 -#: src/app/TopBanner.js:122 +#: src/app/TopBanner.js:106 msgid "Sign Out" msgstr "Déconnexion" #: src/app/FloatingMenu.js:48 -#: src/app/TopBanner.js:41 +#: src/app/TopBanner.js:40 msgid "Sign Out." msgstr "Déconnexion." @@ -3061,11 +3085,11 @@ msgstr "Déconnexion." #~ msgid "Sign in with your username and password." #~ msgstr "Connectez-vous avec votre nom d'utilisateur et votre mot de passe." -#: src/guidance/WebTLSResults.js:357 +#: src/guidance/WebTLSResults.js:398 msgid "Signature Hash:" msgstr "Signature Hash :" -#: src/app/App.js:77 +#: src/app/App.js:71 msgid "Skip to main content" msgstr "Passer au contenu principal" @@ -3073,7 +3097,7 @@ msgstr "Passer au contenu principal" msgid "Slug:" msgstr "Slug:" -#: src/components/SearchBox.js:95 +#: src/components/SearchBox.js:66 msgid "Sort by:" msgstr "Trier par:" @@ -3081,11 +3105,11 @@ msgstr "Trier par:" msgid "Source IP Address" msgstr "Adresse IP source" -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "Staging" msgstr "Dév" -#: src/organizationDetails/OrganizationDomains.js:208 +#: src/organizationDetails/OrganizationDomains.js:191 msgid "Status or tag" msgstr "Statut ou étiquette" @@ -3101,7 +3125,7 @@ msgstr "Statut :" #~ msgid "Strong Curves:" #~ msgstr "Courbes fortes:" -#: src/guidance/WebTLSResults.js:368 +#: src/guidance/WebTLSResults.js:409 msgid "Subject:" msgstr "Sujet :" @@ -3110,7 +3134,7 @@ msgstr "Sujet :" msgid "Submit" msgstr "Soumettre" -#: src/admin/UserListModal.js:164 +#: src/admin/UserListModal.js:154 msgid "Successfully removed user {0}." msgstr "L'utilisateur {0} a été supprimé." @@ -3127,7 +3151,7 @@ msgstr "Super Admin Menu :" #~ msgid "Supports ECDH Key Exchange:" #~ msgstr "Supporte l'échange de clés ECDH:" -#: src/app/TopBanner.js:67 +#: src/app/TopBanner.js:61 msgid "Symbol of the Government of Canada" msgstr "Symbole du gouvernement du Canada" @@ -3147,7 +3171,7 @@ msgstr "le SCT soit identifié comme la source; et" msgid "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." msgstr "TBS se réserve le droit de refuser le service, de rejeter votre demande de compte ou d'annuler un compte existant, pour quelque raison que ce soit, à sa seule discrétion." -#: src/organizationDetails/OrganizationDomains.js:99 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "TEST" msgstr "TEST" @@ -3155,7 +3179,7 @@ msgstr "TEST" #~ msgid "TLS" #~ msgstr "TLS" -#: src/guidance/WebTLSResults.js:208 +#: src/guidance/WebTLSResults.js:211 msgid "TLS Results" msgstr "Résultats TLS" @@ -3167,7 +3191,7 @@ msgstr "Scan TLS terminé" msgid "TLS scan for domain \"{0}\" has completed." msgstr "Le scan TLS pour le domaine \"{0}\" est terminé." -#: src/organizationDetails/OrganizationDomains.js:174 +#: src/organizationDetails/OrganizationDomains.js:165 msgid "Tag" msgstr "Tag" @@ -3179,11 +3203,11 @@ msgstr "Conseils techniques de mise en œuvre :" msgid "Termination" msgstr "Terminaison" -#: src/app/App.js:189 +#: src/app/App.js:180 msgid "Terms & Conditions" msgstr "Termes et conditions" -#: src/app/App.js:338 +#: src/app/App.js:325 #: src/app/FloatingMenu.js:225 #: src/app/SlideMessage.js:92 msgid "Terms & conditions" @@ -3197,7 +3221,7 @@ msgstr "Termes et conditions" msgid "Terms of Use" msgstr "Conditions d'utilisation" -#: src/organizationDetails/OrganizationDomains.js:99 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "Test" msgstr "Test" @@ -3229,16 +3253,16 @@ msgstr "Adresse/domaine utilisé(e) dans le champ \"From\"." msgid "The advice, guidance or services provided to you by TBS will be provided on an “as-is” basis, without warrantee or representation of any kind, and TBS will not be liable for any loss, liability, damage or cost, including loss of data or interruptions of business arising from the provision of such advice, guidance or services by Tracker. Consequently, TBS recommends, that the users exercise their own skill and care with respect to their use of the advice, guidance and services that Tracker provides." msgstr "Les conseils, orientations ou services qui vous sont fournis par le SCT le seront “tels quels“, sans garantie ni déclaration d'aucune sorte, et le SCT ne pourra être tenu responsable de toute perte, responsabilité, dommage ou coût, y compris la perte de données ou les interruptions d'activité découlant de la fourniture de ces conseils, orientations ou services par Suivi. Par conséquent, TBS recommande aux utilisateurs d'exercer leur propre compétence et leur propre prudence en ce qui concerne l'utilisation des conseils, orientations et services fournis par Suivi." +#: src/dmarc/DmarcByDomainPage.js:324 +#: src/domains/DomainsPage.js:154 +#: src/organizationDetails/OrganizationDomains.js:278 +msgid "The domain address." +msgstr "L'adresse du domaine." + #: src/dmarc/DmarcReportPage.js:231 msgid "The domains used for DKIM validation." msgstr "Les domaines utilisés pour la validation DKIM." -#: src/dmarc/DmarcByDomainPage.js:312 -#: src/domains/DomainsPage.js:153 -#: src/organizationDetails/OrganizationDomains.js:280 -msgid "The domain address." -msgstr "L'adresse du domaine." - #: src/guidance/WebTLSResults.js:60 msgid "The following ciphers are from known weak protocols and must be disabled:" msgstr "Les chiffrements suivants proviennent de protocoles faibles connus et doivent être désactivés :" @@ -3279,7 +3303,7 @@ msgstr "Résultats de la vérification DKIM du message. Il peut s'agir d'un succ msgid "The summary cards show two metrics that Tracker scans:" msgstr "Les cartes récapitulatives présentent deux mesures que Suivi analyse :" -#: src/admin/UserListModal.js:114 +#: src/admin/UserListModal.js:106 msgid "The user's role has been successfully updated" msgstr "Le rôle de l'utilisateur a été mis à jour avec succès" @@ -3325,7 +3349,7 @@ msgstr "Ce domaine n'existe plus" msgid "This field cannot be empty" msgstr "Ce champ ne peut pas être vide" -#: src/app/TopBanner.js:106 +#: src/app/TopBanner.js:90 msgid "This is a new service, we are constantly improving." msgstr "Il s'agit d'un nouveau service, que nous améliorons constamment." @@ -3345,11 +3369,11 @@ msgstr "Temps généré" msgid "Time Generated (UTC)" msgstr "Heure générée (UTC)" -#: src/app/App.js:127 +#: src/app/App.js:118 msgid "To enable full app functionality and maximize your account's security, <0>please verify your account." msgstr "Pour activer toutes les fonctionnalités de l'application et maximiser la sécurité de votre compte, <0>vous devez vérifier votre compte." -#: src/app/App.js:141 +#: src/app/App.js:132 msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "Pour maximiser la sécurité de votre compte, <0>vous devez activer une option d'authentification multifactorielle." @@ -3389,11 +3413,11 @@ msgstr "Suivi n'ajoute pas automatiquement les sélecteurs, il est donc probable #~ msgid "Tracker does not automatically add selectors, so it is likely that they are not in the system yet. More information can be found in Getting Started with Tracker - Managing Your Domains." #~ msgstr "Suivi n'ajoute pas automatiquement les sélecteurs, il est donc probable qu'ils ne soient pas encore dans le système. Pour plus d'informations, consultez la section Premiers pas avec Suivi - Gérer vos domaines." -#: src/app/TopBanner.js:81 +#: src/app/TopBanner.js:70 msgid "Tracker logo outline" msgstr "Contour du logo Suivi" -#: src/app/TopBanner.js:89 +#: src/app/TopBanner.js:73 msgid "Tracker logo text" msgstr "Texte du logo du Suivi" @@ -3422,11 +3446,11 @@ msgstr "Authentification à deux facteurs:" msgid "URL:" msgstr "URL :" -#: src/admin/UserListModal.js:276 +#: src/admin/UserListModal.js:258 msgid "USER" msgstr "UTILISATEUR" -#: src/admin/UserListModal.js:103 +#: src/admin/UserListModal.js:95 msgid "Unable to change user role, please try again." msgstr "Impossible de modifier le rôle de l'utilisateur, veuillez réessayer." @@ -3443,7 +3467,7 @@ msgstr "Impossible de fermer ce compte." msgid "Unable to create account, please try again." msgstr "Impossible de créer un compte, veuillez réessayer." -#: src/admin/AdminDomainModal.js:98 +#: src/admin/AdminDomainModal.js:84 msgid "Unable to create new domain." msgstr "Impossible de créer un nouveau domaine." @@ -3455,7 +3479,7 @@ msgstr "Impossible de créer une nouvelle organisation." msgid "Unable to create your account, please try again." msgstr "Impossible de créer votre compte, veuillez réessayer" -#: src/admin/UserListModal.js:74 +#: src/admin/UserListModal.js:68 msgid "Unable to invite user." msgstr "Impossible d'inviter un utilisateur." @@ -3472,7 +3496,7 @@ msgstr "Impossible de supprimer le domaine." msgid "Unable to remove this organization." msgstr "Impossible de supprimer cette organisation." -#: src/admin/UserListModal.js:172 +#: src/admin/UserListModal.js:162 msgid "Unable to remove user." msgstr "Impossible de supprimer l'utilisateur." @@ -3499,7 +3523,7 @@ msgstr "Impossible d'envoyer l'e-mail de vérification" msgid "Unable to sign in to your account, please try again." msgstr "Impossible de vous connecter à votre compte, veuillez réessayer." -#: src/admin/AdminDomainModal.js:147 +#: src/admin/AdminDomainModal.js:133 msgid "Unable to update domain." msgstr "Impossible de mettre à jour le domaine." @@ -3531,7 +3555,7 @@ msgstr "Impossible de mettre à jour votre langue préférée, veuillez réessay msgid "Unable to update to your username, please try again." msgstr "Impossible de mettre à jour votre nom d'utilisateur, veuillez réessayer." -#: src/admin/UserListModal.js:123 +#: src/admin/UserListModal.js:115 msgid "Unable to update user role." msgstr "Impossible de mettre à jour le rôle de l'utilisateur." @@ -3551,10 +3575,15 @@ msgstr "Impossible de vérifier votre numéro de téléphone, veuillez réessaye msgid "Understanding Scan Metrics:" msgstr "Comprendre les métriques d'analyse :" -#: src/domains/DomainCard.js:84 +#: src/domains/DomainCard.js:83 msgid "Unfavourited Domain" msgstr "Domaine non favorisé" +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:256 +msgid "Unknown" +msgstr "" + #: src/summaries/RadialBarChart.js:43 #: src/summaries/SummaryGroup.js:28 #: src/summaries/SummaryGroup.js:54 @@ -3623,20 +3652,20 @@ msgstr "Liste des utilisateurs" msgid "User email does not match" msgstr "L'email de l'utilisateur ne correspond pas" -#: src/admin/UserListModal.js:64 +#: src/admin/UserListModal.js:58 msgid "User invited" msgstr "Utilisateur invité" -#: src/admin/UserListModal.js:163 +#: src/admin/UserListModal.js:153 msgid "User removed." msgstr "Utilisateur supprimé." -#: src/admin/UserListModal.js:247 +#: src/admin/UserListModal.js:230 msgid "User:" msgstr "Utilisateur:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:31 +#: src/admin/AdminPanel.js:29 #: src/organizationDetails/OrganizationDetails.js:143 msgid "Users" msgstr "Utilisateurs" @@ -3645,7 +3674,7 @@ msgstr "Utilisateurs" msgid "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" msgstr "Les utilisateurs font preuve de diligence raisonnable en s'assurant de l'exactitude des documents reproduits;" -#: src/organizationDetails/OrganizationDomains.js:164 +#: src/organizationDetails/OrganizationDomains.js:155 msgid "Value" msgstr "Valeur" @@ -3659,11 +3688,11 @@ msgstr "Le code de vérification ne doit contenir que des chiffres" msgid "Verified" msgstr "Vérifié" -#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:352 msgid "Verified Chain Free of Legacy Symantec Anchor" msgstr "Chaîne vérifiée exempte d'ancre Symantec ancienne" -#: src/guidance/WebTLSResults.js:300 +#: src/guidance/WebTLSResults.js:341 msgid "Verified Chain Free of SHA1 Signature" msgstr "Chaîne vérifiée sans signature SHA1" @@ -3683,11 +3712,11 @@ msgstr "Vérifier le compte" msgid "View Details" msgstr "Voir les détails" -#: src/domains/DomainCard.js:222 +#: src/domains/DomainCard.js:221 msgid "View Results" msgstr "Voir les résultats" -#: src/dmarc/DmarcReportPage.js:577 +#: src/dmarc/DmarcReportPage.js:661 msgid "Volume of messages spoofing domain (reject + quarantine + none):" msgstr "Volume de messages usurpant domaine (rejet + quarantaine + aucun) :" @@ -3695,11 +3724,11 @@ msgstr "Volume de messages usurpant domaine (rejet + quarantaine + aucun) :" #~ msgid "Volume of messages spoofing {domainSlug} (reject + quarantine + none):" #~ msgstr "Volume de messages usurpant {domainSlug} (rejet + quarantaine + aucun) :" -#: src/admin/WebCheckPage.js:176 +#: src/admin/WebCheckPage.js:158 msgid "Vulnerability Scan Dashboard" msgstr "Tableau de bord de l'analyse des vulnérabilités" -#: src/organizationDetails/OrganizationDomains.js:100 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "WEB" msgstr "WEB" @@ -3731,20 +3760,20 @@ msgstr "Nous vous avons envoyé un e-mail avec un code d'authentification pour v #~ msgid "Weak Curves:" #~ msgstr "Courbes faibles:" -#: src/organizationDetails/OrganizationDomains.js:100 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "Web" msgstr "Web" -#: src/domains/DomainCard.js:182 +#: src/domains/DomainCard.js:181 msgid "Web (HTTPS/TLS)" msgstr "Web (HTTPS/TLS)" -#: src/admin/WebCheckPage.js:173 +#: src/admin/WebCheckPage.js:155 msgid "Web Check" msgstr "Vérification du Web" #: src/domains/ScanDomain.js:242 -#: src/guidance/GuidancePage.js:100 +#: src/guidance/GuidancePage.js:102 msgid "Web Guidance" msgstr "Conseils sur le Web" @@ -3822,16 +3851,17 @@ msgstr "Wiki" #: src/guidance/WebConnectionResults.js:188 #: src/guidance/WebConnectionResults.js:206 #: src/guidance/WebConnectionResults.js:215 -#: src/guidance/WebTLSResults.js:250 -#: src/guidance/WebTLSResults.js:261 -#: src/guidance/WebTLSResults.js:270 -#: src/guidance/WebTLSResults.js:281 -#: src/guidance/WebTLSResults.js:292 -#: src/guidance/WebTLSResults.js:303 -#: src/guidance/WebTLSResults.js:314 -#: src/guidance/WebTLSResults.js:380 -#: src/guidance/WebTLSResults.js:389 -#: src/guidance/WebTLSResults.js:392 +#: src/guidance/WebTLSResults.js:233 +#: src/guidance/WebTLSResults.js:291 +#: src/guidance/WebTLSResults.js:302 +#: src/guidance/WebTLSResults.js:311 +#: src/guidance/WebTLSResults.js:322 +#: src/guidance/WebTLSResults.js:333 +#: src/guidance/WebTLSResults.js:344 +#: src/guidance/WebTLSResults.js:355 +#: src/guidance/WebTLSResults.js:421 +#: src/guidance/WebTLSResults.js:430 +#: src/guidance/WebTLSResults.js:433 msgid "Yes" msgstr "Oui" @@ -3851,12 +3881,12 @@ msgstr "Vous acceptez de protéger toute information qui vous est divulguée par msgid "You agree to use our website, products and services only for lawful purposes and in a manner that does not infringe the rights of, or restrict or inhibit the use and enjoyment of, the website, products or services by any third party. Additionally, you must not misuse, compromise or interfere with our services, or introduce material to our services that is malicious or technologically harmful. You must not attempt to gain unauthorized access to, tamper with, reverse engineer, or modify our website, products or services, the server(s) on which they are stored, or any server, computer or database connected to our website, products or services. We may suspend or stop providing our products or services to you if you do not comply with our terms or policies or if we are investigating suspected misconduct. Any suspected illegal use of our website, products or services may be reported to the relevant law enforcement authorities and where necessary we will co-operate with those authorities by disclosing your identity to them." msgstr "Vous acceptez d'utiliser notre site Web, nos produits et nos services uniquement à des fins légales et de manière à ne pas enfreindre les droits d'un tiers, ni à restreindre ou à empêcher l'utilisation et la jouissance du site Web, des produits ou des services par un tiers. En outre, vous ne devez pas abuser, compromettre ou interférer avec nos services, ni introduire dans nos services des éléments malveillants ou technologiquement dangereux. Vous ne devez pas tenter d'obtenir un accès non autorisé à notre site Web, à nos produits ou services, au(x) serveur(s) sur le(s)quel(s) ils sont stockés, ou à tout serveur, ordinateur ou base de données connecté à notre site Web, à nos produits ou à nos services, ni les altérer, les désosser ou les modifier. Nous pouvons suspendre ou cesser de vous fournir nos produits ou services si vous ne respectez pas nos conditions ou politiques ou si nous enquêtons sur une suspicion de mauvaise conduite. Tout soupçon d'utilisation illégale de notre site web, de nos produits ou de nos services peut être signalé aux autorités compétentes chargées de l'application de la loi et, si nécessaire, nous coopérerons avec ces autorités en leur divulguant votre identité." -#: src/domains/DomainCard.js:60 +#: src/domains/DomainCard.js:58 msgid "You have successfully added {url} to myTracker." msgstr "Vous avez ajouté avec succès {url} à monSuivi." #: src/app/FloatingMenu.js:49 -#: src/app/TopBanner.js:42 +#: src/app/TopBanner.js:41 msgid "You have successfully been signed out." msgstr "Vous avez été déconnecté avec succès." @@ -3869,7 +3899,7 @@ msgstr "Vous avez été déconnecté avec succès." msgid "You have successfully removed {0}." msgstr "Vous avez retiré {0} avec succès." -#: src/domains/DomainCard.js:86 +#: src/domains/DomainCard.js:84 msgid "You have successfully removed {url} from myTracker." msgstr "Vous avez réussi à supprimer {url} de monSuivi." @@ -3917,7 +3947,7 @@ msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe" msgid "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." msgstr "Vous aurez besoin d'un compte Suivi pour utiliser certains produits et services. Vous êtes responsable du maintien de la confidentialité de votre compte et de votre mot de passe et de la restriction de l'accès à votre compte. Vous acceptez également d'assumer la responsabilité de toutes les activités qui se déroulent sous votre compte ou votre mot de passe. Le SCT n'accepte aucune responsabilité pour toute perte ou tout dommage résultant de votre incapacité à maintenir la sécurité de votre compte ou de votre mot de passe." -#: src/app/App.js:271 +#: src/app/App.js:262 msgid "Your Account" msgstr "Votre compte" @@ -3953,8 +3983,8 @@ msgstr "contactez-nous" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:105 -#: src/app/App.js:284 +#: src/app/App.js:97 +#: src/app/App.js:275 #: src/user/MyTrackerPage.js:43 #: src/user/MyTrackerPage.js:74 msgid "myTracker" @@ -3996,7 +4026,7 @@ msgstr "e-mail de l'utilisateur" msgid "weak" msgstr "faible" -#: src/admin/AdminDomainModal.js:88 +#: src/admin/AdminDomainModal.js:75 msgid "{0} was added to {orgSlug}" msgstr "{0} a été ajouté à {orgSlug}" @@ -4016,11 +4046,11 @@ msgstr "{count} enregistrements..." msgid "{domainSlug} does not support aggregate data" msgstr "{domainSlug} ne supporte pas les données agrégées" -#: src/admin/AdminDomainModal.js:137 +#: src/admin/AdminDomainModal.js:123 msgid "{editingDomainUrl} from {orgSlug} successfully updated to {0}" msgstr "{editingDomainUrl} de {orgSlug} mis à jour avec succès à {0}" -#: src/components/InfoPanel.js:52 +#: src/components/InfoPanel.js:47 msgid "{info}" msgstr "{info}" @@ -4028,7 +4058,7 @@ msgstr "{info}" #~ msgid "{label}" #~ msgstr "{label}" -#: src/components/InfoPanel.js:49 +#: src/components/InfoPanel.js:44 msgid "{title}" msgstr "{title}" diff --git a/frontend/src/organizationDetails/OrganizationDomains.js b/frontend/src/organizationDetails/OrganizationDomains.js index af626f14e4..0d77ad4393 100644 --- a/frontend/src/organizationDetails/OrganizationDomains.js +++ b/frontend/src/organizationDetails/OrganizationDomains.js @@ -28,8 +28,7 @@ import { SearchBox } from '../components/SearchBox' import { Formik } from 'formik' import { getRequirement, schemaToValidation } from '../utilities/fieldRequirements' import { CheckCircleIcon, InfoIcon, WarningIcon } from '@chakra-ui/icons' -import { ABTestingWrapper } from '../app/ABTestWrapper' -import { ABTestVariant } from '../app/ABTestVariant' +import { ABTestVariant, ABTestingWrapper } from '../app/ABTestWrapper' export function OrganizationDomains({ orgSlug }) { const [orderDirection, setOrderDirection] = useState('ASC') @@ -97,6 +96,9 @@ export function OrganizationDomains({ orgSlug }) { { value: t`TEST`, text: t`Test` }, { value: t`WEB`, text: t`Web` }, { value: t`INACTIVE`, text: t`Inactive` }, + ] + + const hiddenFilterOptions = [ { value: `HIDDEN`, text: t`Hidden` }, { value: `ARCHIVED`, text: t`Archived` }, ] @@ -107,127 +109,136 @@ export function OrganizationDomains({ orgSlug }) { ) : ( - - - - { - setFilters([ - ...new Map( - [...filters, values].map((item) => { - if (item['filterCategory'] !== 'TAGS') return [item['filterCategory'], item] - else return [item['filterValue'], item] - }), - ).values(), - ]) - resetForm() - }} - > - {({ handleChange, handleSubmit, values, errors }) => { - return ( - - - - Filters: - - - { + if ( + (values.filterCategory === 'TAGS' && e.target.value !== 'TAGS') || + (values.filterCategory !== 'TAGS' && e.target.value === 'TAGS') + ) { + values.filterValue = '' + } + handleChange(e) + }} + > + + {orderByOptions.map(({ value, text }, idx) => { + return ( + - )} - - - {errors.filterCategory} - - - - - - {errors.comparison} - - - - + + {errors.filterCategory} + + + + + + {errors.comparison} + + + + - - {errors.filterValue} - - - - - - ) - }} - - - - + })} + + + {hiddenFilterOptions.map(({ value, text }, idx) => { + return ( + + ) + })} + + + + ) : ( + <> + + + + + )} + + + {errors.filterValue} + + + + + + ) + }} +
+ + )} ( @@ -265,6 +276,7 @@ export function OrganizationDomains({ orgSlug }) { + @@ -302,58 +314,57 @@ export function OrganizationDomains({ orgSlug }) { onToggle={onToggle} /> - - - - {filters.map(({ filterCategory, comparison, filterValue }, idx) => { - const statuses = { - HTTPS_STATUS: `HTTPS`, - HSTS_STATUS: `HSTS`, - CIPHERS_STATUS: `Ciphers`, - CURVES_STATUS: t`Curves`, - PROTOCOLS_STATUS: t`Protocols`, - SPF_STATUS: `SPF`, - DKIM_STATUS: `DKIM`, - DMARC_STATUS: `DMARC`, - } - return ( - - {comparison === 'NOT_EQUAL' && !} - {filterCategory === 'TAGS' ? ( - {filterValue} - ) : ( - <> - {statuses[filterCategory]} - - - )} + {orgSlug !== 'my-tracker' && ( + + {filters.map(({ filterCategory, comparison, filterValue }, idx) => { + const statuses = { + HTTPS_STATUS: `HTTPS`, + HSTS_STATUS: `HSTS`, + CERTIFICATES_STATUS: `Certificates`, + CIPHERS_STATUS: `Ciphers`, + CURVES_STATUS: t`Curves`, + PROTOCOLS_STATUS: t`Protocols`, + SPF_STATUS: `SPF`, + DKIM_STATUS: `DKIM`, + DMARC_STATUS: `DMARC`, + } + return ( + + {comparison === 'NOT_EQUAL' && !} + {filterCategory === 'TAGS' ? ( + {filterValue} + ) : ( + <> + {statuses[filterCategory]} + + + )} - setFilters(filters.filter((_, i) => i !== idx))} /> - - ) - })} - - - + setFilters(filters.filter((_, i) => i !== idx))} /> +
+ ) + })} + + )} {domainList} From a7ed74722151120b97970bd9f68076410142a32b Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 15 May 2023 12:32:22 +0000 Subject: [PATCH 026/113] [ci skip] gcr.io/track-compliance/api-js:master-6ff56de-1684153789 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index ac4b5e7220..257ad880a8 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-8173830-1683813552 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6ff56de-1684153789 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index dad9bdc6d1..bd0c102c70 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-8173830-1683813552 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6ff56de-1684153789 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 8b7e84b38ffac1c5735e09993e13f63f9ebcd7a5 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 15 May 2023 12:34:02 +0000 Subject: [PATCH 027/113] [ci skip] gcr.io/track-compliance/frontend:master-6ff56de-1684153895 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index a60ef8abc3..d35a147f09 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-8173830-1683813647 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6ff56de-1684153895 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 8d04b44e8f..774f2e3089 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-8173830-1683813647 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6ff56de-1684153895 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 33da678e3c5b0db24761f1e99d22b7d2a142233c Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Mon, 15 May 2023 09:48:01 -0300 Subject: [PATCH 028/113] Users can request invites to organizations (#4507) * add 'pending' permission to role enums * init request invite mut * add option to only fetch pending users * add gql to frontend * add btn to request invite on org cards and page * show pending users on admin page * refetch affiliations on mut * only show request invite btn when logged in * add notify email for invite requests * change filterPending to includePending * change filterPending to includePending * skip notify emails if no admins found * add modal to confirm invite request * update translations * give pending role green colour * fix var in fe trans * use state for passing org values to invite request modal * fix fetching list of admins for emailing * undo dev changes * update faked schema * update UserList test * fix AdminPage tests * simplify affiliation check in request-org-affiliation * new mut tests * new modal tests * change isLoggedIn props to use userVar * use reveiver displayName in invite request email * add pending to possible user roles in modal * add new env vars to deployment secrets * put request buttons in B stream --- .../load-affiliation-connections-by-org-id.js | 352 +++++----- .../__tests__/request-org-affiliation.test.js | 620 ++++++++++++++++++ api/src/affiliation/mutations/index.js | 1 + .../mutations/remove-user-from-org.js | 78 +-- .../mutations/request-org-affiliation.js | 201 ++++++ .../affiliation/mutations/update-user-role.js | 78 +-- api/src/auth/check-permission.js | 84 ++- api/src/auth/check-user-belongs-to-org.js | 33 +- api/src/auth/check-user-is-admin-for-user.js | 93 ++- api/src/create-context.js | 2 + api/src/enums/roles.js | 9 +- api/src/locale/en/messages.po | 106 +-- api/src/locale/fr/messages.po | 106 +-- api/src/notify/index.js | 1 + .../notify-send-invite-request-email.js | 25 + api/src/organization/objects/organization.js | 56 +- frontend/mocking/faked_schema.js | 360 +++++++--- frontend/src/admin/AdminPanel.js | 17 +- frontend/src/admin/UserList.js | 63 +- frontend/src/admin/UserListModal.js | 20 +- .../src/admin/__tests__/AdminPage.test.js | 21 +- frontend/src/admin/__tests__/UserList.test.js | 39 +- frontend/src/components/UserCard.js | 8 +- frontend/src/graphql/mutations.js | 16 + frontend/src/graphql/queries.js | 10 +- frontend/src/locales/en.po | 329 +++++----- frontend/src/locales/fr.po | 341 +++++----- .../OrganizationDetails.js | 93 +-- frontend/src/organizations/Organizations.js | 107 +-- .../organizations/RequestOrgInviteModal.js | 88 +++ .../__tests__/RequestOrgInviteModal.test.js | 234 +++++++ frontend/src/theme/Icons.js | 18 +- k8s/apps/bases/api/deployment.yaml | 10 + 33 files changed, 2488 insertions(+), 1131 deletions(-) create mode 100644 api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js create mode 100644 api/src/affiliation/mutations/request-org-affiliation.js create mode 100644 api/src/notify/notify-send-invite-request-email.js create mode 100644 frontend/src/organizations/RequestOrgInviteModal.js create mode 100644 frontend/src/organizations/__tests__/RequestOrgInviteModal.test.js diff --git a/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js b/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js index fdc47ddcec..fa32893b24 100644 --- a/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js +++ b/api/src/affiliation/loaders/load-affiliation-connections-by-org-id.js @@ -1,178 +1,168 @@ -import {aql} from 'arangojs' -import {fromGlobalId, toGlobalId} from 'graphql-relay' -import {t} from '@lingui/macro' +import { aql } from 'arangojs' +import { fromGlobalId, toGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' export const loadAffiliationConnectionsByOrgId = - ({query, userKey, cleanseInput, i18n}) => - async ({orgId, after, before, first, last, orderBy, search}) => { - let afterTemplate = aql`` - if (typeof after !== 'undefined') { - const {id: afterId} = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` + ({ query, userKey, cleanseInput, i18n }) => + async ({ orgId, after, before, first, last, orderBy, search, includePending }) => { + let afterTemplate = aql`` + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` } else { - let afterTemplateDirection - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` - } else { - afterTemplateDirection = aql`<` - } + afterTemplateDirection = aql`<` + } - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._to).key).userName` - } + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${afterId})._to).key).userName` + } - afterTemplate = aql` + afterTemplate = aql` FILTER ${affiliationField} ${afterTemplateDirection} ${documentField} OR (${affiliationField} == ${documentField} AND TO_NUMBER(affiliation._key) > TO_NUMBER(${afterId})) ` - } } + } - let beforeTemplate = aql`` - if (typeof before !== 'undefined') { - const {id: beforeId} = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` + let beforeTemplate = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } else { - beforeTemplateDirection = aql`>` - } + beforeTemplateDirection = aql`>` + } - let affiliationField, documentField - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._to).key).userName` - } + let affiliationField, documentField + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + affiliationField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + documentField = aql`DOCUMENT(users, PARSE_IDENTIFIER(DOCUMENT(affiliations, ${beforeId})._to).key).userName` + } - beforeTemplate = aql` + beforeTemplate = aql` FILTER ${affiliationField} ${beforeTemplateDirection} ${documentField} OR (${affiliationField} == ${documentField} AND TO_NUMBER(affiliation._key) < TO_NUMBER(${beforeId})) ` - } } + } - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByOrgId.`, ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Affiliation\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + throw new Error(i18n._(t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAffiliationConnectionsByOrgId.`, + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByOrgId.`, ) throw new Error( i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Affiliation\` connection is not supported.`, + t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, ), ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Affiliation\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Affiliation\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByOrgId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(affiliation._key) DESC LIMIT TO_NUMBER(${last})` } + } else { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAffiliationConnectionsByOrgId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } - let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` - } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` - } + let hasNextPageFilter = aql`FILTER TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } - let affField, hasNextPageDocument, hasPreviousPageDocument - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - affField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` - hasNextPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._to).key).userName` - hasPreviousPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._to).key).userName` - } + let affField, hasNextPageDocument, hasPreviousPageDocument + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + affField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName` + hasNextPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(LAST(retrievedAffiliations)._to).key).userName` + hasPreviousPageDocument = aql`DOCUMENT(users, PARSE_IDENTIFIER(FIRST(retrievedAffiliations)._to).key).userName` + } - hasNextPageFilter = aql` + hasNextPageFilter = aql` FILTER ${affField} ${hasNextPageDirection} ${hasNextPageDocument} OR (${affField} == ${hasNextPageDocument} AND TO_NUMBER(affiliation._key) > TO_NUMBER(LAST(retrievedAffiliations)._key)) ` - hasPreviousPageFilter = aql` + hasPreviousPageFilter = aql` FILTER ${affField} ${hasPreviousPageDirection} ${hasPreviousPageDocument} OR (${affField} == ${hasPreviousPageDocument} AND TO_NUMBER(affiliation._key) < TO_NUMBER(FIRST(retrievedAffiliations)._key)) ` - } + } - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'user-username') { - sortByField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName ${orderBy.direction},` - } + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'user-username') { + sortByField = aql`DOCUMENT(users, PARSE_IDENTIFIER(affiliation._to).key).userName ${orderBy.direction},` } + } - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let userSearchQuery = aql`` - let userIdFilter = aql`` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - userSearchQuery = aql` + let userSearchQuery = aql`` + let userIdFilter = aql`` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + userSearchQuery = aql` LET tokenArr = TOKENS(${search}, "text_en") LET userIds = UNIQUE( FOR token IN tokenArr @@ -184,12 +174,17 @@ export const loadAffiliationConnectionsByOrgId = RETURN user._id ) ` - userIdFilter = aql`FILTER e._to IN userIds` - } + userIdFilter = aql`FILTER e._to IN userIds` + } - let filteredAffiliationCursor - try { - filteredAffiliationCursor = await query` + let pendingFilter = aql`FILTER e.permission != "pending"` + if (includePending) { + pendingFilter = aql`` + } + + let filteredAffiliationCursor + try { + filteredAffiliationCursor = await query` WITH affiliations, organizations, users, userSearch ${userSearchQuery} @@ -197,6 +192,7 @@ export const loadAffiliationConnectionsByOrgId = LET affiliationKeys = ( FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations ${userIdFilter} + ${pendingFilter} RETURN e._key ) @@ -246,55 +242,51 @@ export const loadAffiliationConnectionsByOrgId = "endKey": LAST(retrievedAffiliations)._key } ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to query affiliation(s). Please try again.`), - ) - } - - let filteredAffiliations - try { - filteredAffiliations = await filteredAffiliationCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load affiliation(s). Please try again.`), - ) - } - - if (filteredAffiliations.affiliations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, - } - } + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to query affiliation(s). Please try again.`)) + } - const edges = filteredAffiliations.affiliations.map((affiliation) => { - return { - cursor: toGlobalId('affiliation', affiliation._key), - node: affiliation, - } - }) + let filteredAffiliations + try { + filteredAffiliations = await filteredAffiliationCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather affiliations in loadAffiliationConnectionsByOrgId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load affiliation(s). Please try again.`)) + } + if (filteredAffiliations.affiliations.length === 0) { return { - edges, - totalCount: filteredAffiliations.totalCount, + edges: [], + totalCount: 0, pageInfo: { - hasNextPage: filteredAffiliations.hasNextPage, - hasPreviousPage: filteredAffiliations.hasPreviousPage, - startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), - endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', }, } } + + const edges = filteredAffiliations.affiliations.map((affiliation) => { + return { + cursor: toGlobalId('affiliation', affiliation._key), + node: affiliation, + } + }) + + return { + edges, + totalCount: filteredAffiliations.totalCount, + pageInfo: { + hasNextPage: filteredAffiliations.hasNextPage, + hasPreviousPage: filteredAffiliations.hasPreviousPage, + startCursor: toGlobalId('affiliation', filteredAffiliations.startKey), + endCursor: toGlobalId('affiliation', filteredAffiliations.endKey), + }, + } + } diff --git a/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js b/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js new file mode 100644 index 0000000000..9f9bcbe11d --- /dev/null +++ b/api/src/affiliation/mutations/__tests__/request-org-affiliation.test.js @@ -0,0 +1,620 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema } from 'graphql' +import { toGlobalId } from 'graphql-relay' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { userRequired, verifiedRequired } from '../../../auth' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { cleanseInput } from '../../../validators' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import dbschema from '../../../../database.json' +import { collectionNames } from '../../../collection-names' + +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env + +describe('invite user to org', () => { + let query, drop, truncate, schema, collections, transaction, i18n, tokenize, user, org + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + tokenize = jest.fn().mockReturnValue('token') + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + // given a successful request to join an org + describe('given a successful request to join an org', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + beforeEach(async () => { + org = await ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + describe('users role is super admin', () => { + describe('inviting an existing account', () => { + describe('requested role is admin', () => { + let secondaryUser + beforeEach(async () => { + secondaryUser = await collections.users.save({ + displayName: 'Test Account', + userName: 'test@email.gc.ca', + preferredLang: 'english', + }) + await collections.affiliations.save({ + _from: org._id, + _to: secondaryUser._id, + permission: 'admin', + }) + }) + it('returns status message', async () => { + const sendInviteRequestEmail = jest.fn() + + const response = await graphql( + schema, + ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'en', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: userRequired({ + userKey: user._key, + loadUserByKey: loadUserByKey({ query }), + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendInviteRequestEmail: sendInviteRequestEmail }, + validators: { cleanseInput }, + }, + ) + + const expectedResponse = { + data: { + requestOrgAffiliation: { + result: { + status: 'Successfully requested invite to organization, and sent notification email.', + }, + }, + }, + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([ + `User: ${user._key} successfully requested invite to the org: treasury-board-secretariat.`, + ]) + expect(sendInviteRequestEmail).toHaveBeenCalledWith({ + user: { + _type: 'user', + displayName: 'Test Account', + id: secondaryUser._key, + preferredLang: 'english', + userName: 'test@email.gc.ca', + ...secondaryUser, + }, + orgName: 'Treasury Board of Canada Secretariat', + adminLink: 'https://host/admin/organizations', + }) + }) + }) + }) + }) + }) + }) + describe('given an unsuccessful invitation', () => { + beforeAll(async () => { + ;({ query, drop, truncate, collections, transaction } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + tokenize = jest.fn().mockReturnValue('token') + }) + beforeEach(async () => { + user = ( + await collections.users.save( + { + userName: 'test.account@istio.actually.exists', + emailValidated: true, + tfaSendMethod: 'email', + }, + { returnNew: true }, + ) + ).new + org = ( + await collections.organizations.save( + { + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }, + { returnNew: true }, + ) + ).new + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('user attempts to request an invite to an org that does not exist', () => { + it('returns an error message', async () => { + const response = await graphql( + schema, + ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', 1)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction, + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + userName: 'test.account@exists.ca', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + ) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: 'Unable to request invite to unknown organization.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: 123 attempted to request invite to org: 1 however there is no org associated with that id.`, + ]) + }) + }) + describe('user has already requested to join org', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'pending', + }) + }) + it('returns an error message', async () => { + const response = await graphql( + schema, + ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _id: org._id }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + ) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: + 'Unable to request invite to organization with which you have already requested to join.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to request invite to org: ${org._key} however they have already requested to join that org.`, + ]) + }) + }) + describe('user is already a member of org', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('returns an error message', async () => { + const response = await graphql( + schema, + ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'en', + }, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue({ _id: org._id }), + }, + loadUserByKey: { + load: jest.fn(), + }, + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + ) + + const error = { + data: { + requestOrgAffiliation: { + result: { + code: 400, + description: 'Unable to request invite to organization with which you are already affiliated.', + }, + }, + }, + } + + expect(response).toEqual(error) + expect(consoleOutput).toEqual([ + `User: ${user._key} attempted to request invite to org: ${org._key} however they are already affiliated with that org.`, + ]) + }) + }) + }) + describe('transaction error occurs', () => { + describe('when creating affiliation', () => { + it('returns an error message', async () => { + await graphql( + schema, + ` + mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx step err'), + }), + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + ) + + expect(consoleOutput).toEqual([ + `Transaction step error occurred while user: 123 attempted to request invite to org: treasury-board-secretariat, error: trx step err`, + ]) + }) + }) + describe('when committing transaction', () => { + it('returns an error message', async () => { + await graphql( + schema, + ` mutation { + requestOrgAffiliation(input: { orgId: "${toGlobalId('organizations', org._key)}" }) { + result { + ... on InviteUserToOrgResult { + status + } + ... on AffiliationError { + code + description + } + } + } + } + `, + null, + { + i18n, + request: { + language: 'fr', + protocol: 'https', + get: (text) => text, + }, + query, + collections: collectionNames, + transaction: jest.fn().mockReturnValue({ + step: jest.fn().mockRejectedValue('trx commit err'), + }), + userKey: 123, + auth: { + tokenize, + userRequired: jest.fn().mockReturnValue({ + _id: user._id, + userName: 'test.account@istio.actually.exists', + }), + verifiedRequired: jest.fn(), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, language: i18n.locale }), + loadUserByKey: loadUserByKey({ query }), + }, + notify: { sendInviteRequestEmail: jest.fn() }, + validators: { cleanseInput }, + }, + ) + + expect(consoleOutput).toEqual([ + `Transaction step error occurred while user: 123 attempted to request invite to org: treasury-board-secretariat, error: trx commit err`, + ]) + }) + }) + }) + }) +}) diff --git a/api/src/affiliation/mutations/index.js b/api/src/affiliation/mutations/index.js index 3b3584b898..557b4f8353 100644 --- a/api/src/affiliation/mutations/index.js +++ b/api/src/affiliation/mutations/index.js @@ -1,5 +1,6 @@ export * from './invite-user-to-org' export * from './leave-organization' export * from './remove-user-from-org' +export * from './request-org-affiliation' export * from './transfer-org-ownership' export * from './update-user-role' diff --git a/api/src/affiliation/mutations/remove-user-from-org.js b/api/src/affiliation/mutations/remove-user-from-org.js index 237461d3aa..971d7069fd 100644 --- a/api/src/affiliation/mutations/remove-user-from-org.js +++ b/api/src/affiliation/mutations/remove-user-from-org.js @@ -1,14 +1,13 @@ -import {GraphQLNonNull, GraphQLID} from 'graphql' -import {mutationWithClientMutationId, fromGlobalId} from 'graphql-relay' -import {t} from '@lingui/macro' +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' -import {removeUserFromOrgUnion} from '../unions' +import { removeUserFromOrgUnion } from '../unions' import { logActivity } from '../../audit-logs/mutations/log-activity' export const removeUserFromOrg = new mutationWithClientMutationId({ name: 'RemoveUserFromOrg', - description: - 'This mutation allows admins or higher to remove users from any organizations they belong to.', + description: 'This mutation allows admins or higher to remove users from any organizations they belong to.', inputFields: () => ({ userId: { type: GraphQLNonNull(GraphQLID), @@ -35,20 +34,20 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ collections, transaction, userKey, - auth: {checkPermission, userRequired, verifiedRequired, tfaRequired}, - loaders: {loadOrgByKey, loadUserByKey}, - validators: {cleanseInput}, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByKey }, + validators: { cleanseInput }, }, ) => { // Cleanse Input - const {id: requestedUserKey} = fromGlobalId(cleanseInput(args.userId)) - const {id: requestedOrgKey} = fromGlobalId(cleanseInput(args.orgId)) + const { id: requestedUserKey } = fromGlobalId(cleanseInput(args.userId)) + const { id: requestedOrgKey } = fromGlobalId(cleanseInput(args.orgId)) // Get requesting user const user = await userRequired() - verifiedRequired({user}) - tfaRequired({user}) + verifiedRequired({ user }) + tfaRequired({ user }) // Get requested org const requestedOrg = await loadOrgByKey.load(requestedOrgKey) @@ -59,14 +58,12 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove user from unknown organization.`, - ), + description: i18n._(t`Unable to remove user from unknown organization.`), } } // Check requesting users permission - const permission = await checkPermission({orgId: requestedOrg._id}) + const permission = await checkPermission({ orgId: requestedOrg._id }) if (permission === 'user' || typeof permission === 'undefined') { console.warn( `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrg._key}, however they do not have the permission to remove users.`, @@ -87,9 +84,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove unknown user from organization.`, - ), + description: i18n._(t`Unable to remove unknown user from organization.`), } } @@ -106,11 +101,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ console.error( `Database error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } if (affiliationCursor.count < 1) { @@ -120,9 +111,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to remove a user that already does not belong to this organization.`, - ), + description: i18n._(t`Unable to remove a user that already does not belong to this organization.`), } } @@ -133,20 +122,13 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ console.error( `Cursor error occurred when user: ${userKey} attempted to check the current permission of user: ${requestedUser._key} to see if they could be removed: ${err}`, ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } let canRemove - if ( - permission === 'super_admin' && - (affiliation.permission === 'admin' || affiliation.permission === 'user') - ) { + if (permission === 'super_admin' && ['pending', 'user', 'admin'].includes(affiliation.permission)) { canRemove = true - } else if (permission === 'admin' && affiliation.permission === 'user') { + } else if (permission === 'admin' && ['pending', 'user'].includes(affiliation.permission)) { canRemove = true } else { canRemove = false @@ -171,11 +153,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ console.error( `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } try { @@ -184,16 +162,10 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ console.error( `Trx commit error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, ) - throw new Error( - i18n._( - t`Unable to remove user from this organization. Please try again.`, - ), - ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } - console.info( - `User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`, - ) + console.info(`User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`) await logActivity({ transaction, collections, @@ -229,9 +201,7 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with removing users.`, - ), + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), } } }, diff --git a/api/src/affiliation/mutations/request-org-affiliation.js b/api/src/affiliation/mutations/request-org-affiliation.js new file mode 100644 index 0000000000..ee9f733ba7 --- /dev/null +++ b/api/src/affiliation/mutations/request-org-affiliation.js @@ -0,0 +1,201 @@ +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' + +import { inviteUserToOrgUnion } from '../unions' +import { logActivity } from '../../audit-logs/mutations/log-activity' + +export const requestOrgAffiliation = new mutationWithClientMutationId({ + name: 'RequestOrgAffiliation', + description: `This mutation allows users to request to join an organization.`, + inputFields: () => ({ + orgId: { + type: GraphQLNonNull(GraphQLID), + description: 'The organization you wish to invite the user to.', + }, + }), + outputFields: () => ({ + result: { + type: inviteUserToOrgUnion, + description: + '`InviteUserToOrgUnion` returning either a `InviteUserToOrgResult`, or `InviteUserToOrgError` object.', + resolve: (payload) => payload, + }, + }), + mutateAndGetPayload: async ( + args, + { + i18n, + query, + request, + collections, + transaction, + userKey, + auth: { userRequired, verifiedRequired }, + loaders: { loadOrgByKey, loadUserByKey }, + notify: { sendInviteRequestEmail }, + validators: { cleanseInput }, + }, + ) => { + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) + + // Get requesting user + const user = await userRequired() + verifiedRequired({ user }) + + // Check to see if requested org exists + const org = await loadOrgByKey.load(orgId) + + if (typeof org === 'undefined') { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however there is no org associated with that id.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to request invite to unknown organization.`), + } + } + + // Check to see if user is already a member of the org + let affiliationCursor + try { + affiliationCursor = await query` + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e._to == ${user._id} + RETURN e + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + if (affiliationCursor.count > 0) { + const requestedAffiliation = await affiliationCursor.next() + if (requestedAffiliation.permission === 'pending') { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however they have already requested to join that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._( + t`Unable to request invite to organization with which you have already requested to join.`, + ), + } + } else { + console.warn( + `User: ${userKey} attempted to request invite to org: ${orgId} however they are already affiliated with that org.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Unable to request invite to organization with which you are already affiliated.`), + } + } + } + + // Setup Transaction + const trx = await transaction(collections) + + // Create pending affiliation + try { + await trx.step( + () => + query` + WITH affiliations, organizations, users + INSERT { + _from: ${org._id}, + _to: ${user._id}, + permission: "pending", + owner: false + } INTO affiliations + `, + ) + } catch (err) { + console.error( + `Transaction step error occurred while user: ${userKey} attempted to request invite to org: ${org.slug}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + // get all org admins + let orgAdminsCursor + try { + orgAdminsCursor = await query` + WITH affiliations, organizations, users + FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations + FILTER e.permission == "admin" + RETURN v._key + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + let orgAdmins + try { + orgAdmins = await orgAdminsCursor.all() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to request invite to ${orgId}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + if (orgAdmins.length > 0) { + const adminLink = `https://${request.get('host')}/admin/organizations` + // send notification to org admins + for (const userKey of orgAdmins) { + const adminUser = await loadUserByKey.load(userKey) + await sendInviteRequestEmail({ user: adminUser, orgName: org.name, adminLink }) + } + } + + // Commit Transaction + try { + await trx.commit() + } catch (err) { + console.error( + `Transaction commit error occurred while user: ${userKey} attempted to request invite to org: ${org.slug}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to request invite. Please try again.`)) + } + + console.info(`User: ${userKey} successfully requested invite to the org: ${org.slug}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + }, + action: 'add', + target: { + resource: user.userName, + organization: { + id: org._key, + name: org.name, + }, // name of resource being acted upon + updatedProperties: [ + { + name: 'permission', + oldValue: null, + newValue: 'pending', + }, + ], + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully requested invite to organization, and sent notification email.`), + } + }, +}) diff --git a/api/src/affiliation/mutations/update-user-role.js b/api/src/affiliation/mutations/update-user-role.js index df985043b9..8850d51c54 100644 --- a/api/src/affiliation/mutations/update-user-role.js +++ b/api/src/affiliation/mutations/update-user-role.js @@ -1,10 +1,10 @@ -import {GraphQLNonNull, GraphQLID} from 'graphql' -import {mutationWithClientMutationId, fromGlobalId} from 'graphql-relay' -import {GraphQLEmailAddress} from 'graphql-scalars' -import {t} from '@lingui/macro' +import { GraphQLNonNull, GraphQLID } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { GraphQLEmailAddress } from 'graphql-scalars' +import { t } from '@lingui/macro' -import {RoleEnums} from '../../enums' -import {updateUserRoleUnion} from '../unions' +import { RoleEnums } from '../../enums' +import { updateUserRoleUnion } from '../unions' import { logActivity } from '../../audit-logs/mutations/log-activity' export const updateUserRole = new mutationWithClientMutationId({ @@ -19,20 +19,17 @@ given organization.`, }, orgId: { type: GraphQLNonNull(GraphQLID), - description: - 'The organization that the admin, and the user both belong to.', + description: 'The organization that the admin, and the user both belong to.', }, role: { type: GraphQLNonNull(RoleEnums), - description: - 'The role that the admin wants to give to the selected user.', + description: 'The role that the admin wants to give to the selected user.', }, }), outputFields: () => ({ result: { type: updateUserRoleUnion, - description: - '`UpdateUserRoleUnion` returning either a `UpdateUserRoleResult`, or `UpdateUserRoleError` object.', + description: '`UpdateUserRoleUnion` returning either a `UpdateUserRoleResult`, or `UpdateUserRoleError` object.', resolve: (payload) => payload, }, }), @@ -44,27 +41,25 @@ given organization.`, collections, transaction, userKey, - auth: {checkPermission, userRequired, verifiedRequired, tfaRequired}, - loaders: {loadOrgByKey, loadUserByUserName}, - validators: {cleanseInput}, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + loaders: { loadOrgByKey, loadUserByUserName }, + validators: { cleanseInput }, }, ) => { // Cleanse Input const userName = cleanseInput(args.userName).toLowerCase() - const {id: orgId} = fromGlobalId(cleanseInput(args.orgId)) + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) const role = cleanseInput(args.role) // Get requesting user from db const user = await userRequired() - verifiedRequired({user}) - tfaRequired({user}) + verifiedRequired({ user }) + tfaRequired({ user }) // Make sure user is not attempting to update their own role if (user.userName === userName) { - console.warn( - `User: ${userKey} attempted to update their own role in org: ${orgId}.`, - ) + console.warn(`User: ${userKey} attempted to update their own role in org: ${orgId}.`) return { _type: 'error', code: 400, @@ -101,18 +96,16 @@ given organization.`, } // Check requesting user's permission - const permission = await checkPermission({orgId: org._id}) + const permission = await checkPermission({ orgId: org._id }) - if (permission === 'user' || typeof permission === 'undefined') { + if (!['admin', 'super_admin'].includes(permission) || typeof permission === 'undefined') { console.warn( `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to do so.`, ) return { _type: 'error', code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with user role changes.`, - ), + description: i18n._(t`Permission Denied: Please contact organization admin for help with user role changes.`), } } @@ -129,9 +122,7 @@ given organization.`, console.error( `Database error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } if (affiliationCursor.count < 1) { @@ -141,9 +132,7 @@ given organization.`, return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update role: user does not belong to organization.`, - ), + description: i18n._(t`Unable to update role: user does not belong to organization.`), } } @@ -154,9 +143,7 @@ given organization.`, console.error( `Cursor error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } // Setup Transaction @@ -170,10 +157,7 @@ given organization.`, _to: requestedUser._id, permission: 'super_admin', } - } else if ( - role === 'admin' && - (permission === 'admin' || permission === 'super_admin') - ) { + } else if (role === 'admin' && ['admin', 'super_admin'].includes(permission)) { // If requested user's permission is super admin, make sure they don't get downgraded if (affiliation.permission === 'super_admin') { console.warn( @@ -223,9 +207,7 @@ given organization.`, return { _type: 'error', code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), + description: i18n._(t`Permission Denied: Please contact organization admin for help with updating user roles.`), } } @@ -243,9 +225,7 @@ given organization.`, console.error( `Transaction step error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } try { @@ -254,14 +234,10 @@ given organization.`, console.warn( `Transaction commit error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to update user's role. Please try again.`), - ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } - console.info( - `User: ${userKey} successful updated user: ${requestedUser._key} role to ${role} in org: ${org.slug}.`, - ) + console.info(`User: ${userKey} successful updated user: ${requestedUser._key} role to ${role} in org: ${org.slug}.`) await logActivity({ transaction, collections, diff --git a/api/src/auth/check-permission.js b/api/src/auth/check-permission.js index 814b1735e0..dad9a94eaf 100644 --- a/api/src/auth/check-permission.js +++ b/api/src/auth/check-permission.js @@ -1,63 +1,53 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkPermission = - ({i18n, userKey, query}) => - async ({orgId}) => { - let cursor - const userKeyString = `users/${userKey}` - // Check for super admin - try { - cursor = await query` + ({ i18n, userKey, query }) => + async ({ orgId }) => { + let cursor + const userKeyString = `users/${userKey}` + // Check for super admin + try { + cursor = await query` WITH affiliations, organizations, users FOR v, e IN 1 INBOUND ${userKeyString} affiliations FILTER e.permission == "super_admin" - RETURN e.permission + RETURN e.permission ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } + } catch (err) { + console.error(`Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } - let permission - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`, - ) - throw new Error(i18n._(t`Unable to check permission. Please try again.`)) - } + let permission + try { + permission = await cursor.next() + } catch (err) { + console.error(`Cursor error when checking to see if user ${userKeyString} has super admin permission: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) + } - if (permission === 'super_admin') { - return permission - } else { - // Check for other permission level - try { - cursor = await query` + if (permission === 'super_admin') { + return permission + } else { + // Check for other permission level + try { + cursor = await query` WITH affiliations, organizations, users FOR v, e IN 1 INBOUND ${userKeyString} affiliations FILTER e._from == ${orgId} RETURN e.permission ` - } catch (err) { - console.error( - `Database error occurred when checking ${userKeyString}'s permission: ${err}`, - ) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } + } catch (err) { + console.error(`Database error occurred when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking ${userKeyString}'s permission: ${err}`, - ) - throw new Error( - i18n._(t`Unable to check permission. Please try again.`), - ) - } - return permission + try { + permission = await cursor.next() + } catch (err) { + console.error(`Cursor error when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) } + return permission } + } diff --git a/api/src/auth/check-user-belongs-to-org.js b/api/src/auth/check-user-belongs-to-org.js index 9c58928099..44912ed378 100644 --- a/api/src/auth/check-user-belongs-to-org.js +++ b/api/src/auth/check-user-belongs-to-org.js @@ -1,27 +1,24 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkUserBelongsToOrg = - ({i18n, query, userKey}) => - async ({orgId}) => { - const userIdString = `users/${userKey}` + ({ i18n, query, userKey }) => + async ({ orgId }) => { + const userIdString = `users/${userKey}` - // find affiliation - let affiliationCursor - try { - affiliationCursor = await query` + // find affiliation + let affiliationCursor + try { + affiliationCursor = await query` WITH affiliations, organizations, users FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations FILTER e._to == ${userIdString} + FILTER e.permission != "pending" RETURN e ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} belongs to org: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load affiliation information. Please try again.`), - ) - } - - return affiliationCursor.count > 0 + } catch (err) { + console.error(`Database error when checking to see if user: ${userKey} belongs to org: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load affiliation information. Please try again.`)) } + + return affiliationCursor.count > 0 + } diff --git a/api/src/auth/check-user-is-admin-for-user.js b/api/src/auth/check-user-is-admin-for-user.js index 85cedf3ac4..84a6d3bbc3 100644 --- a/api/src/auth/check-user-is-admin-for-user.js +++ b/api/src/auth/check-user-is-admin-for-user.js @@ -1,40 +1,40 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkUserIsAdminForUser = - ({i18n, userKey, query}) => - async ({userName}) => { - const requestingUserId = `users/${userKey}` - let cursor + ({ i18n, userKey, query }) => + async ({ userName }) => { + const requestingUserId = `users/${userKey}` + let cursor - try { - cursor = await query` + try { + cursor = await query` WITH affiliations, organizations, users FOR v, e IN 1 INBOUND ${requestingUserId} affiliations FILTER e.permission == "super_admin" RETURN e.permission ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} has super admin permission for user: ${userName}, error: ${err}`, - ) - throw new Error(i18n._(t`Permission error, not an admin for this user.`)) - } + } catch (err) { + console.error( + `Database error when checking to see if user: ${userKey} has super admin permission for user: ${userName}, error: ${err}`, + ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) + } - let permission - try { - permission = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user: ${userKey} has super admin permission for user: ${userName}, error: ${err}`, - ) - throw new Error(i18n._(t`Permission error, not an admin for this user.`)) - } + let permission + try { + permission = await cursor.next() + } catch (err) { + console.error( + `Cursor error when checking to see if user: ${userKey} has super admin permission for user: ${userName}, error: ${err}`, + ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) + } - if (permission === 'super_admin') { - return true - } else { - try { - cursor = await query` + if (permission === 'super_admin') { + return true + } else { + try { + cursor = await query` WITH affiliations, organizations, users LET requestingUserOrgKeys = ( FOR v, e IN 1 INBOUND ${requestingUserId} affiliations @@ -50,32 +50,29 @@ export const checkUserIsAdminForUser = LET requestedUserOrgKeys = ( FOR v, e IN 1 INBOUND requestedUser[0]._id affiliations + FILTER e.permission != "pending" RETURN v._key ) RETURN (LENGTH(INTERSECTION(requestingUserOrgKeys, requestedUserOrgKeys)) > 0 ? true : false) ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, - ) - throw new Error( - i18n._(t`Permission error, not an admin for this user.`), - ) - } - - let isAdmin - try { - isAdmin = await cursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, - ) - throw new Error( - i18n._(t`Permission error, not an admin for this user.`), - ) - } + } catch (err) { + console.error( + `Database error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, + ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) + } - return isAdmin + let isAdmin + try { + isAdmin = await cursor.next() + } catch (err) { + console.error( + `Cursor error when checking to see if user: ${userKey} has admin permission for user: ${userName}, error: ${err}`, + ) + throw new Error(i18n._(t`Permission error, not an admin for this user.`)) } + + return isAdmin } + } diff --git a/api/src/create-context.js b/api/src/create-context.js index e2a0b88e95..12fb14ae37 100644 --- a/api/src/create-context.js +++ b/api/src/create-context.js @@ -27,6 +27,7 @@ import { notifyClient, sendAuthEmail, sendAuthTextMsg, + sendInviteRequestEmail, sendOrgInviteCreateAccount, sendOrgInviteEmail, sendPasswordResetEmail, @@ -117,6 +118,7 @@ export async function createContext({ notify: { sendAuthEmail: sendAuthEmail({ notifyClient, i18n }), sendAuthTextMsg: sendAuthTextMsg({ notifyClient, i18n }), + sendInviteRequestEmail: sendInviteRequestEmail({ notifyClient, i18n }), sendOrgInviteCreateAccount: sendOrgInviteCreateAccount({ notifyClient, i18n, diff --git a/api/src/enums/roles.js b/api/src/enums/roles.js index ae3a65a4a3..d6ae2420f1 100644 --- a/api/src/enums/roles.js +++ b/api/src/enums/roles.js @@ -1,8 +1,12 @@ -import {GraphQLEnumType} from 'graphql' +import { GraphQLEnumType } from 'graphql' export const RoleEnums = new GraphQLEnumType({ name: 'RoleEnums', values: { + PENDING: { + value: 'pending', + description: 'A user who has requested an invite to an organization.', + }, USER: { value: 'user', description: 'A user who has been given access to view an organization.', @@ -14,8 +18,7 @@ export const RoleEnums = new GraphQLEnumType({ }, SUPER_ADMIN: { value: 'super_admin', - description: - 'A user who has the same access as an admin, but can define new admins.', + description: 'A user who has the same access as an admin, but can define new admins.', }, }, description: 'An enum used to assign, and test users roles.', diff --git a/api/src/locale/en/messages.po b/api/src/locale/en/messages.po index 3ec44d61fc..c777614f32 100644 --- a/api/src/locale/en/messages.po +++ b/api/src/locale/en/messages.po @@ -11,15 +11,15 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" -#: src/auth/check-permission.js:20 -#: src/auth/check-permission.js:48 +#: src/auth/check-permission.js:18 +#: src/auth/check-permission.js:42 #: src/auth/user-required.js:10 #: src/auth/user-required.js:21 #: src/auth/user-required.js:28 msgid "Authentication error. Please sign in." msgstr "Authentication error. Please sign in." -#: src/organization/objects/organization.js:188 +#: src/organization/objects/organization.js:161 msgid "Cannot query affiliations on organization without admin permission or higher." msgstr "Cannot query affiliations on organization without admin permission or higher." @@ -93,7 +93,7 @@ msgstr "Organization name already in use. Please try again with a different name msgid "Ownership check error. Unable to request domain information." msgstr "Ownership check error. Unable to request domain information." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:80 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:77 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." @@ -227,7 +227,7 @@ msgstr "Permission Denied: Please contact organization admin for help with remov msgid "Permission Denied: Please contact organization admin for help with removing organization." msgstr "Permission Denied: Please contact organization admin for help with removing organization." -#: src/affiliation/mutations/remove-user-from-org.js:233 +#: src/affiliation/mutations/remove-user-from-org.js:204 msgid "Permission Denied: Please contact organization admin for help with removing users." msgstr "Permission Denied: Please contact organization admin for help with removing users." @@ -235,9 +235,9 @@ msgstr "Permission Denied: Please contact organization admin for help with remov msgid "Permission Denied: Please contact organization admin for help with updating organization." msgstr "Permission Denied: Please contact organization admin for help with updating organization." -#: src/affiliation/mutations/update-user-role.js:186 -#: src/affiliation/mutations/update-user-role.js:209 -#: src/affiliation/mutations/update-user-role.js:227 +#: src/affiliation/mutations/update-user-role.js:170 +#: src/affiliation/mutations/update-user-role.js:193 +#: src/affiliation/mutations/update-user-role.js:210 msgid "Permission Denied: Please contact organization admin for help with updating user roles." msgstr "Permission Denied: Please contact organization admin for help with updating user roles." @@ -245,7 +245,7 @@ msgstr "Permission Denied: Please contact organization admin for help with updat msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission Denied: Please contact organization admin for help with user invitations." -#: src/affiliation/mutations/update-user-role.js:114 +#: src/affiliation/mutations/update-user-role.js:108 msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission Denied: Please contact organization admin for help with user role changes." @@ -295,7 +295,7 @@ msgstr "Permission check error. Unable to request domain information." #: src/auth/check-user-is-admin-for-user.js:20 #: src/auth/check-user-is-admin-for-user.js:30 #: src/auth/check-user-is-admin-for-user.js:63 -#: src/auth/check-user-is-admin-for-user.js:75 +#: src/auth/check-user-is-admin-for-user.js:73 msgid "Permission error, not an admin for this user." msgstr "Permission error, not an admin for this user." @@ -320,7 +320,7 @@ msgstr "Phone number has been successfully set, you will receive a verification msgid "Profile successfully updated." msgstr "Profile successfully updated." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:95 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." @@ -447,7 +447,7 @@ msgstr "Successfully removed domain: {0} from {1}." msgid "Successfully removed organization: {0}." msgstr "Successfully removed organization: {0}." -#: src/affiliation/mutations/remove-user-from-org.js:219 +#: src/affiliation/mutations/remove-user-from-org.js:191 msgid "Successfully removed user from organization." msgstr "Successfully removed user from organization." @@ -455,6 +455,10 @@ msgstr "Successfully removed user from organization." msgid "Successfully removed {domainCount} domain(s) from {0}." msgstr "Successfully removed {domainCount} domain(s) from {0}." +#: src/affiliation/mutations/request-org-affiliation.js:201 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "Successfully requested invite to organization, and sent notification email." + #: src/domain/mutations/remove-organizations-domains.js:530 #~ msgid "Successfully removed {domainCount} domains from {0}." #~ msgstr "Successfully removed {domainCount} domains from {0}." @@ -518,8 +522,8 @@ msgstr "Unable to add domains in unknown organization." msgid "Unable to authenticate. Please try again." msgstr "Unable to authenticate. Please try again." -#: src/auth/check-permission.js:30 -#: src/auth/check-permission.js:58 +#: src/auth/check-permission.js:26 +#: src/auth/check-permission.js:49 #: src/auth/check-super-admin.js:20 #: src/auth/check-super-admin.js:30 msgid "Unable to check permission. Please try again." @@ -697,8 +701,9 @@ msgstr "Unable to invite user to organization. User is already affiliated with o msgid "Unable to invite user to unknown organization." msgstr "Unable to invite user to unknown organization." -#: src/affiliation/mutations/invite-user-to-org.js:212 -#: src/affiliation/mutations/invite-user-to-org.js:231 +#: src/affiliation/mutations/invite-user-to-org.js:194 +#: src/affiliation/mutations/invite-user-to-org.js:209 +#: src/affiliation/mutations/request-org-affiliation.js:134 msgid "Unable to invite user. Please try again." msgstr "Unable to invite user. Please try again." @@ -816,11 +821,11 @@ msgstr "Unable to load SSL guidance tag(s). Please try again." #~ msgid "Unable to load SSL scan(s). Please try again." #~ msgstr "Unable to load SSL scan(s). Please try again." -#: src/auth/check-user-belongs-to-org.js:22 +#: src/auth/check-user-belongs-to-org.js:20 msgid "Unable to load affiliation information. Please try again." msgstr "Unable to load affiliation information. Please try again." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:266 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:259 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 msgid "Unable to load affiliation(s). Please try again." msgstr "Unable to load affiliation(s). Please try again." @@ -932,7 +937,7 @@ msgstr "Unable to load web scan(s). Please try again." msgid "Unable to load web summary. Please try again." msgstr "Unable to load web summary. Please try again." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:254 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:249 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 msgid "Unable to query affiliation(s). Please try again." msgstr "Unable to query affiliation(s). Please try again." @@ -960,7 +965,7 @@ msgstr "Unable to query user(s). Please try again." msgid "Unable to refresh tokens, please sign in." msgstr "Unable to refresh tokens, please sign in." -#: src/affiliation/mutations/remove-user-from-org.js:124 +#: src/affiliation/mutations/remove-user-from-org.js:114 msgid "Unable to remove a user that already does not belong to this organization." msgstr "Unable to remove a user that already does not belong to this organization." @@ -1015,22 +1020,22 @@ msgstr "Unable to remove unknown domain." msgid "Unable to remove unknown organization." msgstr "Unable to remove unknown organization." -#: src/affiliation/mutations/remove-user-from-org.js:91 +#: src/affiliation/mutations/remove-user-from-org.js:87 msgid "Unable to remove unknown user from organization." msgstr "Unable to remove unknown user from organization." -#: src/affiliation/mutations/remove-user-from-org.js:77 +#: src/affiliation/mutations/remove-user-from-org.js:74 msgid "Unable to remove user from organization." msgstr "Unable to remove user from organization." -#: src/affiliation/mutations/remove-user-from-org.js:111 -#: src/affiliation/mutations/remove-user-from-org.js:138 -#: src/affiliation/mutations/remove-user-from-org.js:176 -#: src/affiliation/mutations/remove-user-from-org.js:189 +#: src/affiliation/mutations/remove-user-from-org.js:104 +#: src/affiliation/mutations/remove-user-from-org.js:125 +#: src/affiliation/mutations/remove-user-from-org.js:156 +#: src/affiliation/mutations/remove-user-from-org.js:165 msgid "Unable to remove user from this organization. Please try again." msgstr "Unable to remove user from this organization. Please try again." -#: src/affiliation/mutations/remove-user-from-org.js:63 +#: src/affiliation/mutations/remove-user-from-org.js:61 msgid "Unable to remove user from unknown organization." msgstr "Unable to remove user from unknown organization." @@ -1046,6 +1051,25 @@ msgstr "Unable to request a one time scan on an unknown domain." msgid "Unable to request a one time scan. Please try again." msgstr "Unable to request a one time scan. Please try again." +#: src/affiliation/mutations/request-org-affiliation.js:109 +msgid "Unable to request invite to organization with which you are already affiliated." +msgstr "Unable to request invite to organization with which you are already affiliated." + +#: src/affiliation/mutations/request-org-affiliation.js:83 +msgid "Unable to request invite to organization with which you have already requested to join." +msgstr "Unable to request invite to organization with which you have already requested to join." + +#: src/affiliation/mutations/request-org-affiliation.js:56 +msgid "Unable to request invite to unknown organization." +msgstr "Unable to request invite to unknown organization." + +#: src/affiliation/mutations/request-org-affiliation.js:73 +#: src/affiliation/mutations/request-org-affiliation.js:99 +#: src/affiliation/mutations/request-org-affiliation.js:150 +#: src/affiliation/mutations/request-org-affiliation.js:169 +msgid "Unable to request invite. Please try again." +msgstr "Unable to request invite. Please try again." + #: src/user/mutations/reset-password.js:95 msgid "Unable to reset password. Please request a new email." msgstr "Unable to reset password. Please request a new email." @@ -1079,6 +1103,10 @@ msgstr "Unable to send authentication text message. Please try again." msgid "Unable to send org invite email. Please try again." msgstr "Unable to send org invite email. Please try again." +#: src/notify/notify-send-invite-request-email.js:23 +msgid "Unable to send org invite request email. Please try again." +msgstr "Unable to send org invite request email. Please try again." + #: src/notify/notify-send-password-reset-email.js:30 msgid "Unable to send password reset email. Please try again." msgstr "Unable to send password reset email. Please try again." @@ -1207,15 +1235,15 @@ msgstr "Unable to update password. Please try again." msgid "Unable to update profile. Please try again." msgstr "Unable to update profile. Please try again." -#: src/affiliation/mutations/update-user-role.js:99 +#: src/affiliation/mutations/update-user-role.js:94 msgid "Unable to update role: organization unknown." msgstr "Unable to update role: organization unknown." -#: src/affiliation/mutations/update-user-role.js:145 +#: src/affiliation/mutations/update-user-role.js:135 msgid "Unable to update role: user does not belong to organization." msgstr "Unable to update role: user does not belong to organization." -#: src/affiliation/mutations/update-user-role.js:85 +#: src/affiliation/mutations/update-user-role.js:80 msgid "Unable to update role: user unknown." msgstr "Unable to update role: user unknown." @@ -1227,14 +1255,14 @@ msgstr "Unable to update unknown domain." msgid "Unable to update unknown organization." msgstr "Unable to update unknown organization." -#: src/affiliation/mutations/update-user-role.js:133 -#: src/affiliation/mutations/update-user-role.js:158 -#: src/affiliation/mutations/update-user-role.js:247 -#: src/affiliation/mutations/update-user-role.js:258 +#: src/affiliation/mutations/update-user-role.js:125 +#: src/affiliation/mutations/update-user-role.js:146 +#: src/affiliation/mutations/update-user-role.js:228 +#: src/affiliation/mutations/update-user-role.js:237 msgid "Unable to update user's role. Please try again." msgstr "Unable to update user's role. Please try again." -#: src/affiliation/mutations/update-user-role.js:71 +#: src/affiliation/mutations/update-user-role.js:66 msgid "Unable to update your own role." msgstr "Unable to update your own role." @@ -1271,7 +1299,7 @@ msgstr "Unable to verify unknown organization." msgid "User could not be queried." msgstr "User could not be queried." -#: src/affiliation/mutations/update-user-role.js:294 +#: src/affiliation/mutations/update-user-role.js:270 msgid "User role was updated successfully." msgstr "User role was updated successfully." @@ -1287,7 +1315,7 @@ msgstr "Verification error. Please activate multi-factor authentication to acces msgid "Verification error. Please verify your account via email to access content." msgstr "Verification error. Please verify your account via email to access content." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:70 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." @@ -1406,7 +1434,7 @@ msgstr "You must provide at most one pagination method (`before`, `after`, `offs msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." msgstr "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:118 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:109 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:148 #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 @@ -1433,7 +1461,7 @@ msgstr "You must provide at most one pagination method (`before`, `after`, `offs msgid "`{argSet}` must be of type `number` not `{typeSet}`." msgstr "`{argSet}` must be of type `number` not `{typeSet}`." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:92 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:86 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." msgstr "`{argSet}` on the `Affiliation` connection cannot be less than zero." diff --git a/api/src/locale/fr/messages.po b/api/src/locale/fr/messages.po index 6c5ec1fde5..cf5b442e4e 100644 --- a/api/src/locale/fr/messages.po +++ b/api/src/locale/fr/messages.po @@ -11,15 +11,15 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" -#: src/auth/check-permission.js:20 -#: src/auth/check-permission.js:48 +#: src/auth/check-permission.js:18 +#: src/auth/check-permission.js:42 #: src/auth/user-required.js:10 #: src/auth/user-required.js:21 #: src/auth/user-required.js:28 msgid "Authentication error. Please sign in." msgstr "Erreur d'authentification. Veuillez vous connecter." -#: src/organization/objects/organization.js:188 +#: src/organization/objects/organization.js:161 msgid "Cannot query affiliations on organization without admin permission or higher." msgstr "Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus." @@ -93,7 +93,7 @@ msgstr "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec u msgid "Ownership check error. Unable to request domain information." msgstr "Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:80 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:77 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:170 msgid "Passing both `first` and `last` to paginate the `Affiliation` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté." @@ -227,7 +227,7 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat msgid "Permission Denied: Please contact organization admin for help with removing organization." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation." -#: src/affiliation/mutations/remove-user-from-org.js:233 +#: src/affiliation/mutations/remove-user-from-org.js:204 msgid "Permission Denied: Please contact organization admin for help with removing users." msgstr "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." @@ -235,9 +235,9 @@ msgstr "Autorisation refusée : Veuillez contacter l'administrateur de l'organis msgid "Permission Denied: Please contact organization admin for help with updating organization." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." -#: src/affiliation/mutations/update-user-role.js:186 -#: src/affiliation/mutations/update-user-role.js:209 -#: src/affiliation/mutations/update-user-role.js:227 +#: src/affiliation/mutations/update-user-role.js:170 +#: src/affiliation/mutations/update-user-role.js:193 +#: src/affiliation/mutations/update-user-role.js:210 msgid "Permission Denied: Please contact organization admin for help with updating user roles." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." @@ -245,7 +245,7 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs." -#: src/affiliation/mutations/update-user-role.js:114 +#: src/affiliation/mutations/update-user-role.js:108 msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs." @@ -295,7 +295,7 @@ msgstr "Erreur de vérification des permissions. Impossible de demander des info #: src/auth/check-user-is-admin-for-user.js:20 #: src/auth/check-user-is-admin-for-user.js:30 #: src/auth/check-user-is-admin-for-user.js:63 -#: src/auth/check-user-is-admin-for-user.js:75 +#: src/auth/check-user-is-admin-for-user.js:73 msgid "Permission error, not an admin for this user." msgstr "Erreur de permission, pas d'administrateur pour cet utilisateur." @@ -320,7 +320,7 @@ msgstr "Le numéro de téléphone a été configuré avec succès, vous recevrez msgid "Profile successfully updated." msgstr "Le profil a été mis à jour avec succès." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:103 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:95 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:193 msgid "Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `Affiliation` dépasse la limite `{argSet}` de 100 enregistrements." @@ -447,7 +447,7 @@ msgstr "A réussi à supprimer le domaine : {0} de {1}." msgid "Successfully removed organization: {0}." msgstr "A réussi à supprimer l'organisation : {0}." -#: src/affiliation/mutations/remove-user-from-org.js:219 +#: src/affiliation/mutations/remove-user-from-org.js:191 msgid "Successfully removed user from organization." msgstr "L'utilisateur a été retiré de l'organisation avec succès." @@ -455,6 +455,10 @@ msgstr "L'utilisateur a été retiré de l'organisation avec succès." msgid "Successfully removed {domainCount} domain(s) from {0}." msgstr "Supprimé avec succès le(s) domaine(s) {domainCount} de {0}." +#: src/affiliation/mutations/request-org-affiliation.js:201 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé." + #: src/domain/mutations/remove-organizations-domains.js:530 #~ msgid "Successfully removed {domainCount} domains from {0}." #~ msgstr "Suppression réussie des domaines {domainCount} de {0}." @@ -518,8 +522,8 @@ msgstr "Impossible d'ajouter des domaines dans une organisation inconnue." msgid "Unable to authenticate. Please try again." msgstr "Impossible de s'authentifier. Veuillez réessayer." -#: src/auth/check-permission.js:30 -#: src/auth/check-permission.js:58 +#: src/auth/check-permission.js:26 +#: src/auth/check-permission.js:49 #: src/auth/check-super-admin.js:20 #: src/auth/check-super-admin.js:30 msgid "Unable to check permission. Please try again." @@ -697,8 +701,9 @@ msgstr "Impossible d'inviter un utilisateur dans une organisation. L'utilisateur msgid "Unable to invite user to unknown organization." msgstr "Impossible d'inviter un utilisateur à une organisation inconnue." -#: src/affiliation/mutations/invite-user-to-org.js:212 -#: src/affiliation/mutations/invite-user-to-org.js:231 +#: src/affiliation/mutations/invite-user-to-org.js:194 +#: src/affiliation/mutations/invite-user-to-org.js:209 +#: src/affiliation/mutations/request-org-affiliation.js:134 msgid "Unable to invite user. Please try again." msgstr "Impossible d'inviter un utilisateur. Veuillez réessayer." @@ -816,11 +821,11 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessaye #~ msgid "Unable to load SSL scan(s). Please try again." #~ msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." -#: src/auth/check-user-belongs-to-org.js:22 +#: src/auth/check-user-belongs-to-org.js:20 msgid "Unable to load affiliation information. Please try again." msgstr "Impossible de charger les informations d'affiliation. Veuillez réessayer." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:266 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:259 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:449 msgid "Unable to load affiliation(s). Please try again." msgstr "Impossible de charger l'affiliation (s). Veuillez réessayer." @@ -932,7 +937,7 @@ msgstr "Impossible de charger le(s) scan(s) web. Veuillez réessayer." msgid "Unable to load web summary. Please try again." msgstr "Impossible de charger le résumé web. Veuillez réessayer." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:254 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:249 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:437 msgid "Unable to query affiliation(s). Please try again." msgstr "Impossible de demander l'affiliation (s). Veuillez réessayer." @@ -960,7 +965,7 @@ msgstr "Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer." msgid "Unable to refresh tokens, please sign in." msgstr "Impossible de rafraîchir les jetons, veuillez vous connecter." -#: src/affiliation/mutations/remove-user-from-org.js:124 +#: src/affiliation/mutations/remove-user-from-org.js:114 msgid "Unable to remove a user that already does not belong to this organization." msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation." @@ -1015,22 +1020,22 @@ msgstr "Impossible de supprimer un domaine inconnu." msgid "Unable to remove unknown organization." msgstr "Impossible de supprimer une organisation inconnue." -#: src/affiliation/mutations/remove-user-from-org.js:91 +#: src/affiliation/mutations/remove-user-from-org.js:87 msgid "Unable to remove unknown user from organization." msgstr "Impossible de supprimer un utilisateur inconnu de l'organisation." -#: src/affiliation/mutations/remove-user-from-org.js:77 +#: src/affiliation/mutations/remove-user-from-org.js:74 msgid "Unable to remove user from organization." msgstr "Impossible de supprimer un utilisateur de l'organisation." -#: src/affiliation/mutations/remove-user-from-org.js:111 -#: src/affiliation/mutations/remove-user-from-org.js:138 -#: src/affiliation/mutations/remove-user-from-org.js:176 -#: src/affiliation/mutations/remove-user-from-org.js:189 +#: src/affiliation/mutations/remove-user-from-org.js:104 +#: src/affiliation/mutations/remove-user-from-org.js:125 +#: src/affiliation/mutations/remove-user-from-org.js:156 +#: src/affiliation/mutations/remove-user-from-org.js:165 msgid "Unable to remove user from this organization. Please try again." msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer." -#: src/affiliation/mutations/remove-user-from-org.js:63 +#: src/affiliation/mutations/remove-user-from-org.js:61 msgid "Unable to remove user from unknown organization." msgstr "Impossible de supprimer un utilisateur d'une organisation inconnue." @@ -1046,6 +1051,25 @@ msgstr "Impossible de demander un scan unique sur un domaine inconnu." msgid "Unable to request a one time scan. Please try again." msgstr "Impossible de demander une analyse unique. Veuillez réessayer." +#: src/affiliation/mutations/request-org-affiliation.js:109 +msgid "Unable to request invite to organization with which you are already affiliated." +msgstr "Impossible de demander une invitation à une organisation à laquelle vous êtes déjà affilié." + +#: src/affiliation/mutations/request-org-affiliation.js:83 +msgid "Unable to request invite to organization with which you have already requested to join." +msgstr "Impossible de demander une invitation à une organisation à laquelle vous avez déjà demandé à adhérer." + +#: src/affiliation/mutations/request-org-affiliation.js:56 +msgid "Unable to request invite to unknown organization." +msgstr "Impossible de demander une invitation à une organisation inconnue." + +#: src/affiliation/mutations/request-org-affiliation.js:73 +#: src/affiliation/mutations/request-org-affiliation.js:99 +#: src/affiliation/mutations/request-org-affiliation.js:150 +#: src/affiliation/mutations/request-org-affiliation.js:169 +msgid "Unable to request invite. Please try again." +msgstr "Impossible de demander une invitation. Veuillez réessayer." + #: src/user/mutations/reset-password.js:95 msgid "Unable to reset password. Please request a new email." msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail." @@ -1079,6 +1103,10 @@ msgstr "Impossible d'envoyer un message texte d'authentification. Veuillez rées msgid "Unable to send org invite email. Please try again." msgstr "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer." +#: src/notify/notify-send-invite-request-email.js:23 +msgid "Unable to send org invite request email. Please try again." +msgstr "Impossible d'envoyer l'email de demande d'invitation à l'org. Veuillez réessayer." + #: src/notify/notify-send-password-reset-email.js:30 msgid "Unable to send password reset email. Please try again." msgstr "Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer." @@ -1207,15 +1235,15 @@ msgstr "Impossible de mettre à jour le mot de passe. Veuillez réessayer." msgid "Unable to update profile. Please try again." msgstr "Impossible de mettre à jour le profil. Veuillez réessayer." -#: src/affiliation/mutations/update-user-role.js:99 +#: src/affiliation/mutations/update-user-role.js:94 msgid "Unable to update role: organization unknown." msgstr "Impossible de mettre à jour le rôle : organisation inconnue." -#: src/affiliation/mutations/update-user-role.js:145 +#: src/affiliation/mutations/update-user-role.js:135 msgid "Unable to update role: user does not belong to organization." msgstr "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation." -#: src/affiliation/mutations/update-user-role.js:85 +#: src/affiliation/mutations/update-user-role.js:80 msgid "Unable to update role: user unknown." msgstr "Impossible de mettre à jour le rôle : utilisateur inconnu." @@ -1227,14 +1255,14 @@ msgstr "Impossible de mettre à jour un domaine inconnu." msgid "Unable to update unknown organization." msgstr "Impossible de mettre à jour une organisation inconnue." -#: src/affiliation/mutations/update-user-role.js:133 -#: src/affiliation/mutations/update-user-role.js:158 -#: src/affiliation/mutations/update-user-role.js:247 -#: src/affiliation/mutations/update-user-role.js:258 +#: src/affiliation/mutations/update-user-role.js:125 +#: src/affiliation/mutations/update-user-role.js:146 +#: src/affiliation/mutations/update-user-role.js:228 +#: src/affiliation/mutations/update-user-role.js:237 msgid "Unable to update user's role. Please try again." msgstr "Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer." -#: src/affiliation/mutations/update-user-role.js:71 +#: src/affiliation/mutations/update-user-role.js:66 msgid "Unable to update your own role." msgstr "Impossible de mettre à jour votre propre rôle." @@ -1271,7 +1299,7 @@ msgstr "Impossible de vérifier une organisation inconnue." msgid "User could not be queried." msgstr "L'utilisateur n'a pas pu être interrogé." -#: src/affiliation/mutations/update-user-role.js:294 +#: src/affiliation/mutations/update-user-role.js:270 msgid "User role was updated successfully." msgstr "Le rôle de l'utilisateur a été mis à jour avec succès." @@ -1287,7 +1315,7 @@ msgstr "Erreur de vérification. Veuillez activer l'authentification multifactor msgid "Verification error. Please verify your account via email to access content." msgstr "Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:71 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:70 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:161 msgid "You must provide a `first` or `last` value to properly paginate the `Affiliation` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`." @@ -1406,7 +1434,7 @@ msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before msgid "You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection." msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:118 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:109 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 #: src/audit-logs/loaders/load-audit-logs-by-org-id.js:148 #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 @@ -1433,7 +1461,7 @@ msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before msgid "`{argSet}` must be of type `number` not `{typeSet}`." msgstr "`{argSet}` doit être de type `number` et non `{typeSet}`." -#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:92 +#: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:86 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:182 msgid "`{argSet}` on the `Affiliation` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Affiliation` ne peut être inférieur à zéro." diff --git a/api/src/notify/index.js b/api/src/notify/index.js index fc86b60b02..0a1fc75a15 100644 --- a/api/src/notify/index.js +++ b/api/src/notify/index.js @@ -1,6 +1,7 @@ export * from './notify-client' export * from './notify-send-authenticate-email' export * from './notify-send-authenticate-text-msg' +export * from './notify-send-invite-request-email' export * from './notify-send-org-invite-create-account' export * from './notify-send-org-invite-email' export * from './notify-send-password-reset-email' diff --git a/api/src/notify/notify-send-invite-request-email.js b/api/src/notify/notify-send-invite-request-email.js new file mode 100644 index 0000000000..36266f9402 --- /dev/null +++ b/api/src/notify/notify-send-invite-request-email.js @@ -0,0 +1,25 @@ +import { t } from '@lingui/macro' + +const { NOTIFICATION_ORG_INVITE_REQUEST_EN, NOTIFICATION_ORG_INVITE_REQUEST_FR } = process.env + +export const sendInviteRequestEmail = + ({ notifyClient, i18n }) => + async ({ user, orgName, adminLink }) => { + let templateId = NOTIFICATION_ORG_INVITE_REQUEST_EN + if (user.preferredLang === 'french') { + templateId = NOTIFICATION_ORG_INVITE_REQUEST_FR + } + + try { + await notifyClient.sendEmail(templateId, user.userName, { + personalisation: { + admin_link: adminLink, + display_name: user.displayName, + organization_name: orgName, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org invite request email for ${user._key}: ${err}`) + throw new Error(i18n._(t`Unable to send org invite request email. Please try again.`)) + } + } diff --git a/api/src/organization/objects/organization.js b/api/src/organization/objects/organization.js index d97fba897a..beee776b98 100644 --- a/api/src/organization/objects/organization.js +++ b/api/src/organization/objects/organization.js @@ -1,11 +1,5 @@ import { t } from '@lingui/macro' -import { - GraphQLBoolean, - GraphQLInt, - GraphQLObjectType, - GraphQLString, - GraphQLList, -} from 'graphql' +import { GraphQLBoolean, GraphQLInt, GraphQLObjectType, GraphQLString, GraphQLList } from 'graphql' import { connectionArgs, globalIdField } from 'graphql-relay' import { organizationSummaryType } from './organization-summary' @@ -67,8 +61,7 @@ export const organizationType = new GraphQLObjectType({ }, summaries: { type: organizationSummaryType, - description: - 'Summaries based on scan types that are preformed on the given organizations domains.', + description: 'Summaries based on scan types that are preformed on the given organizations domains.', resolve: ({ summaries }) => summaries, }, domainCount: { @@ -80,25 +73,11 @@ export const organizationType = new GraphQLObjectType({ type: GraphQLString, description: 'CSV formatted output of all domains in the organization including their email and web scan statuses.', - resolve: async ( - { _id }, - _args, - { loaders: { loadOrganizationDomainStatuses } }, - ) => { + resolve: async ({ _id }, _args, { loaders: { loadOrganizationDomainStatuses } }) => { const domains = await loadOrganizationDomainStatuses({ orgId: _id, }) - const headers = [ - 'domain', - 'https', - 'hsts', - 'ciphers', - 'curves', - 'protocols', - 'spf', - 'dkim', - 'dmarc', - ] + const headers = ['domain', 'https', 'hsts', 'ciphers', 'curves', 'protocols', 'spf', 'dkim', 'dmarc'] let csvOutput = headers.join(',') domains.forEach((domain) => { let csvLine = `${domain.domain}` @@ -120,8 +99,7 @@ export const organizationType = new GraphQLObjectType({ }, ownership: { type: GraphQLBoolean, - description: - 'Limit domains to those that belong to an organization that has ownership.', + description: 'Limit domains to those that belong to an organization that has ownership.', }, search: { type: GraphQLString, @@ -137,10 +115,7 @@ export const organizationType = new GraphQLObjectType({ { _id }, args, - { - auth: { checkPermission }, - loaders: { loadDomainConnectionsByOrgId }, - }, + { auth: { checkPermission }, loaders: { loadDomainConnectionsByOrgId } }, ) => { // Check to see requesting users permission to the org is const permission = await checkPermission({ orgId: _id }) @@ -164,16 +139,16 @@ export const organizationType = new GraphQLObjectType({ type: GraphQLString, description: 'String used to search for affiliated users.', }, + includePending: { + type: GraphQLBoolean, + description: 'Exclude (false) or include only (true) pending affiliations in the results.', + }, ...connectionArgs, }, resolve: async ( { _id }, args, - { - i18n, - auth: { checkPermission }, - loaders: { loadAffiliationConnectionsByOrgId }, - }, + { i18n, auth: { checkPermission }, loaders: { loadAffiliationConnectionsByOrgId } }, ) => { const permission = await checkPermission({ orgId: _id }) if (permission === 'admin' || permission === 'super_admin') { @@ -183,15 +158,10 @@ export const organizationType = new GraphQLObjectType({ }) return affiliations } - throw new Error( - i18n._( - t`Cannot query affiliations on organization without admin permission or higher.`, - ), - ) + throw new Error(i18n._(t`Cannot query affiliations on organization without admin permission or higher.`)) }, }, }), interfaces: [nodeInterface], - description: - 'Organization object containing information for a given Organization.', + description: 'Organization object containing information for a given Organization.', }) diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 872a472583..5c60a562c8 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -308,8 +308,8 @@ export const getTypeNames = () => gql` # The ID of an object id: ID! - # Datetime string the activity occured. - timestamp: String + # Datetime string the activity occurred. + timestamp: DateTime # Username of admin that initiated the activity. initiatedBy: InitiatedBy @@ -324,6 +324,9 @@ export const getTypeNames = () => gql` reason: DomainRemovalReasonEnum } + # A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the 'date-time' format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. + scalar DateTime + # Information on the user that initiated the logged action type InitiatedBy { # The ID of an object @@ -344,6 +347,9 @@ export const getTypeNames = () => gql` # An enum used to assign, and test users roles. enum RoleEnums { + # A user who has requested an invite to an organization. + PENDING + # A user who has been given access to view an organization. USER @@ -450,12 +456,12 @@ export const getTypeNames = () => gql` RESOURCE_NAME } - # Possible directions in which to order a list of items when provided an "orderBy" argument. + # Possible directions in which to order a list of items when provided an 'orderBy' argument. enum OrderDirection { - # Specifies an ascending order for a given "orderBy" argument. + # Specifies an ascending order for a given 'orderBy' argument. ASC - # Specifies a descending order for a given "orderBy" argument. + # Specifies a descending order for a given 'orderBy' argument. DESC } @@ -539,6 +545,15 @@ export const getTypeNames = () => gql` # The domains scan status, based on the latest scan data. status: DomainStatus + # Value that determines if a domain is excluded from any results and scans. + archived: Boolean + + # Value that determines if a domain is possibly blocked. + blocked: Boolean + + # Value that determines if a domain has a web scan pending. + webScanPending: Boolean + # The organization that this domain belongs to. organizations( # Ordering options for organization connections @@ -569,10 +584,10 @@ export const getTypeNames = () => gql` # DNS scan results. dnsScan( # Start date for date filter. - startDate: Date + startDate: DateTime # End date for date filter. - endDate: Date + endDate: DateTime # Ordering options for DNS connections. orderBy: DNSOrder @@ -596,10 +611,10 @@ export const getTypeNames = () => gql` # HTTPS, and TLS scan results. web( # Start date for date filter. - startDate: Date + startDate: DateTime # End date for date filter. - endDate: Date + endDate: DateTime # Ordering options for web connections. orderBy: WebOrder @@ -607,6 +622,9 @@ export const getTypeNames = () => gql` # Number of web scans to retrieve. limit: Int + # Exclude web scans which have pending status. + excludePending: Boolean + # Returns the items in the list that come after the specified cursor. after: String @@ -637,9 +655,6 @@ export const getTypeNames = () => gql` # Value that determines if a domain is excluded from an organization's results. hidden: Boolean - - # Value that determines if a domain is excluded from any results and scans. - archived: Boolean } # String that conforms to a domain structure. @@ -650,6 +665,9 @@ export const getTypeNames = () => gql` # This object contains how the domain is doing on the various scans we preform, based on the latest scan data. type DomainStatus { + # Certificates Status + certificates: StatusEnum + # Ciphers Status ciphers: StatusEnum @@ -766,6 +784,9 @@ export const getTypeNames = () => gql` # String used to search for domains. search: String + # Filters used to limit domains returned. + filters: [DomainFilter] + # Returns the items in the list that come after the specified cursor. after: String @@ -787,6 +808,9 @@ export const getTypeNames = () => gql` # String used to search for affiliated users. search: String + # Exclude (false) or include only (true) pending affiliations in the results. + includePending: Boolean + # Returns the items in the list that come after the specified cursor. after: String @@ -880,6 +904,9 @@ export const getTypeNames = () => gql` # Properties by which domain connections can be ordered. enum DomainOrderField { + # Order domains by certificates status. + CERTIFICATES_STATUS + # Order domains by ciphers status. CIPHERS_STATUS @@ -909,6 +936,75 @@ export const getTypeNames = () => gql` # Order domains by spf status. SPF_STATUS + + # Order domains by tags. + TAGS + } + + # This object is used to provide filtering options when querying org-claimed domains. + input DomainFilter { + # Category of filter to be applied. + filterCategory: DomainOrderField + + # First value equals or does not equal second value. + comparison: ComparisonEnums + + # Status type or tag label. + filterValue: filterValueEnums + } + + # + enum ComparisonEnums { + # + EQUAL + + # + NOT_EQUAL + } + + # + enum filterValueEnums { + # If the given check meets the passing requirements. + PASS + + # If the given check has flagged something that can provide information on the domain that aren't scan related. + INFO + + # If the given check does not meet the passing requirements + FAIL + + # English label for tagging domains as new to the system. + NEW + + # French label for tagging domains as new to the system. + NOUVEAU + + # Bilingual Label for tagging domains as a production environment. + PROD + + # English label for tagging domains as a staging environment. + STAGING + + # French label for tagging domains as a staging environment. + DEV + + # Bilingual label for tagging domains as a test environment. + TEST + + # Bilingual label for tagging domains as web-hosting. + WEB + + # English label for tagging domains that are not active. + INACTIVE + + # French label for tagging domains that are not active. + INACTIF + + # English label for tagging domains that are hidden. + HIDDEN + + # English label for tagging domains that are archived. + ARCHIVED } # A connection to a list of items. @@ -1164,7 +1260,7 @@ export const getTypeNames = () => gql` domain: String # The time when the scan was initiated. - timestamp: Date + timestamp: DateTime # String of the base domain the scan was run on. baseDomain: String @@ -1194,9 +1290,6 @@ export const getTypeNames = () => gql` dkim: DKIM } - # A date string, such as 2007-12-03, compliant with the "full-date" format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. - scalar Date - type MXRecord { # Hosts listed in the domain's MX record. hosts: [MXHost] @@ -1330,6 +1423,15 @@ export const getTypeNames = () => gql` # The compliance status for DKIM for the scanned domain. status: String + # List of positive tags for the scanned domain from this scan. + positiveTags: [GuidanceTag] + + # List of neutral tags for the scanned domain from this scan. + neutralTags: [GuidanceTag] + + # List of negative tags for the scanned domain from this scan. + negativeTags: [GuidanceTag] + # Individual scans results for each DKIM selector. selectors: [DKIMSelectorResult] } @@ -1416,7 +1518,7 @@ export const getTypeNames = () => gql` domain: String # The time when the scan was initiated. - timestamp: Date + timestamp: DateTime # Results of the web scan at each IP address. results: [WebScan] @@ -1424,9 +1526,6 @@ export const getTypeNames = () => gql` # Information for the TLS and HTTP connection scans on the given domain. type WebScan { - # The time when the scan was initiated. - timestamp: Date - # IP address for scan target. ipAddress: String @@ -1440,7 +1539,7 @@ export const getTypeNames = () => gql` # Results of TLS and HTTP connection scans on the given domain. type WebScanResult { # The time when the scan was initiated. - timestamp: Date + timestamp: DateTime # The result for the TLS scan for the scanned server. tlsResult: TLSResult @@ -1466,6 +1565,9 @@ export const getTypeNames = () => gql` # Whether or not the scanned server is vulnerable to heartbleed. heartbleedVulnerable: Boolean + # Whether or not the scanned server is vulnerable to heartbleed. + robotVulnerable: String + # Whether or not the scanned server is vulnerable to CCS injection. ccsInjectionVulnerable: Boolean @@ -1484,6 +1586,9 @@ export const getTypeNames = () => gql` # List of negative tags for the scanned server from this scan. negativeTags: [GuidanceTag] + # The compliance status of the certificate bundle for the scanned server from this scan. + certificateStatus: String + # The compliance status for TLS for the scanned server from this scan. sslStatus: String @@ -1745,7 +1850,7 @@ export const getTypeNames = () => gql` HSTS: Boolean } - # The "JSONObject" scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). + # The 'JSONObject' scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). scalar JSONObject # Ordering options for web connections. @@ -2413,7 +2518,7 @@ export const getTypeNames = () => gql` domain: DomainScalar # The last time that a scan was ran on this domain. - lastRan: Date + lastRan: DateTime # The domains scan status, based on the latest scan data. status: DomainStatus @@ -2629,6 +2734,9 @@ export const getTypeNames = () => gql` # This mutation allows admins or higher to remove users from any organizations they belong to. removeUserFromOrg(input: RemoveUserFromOrgInput!): RemoveUserFromOrgPayload + # This mutation allows users to request to join an organization. + requestOrgAffiliation(input: RequestOrgAffiliationInput!): RequestOrgAffiliationPayload + # This mutation allows a user to transfer org ownership to another user in the given org. transferOrgOwnership(input: TransferOrgOwnershipInput!): TransferOrgOwnershipPayload @@ -2637,6 +2745,9 @@ export const getTypeNames = () => gql` # given organization. updateUserRole(input: UpdateUserRoleInput!): UpdateUserRolePayload + # Mutation used to create multiple new domains for an organization. + addOrganizationsDomains(input: AddOrganizationsDomainsInput!): AddOrganizationsDomainsPayload + # Mutation used to create a new domain for an organization. createDomain(input: CreateDomainInput!): CreateDomainPayload @@ -2646,6 +2757,9 @@ export const getTypeNames = () => gql` # This mutation allows the removal of unused domains. removeDomain(input: RemoveDomainInput!): RemoveDomainPayload + # This mutation allows the removal of unused domains. + removeOrganizationsDomains(input: RemoveOrganizationsDomainsInput!): RemoveOrganizationsDomainsPayload + # This mutation is used to step a manual scan on a requested domain. requestScan(input: RequestScanInput!): RequestScanPayload @@ -2714,12 +2828,12 @@ export const getTypeNames = () => gql` } type InviteUserToOrgPayload { - # "InviteUserToOrgUnion" returning either a "InviteUserToOrgResult", or "InviteUserToOrgError" object. + # 'InviteUserToOrgUnion' returning either a 'InviteUserToOrgResult', or 'InviteUserToOrgError' object. result: InviteUserToOrgUnion clientMutationId: String } - # This union is used with the "InviteUserToOrg" mutation, allowing for users to invite user to their org, and support any errors that may occur + # This union is used with the 'InviteUserToOrg' mutation, allowing for users to invite user to their org, and support any errors that may occur union InviteUserToOrgUnion = AffiliationError | InviteUserToOrgResult # This object is used to inform the user if any errors occurred while executing affiliation mutations. @@ -2751,12 +2865,12 @@ export const getTypeNames = () => gql` } type LeaveOrganizationPayload { - # "LeaveOrganizationUnion" resolving to either a "LeaveOrganizationResult" or "AffiliationError". + # 'LeaveOrganizationUnion' resolving to either a 'LeaveOrganizationResult' or 'AffiliationError'. result: LeaveOrganizationUnion clientMutationId: String } - # This union is used with the "leaveOrganization" mutation, allowing for users to leave a given organization, and support any errors that may occur. + # This union is used with the 'leaveOrganization' mutation, allowing for users to leave a given organization, and support any errors that may occur. union LeaveOrganizationUnion = AffiliationError | LeaveOrganizationResult # This object is used to inform the user that they successful left a given organization. @@ -2772,12 +2886,12 @@ export const getTypeNames = () => gql` } type RemoveUserFromOrgPayload { - # "RemoveUserFromOrgUnion" returning either a "RemoveUserFromOrgResult", or "RemoveUserFromOrgError" object. + # 'RemoveUserFromOrgUnion' returning either a 'RemoveUserFromOrgResult', or 'RemoveUserFromOrgError' object. result: RemoveUserFromOrgUnion clientMutationId: String } - # This union is used with the "RemoveUserFromOrg" mutation, allowing for users to remove a user from their org, and support any errors that may occur + # This union is used with the 'RemoveUserFromOrg' mutation, allowing for users to remove a user from their org, and support any errors that may occur union RemoveUserFromOrgUnion = AffiliationError | RemoveUserFromOrgResult # This object is used to inform the user of the removal status. @@ -2798,13 +2912,25 @@ export const getTypeNames = () => gql` clientMutationId: String } + type RequestOrgAffiliationPayload { + # 'InviteUserToOrgUnion' returning either a 'InviteUserToOrgResult', or 'InviteUserToOrgError' object. + result: InviteUserToOrgUnion + clientMutationId: String + } + + input RequestOrgAffiliationInput { + # The organization you wish to invite the user to. + orgId: ID! + clientMutationId: String + } + type TransferOrgOwnershipPayload { - # "TransferOrgOwnershipUnion" resolving to either a "TransferOrgOwnershipResult" or "AffiliationError". + # 'TransferOrgOwnershipUnion' resolving to either a 'TransferOrgOwnershipResult' or 'AffiliationError'. result: TransferOrgOwnershipUnion clientMutationId: String } - # This union is used with the "transferOrgOwnership" mutation, allowing for + # This union is used with the 'transferOrgOwnership' mutation, allowing for # users to transfer ownership of a given organization, and support any errors that may occur. union TransferOrgOwnershipUnion = AffiliationError | TransferOrgOwnershipResult @@ -2824,12 +2950,12 @@ export const getTypeNames = () => gql` } type UpdateUserRolePayload { - # "UpdateUserRoleUnion" returning either a "UpdateUserRoleResult", or "UpdateUserRoleError" object. + # 'UpdateUserRoleUnion' returning either a 'UpdateUserRoleResult', or 'UpdateUserRoleError' object. result: UpdateUserRoleUnion clientMutationId: String } - # This union is used with the "UpdateUserRole" mutation, allowing for users to update a users role in an org, and support any errors that may occur + # This union is used with the 'UpdateUserRole' mutation, allowing for users to update a users role in an org, and support any errors that may occur union UpdateUserRoleUnion = AffiliationError | UpdateUserRoleResult # This object is used to inform the user of the status of the role update. @@ -2853,16 +2979,16 @@ export const getTypeNames = () => gql` clientMutationId: String } - type CreateDomainPayload { - # "CreateDomainUnion" returning either a "Domain", or "CreateDomainError" object. - result: CreateDomainUnion + type AddOrganizationsDomainsPayload { + # 'BulkModifyDomainsUnion' returning either a 'DomainBulkResult', or 'DomainErrorType' object. + result: BulkModifyDomainsUnion clientMutationId: String } - # This union is used with the "CreateDomain" mutation, - # allowing for users to create a domain and add it to their org, - # and support any errors that may occur - union CreateDomainUnion = DomainError | Domain + # This union is used with the 'AddOrganizationsDomains' and 'RemoveOrganizationsDomains' mutation, + # allowing for users to add/remove multiple domains belonging to their org, + # and support any errors that may occur + union BulkModifyDomainsUnion = DomainError | DomainBulkResult # This object is used to inform the user if any errors occurred while using a domain mutation. type DomainError { @@ -2873,6 +2999,41 @@ export const getTypeNames = () => gql` description: String } + # This object is used to inform the user that no errors were encountered while mutating a domain. + type DomainBulkResult { + # Informs the user if the domain mutation was successful. + status: String + } + + input AddOrganizationsDomainsInput { + # The global id of the organization you wish to assign this domain to. + orgId: ID! + + # Url that you would like to be added to the database. + domains: [DomainScalar]! + + # New domains will be hidden. + hideNewDomains: Boolean + + # New domains will be tagged with NEW. + tagNewDomains: Boolean + + # Audit logs will be created. + audit: Boolean + clientMutationId: String + } + + type CreateDomainPayload { + # 'CreateDomainUnion' returning either a 'Domain', or 'CreateDomainError' object. + result: CreateDomainUnion + clientMutationId: String + } + + # This union is used with the 'CreateDomain' mutation, + # allowing for users to create a domain and add it to their org, + # and support any errors that may occur + union CreateDomainUnion = DomainError | Domain + input CreateDomainInput { # The global id of the organization you wish to assign this domain to. orgId: ID! @@ -2885,6 +3046,12 @@ export const getTypeNames = () => gql` # List of labelled tags users have applied to the domain. tags: [InputTag] + + # Value that determines if the domain is excluded from an organization's score. + hidden: Boolean + + # Value that determines if the domain is excluded from the scanning process. + archived: Boolean clientMutationId: String } @@ -2925,10 +3092,16 @@ export const getTypeNames = () => gql` # French label for tagging domains that are not active. INACTIF + + # English label for tagging domains that are hidden. + HIDDEN + + # English label for tagging domains that are archived. + ARCHIVED } type FavouriteDomainPayload { - # "CreateDomainUnion" returning either a "Domain", or "CreateDomainError" object. + # 'CreateDomainUnion' returning either a 'Domain', or 'CreateDomainError' object. result: CreateDomainUnion clientMutationId: String } @@ -2940,19 +3113,19 @@ export const getTypeNames = () => gql` } type RemoveDomainPayload { - # "RemoveDomainUnion" returning either a "DomainResultType", or "DomainErrorType" object. + # 'RemoveDomainUnion' returning either a 'DomainResultType', or 'DomainErrorType' object. result: RemoveDomainUnion! clientMutationId: String } - # This union is used with the "RemoveDomain" mutation, + # This union is used with the 'RemoveDomain' mutation, # allowing for users to remove a domain belonging to their org, # and support any errors that may occur union RemoveDomainUnion = DomainError | DomainResult - # This object is used to inform the user that no errors were encountered while removing a domain. + # This object is used to inform the user that no errors were encountered while mutating a domain. type DomainResult { - # Informs the user if the domain removal was successful. + # Informs the user if the domain mutation was successful. status: String # The domain that is being mutated. @@ -2971,6 +3144,27 @@ export const getTypeNames = () => gql` clientMutationId: String } + type RemoveOrganizationsDomainsPayload { + # 'BulkModifyDomainsUnion' returning either a 'DomainBulkResult', or 'DomainErrorType' object. + result: BulkModifyDomainsUnion! + clientMutationId: String + } + + input RemoveOrganizationsDomainsInput { + # Domains you wish to remove from the organization. + domains: [DomainScalar]! + + # The organization you wish to remove the domain from. + orgId: ID! + + # Domains will be archived. + archiveDomains: Boolean + + # Audit logs will be created. + audit: Boolean + clientMutationId: String + } + type RequestScanPayload { # Informs the user if the scan was dispatched successfully. status: String @@ -2984,7 +3178,7 @@ export const getTypeNames = () => gql` } type UnfavouriteDomainPayload { - # "RemoveDomainUnion" returning either a "DomainResultType", or "DomainErrorType" object. + # 'RemoveDomainUnion' returning either a 'DomainResultType', or 'DomainErrorType' object. result: RemoveDomainUnion! clientMutationId: String } @@ -2996,12 +3190,12 @@ export const getTypeNames = () => gql` } type UpdateDomainPayload { - # "UpdateDomainUnion" returning either a "Domain", or "DomainError" object. + # 'UpdateDomainUnion' returning either a 'Domain', or 'DomainError' object. result: UpdateDomainUnion clientMutationId: String } - # This union is used with the "UpdateDomain" mutation, + # This union is used with the 'UpdateDomain' mutation, # allowing for users to update a domain belonging to their org, # and support any errors that may occur union UpdateDomainUnion = DomainError | Domain @@ -3021,16 +3215,22 @@ export const getTypeNames = () => gql` # List of labelled tags users have applied to the domain. tags: [InputTag] + + # Value that determines if the domain is excluded from an organization's score. + hidden: Boolean + + # Value that determines if the domain is excluded from the scanning process. + archived: Boolean clientMutationId: String } type CreateOrganizationPayload { - # "CreateOrganizationUnion" returning either an "Organization", or "OrganizationError" object. + # 'CreateOrganizationUnion' returning either an 'Organization', or 'OrganizationError' object. result: CreateOrganizationUnion clientMutationId: String } - # This union is used with the "CreateOrganization" mutation, + # This union is used with the 'CreateOrganization' mutation, # allowing for users to create an organization, and support any errors that may occur union CreateOrganizationUnion = OrganizationError | Organization @@ -3089,12 +3289,12 @@ export const getTypeNames = () => gql` } type RemoveOrganizationPayload { - # "RemoveOrganizationUnion" returning either an "OrganizationResult", or "OrganizationError" object. + # 'RemoveOrganizationUnion' returning either an 'OrganizationResult', or 'OrganizationError' object. result: RemoveOrganizationUnion! clientMutationId: String } - # This union is used with the "RemoveOrganization" mutation, + # This union is used with the 'RemoveOrganization' mutation, # allowing for users to remove an organization they belong to, # and support any errors that may occur union RemoveOrganizationUnion = OrganizationError | OrganizationResult @@ -3115,12 +3315,12 @@ export const getTypeNames = () => gql` } type UpdateOrganizationPayload { - # "UpdateOrganizationUnion" returning either an "Organization", or "OrganizationError" object. + # 'UpdateOrganizationUnion' returning either an 'Organization', or 'OrganizationError' object. result: UpdateOrganizationUnion! clientMutationId: String } - # This union is used with the "UpdateOrganization" mutation, + # This union is used with the 'UpdateOrganization' mutation, # allowing for users to update an organization, and support any errors that may occur union UpdateOrganizationUnion = OrganizationError | Organization @@ -3173,12 +3373,12 @@ export const getTypeNames = () => gql` } type VerifyOrganizationPayload { - # "VerifyOrganizationUnion" returning either an "OrganizationResult", or "OrganizationError" object. + # 'VerifyOrganizationUnion' returning either an 'OrganizationResult', or 'OrganizationError' object. result: VerifyOrganizationUnion clientMutationId: String } - # This union is used with the "VerifyOrganization" mutation, + # This union is used with the 'VerifyOrganization' mutation, # allowing for super admins to verify an organization, # and support any errors that may occur union VerifyOrganizationUnion = OrganizationError | OrganizationResult @@ -3190,12 +3390,12 @@ export const getTypeNames = () => gql` } type AuthenticatePayload { - # Authenticate union returning either a "authResult" or "authenticateError" object. + # Authenticate union returning either a 'authResult' or 'authenticateError' object. result: AuthenticateUnion clientMutationId: String } - # This union is used with the "authenticate" mutation, allowing for the user to authenticate, and support any errors that may occur + # This union is used with the 'authenticate' mutation, allowing for the user to authenticate, and support any errors that may occur union AuthenticateUnion = AuthResult | AuthenticateError # An object used to return information when users sign up or authenticate. @@ -3226,12 +3426,12 @@ export const getTypeNames = () => gql` } type CloseAccountPayload { - # "CloseAccountUnion" returning either a "CloseAccountResult", or "CloseAccountError" object. + # 'CloseAccountUnion' returning either a 'CloseAccountResult', or 'CloseAccountError' object. result: CloseAccountUnion clientMutationId: String } - # This union is used for the "closeAccount" mutation, to support successful or errors that may occur. + # This union is used for the 'closeAccount' mutation, to support successful or errors that may occur. union CloseAccountUnion = CloseAccountResult | CloseAccountError # This object is used to inform the user of the status of closing their account. @@ -3256,12 +3456,12 @@ export const getTypeNames = () => gql` } type RefreshTokensPayload { - # Refresh tokens union returning either a "authResult" or "authenticateError" object. + # Refresh tokens union returning either a 'authResult' or 'authenticateError' object. result: RefreshTokensUnion clientMutationId: String } - # This union is used with the "refreshTokens" mutation, allowing for the user to refresh their tokens, and support any errors that may occur + # This union is used with the 'refreshTokens' mutation, allowing for the user to refresh their tokens, and support any errors that may occur union RefreshTokensUnion = AuthResult | AuthenticateError input RefreshTokensInput { @@ -3269,12 +3469,12 @@ export const getTypeNames = () => gql` } type RemovePhoneNumberPayload { - # "RemovePhoneNumberUnion" returning either a "RemovePhoneNumberResult", or "RemovePhoneNumberError" object. + # 'RemovePhoneNumberUnion' returning either a 'RemovePhoneNumberResult', or 'RemovePhoneNumberError' object. result: RemovePhoneNumberUnion clientMutationId: String } - # This union is used with the "RemovePhoneNumber" mutation, allowing for users to remove their phone number, and support any errors that may occur + # This union is used with the 'RemovePhoneNumber' mutation, allowing for users to remove their phone number, and support any errors that may occur union RemovePhoneNumberUnion = RemovePhoneNumberError | RemovePhoneNumberResult # This object is used to inform the user if any errors occurred while removing their phone number. @@ -3297,12 +3497,12 @@ export const getTypeNames = () => gql` } type ResetPasswordPayload { - # "ResetPasswordUnion" returning either a "ResetPasswordResult", or "ResetPasswordError" object. + # 'ResetPasswordUnion' returning either a 'ResetPasswordResult', or 'ResetPasswordError' object. result: ResetPasswordUnion clientMutationId: String } - # This union is used with the "ResetPassword" mutation, allowing for users to reset their password, and support any errors that may occur + # This union is used with the 'ResetPassword' mutation, allowing for users to reset their password, and support any errors that may occur union ResetPasswordUnion = ResetPasswordError | ResetPasswordResult # This object is used to inform the user if any errors occurred while resetting their password. @@ -3357,12 +3557,12 @@ export const getTypeNames = () => gql` } type SetPhoneNumberPayload { - # "SetPhoneNumberUnion" returning either a "SetPhoneNumberResult", or "SetPhoneNumberError" object. + # 'SetPhoneNumberUnion' returning either a 'SetPhoneNumberResult', or 'SetPhoneNumberError' object. result: SetPhoneNumberUnion clientMutationId: String } - # This union is used with the "setPhoneNumber" mutation, allowing for users to send a verification code to their phone, and support any errors that may occur + # This union is used with the 'setPhoneNumber' mutation, allowing for users to send a verification code to their phone, and support any errors that may occur union SetPhoneNumberUnion = SetPhoneNumberError | SetPhoneNumberResult # This object is used to inform the user if any errors occurred while setting a new phone number. @@ -3390,12 +3590,12 @@ export const getTypeNames = () => gql` } type SignInPayload { - # "SignInUnion" returning either a "regularSignInResult", "tfaSignInResult", or "signInError" object. + # 'SignInUnion' returning either a 'regularSignInResult', 'tfaSignInResult', or 'signInError' object. result: SignInUnion clientMutationId: String } - # This union is used with the "SignIn" mutation, allowing for multiple styles of logging in, and support any errors that may occur + # This union is used with the 'SignIn' mutation, allowing for multiple styles of logging in, and support any errors that may occur union SignInUnion = AuthResult | SignInError | TFASignInResult # This object is used to inform the user if any errors occurred during sign in. @@ -3439,12 +3639,12 @@ export const getTypeNames = () => gql` } type SignUpPayload { - # "SignUpUnion" returning either a "AuthResult", or "SignUpError" object. + # 'SignUpUnion' returning either a 'AuthResult', or 'SignUpError' object. result: SignUpUnion clientMutationId: String } - # This union is used with the "signUp" mutation, allowing for the user to sign up, and support any errors that may occur. + # This union is used with the 'signUp' mutation, allowing for the user to sign up, and support any errors that may occur. union SignUpUnion = AuthResult | SignUpError # This object is used to inform the user if any errors occurred during sign up. @@ -3481,12 +3681,12 @@ export const getTypeNames = () => gql` } type UpdateUserPasswordPayload { - # "UpdateUserPasswordUnion" returning either a "UpdateUserPasswordResultType", or "UpdateUserPasswordError" object. + # 'UpdateUserPasswordUnion' returning either a 'UpdateUserPasswordResultType', or 'UpdateUserPasswordError' object. result: UpdateUserPasswordUnion clientMutationId: String } - # This union is used with the "updateUserPassword" mutation, allowing for users to update their password, and support any errors that may occur + # This union is used with the 'updateUserPassword' mutation, allowing for users to update their password, and support any errors that may occur union UpdateUserPasswordUnion = UpdateUserPasswordError | UpdateUserPasswordResultType # This object is used to inform the user if any errors occurred while updating their password. @@ -3517,12 +3717,12 @@ export const getTypeNames = () => gql` } type UpdateUserProfilePayload { - # "UpdateUserProfileUnion" returning either a "UpdateUserProfileResult", or "UpdateUserProfileError" object. + # 'UpdateUserProfileUnion' returning either a 'UpdateUserProfileResult', or 'UpdateUserProfileError' object. result: UpdateUserProfileUnion clientMutationId: String } - # This union is used with the "updateUserProfile" mutation, allowing for users to update their profile, and support any errors that may occur + # This union is used with the 'updateUserProfile' mutation, allowing for users to update their profile, and support any errors that may occur union UpdateUserProfileUnion = UpdateUserProfileError | UpdateUserProfileResult # This object is used to inform the user if any errors occurred while updating their profile. @@ -3562,12 +3762,12 @@ export const getTypeNames = () => gql` } type VerifyAccountPayload { - # "VerifyAccountUnion" returning either a "VerifyAccountResult", or "VerifyAccountError" object. + # 'VerifyAccountUnion' returning either a 'VerifyAccountResult', or 'VerifyAccountError' object. result: VerifyAccountUnion clientMutationId: String } - # This union is used with the "verifyAccount" mutation, allowing for users to verify their account, and support any errors that may occur + # This union is used with the 'verifyAccount' mutation, allowing for users to verify their account, and support any errors that may occur union VerifyAccountUnion = VerifyAccountError | VerifyAccountResult # This object is used to inform the user if any errors occurred while verifying their account. @@ -3592,12 +3792,12 @@ export const getTypeNames = () => gql` } type verifyPhoneNumberPayload { - # "VerifyPhoneNumberUnion" returning either a "VerifyPhoneNumberResult", or "VerifyPhoneNumberError" object. + # 'VerifyPhoneNumberUnion' returning either a 'VerifyPhoneNumberResult', or 'VerifyPhoneNumberError' object. result: VerifyPhoneNumberUnion clientMutationId: String } - # This union is used with the "verifyPhoneNumber" mutation, allowing for users to verify their phone number, and support any errors that may occur + # This union is used with the 'verifyPhoneNumber' mutation, allowing for users to verify their phone number, and support any errors that may occur union VerifyPhoneNumberUnion = VerifyPhoneNumberError | VerifyPhoneNumberResult # This object is used to inform the user if any errors occurred while verifying their phone number. diff --git a/frontend/src/admin/AdminPanel.js b/frontend/src/admin/AdminPanel.js index ce2337721a..9fd4cb055c 100644 --- a/frontend/src/admin/AdminPanel.js +++ b/frontend/src/admin/AdminPanel.js @@ -1,12 +1,5 @@ import React from 'react' -import { - Stack, - Tab, - TabList, - TabPanel, - TabPanels, - Tabs, -} from '@chakra-ui/react' +import { Stack, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react' import { Trans } from '@lingui/macro' import { string } from 'prop-types' import { ErrorBoundary } from 'react-error-boundary' @@ -36,17 +29,13 @@ export function AdminPanel({ activeMenu, orgSlug, permission, orgId }) { - + @@ -133,26 +126,11 @@ export function UserList({ permission, orgSlug, usersPerPage, orgId }) { onOpen() }} > - - + + Search: - + @@ -164,11 +142,7 @@ export function UserList({ permission, orgSlug, usersPerPage, orgId }) { /> - @@ -190,9 +164,7 @@ export function UserList({ permission, orgSlug, usersPerPage, orgId }) { isOpen={isOpen} onClose={onClose} orgId={orgId} - editingUserName={ - mutation === 'remove' ? selectedRemoveUser.userName : editingUserName - } + editingUserName={mutation === 'remove' ? selectedRemoveUser.userName : editingUserName} editingUserRole={editingUserRole} editingUserId={selectedRemoveUser.id} orgSlug={orgSlug} @@ -208,4 +180,5 @@ UserList.propTypes = { permission: string, usersPerPage: number, orgId: string.isRequired, + includePending: bool, } diff --git a/frontend/src/admin/UserListModal.js b/frontend/src/admin/UserListModal.js index af2c5ad963..7f113fcc70 100644 --- a/frontend/src/admin/UserListModal.js +++ b/frontend/src/admin/UserListModal.js @@ -38,10 +38,13 @@ export function UserListModal({ const toast = useToast() const initialFocusRef = useRef() - const [addUser, { loading: _addUserLoading }] = useMutation(INVITE_USER_TO_ORG, { - refetchQueries: ['PaginatedOrgAffiliations', 'FindAuditLogs'], + const refetchQueriesValues = { + refetchQueries: ['PaginatedOrgAffiliations', 'FindAuditLogs', 'FindMyUsers'], awaitRefetchQueries: true, + } + const [addUser, { loading: _addUserLoading }] = useMutation(INVITE_USER_TO_ORG, { + ...refetchQueriesValues, onError(error) { toast({ title: t`An error occurred.`, @@ -86,9 +89,7 @@ export function UserListModal({ }) const [updateUserRole, { loading: _updateLoading, error: _updateError }] = useMutation(UPDATE_USER_ROLE, { - refetchQueries: ['FindMyUsers', 'FindAuditLogs'], - awaitRefetchQueries: true, - + ...refetchQueriesValues, onError(updateError) { toast({ title: updateError.message, @@ -133,9 +134,7 @@ export function UserListModal({ }) const [removeUser, { loading: _removeUserLoading }] = useMutation(REMOVE_USER_FROM_ORG, { - refetchQueries: ['FindMyUsers', 'FindAuditLogs'], - awaitRefetchQueries: true, - + ...refetchQueriesValues, onError(error) { toast({ title: t`An error occurred.`, @@ -253,11 +252,12 @@ export function UserListModal({ defaultValue={editingUserRole} onChange={handleChange} > - {(editingUserRole === 'USER' || + {editingUserRole === 'PENDING' && } + {(['PENDING', 'USER'].includes(editingUserRole) || (permission === 'SUPER_ADMIN' && editingUserRole === 'ADMIN')) && ( )} - {(editingUserRole === 'USER' || editingUserRole === 'ADMIN') && ( + {['PENDING', 'USER', 'ADMIN'].includes(editingUserRole) && ( )} {(editingUserRole === 'SUPER_ADMIN' || diff --git a/frontend/src/admin/__tests__/AdminPage.test.js b/frontend/src/admin/__tests__/AdminPage.test.js index 1e5eafa695..4b72b401d5 100644 --- a/frontend/src/admin/__tests__/AdminPage.test.js +++ b/frontend/src/admin/__tests__/AdminPage.test.js @@ -32,15 +32,10 @@ describe('', () => { it('shows a list of the users organizations', async () => { const { getByText } = render( - + - + @@ -67,10 +62,7 @@ describe('', () => { > - + @@ -107,10 +99,7 @@ describe('', () => { > - + @@ -295,7 +284,7 @@ function mocks() { { request: { query: PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE, - variables: { orgSlug: 'Wolf-Group', first: 10, search: '' }, + variables: { orgSlug: 'Wolf-Group', first: 10, search: '', includePending: true }, }, result: { data: { diff --git a/frontend/src/admin/__tests__/UserList.test.js b/frontend/src/admin/__tests__/UserList.test.js index ae93f4d6d3..1b04c40b45 100644 --- a/frontend/src/admin/__tests__/UserList.test.js +++ b/frontend/src/admin/__tests__/UserList.test.js @@ -13,11 +13,7 @@ import { UserList } from '../UserList' import { UserVarProvider } from '../../utilities/userState' import { createCache } from '../../client' import { PAGINATED_ORG_AFFILIATIONS_ADMIN_PAGE as FORWARD } from '../../graphql/queries' -import { - UPDATE_USER_ROLE, - INVITE_USER_TO_ORG, - REMOVE_USER_FROM_ORG, -} from '../../graphql/mutations' +import { UPDATE_USER_ROLE, INVITE_USER_TO_ORG, REMOVE_USER_FROM_ORG } from '../../graphql/mutations' import { rawOrgUserListData } from '../../fixtures/orgUserListData' const i18n = setupI18n({ @@ -34,14 +30,14 @@ const successMocks = [ { request: { query: FORWARD, - variables: { first: 10, orgSlug: 'test-org.slug', search: '' }, + variables: { first: 10, orgSlug: 'test-org.slug', search: '', includePending: true }, }, result: { data: rawOrgUserListData }, }, { request: { query: FORWARD, - variables: { first: 10, orgSlug: 'test-org.slug' }, + variables: { first: 10, orgSlug: 'test-org.slug', search: '', includePending: true }, }, result: { data: rawOrgUserListData }, }, @@ -49,9 +45,7 @@ const successMocks = [ request: { query: UPDATE_USER_ROLE, variables: { - userName: - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.userName, + userName: rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node.user.userName, orgId: rawOrgUserListData.findOrganizationBySlug.id, role: 'ADMIN', }, @@ -94,9 +88,7 @@ const successMocks = [ request: { query: REMOVE_USER_FROM_ORG, variables: { - userId: - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.id, + userId: rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node.user.id, orgId: rawOrgUserListData.findOrganizationBySlug.id, }, }, @@ -122,13 +114,12 @@ describe('', () => { it('successfully renders with mocked data', async () => { const { getByText } = render( - + ', () => { await waitFor(() => expect( - getByText( - rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node - .user.userName, - ), + getByText(rawOrgUserListData.findOrganizationBySlug.affiliations.edges[0].node.user.userName), ).toBeInTheDocument(), ) }) @@ -156,13 +144,7 @@ describe('', () => { // edit success it('updateUserRole elements render', async () => { - const { - getAllByText, - getByDisplayValue, - getByText, - findByLabelText, - findAllByLabelText, - } = render( + const { getAllByText, getByDisplayValue, getByText, findByLabelText, findAllByLabelText } = render( ', () => { ', () => { ', () => { + {userName} @@ -16,7 +12,7 @@ export function UserCard({ userName, displayName, role, ...props }) { {role && ( Sign up" #~ msgid "Dploy DKIM records and keys for all domains and senders; and" #~ msgstr "Dploy DKIM records and keys for all domains and senders; and" -#: src/organizationDetails/OrganizationDomains.js:178 +#: src/organizationDetails/OrganizationDomains.js:179 msgid "EQUALS" msgstr "EQUALS" @@ -1061,7 +1062,7 @@ msgstr "Email Sent" msgid "Email Validated" msgstr "Email Validated" -#: src/app/App.js:288 +#: src/app/App.js:296 msgid "Email Verification" msgstr "Email Verification" @@ -1071,7 +1072,7 @@ msgstr "Email Verification" msgid "Email cannot be empty" msgstr "Email cannot be empty" -#: src/admin/UserListModal.js:59 +#: src/admin/UserListModal.js:62 msgid "Email invitation sent" msgstr "Email invitation sent" @@ -1172,7 +1173,7 @@ msgstr "Export to CSV" #: src/dmarc/DmarcReportPage.js:129 #: src/dmarc/DmarcReportPage.js:130 -#: src/organizationDetails/OrganizationDomains.js:223 +#: src/organizationDetails/OrganizationDomains.js:211 msgid "Fail" msgstr "Fail" @@ -1221,7 +1222,7 @@ msgstr "February" #~ msgid "Filters" #~ msgstr "Filters" -#: src/organizationDetails/OrganizationDomains.js:138 +#: src/organizationDetails/OrganizationDomains.js:137 msgid "Filters:" msgstr "Filters:" @@ -1261,7 +1262,7 @@ msgstr "For details related to terms pertaining to privacy, please refer to" msgid "For users interested in using new features that are still in progress." msgstr "For users interested in using new features that are still in progress." -#: src/app/App.js:176 +#: src/app/App.js:180 #: src/auth/ForgotPasswordPage.js:75 msgid "Forgot Password" msgstr "Forgot Password" @@ -1302,7 +1303,7 @@ msgstr "Fully Aligned Table" msgid "Fully Aligned by IP Address" msgstr "Fully Aligned by IP Address" -#: src/organizations/Organizations.js:130 +#: src/organizations/Organizations.js:143 msgid "Further details for each organization can be found by clicking on its row." msgstr "Further details for each organization can be found by clicking on its row." @@ -1346,7 +1347,7 @@ msgstr "Government of Canada Employees" #~ msgid "Graph direction:" #~ msgstr "Graph direction:" -#: src/app/App.js:337 +#: src/app/App.js:345 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1369,9 +1370,9 @@ msgstr "Guidance results" msgid "HIDDEN" msgstr "HIDDEN" -#: src/domains/DomainCard.js:185 +#: src/domains/DomainCard.js:186 #: src/domains/DomainsPage.js:157 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/organizationDetails/OrganizationDomains.js:270 msgid "HSTS" msgstr "HSTS" @@ -1396,7 +1397,7 @@ msgid "HSTS Preloaded" msgstr "HSTS Preloaded" #: src/domains/DomainsPage.js:70 -#: src/organizationDetails/OrganizationDomains.js:82 +#: src/organizationDetails/OrganizationDomains.js:83 msgid "HSTS Status" msgstr "HSTS Status" @@ -1416,9 +1417,9 @@ msgstr "HTTP Live" msgid "HTTP Upgrades" msgstr "HTTP Upgrades" -#: src/domains/DomainCard.js:184 +#: src/domains/DomainCard.js:185 #: src/domains/DomainsPage.js:159 -#: src/organizationDetails/OrganizationDomains.js:284 +#: src/organizationDetails/OrganizationDomains.js:272 msgid "HTTPS" msgstr "HTTPS" @@ -1431,7 +1432,7 @@ msgid "HTTPS Configuration Summary" msgstr "HTTPS Configuration Summary" #: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:122 +#: src/organizations/Organizations.js:135 msgid "HTTPS Configured" msgstr "HTTPS Configured" @@ -1448,7 +1449,7 @@ msgid "HTTPS Scan Complete" msgstr "HTTPS Scan Complete" #: src/domains/DomainsPage.js:69 -#: src/organizationDetails/OrganizationDomains.js:81 +#: src/organizationDetails/OrganizationDomains.js:82 msgid "HTTPS Status" msgstr "HTTPS Status" @@ -1484,8 +1485,8 @@ msgstr "Heartbleed Vulnerable" #~ msgid "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." #~ msgstr "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." -#: src/admin/AdminDomainCard.js:44 -#: src/organizationDetails/OrganizationDomains.js:102 +#: src/admin/AdminDomainCard.js:68 +#: src/organizationDetails/OrganizationDomains.js:100 msgid "Hidden" msgstr "Hidden" @@ -1493,8 +1494,8 @@ msgstr "Hidden" msgid "Hide domain" msgstr "Hide domain" -#: src/app/App.js:77 -#: src/app/App.js:146 +#: src/app/App.js:78 +#: src/app/App.js:150 #: src/app/FloatingMenu.js:175 msgid "Home" msgstr "Home" @@ -1523,7 +1524,7 @@ msgstr "How can I edit my domain list?" msgid "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" msgstr "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:99 msgid "INACTIVE" msgstr "INACTIVE" @@ -1621,7 +1622,7 @@ msgstr "Implementation: <0>Implementation guidance: email domain protection (ITS msgid "Implemented" msgstr "Implemented" -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:99 msgid "Inactive" msgstr "Inactive" @@ -1642,7 +1643,7 @@ msgstr "Incorrect createDomain.result typename." msgid "Incorrect createOrganization.result typename." msgstr "Incorrect createOrganization.result typename." -#: src/admin/UserListModal.js:78 +#: src/admin/UserListModal.js:81 msgid "Incorrect inviteUserToOrg.result typename." msgstr "Incorrect inviteUserToOrg.result typename." @@ -1667,8 +1668,8 @@ msgstr "Incorrect resetPassword.result typename." #: src/admin/AdminDomainModal.js:142 #: src/admin/AdminDomains.js:133 #: src/admin/SuperAdminUserList.js:110 -#: src/admin/UserListModal.js:77 -#: src/admin/UserListModal.js:124 +#: src/admin/UserListModal.js:80 +#: src/admin/UserListModal.js:125 #: src/auth/CreateUserPage.js:83 #: src/auth/ResetPasswordPage.js:60 #: src/auth/SignInPage.js:100 @@ -1726,7 +1727,7 @@ msgstr "Incorrect updateUserPassword.result typename." msgid "Incorrect updateUserProfile.result typename." msgstr "Incorrect updateUserProfile.result typename." -#: src/admin/UserListModal.js:125 +#: src/admin/UserListModal.js:126 msgid "Incorrect updateUserRole.result typename." msgstr "Incorrect updateUserRole.result typename." @@ -1750,7 +1751,7 @@ msgstr "Individuals from a departmental information technology group may contact #~ msgid "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." #~ msgstr "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." -#: src/organizationDetails/OrganizationDomains.js:220 +#: src/organizationDetails/OrganizationDomains.js:208 msgid "Info" msgstr "Info" @@ -1810,7 +1811,11 @@ msgstr "Internet-facing" msgid "Invalid email" msgstr "Invalid email" -#: src/admin/UserList.js:173 +#: src/organizations/RequestOrgInviteModal.js:36 +msgid "Invite Requested" +msgstr "Invite Requested" + +#: src/admin/UserList.js:147 msgid "Invite User" msgstr "Invite User" @@ -1988,14 +1993,14 @@ msgstr "Must Staple" #~ msgid "My Tracker" #~ msgstr "My Tracker" -#: src/organizationDetails/OrganizationDomains.js:93 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "NEW" msgstr "NEW" #: src/admin/WebCheckPage.js:60 #: src/createOrganization/CreateOrganizationPage.js:173 #: src/createOrganization/CreateOrganizationPage.js:178 -#: src/organizations/Organizations.js:60 +#: src/organizations/Organizations.js:57 msgid "Name" msgstr "Name" @@ -2037,7 +2042,7 @@ msgstr "Negative" msgid "Never" msgstr "Never" -#: src/organizationDetails/OrganizationDomains.js:93 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "New" msgstr "New" @@ -2102,7 +2107,7 @@ msgstr "No DMARC phase information available for this organization." #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 -#: src/organizationDetails/OrganizationDomains.js:246 +#: src/organizationDetails/OrganizationDomains.js:235 msgid "No Domains" msgstr "No Domains" @@ -2111,7 +2116,7 @@ msgid "No HTTPS configuration information available for this organization." msgstr "No HTTPS configuration information available for this organization." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:81 +#: src/organizations/Organizations.js:78 msgid "No Organizations" msgstr "No Organizations" @@ -2181,7 +2186,7 @@ msgid "No scan data is currently available for this service. You may request a s msgstr "No scan data is currently available for this service. You may request a scan using the refresh button, or wait up to 24 hours for data to refresh." #: src/admin/SuperAdminUserList.js:161 -#: src/admin/UserList.js:76 +#: src/admin/UserList.js:69 msgid "No users" msgstr "No users" @@ -2277,7 +2282,7 @@ msgstr "Options include contacting the <0>SSC WebSSL services team and/or us msgid "Organization" msgstr "Organization" -#: src/organizationDetails/OrganizationDetails.js:63 +#: src/organizationDetails/OrganizationDetails.js:67 msgid "Organization Details" msgstr "Organization Details" @@ -2286,7 +2291,7 @@ msgid "Organization Information" msgstr "Organization Information" #: src/admin/OrganizationInformation.js:509 -#: src/organizations/Organizations.js:114 +#: src/organizations/Organizations.js:130 msgid "Organization Name" msgstr "Organization Name" @@ -2318,15 +2323,19 @@ msgid "Organization:" msgstr "Organization:" #: src/admin/AdminPage.js:189 -#: src/app/App.js:83 -#: src/app/App.js:186 +#: src/app/App.js:84 +#: src/app/App.js:190 #: src/app/FloatingMenu.js:103 -#: src/organizations/Organizations.js:72 -#: src/organizations/Organizations.js:109 +#: src/organizations/Organizations.js:69 +#: src/organizations/Organizations.js:125 msgid "Organizations" msgstr "Organizations" -#: src/organizationDetails/OrganizationDomains.js:94 +#: src/admin/UserListModal.js:256 +msgid "PENDING" +msgstr "PENDING" + +#: src/organizationDetails/OrganizationDomains.js:95 msgid "PROD" msgstr "PROD" @@ -2336,7 +2345,7 @@ msgstr "Page {0} of {1}" #: src/dmarc/DmarcReportPage.js:120 #: src/dmarc/DmarcReportPage.js:121 -#: src/organizationDetails/OrganizationDomains.js:217 +#: src/organizationDetails/OrganizationDomains.js:205 msgid "Pass" msgstr "Pass" @@ -2489,7 +2498,7 @@ msgstr "Prevent this domain from being visible, scanned, and being counted in an #~ msgid "Previous" #~ msgstr "Previous" -#: src/app/App.js:321 +#: src/app/App.js:329 #: src/app/FloatingMenu.js:219 #: src/app/SlideMessage.js:88 #: src/termsConditions/TermsConditionsPage.js:41 @@ -2504,7 +2513,7 @@ msgstr "Privacy Act." msgid "Privacy Notice Statement" msgstr "Privacy Notice Statement" -#: src/organizationDetails/OrganizationDomains.js:94 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "Prod" msgstr "Prod" @@ -2512,16 +2521,16 @@ msgstr "Prod" msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" msgstr "Protect domains that do not send email - GOV.UK (www.gov.uk)" -#: src/domains/DomainCard.js:187 +#: src/domains/DomainCard.js:188 #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 -#: src/organizationDetails/OrganizationDomains.js:287 -#: src/organizationDetails/OrganizationDomains.js:326 +#: src/organizationDetails/OrganizationDomains.js:275 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Protocols" msgstr "Protocols" #: src/domains/DomainsPage.js:74 -#: src/organizationDetails/OrganizationDomains.js:86 +#: src/organizationDetails/OrganizationDomains.js:87 msgid "Protocols Status" msgstr "Protocols Status" @@ -2562,7 +2571,7 @@ msgstr "ROBOT Vulnerable" #~ msgid "Read Guidance" #~ msgstr "Read Guidance" -#: src/app/App.js:184 +#: src/app/App.js:188 msgid "Read guidance" msgstr "Read guidance" @@ -2623,12 +2632,17 @@ msgstr "Remove User" msgid "Removed Organization" msgstr "Removed Organization" -#: src/app/App.js:329 +#: src/app/App.js:337 #: src/app/FloatingMenu.js:230 #: src/app/SlideMessage.js:99 msgid "Report an Issue" msgstr "Report an Issue" +#: src/organizationDetails/OrganizationDetails.js:112 +#: src/organizations/RequestOrgInviteModal.js:62 +msgid "Request Invite" +msgstr "Request Invite" + #: src/domains/ScanDomain.js:167 msgid "Request a domain to be scanned:" msgstr "Request a domain to be scanned:" @@ -2653,7 +2667,7 @@ msgstr "Requirements: <0>Email Management Services Configuration RequirementsWeb Sites and Services Management Configuration Requirements" msgstr "Requirements: <0>Web Sites and Services Management Configuration Requirements" -#: src/app/App.js:178 +#: src/app/App.js:182 msgid "Reset Password" msgstr "Reset Password" @@ -2696,7 +2710,7 @@ msgstr "Results for scans of web technologies (TLS, HTTPS)." msgid "Revoked:" msgstr "Revoked:" -#: src/admin/UserListModal.js:105 +#: src/admin/UserListModal.js:106 msgid "Role updated" msgstr "Role updated" @@ -2714,7 +2728,7 @@ msgid "SAN List:" msgstr "SAN List:" #: src/domains/DomainsPage.js:163 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/organizationDetails/OrganizationDomains.js:276 msgid "SPF" msgstr "SPF" @@ -2741,7 +2755,7 @@ msgid "SPF Results" msgstr "SPF Results" #: src/domains/DomainsPage.js:75 -#: src/organizationDetails/OrganizationDomains.js:87 +#: src/organizationDetails/OrganizationDomains.js:88 msgid "SPF Status" msgstr "SPF Status" @@ -2762,11 +2776,11 @@ msgstr "SPF Status" #~ msgid "SSL scan for domain \"{0}\" has completed." #~ msgstr "SSL scan for domain \"{0}\" has completed." -#: src/organizationDetails/OrganizationDomains.js:95 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "STAGING" msgstr "STAGING" -#: src/admin/UserListModal.js:265 +#: src/admin/UserListModal.js:266 msgid "SUPER_ADMIN" msgstr "SUPER_ADMIN" @@ -2783,7 +2797,7 @@ msgstr "Save" msgid "Scan Domain" msgstr "Scan Domain" -#: src/domains/DomainCard.js:141 +#: src/domains/DomainCard.js:143 #: src/guidance/GuidancePage.js:140 msgid "Scan Pending" msgstr "Scan Pending" @@ -2823,7 +2837,7 @@ msgstr "Search by initiated by, resource name" #: src/dmarc/DmarcByDomainPage.js:221 #: src/dmarc/DmarcByDomainPage.js:292 #: src/domains/DomainsPage.js:189 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:301 msgid "Search for a domain" msgstr "Search for a domain" @@ -2839,12 +2853,12 @@ msgstr "Search for a user (email)" #~ msgid "Search for an activity" #~ msgstr "Search for an activity" -#: src/organizations/Organizations.js:151 +#: src/organizations/Organizations.js:161 msgid "Search for an organization" msgstr "Search for an organization" #: src/admin/AdminDomains.js:252 -#: src/admin/UserList.js:149 +#: src/admin/UserList.js:131 #: src/components/ReactTableGlobalFilter.js:36 #: src/components/SearchBox.js:44 msgid "Search:" @@ -2904,8 +2918,8 @@ msgstr "September" msgid "Serial:" msgstr "Serial:" -#: src/organizations/Organizations.js:62 -#: src/organizations/Organizations.js:118 +#: src/organizations/Organizations.js:59 +#: src/organizations/Organizations.js:133 msgid "Services" msgstr "Services" @@ -2978,42 +2992,42 @@ msgstr "Shows if the domain has a valid SSL certificate." #~ msgstr "Shows if the domain is policy compliant." #: src/domains/DomainsPage.js:166 -#: src/organizationDetails/OrganizationDomains.js:291 +#: src/organizationDetails/OrganizationDomains.js:279 msgid "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." msgstr "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." #: src/domains/DomainsPage.js:157 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/organizationDetails/OrganizationDomains.js:270 msgid "Shows if the domain meets the HSTS requirements." msgstr "Shows if the domain meets the HSTS requirements." #: src/domains/DomainsPage.js:160 -#: src/organizationDetails/OrganizationDomains.js:285 +#: src/organizationDetails/OrganizationDomains.js:273 msgid "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." msgstr "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." #: src/domains/DomainsPage.js:170 -#: src/organizationDetails/OrganizationDomains.js:295 +#: src/organizationDetails/OrganizationDomains.js:283 msgid "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." msgstr "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." #: src/domains/DomainsPage.js:163 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/organizationDetails/OrganizationDomains.js:276 msgid "Shows if the domain meets the Sender Policy Framework (SPF) requirements." msgstr "Shows if the domain meets the Sender Policy Framework (SPF) requirements." #: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:287 +#: src/organizationDetails/OrganizationDomains.js:275 msgid "Shows if the domain uses acceptable protocols." msgstr "Shows if the domain uses acceptable protocols." #: src/domains/DomainsPage.js:155 -#: src/organizationDetails/OrganizationDomains.js:280 +#: src/organizationDetails/OrganizationDomains.js:268 msgid "Shows if the domain uses only ciphers that are strong or acceptable." msgstr "Shows if the domain uses only ciphers that are strong or acceptable." #: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:269 msgid "Shows if the domain uses only curves that are strong or acceptable." msgstr "Shows if the domain uses only curves that are strong or acceptable." @@ -3049,11 +3063,11 @@ msgstr "Shows if the server was found to be vulnerable to the ROBOT vulnerabilit msgid "Shows the duration of time, in seconds, that the HSTS header is valid." msgstr "Shows the duration of time, in seconds, that the HSTS header is valid." -#: src/organizations/Organizations.js:119 +#: src/organizations/Organizations.js:133 msgid "Shows the number of domains that the organization is in control of." msgstr "Shows the number of domains that the organization is in control of." -#: src/organizations/Organizations.js:123 +#: src/organizations/Organizations.js:136 msgid "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS" msgstr "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS" @@ -3061,7 +3075,7 @@ msgstr "Shows the percentage of domains which have HTTPS configured and upgrade #~ msgid "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS (ITPIN 6.1.1)" #~ msgstr "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS (ITPIN 6.1.1)" -#: src/organizations/Organizations.js:127 +#: src/organizations/Organizations.js:140 msgid "Shows the percentage of domains which have a valid DMARC policy configuration." msgstr "Shows the percentage of domains which have a valid DMARC policy configuration." @@ -3089,7 +3103,7 @@ msgstr "Shows the total number of emails that have been sent by this domain duri #~ msgid "Siganture Hash:" #~ msgstr "Siganture Hash:" -#: src/app/App.js:156 +#: src/app/App.js:160 #: src/app/FloatingMenu.js:197 #: src/app/TopBanner.js:118 #: src/auth/SignInPage.js:189 @@ -3119,7 +3133,7 @@ msgstr "Sign Out." msgid "Signature Hash:" msgstr "Signature Hash:" -#: src/app/App.js:71 +#: src/app/App.js:72 msgid "Skip to main content" msgstr "Skip to main content" @@ -3135,7 +3149,7 @@ msgstr "Sort by:" msgid "Source IP Address" msgstr "Source IP Address" -#: src/organizationDetails/OrganizationDomains.js:95 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "Staging" msgstr "Staging" @@ -3143,7 +3157,7 @@ msgstr "Staging" #~ msgid "Status" #~ msgstr "Status" -#: src/organizationDetails/OrganizationDomains.js:191 +#: src/organizationDetails/OrganizationDomains.js:192 msgid "Status or tag" msgstr "Status or tag" @@ -3172,11 +3186,11 @@ msgstr "Subject:" msgid "Submit" msgstr "Submit" -#: src/admin/UserListModal.js:154 +#: src/admin/UserListModal.js:153 msgid "Successfully removed user {0}." msgstr "Successfully removed user {0}." -#: src/organizationDetails/OrganizationDetails.js:133 +#: src/organizationDetails/OrganizationDetails.js:132 #: src/user/MyTrackerPage.js:93 msgid "Summary" msgstr "Summary" @@ -3209,7 +3223,7 @@ msgstr "TBS be identified as the source; and" msgid "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." msgstr "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "TEST" msgstr "TEST" @@ -3241,11 +3255,11 @@ msgstr "Technical implementation guidance:" msgid "Termination" msgstr "Termination" -#: src/app/App.js:180 +#: src/app/App.js:184 msgid "Terms & Conditions" msgstr "Terms & Conditions" -#: src/app/App.js:325 +#: src/app/App.js:333 #: src/app/FloatingMenu.js:225 #: src/app/SlideMessage.js:92 msgid "Terms & conditions" @@ -3259,7 +3273,7 @@ msgstr "Terms and Conditions" msgid "Terms of Use" msgstr "Terms of Use" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "Test" msgstr "Test" @@ -3293,7 +3307,7 @@ msgstr "The advice, guidance or services provided to you by TBS will be provided #: src/dmarc/DmarcByDomainPage.js:324 #: src/domains/DomainsPage.js:154 -#: src/organizationDetails/OrganizationDomains.js:278 +#: src/organizationDetails/OrganizationDomains.js:267 msgid "The domain address." msgstr "The domain address." @@ -3341,7 +3355,7 @@ msgstr "The results of DKIM verification of the message. Can be pass, fail, neut msgid "The summary cards show two metrics that Tracker scans:" msgstr "The summary cards show two metrics that Tracker scans:" -#: src/admin/UserListModal.js:106 +#: src/admin/UserListModal.js:107 msgid "The user's role has been successfully updated" msgstr "The user's role has been successfully updated" @@ -3411,11 +3425,11 @@ msgstr "Time Generated (UTC)" #~ msgid "Timestamp" #~ msgstr "Timestamp" -#: src/app/App.js:118 +#: src/app/App.js:122 msgid "To enable full app functionality and maximize your account's security, <0>please verify your account." msgstr "To enable full app functionality and maximize your account's security, <0>please verify your account." -#: src/app/App.js:132 +#: src/app/App.js:136 msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "To maximize your account's security, <0>please activate a multi-factor authentication option." @@ -3492,11 +3506,11 @@ msgstr "Two-Factor Authentication:" msgid "URL:" msgstr "URL:" -#: src/admin/UserListModal.js:258 +#: src/admin/UserListModal.js:259 msgid "USER" msgstr "USER" -#: src/admin/UserListModal.js:95 +#: src/admin/UserListModal.js:96 msgid "Unable to change user role, please try again." msgstr "Unable to change user role, please try again." @@ -3525,7 +3539,7 @@ msgstr "Unable to create new organization." msgid "Unable to create your account, please try again." msgstr "Unable to create your account, please try again." -#: src/admin/UserListModal.js:68 +#: src/admin/UserListModal.js:71 msgid "Unable to invite user." msgstr "Unable to invite user." @@ -3542,10 +3556,15 @@ msgstr "Unable to remove domain." msgid "Unable to remove this organization." msgstr "Unable to remove this organization." -#: src/admin/UserListModal.js:162 +#: src/admin/UserListModal.js:161 msgid "Unable to remove user." msgstr "Unable to remove user." +#: src/organizations/RequestOrgInviteModal.js:26 +#: src/organizations/RequestOrgInviteModal.js:46 +msgid "Unable to request invite, please try again." +msgstr "Unable to request invite, please try again." + #: src/domains/ScanDomain.js:44 msgid "Unable to request scan, please try again." msgstr "Unable to request scan, please try again." @@ -3601,7 +3620,7 @@ msgstr "Unable to update to your preferred language, please try again." msgid "Unable to update to your username, please try again." msgstr "Unable to update to your username, please try again." -#: src/admin/UserListModal.js:115 +#: src/admin/UserListModal.js:116 msgid "Unable to update user role." msgstr "Unable to update user role." @@ -3621,7 +3640,7 @@ msgstr "Unable to verify your phone number, please try again." msgid "Understanding Scan Metrics:" msgstr "Understanding Scan Metrics:" -#: src/domains/DomainCard.js:83 +#: src/domains/DomainCard.js:85 msgid "Unfavourited Domain" msgstr "Unfavourited Domain" @@ -3690,7 +3709,7 @@ msgid "User Email" msgstr "User Email" #: src/admin/SuperAdminUserList.js:157 -#: src/admin/UserList.js:72 +#: src/admin/UserList.js:65 msgid "User List" msgstr "User List" @@ -3698,11 +3717,11 @@ msgstr "User List" msgid "User email does not match" msgstr "User email does not match" -#: src/admin/UserListModal.js:58 +#: src/admin/UserListModal.js:61 msgid "User invited" msgstr "User invited" -#: src/admin/UserListModal.js:153 +#: src/admin/UserListModal.js:152 msgid "User removed." msgstr "User removed." @@ -3711,8 +3730,8 @@ msgid "User:" msgstr "User:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:29 -#: src/organizationDetails/OrganizationDetails.js:143 +#: src/admin/AdminPanel.js:24 +#: src/organizationDetails/OrganizationDetails.js:142 msgid "Users" msgstr "Users" @@ -3720,7 +3739,7 @@ msgstr "Users" msgid "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" msgstr "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" -#: src/organizationDetails/OrganizationDomains.js:155 +#: src/organizationDetails/OrganizationDomains.js:154 msgid "Value" msgstr "Value" @@ -3730,7 +3749,7 @@ msgstr "Verification code must only contains numbers" #: src/admin/SuperAdminUserList.js:150 #: src/admin/SuperAdminUserList.js:341 -#: src/organizations/Organizations.js:63 +#: src/organizations/Organizations.js:60 msgid "Verified" msgstr "Verified" @@ -3774,7 +3793,7 @@ msgstr "Volume of messages spoofing domain (reject + quarantine + none):" msgid "Vulnerability Scan Dashboard" msgstr "Vulnerability Scan Dashboard" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "WEB" msgstr "WEB" @@ -3810,7 +3829,7 @@ msgstr "We've sent you an email with an authentication code to sign into Tracker #~ msgid "Weak Curves:" #~ msgstr "Weak Curves:" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "Web" msgstr "Web" @@ -3904,6 +3923,10 @@ msgstr "Why does the guidance page not show the domain’s DKIM selectors even t msgid "Wiki" msgstr "Wiki" +#: src/organizations/RequestOrgInviteModal.js:66 +msgid "Would you like to request an invite to {orgName}?" +msgstr "Would you like to request an invite to {orgName}?" + #: src/guidance/WebConnectionResults.js:126 #: src/guidance/WebConnectionResults.js:166 #: src/guidance/WebConnectionResults.js:188 @@ -4005,7 +4028,7 @@ msgstr "You may now sign in with your new password" msgid "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." msgstr "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." -#: src/app/App.js:262 +#: src/app/App.js:266 msgid "Your Account" msgstr "Your Account" @@ -4021,6 +4044,10 @@ msgstr "Your account email was successfully verified" msgid "Your account will be fully activated the next time you log in" msgstr "Your account will be fully activated the next time you log in" +#: src/organizations/RequestOrgInviteModal.js:37 +msgid "Your request has been sent to the organization administrators." +msgstr "Your request has been sent to the organization administrators." + #: src/admin/OrganizationInformation.js:421 msgid "Zone:" msgstr "Zone:" @@ -4041,8 +4068,8 @@ msgstr "contact us" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:97 -#: src/app/App.js:275 +#: src/app/App.js:100 +#: src/app/App.js:279 #: src/user/MyTrackerPage.js:43 #: src/user/MyTrackerPage.js:74 msgid "myTracker" @@ -4076,7 +4103,7 @@ msgstr "sp:" msgid "strong" msgstr "strong" -#: src/admin/UserList.js:162 +#: src/admin/UserList.js:140 msgid "user email" msgstr "user email" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 20c9d5dda7..141e371a9c 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -77,7 +77,7 @@ msgstr "Une ventilation plus détaillée de chaque domaine peut être trouvée e msgid "A verification link has been sent to your email account" msgstr "Un lien de vérification a été envoyé à votre compte de messagerie." -#: src/admin/UserListModal.js:261 +#: src/admin/UserListModal.js:262 msgid "ADMIN" msgstr "ADMIN" @@ -110,7 +110,7 @@ msgstr "Compte" msgid "Account Closed Successfully" msgstr "Compte clôturé avec succès" -#: src/app/App.js:101 +#: src/app/App.js:105 #: src/app/FloatingMenu.js:177 #: src/user/UserPage.js:150 msgid "Account Settings" @@ -123,7 +123,7 @@ msgstr "Compte créé" #: src/admin/WebCheckPage.js:61 #: src/createOrganization/CreateOrganizationPage.js:184 #: src/createOrganization/CreateOrganizationPage.js:189 -#: src/organizations/Organizations.js:61 +#: src/organizations/Organizations.js:58 msgid "Acronym" msgstr "Acronyme" @@ -155,7 +155,7 @@ msgstr "Action" msgid "Action:" msgstr "Action :" -#: src/admin/AdminPanel.js:32 +#: src/admin/AdminPanel.js:29 msgid "Activity" msgstr "Activité" @@ -175,7 +175,7 @@ msgstr "Ajouter les détails du domaine" msgid "Add User" msgstr "Ajouter un utilisateur" -#: src/app/App.js:207 +#: src/app/App.js:211 msgid "Admin" msgstr "Administrateur" @@ -183,7 +183,7 @@ msgstr "Administrateur" msgid "Admin Portal" msgstr "Portail Admin" -#: src/app/App.js:109 +#: src/app/App.js:113 msgid "Admin Profile" msgstr "Profil de l'administrateur" @@ -283,8 +283,8 @@ msgstr "Une erreur s'est produite lors de la mise à jour de votre numéro de t #: src/admin/AdminDomainModal.js:63 #: src/admin/AdminDomainModal.js:110 #: src/admin/AdminDomains.js:103 -#: src/admin/UserListModal.js:47 -#: src/admin/UserListModal.js:141 +#: src/admin/UserListModal.js:50 +#: src/admin/UserListModal.js:140 #: src/auth/TwoFactorAuthenticatePage.js:29 #: src/createOrganization/CreateOrganizationPage.js:56 #: src/user/UserPage.js:83 @@ -307,7 +307,7 @@ msgstr "Tous les produits ou services connexes qui vous sont fournis par le SCT #~ msgid "Application Portfolio Management (APM) systems; and" #~ msgstr "les systèmes de gestion du portefeuille d’applications (GPA);" -#: src/organizationDetails/OrganizationDomains.js:233 +#: src/organizationDetails/OrganizationDomains.js:221 msgid "Apply" msgstr "Appliquer" @@ -320,8 +320,8 @@ msgstr "Avril" msgid "Archive domain" msgstr "Archiver ce domaine" -#: src/admin/AdminDomainCard.js:51 -#: src/organizationDetails/OrganizationDomains.js:103 +#: src/admin/AdminDomainCard.js:80 +#: src/organizationDetails/OrganizationDomains.js:101 msgid "Archived" msgstr "Archivé" @@ -352,7 +352,7 @@ msgstr "Journaux d'audit" msgid "August" msgstr "Août" -#: src/app/App.js:173 +#: src/app/App.js:177 msgid "Authenticate" msgstr "Authentifier" @@ -430,9 +430,9 @@ msgid "Certificates" msgstr "Certificats" #: src/domains/DomainsPage.js:71 -#: src/organizationDetails/OrganizationDomains.js:83 +#: src/organizationDetails/OrganizationDomains.js:84 msgid "Certificates Status" -msgstr "" +msgstr "Statut des certificats" #: src/auth/ResetPasswordPage.js:126 #: src/user/EditableUserPassword.js:153 @@ -475,15 +475,15 @@ msgstr "Changements requis pour la mise en conformité ITPIN" msgid "Check your associated Tracker email for the verification link" msgstr "Vérifiez le lien de vérification dans votre courriel de suivi associé." -#: src/domains/DomainCard.js:188 +#: src/domains/DomainCard.js:189 #: src/domains/DomainsPage.js:155 #: src/guidance/WebTLSResults.js:101 -#: src/organizationDetails/OrganizationDomains.js:280 +#: src/organizationDetails/OrganizationDomains.js:268 msgid "Ciphers" msgstr "Ciphers" #: src/domains/DomainsPage.js:72 -#: src/organizationDetails/OrganizationDomains.js:84 +#: src/organizationDetails/OrganizationDomains.js:85 msgid "Ciphers Status" msgstr "État du chiffrement" @@ -529,7 +529,7 @@ msgstr "Le champ de code ne doit pas être vide" msgid "Collect and analyze DMARC reports." msgstr "Recueillir et analyser les rapports DMARC." -#: src/organizationDetails/OrganizationDomains.js:175 +#: src/organizationDetails/OrganizationDomains.js:176 msgid "Comparison" msgstr "Comparaison" @@ -542,7 +542,8 @@ msgstr "Conforme" #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 #: src/admin/SuperAdminUserList.js:441 -#: src/admin/UserListModal.js:274 +#: src/admin/UserListModal.js:275 +#: src/organizations/RequestOrgInviteModal.js:75 #: src/user/EditableUserDisplayName.js:168 #: src/user/EditableUserEmail.js:168 #: src/user/EditableUserPassword.js:182 @@ -580,8 +581,8 @@ msgstr "Envisagez de donner la priorité aux sites web et aux services web qui msgid "Contact" msgstr "Contact" -#: src/app/App.js:182 -#: src/app/App.js:333 +#: src/app/App.js:186 +#: src/app/App.js:341 #: src/app/ContactUsPage.js:39 #: src/app/SlideMessage.js:103 msgid "Contact Us" @@ -633,12 +634,12 @@ msgid "Create Account" msgstr "Créer un compte" #: src/admin/AdminPage.js:130 -#: src/app/App.js:292 +#: src/app/App.js:300 #: src/createOrganization/CreateOrganizationPage.js:237 msgid "Create Organization" msgstr "Créer une organisation" -#: src/app/App.js:150 +#: src/app/App.js:154 msgid "Create an Account" msgstr "Créer un compte" @@ -666,21 +667,21 @@ msgstr "Mot de passe actuel:" msgid "Current Phone Number:" msgstr "Numéro de téléphone actuel:" -#: src/domains/DomainCard.js:189 +#: src/domains/DomainCard.js:190 #: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 -#: src/organizationDetails/OrganizationDomains.js:281 -#: src/organizationDetails/OrganizationDomains.js:325 +#: src/organizationDetails/OrganizationDomains.js:269 +#: src/organizationDetails/OrganizationDomains.js:313 msgid "Curves" msgstr "Courbes" #: src/domains/DomainsPage.js:73 -#: src/organizationDetails/OrganizationDomains.js:85 +#: src/organizationDetails/OrganizationDomains.js:86 msgid "Curves Status" msgstr "État des courbes" #: src/domains/DomainsPage.js:165 -#: src/organizationDetails/OrganizationDomains.js:290 +#: src/organizationDetails/OrganizationDomains.js:278 msgid "DKIM" msgstr "DKIM" @@ -719,7 +720,7 @@ msgid "DKIM Selectors:" msgstr "Sélecteurs DKIM:" #: src/domains/DomainsPage.js:76 -#: src/organizationDetails/OrganizationDomains.js:88 +#: src/organizationDetails/OrganizationDomains.js:89 msgid "DKIM Status" msgstr "Statut DKIM" @@ -728,11 +729,11 @@ msgstr "Statut DKIM" #~ msgstr "Un enregistrement DKIM n'a pas pu être trouvé pour ce sélecteur." #: src/domains/DomainsPage.js:169 -#: src/organizationDetails/OrganizationDomains.js:294 +#: src/organizationDetails/OrganizationDomains.js:282 msgid "DMARC" msgstr "DMARC" -#: src/organizations/Organizations.js:126 +#: src/organizations/Organizations.js:139 msgid "DMARC Configuration" msgstr "Configuration de DMARC" @@ -759,13 +760,13 @@ msgstr "Défaillances du DMARC par adresse IP" msgid "DMARC Implementation Phase: {0}" msgstr "Phase de mise en œuvre de DMARC: {0}" -#: src/organizationDetails/OrganizationDetails.js:136 +#: src/organizationDetails/OrganizationDetails.js:135 #: src/user/MyTrackerPage.js:96 msgid "DMARC Phases" msgstr "Phases DMARC" #: src/dmarc/DmarcReportPage.js:95 -#: src/domains/DomainCard.js:232 +#: src/domains/DomainCard.js:233 #: src/guidance/GuidancePage.js:152 msgid "DMARC Report" msgstr "Rapport DMARC " @@ -775,12 +776,12 @@ msgid "DMARC Report for {domainSlug}" msgstr "Rapport DMARC pour {domainSlug}" #: src/domains/DomainsPage.js:77 -#: src/organizationDetails/OrganizationDomains.js:89 +#: src/organizationDetails/OrganizationDomains.js:90 msgid "DMARC Status" msgstr "Statut DMARC" -#: src/app/App.js:89 -#: src/app/App.js:254 +#: src/app/App.js:90 +#: src/app/App.js:258 #: src/app/FloatingMenu.js:131 #: src/dmarc/DmarcByDomainPage.js:181 #: src/dmarc/DmarcByDomainPage.js:241 @@ -812,7 +813,7 @@ msgstr "Scan DNS terminé" msgid "DNS scan for domain \"{0}\" has completed." msgstr "Le scan DNS du domaine \"{0}\" est terminé." -#: src/organizationDetails/OrganizationDomains.js:181 +#: src/organizationDetails/OrganizationDomains.js:182 msgid "DOES NOT EQUAL" msgstr "N'EST PAS ÉGAL" @@ -890,7 +891,7 @@ msgstr "Nom d'affichage:" msgid "Display name cannot be empty" msgstr "Le nom d'affichage ne peut pas être vide" -#: src/organizations/Organizations.js:115 +#: src/organizations/Organizations.js:131 msgid "Displays the Name of the organization, its acronym, and a blue check mark if it is a verified organization." msgstr "Affiche le nom de l'organisation, son acronyme et une coche bleue s'il s'agit d'une organisation vérifiée." @@ -903,8 +904,8 @@ msgstr "Disposition" #: src/dmarc/DmarcByDomainPage.js:324 #: src/domains/DomainsPage.js:68 #: src/domains/DomainsPage.js:154 -#: src/organizationDetails/OrganizationDomains.js:278 -#: src/organizationDetails/OrganizationDomains.js:312 +#: src/organizationDetails/OrganizationDomains.js:267 +#: src/organizationDetails/OrganizationDomains.js:300 msgid "Domain" msgstr "Domaine" @@ -949,21 +950,21 @@ msgstr "Domaine mis à jour" msgid "Domain url field must not be empty" msgstr "Le champ de l'url du domaine ne doit pas être vide" -#: src/admin/AdminDomainCard.js:16 +#: src/admin/AdminDomainCard.js:29 #: src/admin/WebCheckPage.js:129 -#: src/domains/DomainCard.js:127 +#: src/domains/DomainCard.js:129 #: src/domains/ScanDomain.js:211 msgid "Domain:" msgstr "Domaine:" -#: src/admin/AdminPanel.js:26 -#: src/app/App.js:86 -#: src/app/App.js:220 +#: src/admin/AdminPanel.js:21 +#: src/app/App.js:87 +#: src/app/App.js:224 #: src/app/FloatingMenu.js:116 #: src/domains/DomainsPage.js:82 #: src/domains/DomainsPage.js:116 -#: src/organizationDetails/OrganizationDetails.js:139 -#: src/organizationDetails/OrganizationDomains.js:108 +#: src/organizationDetails/OrganizationDetails.js:138 +#: src/organizationDetails/OrganizationDomains.js:106 #: src/summaries/Doughnut.js:50 #: src/summaries/Doughnut.js:75 #: src/user/MyTrackerPage.js:99 @@ -982,7 +983,7 @@ msgstr "Domaines utilisés pour la validation SPF." msgid "Don't have an account? <0>Sign up" msgstr "Vous n'avez pas de compte ? <0>S'inscrire" -#: src/organizationDetails/OrganizationDomains.js:178 +#: src/organizationDetails/OrganizationDomains.js:179 msgid "EQUALS" msgstr "ÉGAUX" @@ -1053,7 +1054,7 @@ msgstr "Courriel envoyé" msgid "Email Validated" msgstr "Courriel validé" -#: src/app/App.js:288 +#: src/app/App.js:296 msgid "Email Verification" msgstr "Vérification de l'e-mail" @@ -1063,7 +1064,7 @@ msgstr "Vérification de l'e-mail" msgid "Email cannot be empty" msgstr "Le courriel ne peut être vide" -#: src/admin/UserListModal.js:59 +#: src/admin/UserListModal.js:62 msgid "Email invitation sent" msgstr "Envoi d'une invitation par courriel" @@ -1156,7 +1157,7 @@ msgstr "Exportation vers CSV" #: src/dmarc/DmarcReportPage.js:129 #: src/dmarc/DmarcReportPage.js:130 -#: src/organizationDetails/OrganizationDomains.js:223 +#: src/organizationDetails/OrganizationDomains.js:211 msgid "Fail" msgstr "Échec" @@ -1201,7 +1202,7 @@ msgstr "Février" #~ msgid "Filters" #~ msgstr "Filtres" -#: src/organizationDetails/OrganizationDomains.js:138 +#: src/organizationDetails/OrganizationDomains.js:137 msgid "Filters:" msgstr "Filtres :" @@ -1233,7 +1234,7 @@ msgstr "Pour plus de détails concernant les termes relatifs à la vie privée, msgid "For users interested in using new features that are still in progress." msgstr "Pour les utilisateurs intéressés par l'utilisation de nouvelles fonctionnalités qui sont encore en cours de développement." -#: src/app/App.js:176 +#: src/app/App.js:180 #: src/auth/ForgotPasswordPage.js:75 msgid "Forgot Password" msgstr "Mot de passe oublié" @@ -1274,7 +1275,7 @@ msgstr "Tableau entièrement aligné" msgid "Fully Aligned by IP Address" msgstr "Entièrement aligné par adresse IP" -#: src/organizations/Organizations.js:130 +#: src/organizations/Organizations.js:143 msgid "Further details for each organization can be found by clicking on its row." msgstr "Vous trouverez de plus amples informations sur chaque organisation en cliquant sur sa ligne." @@ -1318,7 +1319,7 @@ msgstr "Employés du gouvernement du Canada" #~ msgid "Graph direction:" #~ msgstr "Direction du graphique :" -#: src/app/App.js:337 +#: src/app/App.js:345 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1341,9 +1342,9 @@ msgstr "Résultats de l'orientation" msgid "HIDDEN" msgstr "CACHÉ" -#: src/domains/DomainCard.js:185 +#: src/domains/DomainCard.js:186 #: src/domains/DomainsPage.js:157 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/organizationDetails/OrganizationDomains.js:270 msgid "HSTS" msgstr "HSTS" @@ -1368,7 +1369,7 @@ msgid "HSTS Preloaded" msgstr "HSTS préchargé" #: src/domains/DomainsPage.js:70 -#: src/organizationDetails/OrganizationDomains.js:82 +#: src/organizationDetails/OrganizationDomains.js:83 msgid "HSTS Status" msgstr "Statut HSTS" @@ -1388,9 +1389,9 @@ msgstr "HTTP Live" msgid "HTTP Upgrades" msgstr "Mises à jour HTTP" -#: src/domains/DomainCard.js:184 +#: src/domains/DomainCard.js:185 #: src/domains/DomainsPage.js:159 -#: src/organizationDetails/OrganizationDomains.js:284 +#: src/organizationDetails/OrganizationDomains.js:272 msgid "HTTPS" msgstr "HTTPS" @@ -1403,7 +1404,7 @@ msgid "HTTPS Configuration Summary" msgstr "Résumé de la configuration HTTPS" #: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:122 +#: src/organizations/Organizations.js:135 msgid "HTTPS Configured" msgstr "HTTPS configuré" @@ -1420,7 +1421,7 @@ msgid "HTTPS Scan Complete" msgstr "Scan HTTPS terminé" #: src/domains/DomainsPage.js:69 -#: src/organizationDetails/OrganizationDomains.js:81 +#: src/organizationDetails/OrganizationDomains.js:82 msgid "HTTPS Status" msgstr "Statut HTTPS" @@ -1450,14 +1451,14 @@ msgstr "En-tête De" #: src/guidance/WebTLSResults.js:229 msgid "Heartbleed Vulnerable" -msgstr "" +msgstr "Vulnérabilité Heartbleed" #: src/app/ReadGuidancePage.js:23 #~ msgid "Help us make government websites more secure. Please complete the following steps to become compliant with the Government of Canada's web security standards. If you have any questions about this process, please <0>contact us." #~ msgstr "Aidez-nous à rendre les sites Web du gouvernement plus sûrs. Veuillez suivre les étapes suivantes pour vous conformer aux normes de sécurité Web du gouvernement du Canada. Si vous avez des questions sur ce processus, veuillez <0>nous contacter." -#: src/admin/AdminDomainCard.js:44 -#: src/organizationDetails/OrganizationDomains.js:102 +#: src/admin/AdminDomainCard.js:68 +#: src/organizationDetails/OrganizationDomains.js:100 msgid "Hidden" msgstr "Caché" @@ -1465,8 +1466,8 @@ msgstr "Caché" msgid "Hide domain" msgstr "Cacher ce domaine" -#: src/app/App.js:77 -#: src/app/App.js:146 +#: src/app/App.js:78 +#: src/app/App.js:150 #: src/app/FloatingMenu.js:175 msgid "Home" msgstr "Accueil" @@ -1495,7 +1496,7 @@ msgstr "Comment puis-je modifier ma liste de domaines?" msgid "I agree to all <0>Terms, Privacy Policy & Code of Conduct Guidelines <1/>" msgstr "J'accepte toutes les <0>Conditions générales, la politique de confidentialité et les directives du code de conduite<1/>." -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:99 msgid "INACTIVE" msgstr "INACTIF" @@ -1593,7 +1594,7 @@ msgstr "Mise en œuvre : <0>Conseils de mise en œuvre : protection du domaine d msgid "Implemented" msgstr "Mis en œuvre" -#: src/organizationDetails/OrganizationDomains.js:98 +#: src/organizationDetails/OrganizationDomains.js:99 msgid "Inactive" msgstr "Inactif" @@ -1614,7 +1615,7 @@ msgstr "Incorrect createDomain.result typename." msgid "Incorrect createOrganization.result typename." msgstr "createOrganization.result incorrecte typename." -#: src/admin/UserListModal.js:78 +#: src/admin/UserListModal.js:81 msgid "Incorrect inviteUserToOrg.result typename." msgstr "Incorrect inviteUserToOrg.result typename." @@ -1639,8 +1640,8 @@ msgstr "Incorrect resetPassword.result typename." #: src/admin/AdminDomainModal.js:142 #: src/admin/AdminDomains.js:133 #: src/admin/SuperAdminUserList.js:110 -#: src/admin/UserListModal.js:77 -#: src/admin/UserListModal.js:124 +#: src/admin/UserListModal.js:80 +#: src/admin/UserListModal.js:125 #: src/auth/CreateUserPage.js:83 #: src/auth/ResetPasswordPage.js:60 #: src/auth/SignInPage.js:100 @@ -1698,7 +1699,7 @@ msgstr "Incorrect updateUserPassword.result typename." msgid "Incorrect updateUserProfile.result typename." msgstr "Incorrect updateUserProfile.result typename." -#: src/admin/UserListModal.js:125 +#: src/admin/UserListModal.js:126 msgid "Incorrect updateUserRole.result typename." msgstr "Incorrect updateUserRole.result typename." @@ -1722,7 +1723,7 @@ msgstr "Les personnes d'un groupe ministériel de technologie de l'information p #~ msgid "Individuals with questions about the accuracy of their domain’s compliance data may contact the TBS Cyber Security mailbox." #~ msgstr "Les personnes ayant des questions sur l'exactitude des données de conformité de leur domaine peuvent contacter la boîte aux lettres de la cybersécurité du SCT." -#: src/organizationDetails/OrganizationDomains.js:220 +#: src/organizationDetails/OrganizationDomains.js:208 msgid "Info" msgstr "Info" @@ -1782,7 +1783,11 @@ msgstr "orientés vers l'Internet" msgid "Invalid email" msgstr "Courriel non valide" -#: src/admin/UserList.js:173 +#: src/organizations/RequestOrgInviteModal.js:36 +msgid "Invite Requested" +msgstr "Invitation demandée" + +#: src/admin/UserList.js:147 msgid "Invite User" msgstr "Inviter l'utilisateur" @@ -1956,14 +1961,14 @@ msgstr "Générateur de configuration SSL de Mozilla" msgid "Must Staple" msgstr "Agrafe obligatoire" -#: src/organizationDetails/OrganizationDomains.js:93 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "NEW" msgstr "NOUVEAU" #: src/admin/WebCheckPage.js:60 #: src/createOrganization/CreateOrganizationPage.js:173 #: src/createOrganization/CreateOrganizationPage.js:178 -#: src/organizations/Organizations.js:60 +#: src/organizations/Organizations.js:57 msgid "Name" msgstr "Nom" @@ -2005,7 +2010,7 @@ msgstr "Négatif" msgid "Never" msgstr "Jamais" -#: src/organizationDetails/OrganizationDomains.js:93 +#: src/organizationDetails/OrganizationDomains.js:94 msgid "New" msgstr "Nouveau" @@ -2070,7 +2075,7 @@ msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organi #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 -#: src/organizationDetails/OrganizationDomains.js:246 +#: src/organizationDetails/OrganizationDomains.js:235 msgid "No Domains" msgstr "Aucun domaine" @@ -2079,7 +2084,7 @@ msgid "No HTTPS configuration information available for this organization." msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:81 +#: src/organizations/Organizations.js:78 msgid "No Organizations" msgstr "Aucune organisation" @@ -2149,7 +2154,7 @@ msgid "No scan data is currently available for this service. You may request a s msgstr "Aucune donnée de balayage n'est actuellement disponible pour ce service. Vous pouvez demander un scan en utilisant le bouton d'actualisation, ou attendre jusqu'à 24 heures pour que les données soient actualisées." #: src/admin/SuperAdminUserList.js:161 -#: src/admin/UserList.js:76 +#: src/admin/UserList.js:69 msgid "No users" msgstr "Aucun utilisateur" @@ -2245,7 +2250,7 @@ msgstr "Vous pouvez notamment communiquer avec l’<0>équipe responsable des se msgid "Organization" msgstr "Organisation" -#: src/organizationDetails/OrganizationDetails.js:63 +#: src/organizationDetails/OrganizationDetails.js:67 msgid "Organization Details" msgstr "Détails de l'organisation" @@ -2254,7 +2259,7 @@ msgid "Organization Information" msgstr "Informations sur l'organisation" #: src/admin/OrganizationInformation.js:509 -#: src/organizations/Organizations.js:114 +#: src/organizations/Organizations.js:130 msgid "Organization Name" msgstr "Nom de l'organisation" @@ -2286,15 +2291,19 @@ msgid "Organization:" msgstr "Organisation:" #: src/admin/AdminPage.js:189 -#: src/app/App.js:83 -#: src/app/App.js:186 +#: src/app/App.js:84 +#: src/app/App.js:190 #: src/app/FloatingMenu.js:103 -#: src/organizations/Organizations.js:72 -#: src/organizations/Organizations.js:109 +#: src/organizations/Organizations.js:69 +#: src/organizations/Organizations.js:125 msgid "Organizations" msgstr "Organisations" -#: src/organizationDetails/OrganizationDomains.js:94 +#: src/admin/UserListModal.js:256 +msgid "PENDING" +msgstr "EN ATTENTE" + +#: src/organizationDetails/OrganizationDomains.js:95 msgid "PROD" msgstr "PROD" @@ -2304,7 +2313,7 @@ msgstr "Page {0} de {1}" #: src/dmarc/DmarcReportPage.js:120 #: src/dmarc/DmarcReportPage.js:121 -#: src/organizationDetails/OrganizationDomains.js:217 +#: src/organizationDetails/OrganizationDomains.js:205 msgid "Pass" msgstr "Passez" @@ -2457,7 +2466,7 @@ msgstr "Empêchez ce domaine d'être visible, d'être scanné et d'être compté #~ msgid "Previous" #~ msgstr "Précédent" -#: src/app/App.js:321 +#: src/app/App.js:329 #: src/app/FloatingMenu.js:219 #: src/app/SlideMessage.js:88 #: src/termsConditions/TermsConditionsPage.js:41 @@ -2472,7 +2481,7 @@ msgstr "Loi sur la protection de la vie privée." msgid "Privacy Notice Statement" msgstr "Déclaration de confidentialité" -#: src/organizationDetails/OrganizationDomains.js:94 +#: src/organizationDetails/OrganizationDomains.js:95 msgid "Prod" msgstr "Prod" @@ -2480,16 +2489,16 @@ msgstr "Prod" msgid "Protect domains that do not send email - GOV.UK (www.gov.uk)" msgstr "Protéger les domaines qui n'envoient pas de courrier électronique - GOV.UK (www.gov.uk)" -#: src/domains/DomainCard.js:187 +#: src/domains/DomainCard.js:188 #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 -#: src/organizationDetails/OrganizationDomains.js:287 -#: src/organizationDetails/OrganizationDomains.js:326 +#: src/organizationDetails/OrganizationDomains.js:275 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Protocols" msgstr "Protocoles" #: src/domains/DomainsPage.js:74 -#: src/organizationDetails/OrganizationDomains.js:86 +#: src/organizationDetails/OrganizationDomains.js:87 msgid "Protocols Status" msgstr "Statut des protocoles" @@ -2520,13 +2529,13 @@ msgstr "Province:" #: src/guidance/WebTLSResults.js:253 msgid "ROBOT Vulnerable" -msgstr "" +msgstr "ROBOT Vulnérable" #: src/app/ReadGuidancePage.js:259 #~ msgid "Read Guidance" #~ msgstr "Conseils de lecture" -#: src/app/App.js:184 +#: src/app/App.js:188 msgid "Read guidance" msgstr "Conseils de lecture" @@ -2587,12 +2596,17 @@ msgstr "Supprimer l'utilisateur" msgid "Removed Organization" msgstr "Organisation supprimée" -#: src/app/App.js:329 +#: src/app/App.js:337 #: src/app/FloatingMenu.js:230 #: src/app/SlideMessage.js:99 msgid "Report an Issue" msgstr "Signaler un problème" +#: src/organizationDetails/OrganizationDetails.js:112 +#: src/organizations/RequestOrgInviteModal.js:62 +msgid "Request Invite" +msgstr "Demande d'invitation" + #: src/domains/ScanDomain.js:167 msgid "Request a domain to be scanned:" msgstr "Demander qu'un domaine soit scanné:" @@ -2617,7 +2631,7 @@ msgstr "Exigences : <0>Configuration requise pour les services de gestion du cou msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" msgstr "Exigences : <0>Exigences de configuration de la gestion des sites et services web" -#: src/app/App.js:178 +#: src/app/App.js:182 msgid "Reset Password" msgstr "Réinitialiser le mot de passe" @@ -2660,7 +2674,7 @@ msgstr "Résultats pour les analyses des technologies web (TLS, HTTPS)." msgid "Revoked:" msgstr "Révoqué :" -#: src/admin/UserListModal.js:105 +#: src/admin/UserListModal.js:106 msgid "Role updated" msgstr "Rôle mis à jour" @@ -2678,7 +2692,7 @@ msgid "SAN List:" msgstr "Liste des SAN :" #: src/domains/DomainsPage.js:163 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/organizationDetails/OrganizationDomains.js:276 msgid "SPF" msgstr "SPF" @@ -2705,7 +2719,7 @@ msgid "SPF Results" msgstr "Résultats du SPF" #: src/domains/DomainsPage.js:75 -#: src/organizationDetails/OrganizationDomains.js:87 +#: src/organizationDetails/OrganizationDomains.js:88 msgid "SPF Status" msgstr "Statut SPF" @@ -2726,11 +2740,11 @@ msgstr "Statut SPF" #~ msgid "SSL scan for domain \"{0}\" has completed." #~ msgstr "Le scan SSL pour le domaine \"{0}\" est terminé." -#: src/organizationDetails/OrganizationDomains.js:95 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "STAGING" msgstr "DEV" -#: src/admin/UserListModal.js:265 +#: src/admin/UserListModal.js:266 msgid "SUPER_ADMIN" msgstr "SUPER_ADMIN" @@ -2747,7 +2761,7 @@ msgstr "Sauvez" msgid "Scan Domain" msgstr "Domaine de balayage" -#: src/domains/DomainCard.js:141 +#: src/domains/DomainCard.js:143 #: src/guidance/GuidancePage.js:140 msgid "Scan Pending" msgstr "Scan en attente" @@ -2787,7 +2801,7 @@ msgstr "Recherche par initié par, nom de la ressource" #: src/dmarc/DmarcByDomainPage.js:221 #: src/dmarc/DmarcByDomainPage.js:292 #: src/domains/DomainsPage.js:189 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:301 msgid "Search for a domain" msgstr "Rechercher un domaine" @@ -2803,12 +2817,12 @@ msgstr "Recherche d'un utilisateur (email)" #~ msgid "Search for an activity" #~ msgstr "Recherche d'une activité" -#: src/organizations/Organizations.js:151 +#: src/organizations/Organizations.js:161 msgid "Search for an organization" msgstr "Rechercher une organisation" #: src/admin/AdminDomains.js:252 -#: src/admin/UserList.js:149 +#: src/admin/UserList.js:131 #: src/components/ReactTableGlobalFilter.js:36 #: src/components/SearchBox.js:44 msgid "Search:" @@ -2868,8 +2882,8 @@ msgstr "Septembre" msgid "Serial:" msgstr "En série :" -#: src/organizations/Organizations.js:62 -#: src/organizations/Organizations.js:118 +#: src/organizations/Organizations.js:59 +#: src/organizations/Organizations.js:133 msgid "Services" msgstr "Services" @@ -2942,17 +2956,17 @@ msgstr "" #~ msgstr "Indique si le domaine est conforme à la politique." #: src/domains/DomainsPage.js:166 -#: src/organizationDetails/OrganizationDomains.js:291 +#: src/organizationDetails/OrganizationDomains.js:279 msgid "Shows if the domain meets the DomainKeys Identified Mail (DKIM) requirements." msgstr "Indique si le domaine répond aux exigences de DomainKeys Identified Mail (DKIM)." #: src/domains/DomainsPage.js:157 -#: src/organizationDetails/OrganizationDomains.js:282 +#: src/organizationDetails/OrganizationDomains.js:270 msgid "Shows if the domain meets the HSTS requirements." msgstr "Indique si le domaine répond aux exigences du HSTS." #: src/domains/DomainsPage.js:160 -#: src/organizationDetails/OrganizationDomains.js:285 +#: src/organizationDetails/OrganizationDomains.js:273 msgid "Shows if the domain meets the Hypertext Transfer Protocol Secure (HTTPS) requirements." msgstr "Indique si le domaine répond aux exigences du protocole de transfert hypertexte sécurisé (HTTPS)." @@ -2963,27 +2977,27 @@ msgstr "Indique si le domaine répond aux exigences du protocole de transfert hy #~ msgstr "Indique si le domaine répond aux exigences de Hypertext Transfer ol Secure (HTTPS)." #: src/domains/DomainsPage.js:170 -#: src/organizationDetails/OrganizationDomains.js:295 +#: src/organizationDetails/OrganizationDomains.js:283 msgid "Shows if the domain meets the Message Authentication, Reporting, and Conformance (DMARC) requirements." msgstr "Indique si le domaine répond aux exigences de Message Authentication, Reporting, and Conformance (DMARC)." #: src/domains/DomainsPage.js:163 -#: src/organizationDetails/OrganizationDomains.js:288 +#: src/organizationDetails/OrganizationDomains.js:276 msgid "Shows if the domain meets the Sender Policy Framework (SPF) requirements." msgstr "Indique si le domaine répond aux exigences du Sender Policy Framework (SPF)." #: src/domains/DomainsPage.js:162 -#: src/organizationDetails/OrganizationDomains.js:287 +#: src/organizationDetails/OrganizationDomains.js:275 msgid "Shows if the domain uses acceptable protocols." msgstr "Indique si le domaine utilise des protocoles acceptables." #: src/domains/DomainsPage.js:155 -#: src/organizationDetails/OrganizationDomains.js:280 +#: src/organizationDetails/OrganizationDomains.js:268 msgid "Shows if the domain uses only ciphers that are strong or acceptable." msgstr "Indique si le domaine utilise uniquement des ciphers forts ou acceptables." #: src/domains/DomainsPage.js:156 -#: src/organizationDetails/OrganizationDomains.js:281 +#: src/organizationDetails/OrganizationDomains.js:269 msgid "Shows if the domain uses only curves that are strong or acceptable." msgstr "Indique si le domaine utilise uniquement des courbes fortes ou acceptables" @@ -3009,21 +3023,21 @@ msgstr "Indique si les certificats reçus ne reposent pas sur un certificat raci #: src/guidance/WebTLSResults.js:224 msgid "Shows if the server was found to be vulnerable to the Heartbleed vulnerability." -msgstr "" +msgstr "Indique si le serveur s'est avéré vulnérable à la faille Heartbleed." #: src/guidance/WebTLSResults.js:237 msgid "Shows if the server was found to be vulnerable to the ROBOT vulnerability." -msgstr "" +msgstr "Indique si le serveur a été jugé vulnérable à la vulnérabilité ROBOT." #: src/guidance/WebConnectionResults.js:191 msgid "Shows the duration of time, in seconds, that the HSTS header is valid." msgstr "Indique la durée, en secondes, pendant laquelle l'en-tête HSTS est valide." -#: src/organizations/Organizations.js:119 +#: src/organizations/Organizations.js:133 msgid "Shows the number of domains that the organization is in control of." msgstr "Indique le nombre de domaines dont l'organisation a le contrôle." -#: src/organizations/Organizations.js:123 +#: src/organizations/Organizations.js:136 msgid "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS" msgstr "Indique le pourcentage de domaines qui ont configuré HTTPS et qui mettent à niveau les connexions HTTP vers HTTPS." @@ -3031,7 +3045,7 @@ msgstr "Indique le pourcentage de domaines qui ont configuré HTTPS et qui mette #~ msgid "Shows the percentage of domains which have HTTPS configured and upgrade HTTP connections to HTTPS (ITPIN 6.1.1)" #~ msgstr "Indique le pourcentage de domaines qui ont configuré HTTPS et qui mettent à niveau les connexions HTTP vers HTTPS (ITPIN 6.1.1)." -#: src/organizations/Organizations.js:127 +#: src/organizations/Organizations.js:140 msgid "Shows the percentage of domains which have a valid DMARC policy configuration." msgstr "Indique le pourcentage de domaines qui ont une configuration de politique DMARC valide." @@ -3059,7 +3073,7 @@ msgstr "Indique le nombre total d'e-mails qui ont été envoyés par ce domaine #~ msgid "Siganture Hash:" #~ msgstr "Siganture Hash :" -#: src/app/App.js:156 +#: src/app/App.js:160 #: src/app/FloatingMenu.js:197 #: src/app/TopBanner.js:118 #: src/auth/SignInPage.js:189 @@ -3089,7 +3103,7 @@ msgstr "Déconnexion." msgid "Signature Hash:" msgstr "Signature Hash :" -#: src/app/App.js:71 +#: src/app/App.js:72 msgid "Skip to main content" msgstr "Passer au contenu principal" @@ -3105,11 +3119,11 @@ msgstr "Trier par:" msgid "Source IP Address" msgstr "Adresse IP source" -#: src/organizationDetails/OrganizationDomains.js:95 +#: src/organizationDetails/OrganizationDomains.js:96 msgid "Staging" msgstr "Dév" -#: src/organizationDetails/OrganizationDomains.js:191 +#: src/organizationDetails/OrganizationDomains.js:192 msgid "Status or tag" msgstr "Statut ou étiquette" @@ -3134,11 +3148,11 @@ msgstr "Sujet :" msgid "Submit" msgstr "Soumettre" -#: src/admin/UserListModal.js:154 +#: src/admin/UserListModal.js:153 msgid "Successfully removed user {0}." msgstr "L'utilisateur {0} a été supprimé." -#: src/organizationDetails/OrganizationDetails.js:133 +#: src/organizationDetails/OrganizationDetails.js:132 #: src/user/MyTrackerPage.js:93 msgid "Summary" msgstr "Résumé" @@ -3171,7 +3185,7 @@ msgstr "le SCT soit identifié comme la source; et" msgid "TBS reserves the right to refuse service, and may reject your application for an account, or cancel an existing account, for any reason, at our sole discretion." msgstr "TBS se réserve le droit de refuser le service, de rejeter votre demande de compte ou d'annuler un compte existant, pour quelque raison que ce soit, à sa seule discrétion." -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "TEST" msgstr "TEST" @@ -3203,11 +3217,11 @@ msgstr "Conseils techniques de mise en œuvre :" msgid "Termination" msgstr "Terminaison" -#: src/app/App.js:180 +#: src/app/App.js:184 msgid "Terms & Conditions" msgstr "Termes et conditions" -#: src/app/App.js:325 +#: src/app/App.js:333 #: src/app/FloatingMenu.js:225 #: src/app/SlideMessage.js:92 msgid "Terms & conditions" @@ -3221,7 +3235,7 @@ msgstr "Termes et conditions" msgid "Terms of Use" msgstr "Conditions d'utilisation" -#: src/organizationDetails/OrganizationDomains.js:96 +#: src/organizationDetails/OrganizationDomains.js:97 msgid "Test" msgstr "Test" @@ -3255,7 +3269,7 @@ msgstr "Les conseils, orientations ou services qui vous sont fournis par le SCT #: src/dmarc/DmarcByDomainPage.js:324 #: src/domains/DomainsPage.js:154 -#: src/organizationDetails/OrganizationDomains.js:278 +#: src/organizationDetails/OrganizationDomains.js:267 msgid "The domain address." msgstr "L'adresse du domaine." @@ -3303,7 +3317,7 @@ msgstr "Résultats de la vérification DKIM du message. Il peut s'agir d'un succ msgid "The summary cards show two metrics that Tracker scans:" msgstr "Les cartes récapitulatives présentent deux mesures que Suivi analyse :" -#: src/admin/UserListModal.js:106 +#: src/admin/UserListModal.js:107 msgid "The user's role has been successfully updated" msgstr "Le rôle de l'utilisateur a été mis à jour avec succès" @@ -3369,11 +3383,11 @@ msgstr "Temps généré" msgid "Time Generated (UTC)" msgstr "Heure générée (UTC)" -#: src/app/App.js:118 +#: src/app/App.js:122 msgid "To enable full app functionality and maximize your account's security, <0>please verify your account." msgstr "Pour activer toutes les fonctionnalités de l'application et maximiser la sécurité de votre compte, <0>vous devez vérifier votre compte." -#: src/app/App.js:132 +#: src/app/App.js:136 msgid "To maximize your account's security, <0>please activate a multi-factor authentication option." msgstr "Pour maximiser la sécurité de votre compte, <0>vous devez activer une option d'authentification multifactorielle." @@ -3446,11 +3460,11 @@ msgstr "Authentification à deux facteurs:" msgid "URL:" msgstr "URL :" -#: src/admin/UserListModal.js:258 +#: src/admin/UserListModal.js:259 msgid "USER" msgstr "UTILISATEUR" -#: src/admin/UserListModal.js:95 +#: src/admin/UserListModal.js:96 msgid "Unable to change user role, please try again." msgstr "Impossible de modifier le rôle de l'utilisateur, veuillez réessayer." @@ -3479,7 +3493,7 @@ msgstr "Impossible de créer une nouvelle organisation." msgid "Unable to create your account, please try again." msgstr "Impossible de créer votre compte, veuillez réessayer" -#: src/admin/UserListModal.js:68 +#: src/admin/UserListModal.js:71 msgid "Unable to invite user." msgstr "Impossible d'inviter un utilisateur." @@ -3496,10 +3510,15 @@ msgstr "Impossible de supprimer le domaine." msgid "Unable to remove this organization." msgstr "Impossible de supprimer cette organisation." -#: src/admin/UserListModal.js:162 +#: src/admin/UserListModal.js:161 msgid "Unable to remove user." msgstr "Impossible de supprimer l'utilisateur." +#: src/organizations/RequestOrgInviteModal.js:26 +#: src/organizations/RequestOrgInviteModal.js:46 +msgid "Unable to request invite, please try again." +msgstr "Impossible de demander une invitation, veuillez réessayer." + #: src/domains/ScanDomain.js:44 msgid "Unable to request scan, please try again." msgstr "Impossible de demander un balayage, veuillez réessayer." @@ -3555,7 +3574,7 @@ msgstr "Impossible de mettre à jour votre langue préférée, veuillez réessay msgid "Unable to update to your username, please try again." msgstr "Impossible de mettre à jour votre nom d'utilisateur, veuillez réessayer." -#: src/admin/UserListModal.js:115 +#: src/admin/UserListModal.js:116 msgid "Unable to update user role." msgstr "Impossible de mettre à jour le rôle de l'utilisateur." @@ -3575,14 +3594,14 @@ msgstr "Impossible de vérifier votre numéro de téléphone, veuillez réessaye msgid "Understanding Scan Metrics:" msgstr "Comprendre les métriques d'analyse :" -#: src/domains/DomainCard.js:83 +#: src/domains/DomainCard.js:85 msgid "Unfavourited Domain" msgstr "Domaine non favorisé" #: src/guidance/WebTLSResults.js:233 #: src/guidance/WebTLSResults.js:256 msgid "Unknown" -msgstr "" +msgstr "Inconnu" #: src/summaries/RadialBarChart.js:43 #: src/summaries/SummaryGroup.js:28 @@ -3644,7 +3663,7 @@ msgid "User Email" msgstr "Courriel de l'utilisateur" #: src/admin/SuperAdminUserList.js:157 -#: src/admin/UserList.js:72 +#: src/admin/UserList.js:65 msgid "User List" msgstr "Liste des utilisateurs" @@ -3652,11 +3671,11 @@ msgstr "Liste des utilisateurs" msgid "User email does not match" msgstr "L'email de l'utilisateur ne correspond pas" -#: src/admin/UserListModal.js:58 +#: src/admin/UserListModal.js:61 msgid "User invited" msgstr "Utilisateur invité" -#: src/admin/UserListModal.js:153 +#: src/admin/UserListModal.js:152 msgid "User removed." msgstr "Utilisateur supprimé." @@ -3665,8 +3684,8 @@ msgid "User:" msgstr "Utilisateur:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:29 -#: src/organizationDetails/OrganizationDetails.js:143 +#: src/admin/AdminPanel.js:24 +#: src/organizationDetails/OrganizationDetails.js:142 msgid "Users" msgstr "Utilisateurs" @@ -3674,7 +3693,7 @@ msgstr "Utilisateurs" msgid "Users exercise due diligence in ensuring the accuracy of the materials reproduced;" msgstr "Les utilisateurs font preuve de diligence raisonnable en s'assurant de l'exactitude des documents reproduits;" -#: src/organizationDetails/OrganizationDomains.js:155 +#: src/organizationDetails/OrganizationDomains.js:154 msgid "Value" msgstr "Valeur" @@ -3684,7 +3703,7 @@ msgstr "Le code de vérification ne doit contenir que des chiffres" #: src/admin/SuperAdminUserList.js:150 #: src/admin/SuperAdminUserList.js:341 -#: src/organizations/Organizations.js:63 +#: src/organizations/Organizations.js:60 msgid "Verified" msgstr "Vérifié" @@ -3728,7 +3747,7 @@ msgstr "Volume de messages usurpant domaine (rejet + quarantaine + aucun) :" msgid "Vulnerability Scan Dashboard" msgstr "Tableau de bord de l'analyse des vulnérabilités" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "WEB" msgstr "WEB" @@ -3760,7 +3779,7 @@ msgstr "Nous vous avons envoyé un e-mail avec un code d'authentification pour v #~ msgid "Weak Curves:" #~ msgstr "Courbes faibles:" -#: src/organizationDetails/OrganizationDomains.js:97 +#: src/organizationDetails/OrganizationDomains.js:98 msgid "Web" msgstr "Web" @@ -3846,6 +3865,10 @@ msgstr "Pourquoi la page d'orientation n'affiche-t-elle pas les sélecteurs DKIM msgid "Wiki" msgstr "Wiki" +#: src/organizations/RequestOrgInviteModal.js:66 +msgid "Would you like to request an invite to {orgName}?" +msgstr "Souhaitez-vous demander une invitation à {orgName} ?" + #: src/guidance/WebConnectionResults.js:126 #: src/guidance/WebConnectionResults.js:166 #: src/guidance/WebConnectionResults.js:188 @@ -3947,7 +3970,7 @@ msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe" msgid "You will need a Tracker account to use certain products and services. You are responsible for maintaining the confidentiality of your account, password and for restricting access to your account. You also agree to accept responsibility for all activities that occur under your account or password. TBS accepts no liability for any loss or damage arising from your failure to maintain the security of your account or password." msgstr "Vous aurez besoin d'un compte Suivi pour utiliser certains produits et services. Vous êtes responsable du maintien de la confidentialité de votre compte et de votre mot de passe et de la restriction de l'accès à votre compte. Vous acceptez également d'assumer la responsabilité de toutes les activités qui se déroulent sous votre compte ou votre mot de passe. Le SCT n'accepte aucune responsabilité pour toute perte ou tout dommage résultant de votre incapacité à maintenir la sécurité de votre compte ou de votre mot de passe." -#: src/app/App.js:262 +#: src/app/App.js:266 msgid "Your Account" msgstr "Votre compte" @@ -3963,6 +3986,10 @@ msgstr "L'email de votre compte a été vérifié avec succès" msgid "Your account will be fully activated the next time you log in" msgstr "Votre compte sera entièrement activé lors de votre prochaine connexion." +#: src/organizations/RequestOrgInviteModal.js:37 +msgid "Your request has been sent to the organization administrators." +msgstr "Votre demande a été envoyée aux administrateurs de l'organisation." + #: src/admin/OrganizationInformation.js:421 msgid "Zone:" msgstr "Zone:" @@ -3983,8 +4010,8 @@ msgstr "contactez-nous" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:97 -#: src/app/App.js:275 +#: src/app/App.js:100 +#: src/app/App.js:279 #: src/user/MyTrackerPage.js:43 #: src/user/MyTrackerPage.js:74 msgid "myTracker" @@ -4018,7 +4045,7 @@ msgstr "sp:" msgid "strong" msgstr "fort" -#: src/admin/UserList.js:162 +#: src/admin/UserList.js:140 msgid "user email" msgstr "e-mail de l'utilisateur" diff --git a/frontend/src/organizationDetails/OrganizationDetails.js b/frontend/src/organizationDetails/OrganizationDetails.js index 72c67877b4..4f23d46a91 100644 --- a/frontend/src/organizationDetails/OrganizationDetails.js +++ b/frontend/src/organizationDetails/OrganizationDetails.js @@ -3,6 +3,7 @@ import { useLazyQuery, useQuery } from '@apollo/client' import { Trans } from '@lingui/macro' import { Box, + Button, Flex, Heading, IconButton, @@ -12,8 +13,10 @@ import { TabPanels, Tabs, Text, + useDisclosure, } from '@chakra-ui/react' import { ArrowLeftIcon, CheckCircleIcon } from '@chakra-ui/icons' +import { UserIcon } from '../theme/Icons' import { Link as RouteLink, useParams, useHistory } from 'react-router-dom' import { ErrorBoundary } from 'react-error-boundary' @@ -24,16 +27,18 @@ import { OrganizationSummary } from './OrganizationSummary' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { LoadingMessage } from '../components/LoadingMessage' import { useDocumentTitle } from '../utilities/useDocumentTitle' -import { - GET_ORGANIZATION_DOMAINS_STATUSES_CSV, - ORG_DETAILS_PAGE, -} from '../graphql/queries' +import { GET_ORGANIZATION_DOMAINS_STATUSES_CSV, ORG_DETAILS_PAGE } from '../graphql/queries' import { RadialBarChart } from '../summaries/RadialBarChart' import { ExportButton } from '../components/ExportButton' +import { RequestOrgInviteModal } from '../organizations/RequestOrgInviteModal' +import { useUserVar } from '../utilities/userState' +import { ABTestingWrapper, ABTestVariant } from '../app/ABTestWrapper' export default function OrganizationDetails() { + const { isLoggedIn } = useUserVar() const { orgSlug, activeTab } = useParams() const history = useHistory() + const { isOpen, onOpen, onClose } = useDisclosure() const tabNames = ['summary', 'dmarc_phases', 'domains', 'users'] const defaultActiveTab = tabNames[0] @@ -44,12 +49,12 @@ export default function OrganizationDetails() { // errorPolicy: 'ignore', // allow partial success }) - const [ - getOrgDomainStatuses, - { loading: orgDomainStatusesLoading, _error, _data }, - ] = useLazyQuery(GET_ORGANIZATION_DOMAINS_STATUSES_CSV, { - variables: { orgSlug: orgSlug }, - }) + const [getOrgDomainStatuses, { loading: orgDomainStatusesLoading, _error, _data }] = useLazyQuery( + GET_ORGANIZATION_DOMAINS_STATUSES_CSV, + { + variables: { orgSlug: orgSlug }, + }, + ) useEffect(() => { if (!activeTab) { @@ -79,12 +84,7 @@ export default function OrganizationDetails() { return ( - + } as={RouteLink} @@ -102,25 +102,29 @@ export default function OrganizationDetails() { order={{ base: 2, md: 1 }} flexBasis={{ base: '100%', md: 'auto' }} > - {orgName} - {data?.organization?.verified && ( - <> - {' '} - - - )} + + {orgName} + {data?.organization?.verified && } + - { - const result = await getOrgDomainStatuses() - return result.data?.findOrganizationBySlug?.toCsv - }} - isLoading={orgDomainStatusesLoading} - /> + + + {isLoggedIn && ( + <> + + + + )} + + DMARC Phases - + - + { + const result = await getOrgDomainStatuses() + return result.data?.findOrganizationBySlug?.toCsv + }} + isLoading={orgDomainStatusesLoading} + /> + {!isNaN(data?.organization?.affiliations?.totalCount) && ( - + )} diff --git a/frontend/src/organizations/Organizations.js b/frontend/src/organizations/Organizations.js index 9627b27dc8..216675ceb0 100644 --- a/frontend/src/organizations/Organizations.js +++ b/frontend/src/organizations/Organizations.js @@ -1,7 +1,7 @@ import React, { useCallback, useState } from 'react' import { t, Trans } from '@lingui/macro' import { ListOf } from '../components/ListOf' -import { Box, Divider, Heading, Text, useDisclosure } from '@chakra-ui/react' +import { Box, Divider, Flex, Heading, IconButton, Text, useDisclosure } from '@chakra-ui/react' import { ErrorBoundary } from 'react-error-boundary' import { OrganizationCard } from './OrganizationCard' @@ -14,13 +14,20 @@ import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' import { PAGINATED_ORGANIZATIONS as FORWARD } from '../graphql/queries' import { SearchBox } from '../components/SearchBox' +import { UserIcon } from '../theme/Icons' +import { RequestOrgInviteModal } from './RequestOrgInviteModal' +import { useUserVar } from '../utilities/userState' +import { ABTestingWrapper, ABTestVariant } from '../app/ABTestWrapper' export default function Organizations() { + const { isLoggedIn } = useUserVar() const [orderDirection, setOrderDirection] = useState('ASC') const [orderField, setOrderField] = useState('NAME') const [searchTerm, setSearchTerm] = useState('') const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') const [orgsPerPage, setOrgsPerPage] = useState(10) + const { isOpen: inviteRequestIsOpen, onOpen, onClose } = useDisclosure() + const [orgInfo, setOrgInfo] = useState({}) const memoizedSetDebouncedSearchTermCallback = useCallback(() => { setDebouncedSearchTerm(searchTerm) @@ -30,29 +37,20 @@ export default function Organizations() { const { isOpen, onToggle } = useDisclosure() - const { - loading, - isLoadingMore, - error, - nodes, - next, - previous, - resetToFirstPage, - hasNextPage, - hasPreviousPage, - } = usePaginatedCollection({ - fetchForward: FORWARD, - variables: { - field: orderField, - direction: orderDirection, - search: debouncedSearchTerm, - includeSuperAdminOrg: false, - }, - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first', - recordsPerPage: orgsPerPage, - relayRoot: 'findMyOrganizations', - }) + const { loading, isLoadingMore, error, nodes, next, previous, resetToFirstPage, hasNextPage, hasPreviousPage } = + usePaginatedCollection({ + fetchForward: FORWARD, + variables: { + field: orderField, + direction: orderDirection, + search: debouncedSearchTerm, + includeSuperAdminOrg: false, + }, + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first', + recordsPerPage: orgsPerPage, + relayRoot: 'findMyOrganizations', + }) if (error) return @@ -83,20 +81,43 @@ export default function Organizations() { )} mb="4" > - {({ name, slug, acronym, domainCount, verified, summaries }, index) => ( - - + {({ id, name, slug, acronym, domainCount, verified, summaries }, index) => ( + + + + + + {isLoggedIn && ( + <> + } + onClick={() => { + setOrgInfo({ id, name }) + onOpen() + }} + /> + + + )} + + + )}
@@ -114,10 +135,7 @@ export default function Organizations() { title={t`Organization Name`} info={t`Displays the Name of the organization, its acronym, and a blue check mark if it is a verified organization.`} /> - + - - Further details for each organization can be found by clicking on its - row. - + Further details for each organization can be found by clicking on its row. diff --git a/frontend/src/organizations/RequestOrgInviteModal.js b/frontend/src/organizations/RequestOrgInviteModal.js new file mode 100644 index 0000000000..7fb9d39a15 --- /dev/null +++ b/frontend/src/organizations/RequestOrgInviteModal.js @@ -0,0 +1,88 @@ +import React from 'react' +import { REQUEST_INVITE_TO_ORG } from '../graphql/mutations' +import { useMutation } from '@apollo/client' +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + Button, + useToast, +} from '@chakra-ui/react' +import { Trans, t } from '@lingui/macro' +import { bool } from 'prop-types' +import { func } from 'prop-types' +import { string } from 'prop-types' + +export function RequestOrgInviteModal({ isOpen, onClose, orgId, orgName }) { + const toast = useToast() + const [requestInviteToOrg, { loading }] = useMutation(REQUEST_INVITE_TO_ORG, { + onError(error) { + toast({ + title: error.message, + description: t`Unable to request invite, please try again.`, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ requestOrgAffiliation }) { + if (requestOrgAffiliation.result.__typename === 'InviteUserToOrgResult') { + toast({ + title: t`Invite Requested`, + description: t`Your request has been sent to the organization administrators.`, + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + onClose() + } else { + toast({ + title: t`Unable to request invite, please try again.`, + description: requestOrgAffiliation.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } + }, + }) + + return ( + + + + + Request Invite + + + + Would you like to request an invite to {orgName}? + + + + + + + + ) +} + +RequestOrgInviteModal.propTypes = { + isOpen: bool, + onClose: func, + orgId: string, + orgName: string, +} diff --git a/frontend/src/organizations/__tests__/RequestOrgInviteModal.test.js b/frontend/src/organizations/__tests__/RequestOrgInviteModal.test.js new file mode 100644 index 0000000000..0b5ba5eefc --- /dev/null +++ b/frontend/src/organizations/__tests__/RequestOrgInviteModal.test.js @@ -0,0 +1,234 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { theme, ChakraProvider, useDisclosure } from '@chakra-ui/react' +import { I18nProvider } from '@lingui/react' +import { MockedProvider } from '@apollo/client/testing' +import { setupI18n } from '@lingui/core' +import { en } from 'make-plural/plurals' +import userEvent from '@testing-library/user-event' +import { RequestOrgInviteModal } from '../RequestOrgInviteModal' +import { REQUEST_INVITE_TO_ORG } from '../../graphql/mutations' +import { matchMediaSize } from '../../helpers/matchMedia' +import { createCache } from '../../client' +import { makeVar } from '@apollo/client' +import { UserVarProvider } from '../../utilities/userState' + +matchMediaSize() + +const i18n = setupI18n({ + locale: 'en', + messages: { + en: {}, + }, + localeData: { + en: { plurals: en }, + }, +}) + +const orgId = 'test-id' +const orgName = 'test-org-name' + +const RequestModal = () => { + const { isOpen, onOpen, onClose } = useDisclosure() + + return ( + <> + + + + ) +} + +describe('', () => { + it("successfully renders modal when 'Open Modal' btn is clicked", async () => { + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-org-name/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + await waitFor(() => { + expect(queryByText(/test-org-name/)).toBeVisible() + }) + const closeModalButton = getByRole('button', { name: /Close/ }) + + // modal closed + userEvent.click(closeModalButton) + + await waitFor(() => expect(queryByText(/test-org-name/)).not.toBeInTheDocument()) + }) + + describe('when confirm btn is clicked', () => { + it('successfully requests invite', async () => { + const mocks = [ + { + request: { + query: REQUEST_INVITE_TO_ORG, + variables: { + orgId: orgId, + }, + }, + result: { + data: { + requestOrgAffiliation: { + result: { + status: 'Hello World', + __typename: 'InviteUserToOrgResult', + }, + }, + }, + }, + }, + ] + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-org-name/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + await waitFor(() => { + expect(queryByText(/test-org-name/)).toBeVisible() + }) + const confirmButton = getByRole('button', { name: /Confirm/ }) + + // modal closed + userEvent.click(confirmButton) + + await waitFor(() => + expect(queryByText(/Your request has been sent to the organization administrators./)).toBeInTheDocument(), + ) + }) + describe('fails to request invite', () => { + it('a server-side error occurs', async () => { + const mocks = [ + { + request: { + query: REQUEST_INVITE_TO_ORG, + variables: { + orgId: orgId, + }, + }, + result: { + error: { errors: [{ message: 'error' }] }, + }, + }, + ] + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-org-name/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + await waitFor(() => { + expect(queryByText(/test-org-name/)).toBeVisible() + }) + const confirmButton = getByRole('button', { name: /Confirm/ }) + + // modal closed + userEvent.click(confirmButton) + + await waitFor(() => expect(queryByText(/Unable to request invite, please try again./)).toBeInTheDocument()) + }) + it('a client-side error occurs', async () => { + const mocks = [ + { + request: { + query: REQUEST_INVITE_TO_ORG, + variables: { + orgId: orgId, + }, + }, + result: { + data: { + requestOrgAffiliation: { + result: { + code: 92, + description: 'Hello World', + __typename: 'AffiliationError', + }, + }, + __typename: 'RequestOrgAffiliationPayload', + }, + }, + }, + ] + const { queryByText, getByRole } = render( + + + + + + + + + + + , + ) + + // modal closed + const openModalButton = getByRole('button', { name: /Open Modal/ }) + expect(queryByText(/test-org-name/)).not.toBeInTheDocument() + + // modal opened + userEvent.click(openModalButton) + + await waitFor(() => { + expect(queryByText(/test-org-name/)).toBeVisible() + }) + const confirmButton = getByRole('button', { name: /Confirm/ }) + + // modal closed + userEvent.click(confirmButton) + + await waitFor(() => expect(queryByText(/Unable to request invite, please try again./)).toBeInTheDocument()) + }) + }) + }) +}) diff --git a/frontend/src/theme/Icons.js b/frontend/src/theme/Icons.js index 7656f37622..7c6090cf01 100644 --- a/frontend/src/theme/Icons.js +++ b/frontend/src/theme/Icons.js @@ -43,11 +43,7 @@ export const TwoFactorIcon = createIcon({ viewBox: '0 0 100 118.23771', // path can also be an array of elements, if you have multiple paths, lines, shapes, etc. path: ( - + - + Date: Mon, 15 May 2023 12:53:27 +0000 Subject: [PATCH 029/113] [ci skip] gcr.io/track-compliance/api-js:master-33da678-1684155087 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 257ad880a8..59e3446c71 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6ff56de-1684153789 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-33da678-1684155087 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 68596b767a..14a0c7fa9e 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6ff56de-1684153789 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-33da678-1684155087 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From e4c8ab90663b56a919aa83a31ebab4c044c535b7 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 15 May 2023 12:56:07 +0000 Subject: [PATCH 030/113] [ci skip] gcr.io/track-compliance/frontend:master-33da678-1684155175 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index d35a147f09..27f49890a4 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-6ff56de-1684153895 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-33da678-1684155175 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 774f2e3089..7655d67b8c 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-6ff56de-1684153895 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-33da678-1684155175 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 8a2e7236d812e484ff094776c4ff2de09cc771c5 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Mon, 15 May 2023 10:02:18 -0300 Subject: [PATCH 031/113] New summary charts (#4498) * create summaries for web connections(https+hsts), ssl(TLS), spf, and dkim * create api queries * create frontend queries for tiered summaries * fix ssl summary name * update faked schema * create components for tiered summaries * add new summaries to org summaries * make tabbed summary component * give orgs summary stats for hidden domains * move arrow btns to top of TieredSummaries component * add new org summaries to api * add chart dmarc summary * replace dmarcPhase summary with dmarcSummary on landing page * refactor to use tiered summaries on landing page and org pages * put tier 2 and 3 behind AB testing * add show btn for tier one org summaries * use SummaryGroup for rendering SummaryCards * remove unused org summary component * add dmarc summary to myTracker result * put tooltip on button * remove unused icon * change tabs to accordions * add AB testing to tiered summaries * remove hide/show btn from A stream * update SummaryGroup test * tests * fix summaries test * fix my-tracker test * fix summaries test * fix web_connections summary key * fix summaries test * add certificates status to filter tags * translations * summary card matching formatting * remove comment * update faked schema * fix tier2 dmarcPhase legend * refactor summaries service for optimized time * remove tests for deprecated function * fix ABTestVariant import --- .../__tests__/organization-summary.test.js | 80 ++-- .../objects/organization-summary.js | 246 +++++++++-- .../queries/__tests__/dkim-summary.test.js | 174 ++++++++ .../queries/__tests__/dmarc-summary.test.js | 174 ++++++++ .../queries/__tests__/spf-summary.test.js | 174 ++++++++ .../queries/__tests__/ssl-summary.test.js | 174 ++++++++ .../__tests__/web-connections-summary.test.js | 174 ++++++++ api/src/summaries/queries/dkim-summary.js | 33 ++ api/src/summaries/queries/dmarc-summary.js | 33 ++ api/src/summaries/queries/index.js | 9 +- api/src/summaries/queries/spf-summary.js | 33 ++ api/src/summaries/queries/ssl-summary.js | 33 ++ .../queries/web-connections-summary.js | 33 ++ .../loaders/load-my-tracker-by-user-id.js | 19 +- frontend/mocking/faked_schema.js | 43 +- frontend/mocking/mocker.js | 57 ++- frontend/src/graphql/fragments.js | 15 + frontend/src/graphql/queries.js | 104 +++-- frontend/src/landing/LandingPage.js | 6 +- frontend/src/landing/LandingPageSummaries.js | 28 +- frontend/src/locales/en.po | 153 +++++-- frontend/src/locales/fr.po | 153 +++++-- .../OrganizationDetails.js | 4 +- .../OrganizationSummary.js | 23 - .../src/organizations/OrganizationCard.js | 57 +-- frontend/src/summaries/SummaryCard.js | 56 +-- frontend/src/summaries/SummaryGroup.js | 105 ++--- frontend/src/summaries/TierOneSummaries.js | 29 ++ frontend/src/summaries/TierThreeSummaries.js | 28 ++ frontend/src/summaries/TierTwoSummaries.js | 76 ++++ frontend/src/summaries/TieredSummaries.js | 108 +++++ .../summaries/__tests__/SummaryGroup.test.js | 82 ++-- .../__tests__/TieredSummaries.test.js | 222 ++++++++++ frontend/src/theme/canada.js | 4 + frontend/src/user/MyTrackerPage.js | 35 +- services/summaries/summaries.py | 398 ++++++++++-------- services/summaries/tests/test_summaries.py | 86 ++-- 37 files changed, 2542 insertions(+), 719 deletions(-) create mode 100644 api/src/summaries/queries/__tests__/dkim-summary.test.js create mode 100644 api/src/summaries/queries/__tests__/dmarc-summary.test.js create mode 100644 api/src/summaries/queries/__tests__/spf-summary.test.js create mode 100644 api/src/summaries/queries/__tests__/ssl-summary.test.js create mode 100644 api/src/summaries/queries/__tests__/web-connections-summary.test.js create mode 100644 api/src/summaries/queries/dkim-summary.js create mode 100644 api/src/summaries/queries/dmarc-summary.js create mode 100644 api/src/summaries/queries/spf-summary.js create mode 100644 api/src/summaries/queries/ssl-summary.js create mode 100644 api/src/summaries/queries/web-connections-summary.js delete mode 100644 frontend/src/organizationDetails/OrganizationSummary.js create mode 100644 frontend/src/summaries/TierOneSummaries.js create mode 100644 frontend/src/summaries/TierThreeSummaries.js create mode 100644 frontend/src/summaries/TierTwoSummaries.js create mode 100644 frontend/src/summaries/TieredSummaries.js create mode 100644 frontend/src/summaries/__tests__/TieredSummaries.test.js diff --git a/api/src/organization/objects/__tests__/organization-summary.test.js b/api/src/organization/objects/__tests__/organization-summary.test.js index ee4ce5a9b5..273661e993 100644 --- a/api/src/organization/objects/__tests__/organization-summary.test.js +++ b/api/src/organization/objects/__tests__/organization-summary.test.js @@ -33,6 +33,30 @@ describe('given the organization summary object', () => { expect(demoType).toHaveProperty('dmarcPhase') expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) }) + it('has a webConnections field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('webConnections') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a ssl field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('ssl') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a spf field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('spf') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) + it('has a dkim field', () => { + const demoType = organizationSummaryType.getFields() + + expect(demoType).toHaveProperty('dkim') + expect(demoType.dmarcPhase.type).toMatchObject(categorizedSummaryType) + }) }) describe('field resolvers', () => { @@ -48,10 +72,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -83,10 +104,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.dmarc.resolve({ dmarc }, {}, { i18n })).toEqual({ @@ -120,10 +138,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -155,10 +170,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.https.resolve({ https }, {}, { i18n })).toEqual({ @@ -191,10 +203,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -226,10 +235,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.mail.resolve({ mail }, {}, { i18n })).toEqual({ @@ -262,10 +268,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -296,10 +299,7 @@ describe('given the organization summary object', () => { } const i18n = { - _: jest - .fn() - .mockReturnValueOnce('pass') - .mockReturnValueOnce('fail'), + _: jest.fn().mockReturnValueOnce('pass').mockReturnValueOnce('fail'), } expect(demoType.web.resolve({ web }, {}, { i18n })).toEqual({ @@ -344,13 +344,7 @@ describe('given the organization summary object', () => { .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ { count: 0, @@ -406,13 +400,7 @@ describe('given the organization summary object', () => { .mockReturnValueOnce('maintain'), } - expect( - demoType.dmarcPhase.resolve( - { dmarc_phase: dmarcPhase }, - {}, - { i18n }, - ), - ).toEqual({ + expect(demoType.dmarcPhase.resolve({ dmarc_phase: dmarcPhase }, {}, { i18n })).toEqual({ categories: [ { count: 50, diff --git a/api/src/organization/objects/organization-summary.js b/api/src/organization/objects/organization-summary.js index 68e00334f6..8380965061 100644 --- a/api/src/organization/objects/organization-summary.js +++ b/api/src/organization/objects/organization-summary.js @@ -8,8 +8,7 @@ export const organizationSummaryType = new GraphQLObjectType({ fields: () => ({ dmarc: { type: categorizedSummaryType, - description: - 'Summary based on DMARC scan results for a given organization.', + description: 'Summary based on DMARC scan results for a given organization.', resolve: ({ dmarc }, _) => { let percentPass, percentageFail if (dmarc.total <= 0) { @@ -41,8 +40,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, https: { type: categorizedSummaryType, - description: - 'Summary based on HTTPS scan results for a given organization.', + description: 'Summary based on HTTPS scan results for a given organization.', resolve: ({ https }, _) => { let percentPass, percentageFail if (https.total <= 0) { @@ -74,8 +72,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, mail: { type: categorizedSummaryType, - description: - 'Summary based on mail scan results for a given organization.', + description: 'Summary based on mail scan results for a given organization.', resolve: ({ mail }, _) => { let percentPass, percentageFail if (mail.total <= 0) { @@ -107,8 +104,7 @@ export const organizationSummaryType = new GraphQLObjectType({ }, web: { type: categorizedSummaryType, - description: - 'Summary based on web scan results for a given organization.', + description: 'Summary based on web scan results for a given organization.', resolve: ({ web }, _) => { let percentPass, percentageFail if (web.total <= 0) { @@ -142,35 +138,19 @@ export const organizationSummaryType = new GraphQLObjectType({ type: categorizedSummaryType, description: 'Summary based on DMARC phases for a given organization.', resolve: ({ dmarc_phase }, _) => { - let percentNotImplemented, - percentAsses, - percentDeploy, - percentEnforce, - percentMaintain + let percentNotImplemented, percentAssess, percentDeploy, percentEnforce, percentMaintain if (dmarc_phase.total <= 0) { percentNotImplemented = 0 - percentAsses = 0 + percentAssess = 0 percentDeploy = 0 percentEnforce = 0 percentMaintain = 0 } else { - percentNotImplemented = Number( - ((dmarc_phase.not_implemented / dmarc_phase.total) * 100).toFixed( - 1, - ), - ) - percentAsses = Number( - ((dmarc_phase.assess / dmarc_phase.total) * 100).toFixed(1), - ) - percentDeploy = Number( - ((dmarc_phase.deploy / dmarc_phase.total) * 100).toFixed(1), - ) - percentEnforce = Number( - ((dmarc_phase.enforce / dmarc_phase.total) * 100).toFixed(1), - ) - percentMaintain = Number( - ((dmarc_phase.maintain / dmarc_phase.total) * 100).toFixed(1), - ) + percentNotImplemented = Number(((dmarc_phase.not_implemented / dmarc_phase.total) * 100).toFixed(1)) + percentAssess = Number(((dmarc_phase.assess / dmarc_phase.total) * 100).toFixed(1)) + percentDeploy = Number(((dmarc_phase.deploy / dmarc_phase.total) * 100).toFixed(1)) + percentEnforce = Number(((dmarc_phase.enforce / dmarc_phase.total) * 100).toFixed(1)) + percentMaintain = Number(((dmarc_phase.maintain / dmarc_phase.total) * 100).toFixed(1)) } const categories = [ @@ -182,7 +162,7 @@ export const organizationSummaryType = new GraphQLObjectType({ { name: 'assess', count: dmarc_phase.assess, - percentage: percentAsses, + percentage: percentAssess, }, { name: 'deploy', @@ -207,5 +187,207 @@ export const organizationSummaryType = new GraphQLObjectType({ } }, }, + ssl: { + type: categorizedSummaryType, + description: 'Summary based on SSL scan results for a given organization.', + resolve: ({ ssl }, _) => { + let percentPass, percentageFail + if (ssl.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((ssl.pass / ssl.total) * 100).toFixed(1)) + percentageFail = Number(((ssl.fail / ssl.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: ssl.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: ssl.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: ssl.total, + } + }, + }, + webConnections: { + type: categorizedSummaryType, + description: 'Summary based on HTTPS and HSTS scan results for a given organization.', + resolve: ({ web_connections }, _) => { + let percentPass, percentageFail + if (web_connections.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((web_connections.pass / web_connections.total) * 100).toFixed(1)) + percentageFail = Number(((web_connections.fail / web_connections.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: web_connections.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: web_connections.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: web_connections.total, + } + }, + }, + spf: { + type: categorizedSummaryType, + description: 'Summary based on SPF scan results for a given organization.', + resolve: ({ spf }, _) => { + let percentPass, percentageFail + if (spf.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((spf.pass / spf.total) * 100).toFixed(1)) + percentageFail = Number(((spf.fail / spf.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: spf.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: spf.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: spf.total, + } + }, + }, + dkim: { + type: categorizedSummaryType, + description: 'Summary based on DKIM scan results for a given organization.', + resolve: ({ dkim }, _) => { + let percentPass, percentageFail + if (dkim.total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((dkim.pass / dkim.total) * 100).toFixed(1)) + percentageFail = Number(((dkim.fail / dkim.total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: dkim.pass, + percentage: percentPass, + }, + { + name: 'fail', + count: dkim.fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total: dkim.total, + } + }, + }, + httpsIncludeHidden: { + type: categorizedSummaryType, + description: + 'Summary based on HTTPS scan results for a given organization that includes domains marked as hidden.', + resolve: ({ https, hidden }, _) => { + const pass = https.pass + hidden.https.pass + const fail = https.fail + hidden.https.fail + const total = https.total + hidden.https.total + + let percentPass, percentageFail + if (total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((pass / total) * 100).toFixed(1)) + percentageFail = Number(((fail / total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: pass, + percentage: percentPass, + }, + { + name: 'fail', + count: fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total, + } + }, + }, + dmarcIncludeHidden: { + type: categorizedSummaryType, + description: + 'Summary based on HTTPS scan results for a given organization that includes domains marked as hidden.', + resolve: ({ dmarc, hidden }, _) => { + const pass = dmarc.pass + hidden.dmarc.pass + const fail = dmarc.fail + hidden.dmarc.fail + const total = dmarc.total + hidden.https.total + + let percentPass, percentageFail + if (total <= 0) { + percentPass = 0 + percentageFail = 0 + } else { + percentPass = Number(((pass / total) * 100).toFixed(1)) + percentageFail = Number(((fail / total) * 100).toFixed(1)) + } + + const categories = [ + { + name: 'pass', + count: pass, + percentage: percentPass, + }, + { + name: 'fail', + count: fail, + percentage: percentageFail, + }, + ] + + return { + categories, + total, + } + }, + }, }), }) diff --git a/api/src/summaries/queries/__tests__/dkim-summary.test.js b/api/src/summaries/queries/__tests__/dkim-summary.test.js new file mode 100644 index 0000000000..1865d8cbb8 --- /dev/null +++ b/api/src/summaries/queries/__tests__/dkim-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given dkimSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful dkim summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'dkim', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns dkim summary', async () => { + const response = await graphql( + schema, + ` + query { + dkimSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + dkimSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful dkim summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + dkimSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load DKIM summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve DKIM summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/dmarc-summary.test.js b/api/src/summaries/queries/__tests__/dmarc-summary.test.js new file mode 100644 index 0000000000..395a557675 --- /dev/null +++ b/api/src/summaries/queries/__tests__/dmarc-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given dmarcSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful dmarc summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'dmarc', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns dmarc summary', async () => { + const response = await graphql( + schema, + ` + query { + dmarcSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + dmarcSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful dmarc summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + dmarcSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load DMARC summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve DMARC summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/spf-summary.test.js b/api/src/summaries/queries/__tests__/spf-summary.test.js new file mode 100644 index 0000000000..74b12f6753 --- /dev/null +++ b/api/src/summaries/queries/__tests__/spf-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given spfSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful spf summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'spf', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns spf summary', async () => { + const response = await graphql( + schema, + ` + query { + spfSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + spfSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful spf summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + spfSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load SPF summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve SPF summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/ssl-summary.test.js b/api/src/summaries/queries/__tests__/ssl-summary.test.js new file mode 100644 index 0000000000..178c5d2a2e --- /dev/null +++ b/api/src/summaries/queries/__tests__/ssl-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given sslSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful ssl summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'ssl', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns ssl summary', async () => { + const response = await graphql( + schema, + ` + query { + sslSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + sslSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful ssl summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + sslSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load SSL summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve SSL summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/__tests__/web-connections-summary.test.js b/api/src/summaries/queries/__tests__/web-connections-summary.test.js new file mode 100644 index 0000000000..e216a8c19a --- /dev/null +++ b/api/src/summaries/queries/__tests__/web-connections-summary.test.js @@ -0,0 +1,174 @@ +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { setupI18n } from '@lingui/core' + +import englishMessages from '../../../locale/en/messages' +import frenchMessages from '../../../locale/fr/messages' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { loadChartSummaryByKey } from '../../loaders' +import dbschema from '../../../../database.json' + +const { DB_PASS: rootPass, DB_URL: url } = process.env + +describe('given webConnectionsSummary query', () => { + let query, drop, truncate, schema, collections, i18n + + const consoleOutput = [] + const mockedInfo = (output) => consoleOutput.push(output) + const mockedWarn = (output) => consoleOutput.push(output) + const mockedError = (output) => consoleOutput.push(output) + beforeAll(() => { + console.info = mockedInfo + console.warn = mockedWarn + console.error = mockedError + // Create GQL Schema + schema = new GraphQLSchema({ + query: createQuerySchema(), + mutation: createMutationSchema(), + }) + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + afterEach(() => { + consoleOutput.length = 0 + }) + + describe('given successful webConnections summary retrieval', () => { + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, + + schema: dbschema, + })) + }) + beforeEach(async () => { + await collections.chartSummaries.save({ + _key: 'web_connections', + total: 1000, + fail: 500, + pass: 500, + }) + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + it('returns webConnections summary', async () => { + const response = await graphql( + schema, + ` + query { + webConnectionsSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: loadChartSummaryByKey({ query }), + }, + }, + ) + + const expectedResponse = { + data: { + webConnectionsSummary: { + total: 1000, + categories: [ + { + name: 'pass', + count: 500, + percentage: 50, + }, + { + name: 'fail', + count: 500, + percentage: 50, + }, + ], + }, + }, + } + expect(response).toEqual(expectedResponse) + }) + }) + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('given unsuccessful webConnections summary retrieval', () => { + describe('summary cannot be found', () => { + it('returns an appropriate error message', async () => { + const response = await graphql( + schema, + ` + query { + webConnectionsSummary { + total + categories { + name + count + percentage + } + } + } + `, + null, + { + i18n, + loaders: { + loadChartSummaryByKey: { + load: jest.fn().mockReturnValue(undefined), + }, + }, + }, + ) + + const error = [new GraphQLError(`Unable to load web connections summary. Please try again.`)] + + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([`User could not retrieve web connections summary.`]) + }) + }) + }) + }) +}) diff --git a/api/src/summaries/queries/dkim-summary.js b/api/src/summaries/queries/dkim-summary.js new file mode 100644 index 0000000000..9a91978ce4 --- /dev/null +++ b/api/src/summaries/queries/dkim-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const dkimSummary = { + type: categorizedSummaryType, + description: 'DKIM summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('dkim') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve DKIM summary.`) + throw new Error(i18n._(t`Unable to load DKIM summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/dmarc-summary.js b/api/src/summaries/queries/dmarc-summary.js new file mode 100644 index 0000000000..e7ca64b3c8 --- /dev/null +++ b/api/src/summaries/queries/dmarc-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const dmarcSummary = { + type: categorizedSummaryType, + description: 'DMARC summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('dmarc') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve DMARC summary.`) + throw new Error(i18n._(t`Unable to load DMARC summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/index.js b/api/src/summaries/queries/index.js index 6b51658f0f..c4a8068b9a 100644 --- a/api/src/summaries/queries/index.js +++ b/api/src/summaries/queries/index.js @@ -1,4 +1,9 @@ -export * from './mail-summary' -export * from './web-summary' +export * from './dkim-summary' export * from './dmarc-phase-summary' +export * from './dmarc-summary' export * from './https-summary' +export * from './mail-summary' +export * from './spf-summary' +export * from './ssl-summary' +export * from './web-connections-summary' +export * from './web-summary' diff --git a/api/src/summaries/queries/spf-summary.js b/api/src/summaries/queries/spf-summary.js new file mode 100644 index 0000000000..c320ff40ff --- /dev/null +++ b/api/src/summaries/queries/spf-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const spfSummary = { + type: categorizedSummaryType, + description: 'SPF summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('spf') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve SPF summary.`) + throw new Error(i18n._(t`Unable to load SPF summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/ssl-summary.js b/api/src/summaries/queries/ssl-summary.js new file mode 100644 index 0000000000..ceafecefde --- /dev/null +++ b/api/src/summaries/queries/ssl-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const sslSummary = { + type: categorizedSummaryType, + description: 'SSL summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('ssl') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve SSL summary.`) + throw new Error(i18n._(t`Unable to load SSL summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/summaries/queries/web-connections-summary.js b/api/src/summaries/queries/web-connections-summary.js new file mode 100644 index 0000000000..a25acc7d38 --- /dev/null +++ b/api/src/summaries/queries/web-connections-summary.js @@ -0,0 +1,33 @@ +import { categorizedSummaryType } from '../objects' +import { t } from '@lingui/macro' + +export const webConnectionsSummary = { + type: categorizedSummaryType, + description: 'Web connections (HTTPS + HSTS) summary computed values, used to build summary cards.', + resolve: async (_, __, { i18n, loaders: { loadChartSummaryByKey } }) => { + const summary = await loadChartSummaryByKey.load('web_connections') + + if (typeof summary === 'undefined') { + console.warn(`User could not retrieve web connections summary.`) + throw new Error(i18n._(t`Unable to load web connections summary. Please try again.`)) + } + + const categories = [ + { + name: 'pass', + count: summary.pass, + percentage: Number(((summary.pass / summary.total) * 100).toFixed(1)), + }, + { + name: 'fail', + count: summary.fail, + percentage: Number(((summary.fail / summary.total) * 100).toFixed(1)), + }, + ] + + return { + categories, + total: summary.total, + } + }, +} diff --git a/api/src/user/loaders/load-my-tracker-by-user-id.js b/api/src/user/loaders/load-my-tracker-by-user-id.js index a88d42b57b..559bdf601d 100644 --- a/api/src/user/loaders/load-my-tracker-by-user-id.js +++ b/api/src/user/loaders/load-my-tracker-by-user-id.js @@ -21,7 +21,8 @@ export const loadMyTrackerByUserId = id: domain._key, _type: "domain", "phase": domain.phase, - "httpsStatus": domain.status.https + "https": domain.status.https, + "dmarc": domain.status.dmarc } ) RETURN { "domains": favDomains } @@ -49,6 +50,11 @@ export const loadMyTrackerByUserId = fail: 0, total: 0, }, + dmarc: { + pass: 0, + fail: 0, + total: 0, + }, dmarc_phase: { not_implemented: 0, assess: 0, @@ -59,16 +65,21 @@ export const loadMyTrackerByUserId = }, } - domainsInfo.domains.forEach(({ phase, httpsStatus }) => { + domainsInfo.domains.forEach(({ phase, https, dmarc }) => { // calculate https summary - if (httpsStatus === 'pass') { + if (https === 'pass') { returnSummaries.https.pass++ returnSummaries.https.total++ - } else if (httpsStatus === 'fail') { + } else if (https === 'fail') { returnSummaries.https.fail++ returnSummaries.https.total++ } + // calculate DMARC summary + if (dmarc === 'pass') returnSummaries.dmarc.pass++ + else if (dmarc === 'fail') returnSummaries.dmarc.fail++ + returnSummaries.dmarc.total++ + // calculate dmarcPhase summary if (phase === 'not implemented') returnSummaries.dmarc_phase.not_implemented++ else if (phase === 'assess') returnSummaries.dmarc_phase.assess++ diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 5c60a562c8..509f47838e 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -155,18 +155,33 @@ export const getTypeNames = () => gql` # CSV formatted output of all domains in all organizations including their email and web scan statuses. getAllOrganizationDomainStatuses: String - # Email summary computed values, used to build summary cards. - mailSummary: CategorizedSummary - - # Web summary computed values, used to build summary cards. - webSummary: CategorizedSummary + # DKIM summary computed values, used to build summary cards. + dkimSummary: CategorizedSummary # DMARC phase summary computed values, used to build summary cards. dmarcPhaseSummary: CategorizedSummary + # DMARC summary computed values, used to build summary cards. + dmarcSummary: CategorizedSummary + # HTTPS summary computed values, used to build summary cards. httpsSummary: CategorizedSummary + # Email summary computed values, used to build summary cards. + mailSummary: CategorizedSummary + + # SPF summary computed values, used to build summary cards. + spfSummary: CategorizedSummary + + # SSL summary computed values, used to build summary cards. + sslSummary: CategorizedSummary + + # SSL summary computed values, used to build summary cards. + webConnectionsSummary: CategorizedSummary + + # Web summary computed values, used to build summary cards. + webSummary: CategorizedSummary + # Query the currently logged in user. findMe: PersonalUser @@ -847,6 +862,24 @@ export const getTypeNames = () => gql` # Summary based on DMARC phases for a given organization. dmarcPhase: CategorizedSummary + + # Summary based on SSL scan results for a given organization. + ssl: CategorizedSummary + + # Summary based on HTTPS and HSTS scan results for a given organization. + webConnections: CategorizedSummary + + # Summary based on SPF scan results for a given organization. + spf: CategorizedSummary + + # Summary based on DKIM scan results for a given organization. + dkim: CategorizedSummary + + # Summary based on HTTPS scan results for a given organization that includes domains marked as hidden. + httpsIncludeHidden: CategorizedSummary + + # Summary based on HTTPS scan results for a given organization that includes domains marked as hidden. + dmarcIncludeHidden: CategorizedSummary } # This object contains the list of different categories for pre-computed diff --git a/frontend/mocking/mocker.js b/frontend/mocking/mocker.js index dc3f288554..c06e31d3fc 100644 --- a/frontend/mocking/mocker.js +++ b/frontend/mocking/mocker.js @@ -336,7 +336,7 @@ const mocks = { const tagId = 'tag' + faker.datatype.number({ min: 1, max: 14 }) const tagName = 'TAG-' + faker.helpers.randomize(['missing', 'downgraded', 'bad-chain', 'short-age', 'certificate-expired']) - const guidance = faker.lorem.sentence() + // const guidance = faker.lorem.sentence() const refLinks = [...new Array(1)] const refLinksTech = [...new Array(1)] @@ -356,6 +356,58 @@ const mocks = { totalCount: numberOfEdges, } }, + MyTrackerResult: () => { + const domainCount = faker.datatype.number({ min: 0, max: 500 }) + const httpsPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const httpsFailCount = domainCount - httpsPassCount + const httpsPassPercentage = (httpsPassCount / domainCount) * 100 + const httpsFailPercentage = 100 - httpsPassPercentage + const https = { + total: domainCount, + categories: [ + { + name: 'pass', + count: httpsPassCount, + percentage: httpsPassPercentage, + }, + { + name: 'fail', + count: httpsFailCount, + percentage: httpsFailPercentage, + }, + ], + } + + const mailPassCount = faker.datatype.number({ min: 0, max: domainCount }) + const mailFailCount = domainCount - mailPassCount + const mailPassPercentage = (mailPassCount / domainCount) * 100 + const mailFailPercentage = 100 - mailPassPercentage + const dmarc = { + total: domainCount, + categories: [ + { + name: 'pass', + count: mailPassCount, + percentage: mailPassPercentage, + }, + { + name: 'fail', + count: mailFailCount, + percentage: mailFailPercentage, + }, + ], + } + + const dmarcPhase = dmarcPhaseSummaryMock() + return { + domainCount, + domains: { + edges: [...new Array(domainCount)], + totalCount: domainCount, + }, + summaries: { https, dmarc, dmarcPhase }, + } + }, Organization: () => { const name = faker.company.companyName() const slug = faker.helpers.slugify(name) @@ -600,6 +652,9 @@ const schemaWithMocks = addMocksToSchema({ findMyOrganizations: (_, args, _context, resolveInfo) => { return getConnectionObject(store, args, resolveInfo) }, + findMyTracker: (_, _args, _context, _resolveInfo) => { + return store.get('MyTrackerResult') + }, dmarcPhaseSummary: (_, _args, _context, _resolveInfo) => { return dmarcPhaseSummaryMock() }, diff --git a/frontend/src/graphql/fragments.js b/frontend/src/graphql/fragments.js index 0e1051b6b9..af0d10cb72 100644 --- a/frontend/src/graphql/fragments.js +++ b/frontend/src/graphql/fragments.js @@ -18,6 +18,21 @@ export const Authorization = { }, } +export const Summary = { + fragments: { + requiredFields: gql` + fragment RequiredSummaryFields on CategorizedSummary { + total + categories { + name + count + percentage + } + } + `, + }, +} + export const Guidance = { fragments: { requiredFields: gql` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 81e20448db..ea7588da83 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -1,5 +1,5 @@ import { gql } from '@apollo/client' -import { Guidance, Status } from './fragments' +import { Guidance, Summary, Status } from './fragments' export const PAGINATED_ORGANIZATIONS = gql` query PaginatedOrganizations( @@ -56,25 +56,40 @@ export const PAGINATED_ORGANIZATIONS = gql` } ` -export const HTTPS_AND_DMARC_SUMMARY = gql` +export const LANDING_PAGE_SUMMARIES = gql` query LandingPageSummaries { + # Tier 1 httpsSummary { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + dmarcSummary { + ...RequiredSummaryFields + } + # Tier 2 + webConnectionsSummary { + ...RequiredSummaryFields + } + sslSummary { + ...RequiredSummaryFields + } + spfSummary { + ...RequiredSummaryFields + } + dkimSummary { + ...RequiredSummaryFields } dmarcPhaseSummary { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + # Tier 3 + webSummary { + ...RequiredSummaryFields + } + mailSummary { + ...RequiredSummaryFields } } + ${Summary.fragments.requiredFields} ` export const GET_ORGANIZATION_DOMAINS_STATUSES_CSV = gql` @@ -463,24 +478,42 @@ export const ORG_DETAILS_PAGE = gql` verified summaries { https { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + dmarc { + ...RequiredSummaryFields + } + httpsIncludeHidden { + ...RequiredSummaryFields + } + dmarcIncludeHidden { + ...RequiredSummaryFields + } + dkim { + ...RequiredSummaryFields + } + spf { + ...RequiredSummaryFields + } + ssl { + ...RequiredSummaryFields + } + webConnections { + ...RequiredSummaryFields } dmarcPhase { - total - categories { - name - count - percentage - } + ...RequiredSummaryFields + } + web { + ...RequiredSummaryFields + } + mail { + ...RequiredSummaryFields } } } } + ${Summary.fragments.requiredFields} ` export const PAGINATED_ORG_DOMAINS = gql` @@ -985,25 +1018,18 @@ export const MY_TRACKER_SUMMARY = gql` findMyTracker { summaries { https { - categories { - name - count - percentage - } - total + ...RequiredSummaryFields + } + dmarc { + ...RequiredSummaryFields } dmarcPhase { - categories { - name - count - percentage - } - total + ...RequiredSummaryFields } } - domainCount } } + ${Summary.fragments.requiredFields} ` export const MY_TRACKER_DOMAINS = gql` diff --git a/frontend/src/landing/LandingPage.js b/frontend/src/landing/LandingPage.js index 810a44efa4..e1d5177866 100644 --- a/frontend/src/landing/LandingPage.js +++ b/frontend/src/landing/LandingPage.js @@ -26,10 +26,8 @@ export function LandingPage({ loginRequired, isLoggedIn }) { - Canadians rely on the Government of Canada to provide secure digital - services. The Policy on Service and Digital guides government online - services to adopt good security practices for practices outlined in - the{' '} + Canadians rely on the Government of Canada to provide secure digital services. The Policy on Service and + Digital guides government online services to adopt good security practices for practices outlined in the{' '} if (error) return - return ( - - - - ) + const summaries = { + https: data?.httpsSummary, + dmarc: data?.dmarcSummary, + webConnections: data?.webConnectionsSummary, + ssl: data?.sslSummary, + spf: data?.spfSummary, + dkim: data?.dkimSummary, + dmarcPhase: data?.dmarcPhaseSummary, + web: data?.webSummary, + mail: data?.mailSummary, + } + + return } diff --git a/frontend/src/locales/en.po b/frontend/src/locales/en.po index 260f1ef592..160e701081 100644 --- a/frontend/src/locales/en.po +++ b/frontend/src/locales/en.po @@ -21,23 +21,23 @@ msgstr ", and" msgid ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" msgstr ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" -#: src/summaries/RadialBarChart.js:25 +#: src/summaries/RadialBarChart.js:30 msgid "0. Not Implemented" msgstr "0. Not Implemented" -#: src/summaries/RadialBarChart.js:27 +#: src/summaries/RadialBarChart.js:32 msgid "1. Assess" msgstr "1. Assess" -#: src/summaries/RadialBarChart.js:31 +#: src/summaries/RadialBarChart.js:36 msgid "2. Deploy" msgstr "2. Deploy" -#: src/summaries/RadialBarChart.js:35 +#: src/summaries/RadialBarChart.js:40 msgid "3. Enforce" msgstr "3. Enforce" -#: src/summaries/RadialBarChart.js:39 +#: src/summaries/RadialBarChart.js:44 msgid "4. Maintain" msgstr "4. Maintain" @@ -65,7 +65,7 @@ msgstr "A DNS request for this service has resulted in the following error code: msgid "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." msgstr "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." -#: src/summaries/SummaryGroup.js:47 +#: src/summaries/TierOneSummaries.js:18 msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" @@ -533,11 +533,19 @@ msgstr "Collect and analyze DMARC reports." msgid "Comparison" msgstr "Comparison" -#: src/summaries/SummaryGroup.js:24 +#: src/summaries/SummaryGroup.js:20 msgid "Compliant" msgstr "Compliant" -#: src/admin/AdminDomainModal.js:391 +#: src/summaries/TierThreeSummaries.js:18 +msgid "Configuration requirements for email services completely met" +msgstr "Configuration requirements for email services completely met" + +#: src/summaries/TierThreeSummaries.js:12 +msgid "Configuration requirements for web sites and services completely met" +msgstr "Configuration requirements for web sites and services completely met" + +#: src/admin/AdminDomainModal.js:459 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 @@ -671,7 +679,7 @@ msgstr "Current Phone Number:" #: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 #: src/organizationDetails/OrganizationDomains.js:269 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Curves" msgstr "Curves" @@ -724,6 +732,14 @@ msgstr "DKIM Selectors:" msgid "DKIM Status" msgstr "DKIM Status" +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM Summary" +msgstr "DKIM Summary" + +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM record and keys are deployed and valid" +msgstr "DKIM record and keys are deployed and valid" + #: src/guidance/EmailGuidance.js:218 #~ msgid "DKIM record could not be found for this selector." #~ msgstr "DKIM record could not be found for this selector." @@ -737,11 +753,11 @@ msgstr "DMARC" msgid "DMARC Configuration" msgstr "DMARC Configuration" -#: src/summaries/SummaryGroup.js:46 +#: src/summaries/TierOneSummaries.js:17 msgid "DMARC Configuration Summary" msgstr "DMARC Configuration Summary" -#: src/organizations/OrganizationCard.js:130 +#: src/organizations/OrganizationCard.js:93 msgid "DMARC Configured" msgstr "DMARC Configured" @@ -760,8 +776,8 @@ msgstr "DMARC Failures by IP Address" msgid "DMARC Implementation Phase: {0}" msgstr "DMARC Implementation Phase: {0}" -#: src/organizationDetails/OrganizationDetails.js:135 -#: src/user/MyTrackerPage.js:96 +#: src/organizationDetails/OrganizationDetails.js:126 +#: src/user/MyTrackerPage.js:79 msgid "DMARC Phases" msgstr "DMARC Phases" @@ -788,10 +804,18 @@ msgstr "DMARC Status" msgid "DMARC Summaries" msgstr "DMARC Summaries" +#: src/summaries/TierTwoSummaries.js:56 +msgid "DMARC Summary" +msgstr "DMARC Summary" + #: src/summaries/SummaryGroup.js:47 #~ msgid "DMARC phase summary" #~ msgstr "DMARC phase summary" +#: src/summaries/TierTwoSummaries.js:57 +msgid "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" +msgstr "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" + #: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." @@ -963,11 +987,11 @@ msgstr "Domain:" #: src/app/FloatingMenu.js:116 #: src/domains/DomainsPage.js:82 #: src/domains/DomainsPage.js:116 -#: src/organizationDetails/OrganizationDetails.js:138 +#: src/organizationDetails/OrganizationDetails.js:129 #: src/organizationDetails/OrganizationDomains.js:106 #: src/summaries/Doughnut.js:50 #: src/summaries/Doughnut.js:75 -#: src/user/MyTrackerPage.js:99 +#: src/user/MyTrackerPage.js:82 msgid "Domains" msgstr "Domains" @@ -1058,6 +1082,10 @@ msgstr "Email Security:" msgid "Email Sent" msgstr "Email Sent" +#: src/summaries/TierThreeSummaries.js:17 +msgid "Email Summary" +msgstr "Email Summary" + #: src/user/EditableUserTFAMethod.js:111 msgid "Email Validated" msgstr "Email Validated" @@ -1347,7 +1375,7 @@ msgstr "Government of Canada Employees" #~ msgid "Graph direction:" #~ msgstr "Graph direction:" -#: src/app/App.js:345 +#: src/app/App.js:350 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1427,12 +1455,12 @@ msgstr "HTTPS" msgid "HTTPS (443) Chain" msgstr "HTTPS (443) Chain" -#: src/summaries/SummaryGroup.js:16 +#: src/summaries/TierOneSummaries.js:11 msgid "HTTPS Configuration Summary" msgstr "HTTPS Configuration Summary" -#: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:135 +#: src/organizations/OrganizationCard.js:85 +#: src/organizations/Organizations.js:122 msgid "HTTPS Configured" msgstr "HTTPS Configured" @@ -1453,7 +1481,7 @@ msgstr "HTTPS Scan Complete" msgid "HTTPS Status" msgstr "HTTPS Status" -#: src/summaries/SummaryGroup.js:17 +#: src/summaries/TierOneSummaries.js:12 msgid "HTTPS is configured and HTTP connections redirect to HTTPS" msgstr "HTTPS is configured and HTTP connections redirect to HTTPS" @@ -1461,6 +1489,10 @@ msgstr "HTTPS is configured and HTTP connections redirect to HTTPS" #~ msgid "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" #~ msgstr "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" +#: src/summaries/TierTwoSummaries.js:40 +msgid "HTTPS is configured, HTTP redirects, and HSTS is enabled" +msgstr "HTTPS is configured, HTTP redirects, and HSTS is enabled" + #: src/app/RequestScanNotificationHandler.js:90 msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "HTTPS scan for domain \"{0}\" has completed." @@ -1618,7 +1650,7 @@ msgstr "Implementation: <0>Guidance on securely configuring network protocols (I msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" msgstr "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" -#: src/summaries/SummaryGroup.js:58 +#: src/summaries/SummaryGroup.js:31 msgid "Implemented" msgstr "Implemented" @@ -1626,6 +1658,11 @@ msgstr "Implemented" msgid "Inactive" msgstr "Inactive" +#: src/summaries/TieredSummaries.js:55 +#: src/summaries/TieredSummaries.js:57 +msgid "Include hidden domains in summaries." +msgstr "Include hidden domains in summaries." + #: src/auth/TwoFactorAuthenticatePage.js:81 msgid "Incorrect authenticate.result typename." msgstr "Incorrect authenticate.result typename." @@ -2102,8 +2139,8 @@ msgid "No DKIM selectors are currently attached to this domain. Please contact a msgstr "No DKIM selectors are currently attached to this domain. Please contact an admin of an affiliated organization to add selectors." #: src/summaries/SummaryGroup.js:67 -msgid "No DMARC phase information available for this organization." -msgstr "No DMARC phase information available for this organization." +#~ msgid "No DMARC phase information available for this organization." +#~ msgstr "No DMARC phase information available for this organization." #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 @@ -2112,11 +2149,11 @@ msgid "No Domains" msgstr "No Domains" #: src/summaries/SummaryGroup.js:37 -msgid "No HTTPS configuration information available for this organization." -msgstr "No HTTPS configuration information available for this organization." +#~ msgid "No HTTPS configuration information available for this organization." +#~ msgstr "No HTTPS configuration information available for this organization." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:78 +#: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "No Organizations" @@ -2194,7 +2231,7 @@ msgstr "No users" msgid "No values were supplied when attempting to update organization details." msgstr "No values were supplied when attempting to update organization details." -#: src/summaries/SummaryGroup.js:20 +#: src/summaries/SummaryGroup.js:16 msgid "Non-compliant" msgstr "Non-compliant" @@ -2211,7 +2248,7 @@ msgstr "Not After:" msgid "Not Before:" msgstr "Not Before:" -#: src/summaries/SummaryGroup.js:50 +#: src/summaries/SummaryGroup.js:27 msgid "Not Implemented" msgstr "Not Implemented" @@ -2282,7 +2319,7 @@ msgstr "Options include contacting the <0>SSC WebSSL services team and/or us msgid "Organization" msgstr "Organization" -#: src/organizationDetails/OrganizationDetails.js:67 +#: src/organizationDetails/OrganizationDetails.js:58 msgid "Organization Details" msgstr "Organization Details" @@ -2525,7 +2562,7 @@ msgstr "Protect domains that do not send email - GOV.UK (www.gov.uk)" #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 #: src/organizationDetails/OrganizationDomains.js:275 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/organizationDetails/OrganizationDomains.js:315 msgid "Protocols" msgstr "Protocols" @@ -2667,7 +2704,7 @@ msgstr "Requirements: <0>Email Management Services Configuration RequirementsWeb Sites and Services Management Configuration Requirements" msgstr "Requirements: <0>Web Sites and Services Management Configuration Requirements" -#: src/app/App.js:182 +#: src/app/App.js:187 msgid "Reset Password" msgstr "Reset Password" @@ -2759,10 +2796,18 @@ msgstr "SPF Results" msgid "SPF Status" msgstr "SPF Status" +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF Summary" +msgstr "SPF Summary" + #: src/guidance/EmailGuidance.js:167 #~ msgid "SPF record could not be found during the scan." #~ msgstr "SPF record could not be found during the scan." +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF record is deployed and valid" +msgstr "SPF record is deployed and valid" + #: src/app/RequestScanNotificationHandler.js:72 #~ msgid "SSL Scan Complete" #~ msgstr "SSL Scan Complete" @@ -2923,7 +2968,7 @@ msgstr "Serial:" msgid "Services" msgstr "Services" -#: src/organizations/OrganizationCard.js:107 +#: src/organizations/OrganizationCard.js:79 msgid "Services: {domainCount}" msgstr "Services: {domainCount}" @@ -3190,8 +3235,8 @@ msgstr "Submit" msgid "Successfully removed user {0}." msgstr "Successfully removed user {0}." -#: src/organizationDetails/OrganizationDetails.js:132 -#: src/user/MyTrackerPage.js:93 +#: src/organizationDetails/OrganizationDetails.js:123 +#: src/user/MyTrackerPage.js:76 msgid "Summary" msgstr "Summary" @@ -3239,6 +3284,10 @@ msgstr "TLS Results" msgid "TLS Scan Complete" msgstr "TLS Scan Complete" +#: src/summaries/TierTwoSummaries.js:45 +msgid "TLS Summary" +msgstr "TLS Summary" + #: src/app/RequestScanNotificationHandler.js:73 msgid "TLS scan for domain \"{0}\" has completed." msgstr "TLS scan for domain \"{0}\" has completed." @@ -3413,6 +3462,18 @@ msgstr "This service is not web-hosting and does not require compliance with the msgid "This user is not affiliated with any organizations" msgstr "This user is not affiliated with any organizations" +#: src/summaries/TieredSummaries.js:48 +msgid "Tier 1: Minimum Requirements" +msgstr "Tier 1: Minimum Requirements" + +#: src/summaries/TieredSummaries.js:75 +msgid "Tier 2: Improved Posture" +msgstr "Tier 2: Improved Posture" + +#: src/summaries/TieredSummaries.js:92 +msgid "Tier 3: Compliance" +msgstr "Tier 3: Compliance" + #: src/admin/AuditLogTable.js:70 msgid "Time Generated" msgstr "Time Generated" @@ -3730,8 +3791,8 @@ msgid "User:" msgstr "User:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:24 -#: src/organizationDetails/OrganizationDetails.js:142 +#: src/admin/AdminPanel.js:31 +#: src/organizationDetails/OrganizationDetails.js:133 msgid "Users" msgstr "Users" @@ -3773,7 +3834,7 @@ msgstr "Verify Account" #~ msgid "Vertical View" #~ msgstr "Vertical View" -#: src/organizations/OrganizationCard.js:144 +#: src/organizations/OrganizationCard.js:101 msgid "View Details" msgstr "View Details" @@ -3841,6 +3902,10 @@ msgstr "Web (HTTPS/TLS)" msgid "Web Check" msgstr "Web Check" +#: src/summaries/TierTwoSummaries.js:39 +msgid "Web Connections Summary" +msgstr "Web Connections Summary" + #: src/domains/ScanDomain.js:242 #: src/guidance/GuidancePage.js:102 msgid "Web Guidance" @@ -3858,6 +3923,10 @@ msgstr "Web Security:" #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Web Sites and Services Management Configuration Requirements Compliant" +#: src/summaries/TierThreeSummaries.js:11 +msgid "Web Summary" +msgstr "Web Summary" + #: src/summaries/Doughnut.js:34 msgid "Web-hosting" msgstr "Web-hosting" @@ -3874,7 +3943,7 @@ msgstr "Web-hosting" msgid "Welcome to Tracker, please enter your details." msgstr "Welcome to Tracker, please enter your details." -#: src/user/MyTrackerPage.js:78 +#: src/user/MyTrackerPage.js:63 msgid "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." msgstr "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." @@ -4068,10 +4137,10 @@ msgstr "contact us" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:100 -#: src/app/App.js:279 -#: src/user/MyTrackerPage.js:43 -#: src/user/MyTrackerPage.js:74 +#: src/app/App.js:105 +#: src/app/App.js:284 +#: src/user/MyTrackerPage.js:33 +#: src/user/MyTrackerPage.js:59 msgid "myTracker" msgstr "myTracker" diff --git a/frontend/src/locales/fr.po b/frontend/src/locales/fr.po index 141e371a9c..65f155fba8 100644 --- a/frontend/src/locales/fr.po +++ b/frontend/src/locales/fr.po @@ -21,23 +21,23 @@ msgstr ", et" msgid ". Personal information will not be disclosed by Treasury Board Secretariat of Canada (TBS) except in accordance with the" msgstr ". Les renseignements personnels ne seront pas divulgués par le Secrétariat du Conseil du Trésor du Canada (SCT), sauf en conformité avec les dispositions du" -#: src/summaries/RadialBarChart.js:25 +#: src/summaries/RadialBarChart.js:30 msgid "0. Not Implemented" msgstr "0. Non mis en œuvre" -#: src/summaries/RadialBarChart.js:27 +#: src/summaries/RadialBarChart.js:32 msgid "1. Assess" msgstr "1. Évaluez" -#: src/summaries/RadialBarChart.js:31 +#: src/summaries/RadialBarChart.js:36 msgid "2. Deploy" msgstr "2. Déployer" -#: src/summaries/RadialBarChart.js:35 +#: src/summaries/RadialBarChart.js:40 msgid "3. Enforce" msgstr "3. Appliquer" -#: src/summaries/RadialBarChart.js:39 +#: src/summaries/RadialBarChart.js:44 msgid "4. Maintain" msgstr "4. Maintenir" @@ -65,7 +65,7 @@ msgstr "Une requête DNS pour ce service a donné lieu au code d'erreur suivant msgid "A domain may only be removed for one of the reasons below. For a domain to no longer exist, it must be removed from the DNS. If you need to remove this domain for a different reason, please contact TBS Cyber Security." msgstr "Un domaine ne peut être supprimé que pour l'une des raisons ci-dessous. Pour qu'un domaine n'existe plus, il doit être supprimé du DNS. Si vous devez supprimer ce domaine pour une autre raison, veuillez contacter TBS Cyber Security." -#: src/summaries/SummaryGroup.js:47 +#: src/summaries/TierOneSummaries.js:18 msgid "A minimum DMARC policy of “p=none” with at least one address defined as a recipient of aggregate reports" msgstr "Une politique DMARC minimale de \"p=none\" avec au moins une adresse définie comme destinataire des rapports agrégés." @@ -533,11 +533,19 @@ msgstr "Recueillir et analyser les rapports DMARC." msgid "Comparison" msgstr "Comparaison" -#: src/summaries/SummaryGroup.js:24 +#: src/summaries/SummaryGroup.js:20 msgid "Compliant" msgstr "Conforme" -#: src/admin/AdminDomainModal.js:391 +#: src/summaries/TierThreeSummaries.js:18 +msgid "Configuration requirements for email services completely met" +msgstr "Les exigences de configuration pour les services de courrier électronique sont entièrement satisfaites" + +#: src/summaries/TierThreeSummaries.js:12 +msgid "Configuration requirements for web sites and services completely met" +msgstr "Les exigences de configuration des sites et services web sont entièrement satisfaites" + +#: src/admin/AdminDomainModal.js:459 #: src/admin/AdminDomains.js:386 #: src/admin/OrganizationInformation.js:393 #: src/admin/OrganizationInformation.js:520 @@ -671,7 +679,7 @@ msgstr "Numéro de téléphone actuel:" #: src/domains/DomainsPage.js:156 #: src/guidance/WebTLSResults.js:155 #: src/organizationDetails/OrganizationDomains.js:269 -#: src/organizationDetails/OrganizationDomains.js:313 +#: src/organizationDetails/OrganizationDomains.js:314 msgid "Curves" msgstr "Courbes" @@ -724,6 +732,14 @@ msgstr "Sélecteurs DKIM:" msgid "DKIM Status" msgstr "Statut DKIM" +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM Summary" +msgstr "Résumé DKIM" + +#: src/summaries/TierTwoSummaries.js:53 +msgid "DKIM record and keys are deployed and valid" +msgstr "L'enregistrement DKIM et les clés sont déployés et valides" + #: src/guidance/EmailGuidance.js:218 #~ msgid "DKIM record could not be found for this selector." #~ msgstr "Un enregistrement DKIM n'a pas pu être trouvé pour ce sélecteur." @@ -737,11 +753,11 @@ msgstr "DMARC" msgid "DMARC Configuration" msgstr "Configuration de DMARC" -#: src/summaries/SummaryGroup.js:46 +#: src/summaries/TierOneSummaries.js:17 msgid "DMARC Configuration Summary" msgstr "Résumé de la configuration DMARC" -#: src/organizations/OrganizationCard.js:130 +#: src/organizations/OrganizationCard.js:93 msgid "DMARC Configured" msgstr "DMARC configuré" @@ -760,8 +776,8 @@ msgstr "Défaillances du DMARC par adresse IP" msgid "DMARC Implementation Phase: {0}" msgstr "Phase de mise en œuvre de DMARC: {0}" -#: src/organizationDetails/OrganizationDetails.js:135 -#: src/user/MyTrackerPage.js:96 +#: src/organizationDetails/OrganizationDetails.js:126 +#: src/user/MyTrackerPage.js:79 msgid "DMARC Phases" msgstr "Phases DMARC" @@ -788,10 +804,18 @@ msgstr "Statut DMARC" msgid "DMARC Summaries" msgstr "Résumés DMARC" +#: src/summaries/TierTwoSummaries.js:56 +msgid "DMARC Summary" +msgstr "Résumé DMARC" + #: src/summaries/SummaryGroup.js:47 #~ msgid "DMARC phase summary" #~ msgstr "Résumé de la phase DMARC" +#: src/summaries/TierTwoSummaries.js:57 +msgid "DMARC policy of quarantine or reject, and all messages from non-mail domain is rejected" +msgstr "Politique DMARC de mise en quarantaine ou de rejet, et rejet de tous les messages provenant d'un domaine autre que la messagerie." + #: src/dmarc/DmarcReportPage.js:185 #: src/guidance/EmailGuidance.js:272 #~ msgid "DMARC record could not be found during the scan." @@ -963,11 +987,11 @@ msgstr "Domaine:" #: src/app/FloatingMenu.js:116 #: src/domains/DomainsPage.js:82 #: src/domains/DomainsPage.js:116 -#: src/organizationDetails/OrganizationDetails.js:138 +#: src/organizationDetails/OrganizationDetails.js:129 #: src/organizationDetails/OrganizationDomains.js:106 #: src/summaries/Doughnut.js:50 #: src/summaries/Doughnut.js:75 -#: src/user/MyTrackerPage.js:99 +#: src/user/MyTrackerPage.js:82 msgid "Domains" msgstr "Domaines" @@ -1050,6 +1074,10 @@ msgstr "Sécurité du courrier électronique :" msgid "Email Sent" msgstr "Courriel envoyé" +#: src/summaries/TierThreeSummaries.js:17 +msgid "Email Summary" +msgstr "Résumé de l'e-mail" + #: src/user/EditableUserTFAMethod.js:111 msgid "Email Validated" msgstr "Courriel validé" @@ -1319,7 +1347,7 @@ msgstr "Employés du gouvernement du Canada" #~ msgid "Graph direction:" #~ msgstr "Direction du graphique :" -#: src/app/App.js:345 +#: src/app/App.js:350 #: src/dmarc/DmarcReportPage.js:196 #: src/dmarc/DmarcReportPage.js:690 msgid "Guidance" @@ -1399,12 +1427,12 @@ msgstr "HTTPS" msgid "HTTPS (443) Chain" msgstr "Chaîne HTTPS (443)" -#: src/summaries/SummaryGroup.js:16 +#: src/summaries/TierOneSummaries.js:11 msgid "HTTPS Configuration Summary" msgstr "Résumé de la configuration HTTPS" -#: src/organizations/OrganizationCard.js:118 -#: src/organizations/Organizations.js:135 +#: src/organizations/OrganizationCard.js:85 +#: src/organizations/Organizations.js:122 msgid "HTTPS Configured" msgstr "HTTPS configuré" @@ -1425,7 +1453,7 @@ msgstr "Scan HTTPS terminé" msgid "HTTPS Status" msgstr "Statut HTTPS" -#: src/summaries/SummaryGroup.js:17 +#: src/summaries/TierOneSummaries.js:12 msgid "HTTPS is configured and HTTP connections redirect to HTTPS" msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS." @@ -1433,6 +1461,10 @@ msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS. #~ msgid "HTTPS is configured and HTTP connections redirect to HTTPS (ITPIN 6.1.1)" #~ msgstr "HTTPS est configuré et les connexions HTTP sont redirigées vers HTTPS (ITPIN 6.1.1)" +#: src/summaries/TierTwoSummaries.js:40 +msgid "HTTPS is configured, HTTP redirects, and HSTS is enabled" +msgstr "HTTPS est configuré, les redirections HTTP et HSTS sont activés." + #: src/app/RequestScanNotificationHandler.js:90 msgid "HTTPS scan for domain \"{0}\" has completed." msgstr "L'analyse HTTPS du domaine \"{0}\" est terminée." @@ -1590,7 +1622,7 @@ msgstr "Mise en œuvre : <0>Conseils sur la configuration sécurisée des protoc msgid "Implementation: <0>Implementation guidance: email domain protection (ITSP.40.065 v1.1)" msgstr "Mise en œuvre : <0>Conseils de mise en œuvre : protection du domaine de messagerie (ITSP.40.065 v1.1)" -#: src/summaries/SummaryGroup.js:58 +#: src/summaries/SummaryGroup.js:31 msgid "Implemented" msgstr "Mis en œuvre" @@ -1598,6 +1630,11 @@ msgstr "Mis en œuvre" msgid "Inactive" msgstr "Inactif" +#: src/summaries/TieredSummaries.js:55 +#: src/summaries/TieredSummaries.js:57 +msgid "Include hidden domains in summaries." +msgstr "Inclure les domaines cachés dans les résumés." + #: src/auth/TwoFactorAuthenticatePage.js:81 msgid "Incorrect authenticate.result typename." msgstr "Incorrect authenticate.result typename." @@ -2070,8 +2107,8 @@ msgid "No DKIM selectors are currently attached to this domain. Please contact a msgstr "Aucun sélecteur DKIM n'est actuellement associé à ce domaine. Veuillez contacter un administrateur d'une organisation affiliée pour ajouter des sélecteurs." #: src/summaries/SummaryGroup.js:67 -msgid "No DMARC phase information available for this organization." -msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organisation." +#~ msgid "No DMARC phase information available for this organization." +#~ msgstr "Aucune information sur la phase DMARC n'est disponible pour cette organisation." #: src/admin/AdminDomains.js:156 #: src/domains/DomainsPage.js:89 @@ -2080,11 +2117,11 @@ msgid "No Domains" msgstr "Aucun domaine" #: src/summaries/SummaryGroup.js:37 -msgid "No HTTPS configuration information available for this organization." -msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." +#~ msgid "No HTTPS configuration information available for this organization." +#~ msgstr "Aucune information de configuration HTTPS disponible pour cette organisation." #: src/admin/WebCheckPage.js:94 -#: src/organizations/Organizations.js:78 +#: src/organizations/Organizations.js:81 msgid "No Organizations" msgstr "Aucune organisation" @@ -2162,7 +2199,7 @@ msgstr "Aucun utilisateur" msgid "No values were supplied when attempting to update organization details." msgstr "Aucune valeur n'a été fournie lors de la tentative de mise à jour des détails de l'organisation." -#: src/summaries/SummaryGroup.js:20 +#: src/summaries/SummaryGroup.js:16 msgid "Non-compliant" msgstr "Non conforme" @@ -2179,7 +2216,7 @@ msgstr "Pas après :" msgid "Not Before:" msgstr "Pas avant :" -#: src/summaries/SummaryGroup.js:50 +#: src/summaries/SummaryGroup.js:27 msgid "Not Implemented" msgstr "Non mis en œuvre" @@ -2250,7 +2287,7 @@ msgstr "Vous pouvez notamment communiquer avec l’<0>équipe responsable des se msgid "Organization" msgstr "Organisation" -#: src/organizationDetails/OrganizationDetails.js:67 +#: src/organizationDetails/OrganizationDetails.js:58 msgid "Organization Details" msgstr "Détails de l'organisation" @@ -2493,7 +2530,7 @@ msgstr "Protéger les domaines qui n'envoient pas de courrier électronique - GO #: src/domains/DomainsPage.js:162 #: src/guidance/WebTLSResults.js:52 #: src/organizationDetails/OrganizationDomains.js:275 -#: src/organizationDetails/OrganizationDomains.js:314 +#: src/organizationDetails/OrganizationDomains.js:315 msgid "Protocols" msgstr "Protocoles" @@ -2631,7 +2668,7 @@ msgstr "Exigences : <0>Configuration requise pour les services de gestion du cou msgid "Requirements: <0>Web Sites and Services Management Configuration Requirements" msgstr "Exigences : <0>Exigences de configuration de la gestion des sites et services web" -#: src/app/App.js:182 +#: src/app/App.js:187 msgid "Reset Password" msgstr "Réinitialiser le mot de passe" @@ -2723,10 +2760,18 @@ msgstr "Résultats du SPF" msgid "SPF Status" msgstr "Statut SPF" +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF Summary" +msgstr "Résumé du SPF" + #: src/guidance/EmailGuidance.js:167 #~ msgid "SPF record could not be found during the scan." #~ msgstr "L'enregistrement SPF n'a pas pu être trouvé pendant l'analyse." +#: src/summaries/TierTwoSummaries.js:52 +msgid "SPF record is deployed and valid" +msgstr "L'enregistrement SPF est déployé et valide" + #: src/app/RequestScanNotificationHandler.js:72 #~ msgid "SSL Scan Complete" #~ msgstr "Analyse SSL terminée" @@ -2887,7 +2932,7 @@ msgstr "En série :" msgid "Services" msgstr "Services" -#: src/organizations/OrganizationCard.js:107 +#: src/organizations/OrganizationCard.js:79 msgid "Services: {domainCount}" msgstr "Services: {domainCount}" @@ -3152,8 +3197,8 @@ msgstr "Soumettre" msgid "Successfully removed user {0}." msgstr "L'utilisateur {0} a été supprimé." -#: src/organizationDetails/OrganizationDetails.js:132 -#: src/user/MyTrackerPage.js:93 +#: src/organizationDetails/OrganizationDetails.js:123 +#: src/user/MyTrackerPage.js:76 msgid "Summary" msgstr "Résumé" @@ -3201,6 +3246,10 @@ msgstr "Résultats TLS" msgid "TLS Scan Complete" msgstr "Scan TLS terminé" +#: src/summaries/TierTwoSummaries.js:45 +msgid "TLS Summary" +msgstr "Résumé TLS" + #: src/app/RequestScanNotificationHandler.js:73 msgid "TLS scan for domain \"{0}\" has completed." msgstr "Le scan TLS pour le domaine \"{0}\" est terminé." @@ -3375,6 +3424,18 @@ msgstr "Ce service n'est pas un service d'hébergement Web et ne nécessite pas msgid "This user is not affiliated with any organizations" msgstr "Cet utilisateur n'est pas affilié à une quelconque organisation" +#: src/summaries/TieredSummaries.js:48 +msgid "Tier 1: Minimum Requirements" +msgstr "Niveau 1 : Exigences minimales" + +#: src/summaries/TieredSummaries.js:75 +msgid "Tier 2: Improved Posture" +msgstr "Niveau 2 : Amélioration de la posture" + +#: src/summaries/TieredSummaries.js:92 +msgid "Tier 3: Compliance" +msgstr "Niveau 3 : Conformité" + #: src/admin/AuditLogTable.js:70 msgid "Time Generated" msgstr "Temps généré" @@ -3684,8 +3745,8 @@ msgid "User:" msgstr "Utilisateur:" #: src/admin/AdminPage.js:190 -#: src/admin/AdminPanel.js:24 -#: src/organizationDetails/OrganizationDetails.js:142 +#: src/admin/AdminPanel.js:31 +#: src/organizationDetails/OrganizationDetails.js:133 msgid "Users" msgstr "Utilisateurs" @@ -3727,7 +3788,7 @@ msgstr "Vérifier le compte" #~ msgid "Vertical View" #~ msgstr "Vue verticale" -#: src/organizations/OrganizationCard.js:144 +#: src/organizations/OrganizationCard.js:101 msgid "View Details" msgstr "Voir les détails" @@ -3791,6 +3852,10 @@ msgstr "Web (HTTPS/TLS)" msgid "Web Check" msgstr "Vérification du Web" +#: src/summaries/TierTwoSummaries.js:39 +msgid "Web Connections Summary" +msgstr "Résumé des connexions web" + #: src/domains/ScanDomain.js:242 #: src/guidance/GuidancePage.js:102 msgid "Web Guidance" @@ -3808,6 +3873,10 @@ msgstr "Sécurité du Web :" #~ msgid "Web Sites and Services Management Configuration Requirements Compliant" #~ msgstr "Gestion des sites et services Web - Exigences de configuration conformes" +#: src/summaries/TierThreeSummaries.js:11 +msgid "Web Summary" +msgstr "Résumé du site web" + #: src/summaries/Doughnut.js:34 msgid "Web-hosting" msgstr "d'hébergement web" @@ -3816,7 +3885,7 @@ msgstr "d'hébergement web" msgid "Welcome to Tracker, please enter your details." msgstr "Bienvenue sur Suivi, veuillez entrer vos coordonnées." -#: src/user/MyTrackerPage.js:78 +#: src/user/MyTrackerPage.js:63 msgid "Welcome to your personal view of Tracker. Moderate the security posture of domains of interest across multiple organizations. To add domains to this view, use the star icon buttons available on domain lists." msgstr "Bienvenue dans votre vision personnelle de Suivi. Modérez la posture de sécurité des domaines d'intérêt à travers plusieurs organisations. Pour ajouter des domaines à cette vue, utilisez les boutons de l'icône étoile disponibles sur les listes de domaines." @@ -4010,10 +4079,10 @@ msgstr "contactez-nous" #~ msgid "https://https-everywhere.canada.ca/en/help/" #~ msgstr "https://https-everywhere.canada.ca/en/help/" -#: src/app/App.js:100 -#: src/app/App.js:279 -#: src/user/MyTrackerPage.js:43 -#: src/user/MyTrackerPage.js:74 +#: src/app/App.js:105 +#: src/app/App.js:284 +#: src/user/MyTrackerPage.js:33 +#: src/user/MyTrackerPage.js:59 msgid "myTracker" msgstr "monSuivi" diff --git a/frontend/src/organizationDetails/OrganizationDetails.js b/frontend/src/organizationDetails/OrganizationDetails.js index 4f23d46a91..9dc7ff66b8 100644 --- a/frontend/src/organizationDetails/OrganizationDetails.js +++ b/frontend/src/organizationDetails/OrganizationDetails.js @@ -22,7 +22,7 @@ import { ErrorBoundary } from 'react-error-boundary' import { OrganizationDomains } from './OrganizationDomains' import { OrganizationAffiliations } from './OrganizationAffiliations' -import { OrganizationSummary } from './OrganizationSummary' +import { TieredSummaries } from '../summaries/TieredSummaries' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { LoadingMessage } from '../components/LoadingMessage' @@ -152,7 +152,7 @@ export default function OrganizationDetails() { - + diff --git a/frontend/src/organizationDetails/OrganizationSummary.js b/frontend/src/organizationDetails/OrganizationSummary.js deleted file mode 100644 index 133c47da12..0000000000 --- a/frontend/src/organizationDetails/OrganizationSummary.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { Box } from '@chakra-ui/react' -import { SummaryGroup } from '../summaries/SummaryGroup' -import { number, object, string } from 'prop-types' - -export function OrganizationSummary({ summaries }) { - return ( - - - - ) -} - -OrganizationSummary.propTypes = { - summaries: object, - domainCount: number, - userCount: number, - city: string, - province: string, -} diff --git a/frontend/src/organizations/OrganizationCard.js b/frontend/src/organizations/OrganizationCard.js index db6a2738e6..7b81c30827 100644 --- a/frontend/src/organizations/OrganizationCard.js +++ b/frontend/src/organizations/OrganizationCard.js @@ -1,28 +1,11 @@ import React from 'react' -import { - Box, - Button, - Flex, - ListItem, - Progress, - Stack, - Text, - useBreakpointValue, -} from '@chakra-ui/react' +import { Box, Button, Flex, ListItem, Progress, Stack, Text, useBreakpointValue } from '@chakra-ui/react' import { CheckCircleIcon } from '@chakra-ui/icons' import { Link as RouteLink, useRouteMatch } from 'react-router-dom' import { bool, number, object, string } from 'prop-types' import { Trans } from '@lingui/macro' -export function OrganizationCard({ - name, - acronym, - slug, - domainCount, - verified, - summaries, - ...rest -}) { +export function OrganizationCard({ name, acronym, slug, domainCount, verified, summaries, ...rest }) { const { path, _url } = useRouteMatch() let httpsValue = 0 let dmarcValue = 0 @@ -75,24 +58,13 @@ export function OrganizationCard({ maxWidth="100%" > - + {name} ({acronym}) - {verified && ( - - )} + {verified && } - + HTTPS Configured @@ -121,11 +88,7 @@ export function OrganizationCard({ - + DMARC Configured @@ -133,13 +96,7 @@ export function OrganizationCard({ {hasButton && ( - )} - {/* */} + - diff --git a/k8s/apps/bases/api/kustomization.yaml b/k8s/apps/bases/api/kustomization.yaml index 9dd33452de..aba83d6b73 100644 --- a/k8s/apps/bases/api/kustomization.yaml +++ b/k8s/apps/bases/api/kustomization.yaml @@ -2,8 +2,9 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: api resources: -- namespace.yaml -- deployment.yaml -- service.yaml -- virtual-service.yaml -- database-configmap.yaml + - namespace.yaml + - deployment.yaml + - service.yaml + - virtual-service.yaml + - database-configmap.yaml + - org-footprint-cronjob.yaml diff --git a/k8s/apps/bases/api/org-footprint-cronjob.yaml b/k8s/apps/bases/api/org-footprint-cronjob.yaml new file mode 100644 index 0000000000..3c992d8e39 --- /dev/null +++ b/k8s/apps/bases/api/org-footprint-cronjob.yaml @@ -0,0 +1,53 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: org-footprint + namespace: api +spec: + schedule: "30 10 * * *" + concurrencyPolicy: Replace + startingDeadlineSeconds: 180 + jobTemplate: + spec: + template: + spec: + containers: + - name: org-footprint + image: gcr.io/track-compliance/services/org-footprint:master-001 # {"$imagepolicy": "flux-system:org-footprint"} + env: + - name: DB_PASS + valueFrom: + secretKeyRef: + name: api + key: DB_PASS + - name: DB_URL + valueFrom: + secretKeyRef: + name: api + key: DB_URL + - name: DB_NAME + valueFrom: + secretKeyRef: + name: api + key: DB_NAME + - name: NOTIFICATION_API_KEY + valueFrom: + secretKeyRef: + name: api + key: NOTIFICATION_API_KEY + - name: NOTIFICATION_API_URL + valueFrom: + secretKeyRef: + name: api + key: NOTIFICATION_API_URL + - name: NOTIFICATION_ORG_FOOTPRINT_EN + valueFrom: + secretKeyRef: + name: api + key: NOTIFICATION_ORG_FOOTPRINT_EN + - name: NOTIFICATION_ORG_FOOTPRINT_FR + valueFrom: + secretKeyRef: + name: api + key: NOTIFICATION_ORG_FOOTPRINT_FR + restartPolicy: OnFailure diff --git a/k8s/clusters/auto-image-update/bases/image-repo-policies/kustomization.yaml b/k8s/clusters/auto-image-update/bases/image-repo-policies/kustomization.yaml index 3cd864e296..7cc8ac0388 100644 --- a/k8s/clusters/auto-image-update/bases/image-repo-policies/kustomization.yaml +++ b/k8s/clusters/auto-image-update/bases/image-repo-policies/kustomization.yaml @@ -10,6 +10,7 @@ resources: - guidance-image-repo-policy.yaml - log4shell-processor-image-repo-policy.yaml - log4shell-scanner-image-repo-policy.yaml + - org-footprint-image-repo-policy.yaml - spring4shell-scanner-image-repo-policy.yaml - summaries-image-repo-policy.yaml - super-admin-image-repo-policy.yaml diff --git a/k8s/clusters/auto-image-update/bases/image-repo-policies/org-footprint-image-repo-policy.yaml b/k8s/clusters/auto-image-update/bases/image-repo-policies/org-footprint-image-repo-policy.yaml new file mode 100644 index 0000000000..2422ad9916 --- /dev/null +++ b/k8s/clusters/auto-image-update/bases/image-repo-policies/org-footprint-image-repo-policy.yaml @@ -0,0 +1,23 @@ +apiVersion: image.toolkit.fluxcd.io/v1alpha2 +kind: ImageRepository +metadata: + name: org-footprint + namespace: flux-system +spec: + image: gcr.io/track-compliance/org-footprint + interval: 1m0s +--- +apiVersion: image.toolkit.fluxcd.io/v1alpha1 +kind: ImagePolicy +metadata: + name: org-footprint + namespace: flux-system +spec: + filterTags: + extract: $ts + pattern: ^master-[a-fA-F0-9]+-(?P.*) + imageRepositoryRef: + name: org-footprint + policy: + numerical: + order: asc diff --git a/services/org-footprint/.dockerignore b/services/org-footprint/.dockerignore new file mode 100644 index 0000000000..463aee45ce --- /dev/null +++ b/services/org-footprint/.dockerignore @@ -0,0 +1,6 @@ +node_modules +cloudbuild.yaml +coverage +**/*.test.js +**/*/.env +.git diff --git a/services/org-footprint/.env.example b/services/org-footprint/.env.example new file mode 100644 index 0000000000..fa50c6a2ff --- /dev/null +++ b/services/org-footprint/.env.example @@ -0,0 +1,8 @@ +DB_PASS= +DB_URL= +DB_NAME= + +NOTIFICATION_API_KEY= +NOTIFICATION_API_URL= +NOTIFICATION_ORG_FOOTPRINT_EN= +NOTIFICATION_ORG_FOOTPRINT_FR= \ No newline at end of file diff --git a/services/org-footprint/.eslintrc b/services/org-footprint/.eslintrc new file mode 100644 index 0000000000..c82ccfe77d --- /dev/null +++ b/services/org-footprint/.eslintrc @@ -0,0 +1,46 @@ +{ + "extends": [ + "standard", + "prettier" + ], + "plugins": ["jest"], + "env": { + "browser": false, + "node": true, + "jest/globals": true + }, + "globals": { + "fetch": true, + "fetchMock": true + }, + "rules": { + "no-control-regex": "off", + "semi": ["error", "never"], + "comma-dangle": ["error", "always-multiline"], + "no-unused-vars": [ + "error", + { + "vars": "all", + "args": "all", + "varsIgnorePattern": "_", + "argsIgnorePattern": "_" + } + ], + "camelcase": [ + "error", + { + "ignoreDestructuring": true, + "properties": "never" + } + ], + "new-cap": [ + "error", + { + "capIsNewExceptions": [ + "ArangoTools" + ] + } + ] + } + } + \ No newline at end of file diff --git a/services/org-footprint/.prettierrc b/services/org-footprint/.prettierrc new file mode 100644 index 0000000000..18e3a08569 --- /dev/null +++ b/services/org-footprint/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all" +} + \ No newline at end of file diff --git a/services/org-footprint/Dockerfile b/services/org-footprint/Dockerfile new file mode 100644 index 0000000000..a09fcf9540 --- /dev/null +++ b/services/org-footprint/Dockerfile @@ -0,0 +1,14 @@ +FROM node:alpine + +ENV NODE_ENV production + +WORKDIR /app +COPY src ./src +COPY index.js ./index.js +COPY package* ./ +COPY database-options.js ./ +COPY .env.example ./ +RUN npm ci + +USER node +CMD ["npm", "start"] diff --git a/services/org-footprint/README.md b/services/org-footprint/README.md new file mode 100644 index 0000000000..d8b962a278 --- /dev/null +++ b/services/org-footprint/README.md @@ -0,0 +1,72 @@ +# Organization Footprint Notification Service + +The purpose of this service is to search the database for recent audit logs in each organization, and alert affiliated admins of the changes. + +## Install dependencies + +Like all other JavaScript applications, dependencies are installed with npm. + +```bash +npm i +``` + +## One time setup + +This application uses the [dotenv-safe](https://github.com/rolodato/dotenv-safe) library to enforce that the environment variables it is counting on actually exist. This makes the application fail immediately with a descriptive error if anything important is missing. + +To set up the environment, duplicate the `.env.example` file and fill in the values. Example values are shown. + +```bash +cp .env.example .env +cat .env +DB_PASS=test +DB_URL=http://localhost:8529 +DB_NAME=track_dmarc +NOTIFICATION_API_KEY=asdf1234 +NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca +NOTIFICATION_ORG_FOOTPRINT_EN=test_id +NOTIFICATION_ORG_FOOTPRINT_FR=test_id +``` + +## Testing + +### Local development + +The tests require a copy of [ArangoDB](https://www.arangodb.com/) to be running locally. ArangoDB should have it's own .env file, and the value of the root password should align with the value of `DB_PASS` in the services `.env` file. + +```bash +# Write the arango test credentials into an env file: +echo ARANGO_ROOT_PASSWORD=test > arangodb.env +# Run a detached arangodb container using the root password from the env: +docker run -d -p=8529:8529 --env-file arangodb.env --name=arango arangodb +# Run the tests: +npm test +``` + +With ArangoDB running you will need to create a `test.env` file, with values to be used during the test run. + +``` +[mike@ouroboros org-footprint]$ cat test.env +DB_PASS=test +DB_URL=http://localhost:8529 +DB_NAME=track_dmarc +NOTIFICATION_API_KEY=asdf1234 +NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca +NOTIFICATION_ORG_FOOTPRINT_EN=test_id +NOTIFICATION_ORG_FOOTPRINT_FR=test_id +``` + +### Cloudbuild + +Cloudbuild is used for CI, and one of the reasons for that is that it has great tooling for running jobs locally which is very helpful for debugging. To see how this will behave in CI you can run something like the following to run the tests with cloud-build-local. + +```bash +cloud-build-local --config cloudbuild.yaml --substitutions=BRANCH_NAME=foo,SHORT_SHA=asdf1234,_DB_PASS=test,_DB_URL=http://arangodb:8529,_DB_NAME=track_dmarc,NOTIFICATION_API_KEY=asdf1234, NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca, NOTIFICATION_ORG_FOOTPRINT_EN=test_id, NOTIFICATION_ORG_FOOTPRINT_FR=test_id --dryrun=false . +``` + +Because of the way the cloudbuild config spins up and detaches a copy of ArangoDB, you will need to run the following commands to clean up after. + +```bash +docker stop $(docker ps -q) +docker rm arangodb +``` diff --git a/services/org-footprint/cloudbuild.yaml b/services/org-footprint/cloudbuild.yaml new file mode 100644 index 0000000000..a4333c813f --- /dev/null +++ b/services/org-footprint/cloudbuild.yaml @@ -0,0 +1,77 @@ +steps: + - name: 'gcr.io/cloud-builders/docker' + id: start_arango + entrypoint: /bin/sh + args: + [ + '-c', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + ] + + - name: mikewilliamson/wait-for + id: wait + args: ['arangodb:8529'] + + - name: node:alpine + id: install-dependencies + dir: services/org-footprint + entrypoint: npm + args: ['ci', '--no-optional'] + + - name: node:alpine + id: run-eslint + dir: services/org-footprint + entrypoint: npm + args: ['run', lint] + + - name: node:alpine + id: test + dir: services/org-footprint + entrypoint: npm + args: ['test'] + env: + - DB_URL=$_DB_URL + - DB_PASS=$_DB_PASS + - DB_NAME=$_DB_NAME + - NOTIFICATION_API_KEY=$_NOTIFICATION_API_KEY + - NOTIFICATION_API_URL=$_NOTIFICATION_API_URL + - NOTIFICATION_ORG_FOOTPRINT_EN=$_NOTIFICATION_TEST_TEMPLATE_ID + - NOTIFICATION_ORG_FOOTPRINT_FR=$_NOTIFICATION_TEST_TEMPLATE_ID + - name: 'gcr.io/cloud-builders/docker' + id: generate-image-name + entrypoint: 'bash' + dir: services/org-footprint + args: + - '-c' + - | + echo "gcr.io/$PROJECT_ID/org-footprint:$BRANCH_NAME-$SHORT_SHA-$(date +%s)" > /workspace/imagename + + - name: 'gcr.io/cloud-builders/docker' + id: build-if-master + entrypoint: 'bash' + dir: services/org-footprint + args: + - '-c' + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker build -t $image . + else + exit 0 + fi + + - name: 'gcr.io/cloud-builders/docker' + id: push-if-master + entrypoint: 'bash' + dir: services/org-footprint + args: + - '-c' + - | + if [[ "$BRANCH_NAME" == "master" ]] + then + image=$(cat /workspace/imagename) + docker push $image + else + exit 0 + fi diff --git a/services/org-footprint/database-options.js b/services/org-footprint/database-options.js new file mode 100644 index 0000000000..363492fc96 --- /dev/null +++ b/services/org-footprint/database-options.js @@ -0,0 +1,27 @@ +const databaseOptions = ({ rootPass }) => [ + { type: 'user', username: 'root', password: rootPass }, + { + type: 'documentcollection', + name: 'users', + options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, + }, + { + type: 'documentcollection', + name: 'organizations', + options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, + }, + { + type: 'edgecollection', + name: 'affiliations', + options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, + }, + { + type: 'documentcollection', + name: 'auditLogs', + options: { replicationFactor: 3, writeConcern: 1, numberOfShards: 6 }, + }, +] + +module.exports = { + databaseOptions, +} diff --git a/services/org-footprint/index.js b/services/org-footprint/index.js new file mode 100644 index 0000000000..2479fb3506 --- /dev/null +++ b/services/org-footprint/index.js @@ -0,0 +1,33 @@ +require('dotenv-safe').config({ + allowEmptyValues: true, + example: '.env.example', +}) + +const { + DB_PASS: rootPass, + DB_URL: url, + DB_NAME: databaseName, + NOTIFICATION_API_KEY, + NOTIFICATION_API_URL, +} = process.env + +const { ensure } = require('arango-tools') +const { NotifyClient } = require('notifications-node-client') +const { databaseOptions } = require('./database-options') + +const { orgFootprintService } = require('./src') + +const notifyClient = new NotifyClient(NOTIFICATION_API_URL, NOTIFICATION_API_KEY) + +;(async () => { + // Generate Database information + const { query } = await ensure({ + type: 'database', + name: databaseName, + url, + rootPassword: rootPass, + options: databaseOptions({ rootPass }), + }) + + await orgFootprintService({ query, log: console.log, notifyClient }) +})() diff --git a/services/org-footprint/jest.config.js b/services/org-footprint/jest.config.js new file mode 100644 index 0000000000..36e161369e --- /dev/null +++ b/services/org-footprint/jest.config.js @@ -0,0 +1,13 @@ +module.exports = { + setupFiles: ['/src/setupEnv.js'], + collectCoverage: false, + collectCoverageFrom: ['src/**/*.js'], + coveragePathIgnorePatterns: [ + 'node_modules', + 'test-config', + 'jestGlobalMocks.js', + '.module.js', + ], + testTimeout: 10000, + verbose: true, +} diff --git a/services/org-footprint/package-lock.json b/services/org-footprint/package-lock.json new file mode 100644 index 0000000000..bdcae2dff4 --- /dev/null +++ b/services/org-footprint/package-lock.json @@ -0,0 +1,10866 @@ +{ + "name": "org-footprint", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "org-footprint", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "arango-tools": "^0.6.0", + "dotenv-safe": "^8.2.0", + "notifications-node-client": "^7.0.0" + }, + "devDependencies": { + "eslint": "^8.40.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^5.0.0", + "jest": "^29.5.0", + "jest-fetch-mock": "^3.0.3", + "jest-matcher-utils": "^29.5.0", + "prettier": "^2.8.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz", + "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.1.tgz", + "integrity": "sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==" + }, + "node_modules/@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", + "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", + "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", + "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", + "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", + "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.59.5", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "dependencies": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + } + }, + "node_modules/arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "dependencies": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "dependencies": { + "assign-symbols": "^2.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/builtins/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001486", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", + "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "dependencies": { + "dotenv": "^8.2.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.387", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.387.tgz", + "integrity": "sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "27.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz", + "integrity": "sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "peer": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-standard": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz", + "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", + "deprecated": "standard 16.0.0 and eslint-config-standard 16.0.0 no longer require the eslint-plugin-standard package. You can remove it from your dependencies with 'npm rm eslint-plugin-standard'. More info here: https://github.com/standard/standard/issues/1316", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==", + "bin": { + "jpr": "dist/index.js", + "json-placeholder-replacer": "dist/index.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "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==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "dependencies": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==" + }, + "node_modules/multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "dependencies": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/notifications-node-client": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-7.0.0.tgz", + "integrity": "sha512-lVVUAlllj7HWRTXJMXoLGdNi5XXYtQyuU50m/m9bRKt/3TK0aKa2cyzYjseElW9kFpQRx2rM3SgaEvPrVcnGqg==", + "dependencies": { + "axios": "^0.25.0", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=14.17.3", + "npm": ">=6.14.13" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "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==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==", + "engines": { + "node": ">=10" + } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "dev": true + }, + "@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz", + "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.5", + "@babel/types": "^7.21.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@eslint/js": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", + "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0" + } + }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz", + "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/node": { + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.1.tgz", + "integrity": "sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==" + }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@typescript-eslint/scope-manager": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", + "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5" + } + }, + "@typescript-eslint/types": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", + "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", + "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/visitor-keys": "5.59.5", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", + "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.5", + "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.5", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.59.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", + "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.59.5", + "eslint-visitor-keys": "^3.3.0" + } + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arango-tools": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/arango-tools/-/arango-tools-0.6.0.tgz", + "integrity": "sha512-2XjPPddz7Vc07JrOyXFYGzXI+QZOAR+I0kiyklKBevWVTTDh1lYRetZh3X1fN+smibgL2DY3jKwHK14aSzVkSw==", + "requires": { + "arangojs": "^7.2.0", + "assign-deep": "^1.0.1", + "json-placeholder-replacer": "^1.0.35" + } + }, + "arangojs": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/arangojs/-/arangojs-7.8.0.tgz", + "integrity": "sha512-aJFlMKlVr4sIO5GDMuykBVNVxWeZTkWDgYbbl9cIuxVctp8Lhs6dW5fr5MYlwAndnOEyi3bvbrhZIucly2IpWQ==", + "requires": { + "@types/node": ">=13.13.4", + "es6-error": "^4.0.1", + "multi-part": "^3.0.0", + "x3-linkedlist": "1.2.0", + "xhr": "^2.4.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + } + }, + "assign-deep": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/assign-deep/-/assign-deep-1.0.1.tgz", + "integrity": "sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==", + "requires": { + "assign-symbols": "^2.0.2" + } + }, + "assign-symbols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-2.0.2.tgz", + "integrity": "sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "requires": { + "follow-redirects": "^1.14.7" + } + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "peer": true, + "requires": { + "semver": "^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001486", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz", + "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "dotenv-safe": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv-safe/-/dotenv-safe-8.2.0.tgz", + "integrity": "sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==", + "requires": { + "dotenv": "^8.2.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "electron-to-chromium": { + "version": "1.4.387", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.387.tgz", + "integrity": "sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", + "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.40.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "peer": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "peer": true + } + } + }, + "eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-jest": { + "version": "27.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz", + "integrity": "sha512-l067Uxx7ZT8cO9NJuf+eJHvt6bqJyz2Z29wykyEdz/OtmcELQl2MQGQLX8J94O1cSJWAwUSEvCjwjA7KEK3Hmg==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "^5.10.0" + } + }, + "eslint-plugin-n": { + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", + "dev": true, + "peer": true, + "requires": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "peer": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "peer": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "peer": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "requires": {} + }, + "eslint-plugin-standard": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz", + "integrity": "sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg==", + "dev": true, + "requires": {} + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "peer": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "peer": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true + }, + "espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "requires": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-placeholder-replacer": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/json-placeholder-replacer/-/json-placeholder-replacer-1.0.35.tgz", + "integrity": "sha512-edlSWqcFVUpKPshaIcJfXpQ8eu0//gk8iU6XHWkCZIp5QEp4hoCFR7uk+LrIzhLTSqmBQ9VBs+EYK8pvWGEpRg==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "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==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-kind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime-kind/-/mime-kind-3.0.0.tgz", + "integrity": "sha512-sx9lClVP7GXY2mO3aVDWTQLhfvAdDvNhGi3o3g7+ae3aKaoybeGbEIlnreoRKjrbDpvlPltlkIryxOtatojeXQ==", + "requires": { + "file-type": "^12.1.0", + "mime-types": "^2.1.24" + } + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multi-part": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multi-part/-/multi-part-3.0.0.tgz", + "integrity": "sha512-pDbdYQ6DLDxAsD83w9R7r7rlW56cETL7hIB5bCWX7FJYw0K+kL5JwHr0I8tRk9lGeFcAzf+2OEzXWlG/4wCnFw==", + "requires": { + "mime-kind": "^3.0.0", + "multi-part-lite": "^1.0.0" + } + }, + "multi-part-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multi-part-lite/-/multi-part-lite-1.0.0.tgz", + "integrity": "sha512-KxIRbBZZ45hoKX1ROD/19wJr0ql1bef1rE8Y1PCwD3PuNXV42pp7Wo8lEHYuAajoT4vfAFcd3rPjlkyEEyt1nw==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "notifications-node-client": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/notifications-node-client/-/notifications-node-client-7.0.0.tgz", + "integrity": "sha512-lVVUAlllj7HWRTXJMXoLGdNi5XXYtQyuU50m/m9bRKt/3TK0aKa2cyzYjseElW9kFpQRx2rM3SgaEvPrVcnGqg==", + "requires": { + "axios": "^0.25.0", + "jsonwebtoken": "^9.0.0" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "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==", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "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==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "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==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "peer": true + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "dependencies": { + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + } + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "x3-linkedlist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/x3-linkedlist/-/x3-linkedlist-1.2.0.tgz", + "integrity": "sha512-mH/YwxpYSKNa8bDNF1yOuZCMuV+K80LtDN8vcLDUAwNazCxptDNsYt+zA/EJeYiGbdtKposhKLZjErGVOR8mag==" + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/services/org-footprint/package.json b/services/org-footprint/package.json new file mode 100644 index 0000000000..c34e26b100 --- /dev/null +++ b/services/org-footprint/package.json @@ -0,0 +1,34 @@ +{ + "name": "org-footprint", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "jest", + "test-coverage": "jest --coverage", + "lint": "eslint src", + "prettier": "prettier --write \"**/*.js\"" + }, + "author": "", + "license": "ISC", + "dependencies": { + "arango-tools": "^0.6.0", + "dotenv-safe": "^8.2.0", + "notifications-node-client": "^7.0.0" + }, + "devDependencies": { + "eslint": "^8.40.0", + "eslint-config-prettier": "^8.8.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.2.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-standard": "^5.0.0", + "jest": "^29.5.0", + "jest-fetch-mock": "^3.0.3", + "jest-matcher-utils": "^29.5.0", + "prettier": "^2.8.8" + } +} diff --git a/services/org-footprint/src/__tests__/get-all-org-keys.test.js b/services/org-footprint/src/__tests__/get-all-org-keys.test.js new file mode 100644 index 0000000000..ae8983ede9 --- /dev/null +++ b/services/org-footprint/src/__tests__/get-all-org-keys.test.js @@ -0,0 +1,103 @@ +const { DB_PASS: rootPass, DB_URL: url } = process.env + +const { ensure, dbNameFromFile } = require('arango-tools') +const { databaseOptions } = require('../../database-options') + +const { getAllOrgKeys } = require('../database') + +describe('given the getAllOrgKeys function', () => { + const consoleErrorOutput = [] + const consoleInfoOutput = [] + const mockedError = (output) => consoleErrorOutput.push(output) + const mockedInfo = (output) => consoleInfoOutput.push(output) + + let query, drop, truncate, collections + + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + type: 'database', + name: dbNameFromFile(__filename), + url, + rootPassword: rootPass, + options: databaseOptions({ rootPass }), + })) + }) + + beforeEach(async () => { + console.error = mockedError + console.info = mockedInfo + consoleErrorOutput.length = 0 + consoleInfoOutput.length = 0 + + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('given a successful query', () => { + let org, org2 + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + name: 'test org', + }, + fr: { + name: 'test org', + }, + }, + }) + org2 = await collections.organizations.save({ + orgDetails: { + en: { + name: 'test org 2', + }, + fr: { + name: 'test org 2', + }, + }, + }) + }) + it('returns the org keys', async () => { + const orgKeys = await getAllOrgKeys({ query }) + + const expectedOrgKeys = [org._key, org2._key] + + expect(orgKeys).toEqual(expectedOrgKeys) + }) + }) + describe('given an unsuccessful query', () => { + describe('when the query fails', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + await getAllOrgKeys({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Database error occurred while trying to find org keys: Error: Database error occurred.'), + ) + } + }) + }) + describe('when the cursor fails', () => { + it('throws an error', async () => { + const cursor = { + all() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockReturnValue(cursor) + try { + await getAllOrgKeys({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Cursor error occurred while trying to find org keys: Error: Cursor error occurred.'), + ) + } + }) + }) + }) +}) diff --git a/services/org-footprint/src/__tests__/get-bilingual-org-names.test.js b/services/org-footprint/src/__tests__/get-bilingual-org-names.test.js new file mode 100644 index 0000000000..ec105ba7ef --- /dev/null +++ b/services/org-footprint/src/__tests__/get-bilingual-org-names.test.js @@ -0,0 +1,99 @@ +const { DB_PASS: rootPass, DB_URL: url } = process.env + +const { ensure, dbNameFromFile } = require('arango-tools') +const { databaseOptions } = require('../../database-options') + +const { getBilingualOrgNames } = require('../database') + +describe('given the getBilingualOrgNames function', () => { + const consoleErrorOutput = [] + const consoleInfoOutput = [] + const mockedError = (output) => consoleErrorOutput.push(output) + const mockedInfo = (output) => consoleInfoOutput.push(output) + + let query, drop, truncate, collections + + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + type: 'database', + name: dbNameFromFile(__filename), + url, + rootPassword: rootPass, + options: databaseOptions({ rootPass }), + })) + }) + + beforeEach(async () => { + console.error = mockedError + console.info = mockedInfo + consoleErrorOutput.length = 0 + consoleInfoOutput.length = 0 + + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('given a successful query', () => { + let org + beforeEach(async () => { + org = await collections.organizations.save({ + orgDetails: { + en: { + name: 'test org', + }, + fr: { + name: 'test org', + }, + }, + }) + }) + it('returns the org names', async () => { + const orgNames = await getBilingualOrgNames({ + query, + orgKey: org._key, + }) + + const expectedOrgNames = { + en: 'test org', + fr: 'test org', + } + + expect(orgNames).toEqual(expectedOrgNames) + }) + }) + describe('given an unsuccessful query', () => { + describe('when the query fails', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + await getBilingualOrgNames({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Database error occurred while trying to find org names: Error: Database error occurred.'), + ) + } + }) + }) + describe('when the cursor fails', () => { + it('throws an error', async () => { + const cursor = { + next() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockReturnValue(cursor) + try { + await getBilingualOrgNames({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Cursor error occurred while trying to find org names: Error: Cursor error occurred.'), + ) + } + }) + }) + }) +}) diff --git a/services/org-footprint/src/__tests__/get-new-audit-logs.test.js b/services/org-footprint/src/__tests__/get-new-audit-logs.test.js new file mode 100644 index 0000000000..07e96e8425 --- /dev/null +++ b/services/org-footprint/src/__tests__/get-new-audit-logs.test.js @@ -0,0 +1,157 @@ +const { DB_PASS: rootPass, DB_URL: url } = process.env + +const { ensure, dbNameFromFile } = require('arango-tools') +const { databaseOptions } = require('../../database-options') + +const { getNewAuditLogs } = require('../database') + +describe('given the getNewAuditLogs function', () => { + const consoleErrorOutput = [] + const consoleInfoOutput = [] + const mockedError = (output) => consoleErrorOutput.push(output) + const mockedInfo = (output) => consoleInfoOutput.push(output) + + let query, drop, truncate, collections + + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + type: 'database', + name: dbNameFromFile(__filename), + url, + rootPassword: rootPass, + options: databaseOptions({ rootPass }), + })) + }) + + beforeEach(async () => { + console.error = mockedError + console.info = mockedInfo + consoleErrorOutput.length = 0 + consoleInfoOutput.length = 0 + + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('given a successful query', () => { + const time = new Date() + let log, log2 + beforeEach(async () => { + log = await collections.auditLogs.save({ + timestamp: time - 1000, + initiatedBy: { + id: '123', + userName: 'user@test.ca', + role: 'admin', + }, + action: 'add', + target: { + resource: 'test.domain.gc.ca', + updatedProperties: [], + organization: { + id: '123', + name: 'test org', + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + log2 = await collections.auditLogs.save({ + timestamp: time - 1000, + initiatedBy: { + id: '123', + userName: 'user@test.ca', + role: 'admin', + }, + action: 'remove', + target: { + resource: 'removed-user@test.ca', + updatedProperties: [], + organization: { + id: '123', + name: 'test org', + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + }) + it('returns the org admins', async () => { + const auditLogs = await getNewAuditLogs({ query, orgKey: '123' }) + const expectedAuditLogs = [ + { + ...log, + timestamp: time - 1000, + initiatedBy: { + id: '123', + userName: 'user@test.ca', + role: 'admin', + }, + action: 'add', + target: { + resource: 'test.domain.gc.ca', + updatedProperties: [], + organization: { + id: '123', + name: 'test org', + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }, + { + ...log2, + timestamp: time - 1000, + initiatedBy: { + id: '123', + userName: 'user@test.ca', + role: 'admin', + }, + action: 'remove', + target: { + resource: 'removed-user@test.ca', + updatedProperties: [], + organization: { + id: '123', + name: 'test org', + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }, + ] + expect(auditLogs).toEqual(expectedAuditLogs) + }) + }) + describe('given an unsuccessful query', () => { + describe('when the query fails', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + await getNewAuditLogs({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Database error occurred while trying to find audit logs: Error: Database error occurred.'), + ) + } + }) + }) + describe('when the cursor fails', () => { + it('throws an error', async () => { + const cursor = { + all() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockReturnValue(cursor) + try { + await getNewAuditLogs({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Cursor error occurred while trying to find audit logs: Error: Cursor error occurred.'), + ) + } + }) + }) + }) +}) diff --git a/services/org-footprint/src/__tests__/get-org-admins.test.js b/services/org-footprint/src/__tests__/get-org-admins.test.js new file mode 100644 index 0000000000..525b68ee69 --- /dev/null +++ b/services/org-footprint/src/__tests__/get-org-admins.test.js @@ -0,0 +1,93 @@ +const { DB_PASS: rootPass, DB_URL: url } = process.env + +const { ensure, dbNameFromFile } = require('arango-tools') +const { databaseOptions } = require('../../database-options') + +const { getOrgAdmins } = require('../database') + +describe('given the getOrgAdmins function', () => { + const consoleErrorOutput = [] + const consoleInfoOutput = [] + const mockedError = (output) => consoleErrorOutput.push(output) + const mockedInfo = (output) => consoleInfoOutput.push(output) + + let query, drop, truncate, collections + + beforeAll(async () => { + // Generate DB Items + ;({ query, drop, truncate, collections } = await ensure({ + type: 'database', + name: dbNameFromFile(__filename), + url, + rootPassword: rootPass, + options: databaseOptions({ rootPass }), + })) + }) + + beforeEach(async () => { + console.error = mockedError + console.info = mockedInfo + consoleErrorOutput.length = 0 + consoleInfoOutput.length = 0 + + await truncate() + }) + + afterAll(async () => { + await drop() + }) + + describe('given a successful query', () => { + // eslint-disable-next-line no-unused-vars + let org, user, affiliation + beforeEach(async () => { + org = await collections.organizations.save({}) + user = await collections.users.save({ + userName: 'user@test.ca', + }) + affiliation = await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'admin', + }) + }) + it('returns the org admins', async () => { + const orgAdmins = await getOrgAdmins({ query, orgKey: org._key }) + + const expectedOrgAdmins = [{ ...user, userName: 'user@test.ca' }] + + expect(orgAdmins).toEqual(expectedOrgAdmins) + }) + }) + describe('given an unsuccessful query', () => { + describe('when the query fails', () => { + it('throws an error', async () => { + const mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) + try { + await getOrgAdmins({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Database error occurred while trying to find org admins: Error: Database error occurred.'), + ) + } + }) + }) + describe('when the cursor fails', () => { + it('throws an error', async () => { + const cursor = { + all() { + throw new Error('Cursor error occurred.') + }, + } + const mockQuery = jest.fn().mockReturnValue(cursor) + try { + await getOrgAdmins({ query: mockQuery }) + } catch (err) { + expect(err).toEqual( + new Error('Cursor error occurred while trying to find org admins: Error: Cursor error occurred.'), + ) + } + }) + }) + }) +}) diff --git a/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js b/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js new file mode 100644 index 0000000000..491422ee27 --- /dev/null +++ b/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js @@ -0,0 +1,104 @@ +const { sendOrgFootprintEmail } = require('../notify') +const { NOTIFICATION_ORG_FOOTPRINT_EN, NOTIFICATION_ORG_FOOTPRINT_FR } = process.env + +describe('given the sendOrgFootprintEmail function', () => { + let consoleOutput = [] + const mockedError = (output) => consoleOutput.push(output) + beforeAll(async () => { + console.error = mockedError + }) + beforeEach(async () => { + consoleOutput = [] + }) + describe('email successfully sent', () => { + it('returns nothing', async () => { + const sendEmail = jest.fn() + const notifyClient = { + sendEmail, + } + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + preferredLang: 'english', + } + + const auditLogs = [ + { + action: 'add', + target: { + resourceType: 'user', + }, + }, + { + action: 'add', + target: { + resourceType: 'user', + }, + }, + { + action: 'remove', + target: { + resourceType: 'domain', + }, + }, + { + action: 'update', + target: { + resourceType: 'domain', + }, + }, + ] + + await sendOrgFootprintEmail({ + notifyClient, + user, + auditLogs, + orgNames: { + en: 'Test Org', + fr: 'Le Test Org', + }, + }) + + expect(notifyClient.sendEmail).toHaveBeenCalledWith(NOTIFICATION_ORG_FOOTPRINT_EN, user.userName, { + personalisation: { + display_name: user.displayName, + organization_name: 'Test Org', + add_users_count: 2, + update_users_count: 0, + remove_users_count: 0, + add_domains_count: 0, + update_domains_count: 1, + remove_domains_count: 1, + }, + }) + }) + }) + describe('an error occurs while sending email', () => { + it('throws an error message', async () => { + const sendEmail = jest.fn().mockRejectedValue(new Error('Notification error occurred.')) + const notifyClient = { + sendEmail, + } + + const user = { + userName: 'test@email.ca', + displayName: 'Test Account', + preferredLang: 'english', + } + + await sendOrgFootprintEmail({ + notifyClient, + user, + auditLogs: [], + orgNames: { + en: 'Test Org', + fr: 'Le Test Org', + }, + }) + + expect(consoleOutput).toEqual([ + `Error occurred when sending org footprint changes via email for ${user._key}: Error: Notification error occurred.`, + ]) + }) + }) +}) diff --git a/services/org-footprint/src/database/get-all-org-keys.js b/services/org-footprint/src/database/get-all-org-keys.js new file mode 100644 index 0000000000..71a2745b75 --- /dev/null +++ b/services/org-footprint/src/database/get-all-org-keys.js @@ -0,0 +1,24 @@ +const getAllOrgKeys = async ({ query }) => { + let cursor + try { + cursor = await query` + FOR org IN organizations + RETURN org._key + ` + } catch (err) { + throw new Error(`Database error occurred while trying to find org keys: ${err}`) + } + + let orgKeys + try { + orgKeys = await cursor.all() + } catch (err) { + throw new Error(`Cursor error occurred while trying to find org keys: ${err}`) + } + + return orgKeys +} + +module.exports = { + getAllOrgKeys, +} diff --git a/services/org-footprint/src/database/get-bilingual-org-names.js b/services/org-footprint/src/database/get-bilingual-org-names.js new file mode 100644 index 0000000000..4293a29908 --- /dev/null +++ b/services/org-footprint/src/database/get-bilingual-org-names.js @@ -0,0 +1,25 @@ +const getBilingualOrgNames = async ({ query, orgKey }) => { + let cursor + try { + cursor = await query` + FOR org IN organizations + FILTER org._key == ${orgKey} + RETURN { "en": org.orgDetails.en.name, "fr": org.orgDetails.fr.name } + ` + } catch (err) { + throw new Error(`Database error occurred while trying to find org names: ${err}`) + } + + let orgNames + try { + orgNames = await cursor.next() + } catch (err) { + throw new Error(`Cursor error occurred while trying to find org names: ${err}`) + } + + return orgNames +} + +module.exports = { + getBilingualOrgNames, +} diff --git a/services/org-footprint/src/database/get-new-audit-logs.js b/services/org-footprint/src/database/get-new-audit-logs.js new file mode 100644 index 0000000000..08cb36f750 --- /dev/null +++ b/services/org-footprint/src/database/get-new-audit-logs.js @@ -0,0 +1,30 @@ +const getNewAuditLogs = async ({ query, orgKey, days = 1 }) => { + const twentyFourHours = 1000 * 60 * 60 * 24 + let cursor + try { + cursor = await query` + LET timeframe = ${twentyFourHours} * ${days} + let currentTime = DATE_NOW() + FOR log IN auditLogs + let logTime = DATE_TIMESTAMP(log.timestamp) + FILTER log.target.organization.id == ${orgKey} + FILTER (currentTime - logTime) < timeframe + RETURN log + ` + } catch (err) { + throw new Error(`Database error occurred while trying to find audit logs: ${err}`) + } + + let auditLogs + try { + auditLogs = await cursor.all() + } catch (err) { + throw new Error(`Cursor error occurred while trying to find audit logs: ${err}`) + } + + return auditLogs +} + +module.exports = { + getNewAuditLogs, +} diff --git a/services/org-footprint/src/database/get-org-admins.js b/services/org-footprint/src/database/get-org-admins.js new file mode 100644 index 0000000000..6643b55e88 --- /dev/null +++ b/services/org-footprint/src/database/get-org-admins.js @@ -0,0 +1,27 @@ +const getOrgAdmins = async ({ query, orgKey }) => { + let cursor + const orgId = `organizations/${orgKey}` + try { + cursor = await query` + FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations + FILTER e.permission == "admin" OR e.permission == "owner" + FILTER v.receiveUpdateEmails != false + RETURN v + ` + } catch (err) { + throw new Error(`Database error occurred while trying to find org admins: ${err}`) + } + + let orgAdmins + try { + orgAdmins = await cursor.all() + } catch (err) { + throw new Error(`Cursor error occurred while trying to find org admins: ${err}`) + } + + return orgAdmins +} + +module.exports = { + getOrgAdmins, +} diff --git a/services/org-footprint/src/database/index.js b/services/org-footprint/src/database/index.js new file mode 100644 index 0000000000..3fa210c3f7 --- /dev/null +++ b/services/org-footprint/src/database/index.js @@ -0,0 +1,6 @@ +const { getOrgAdmins } = require('./get-org-admins') +const { getAllOrgKeys } = require('./get-all-org-keys') +const { getNewAuditLogs } = require('./get-new-audit-logs') +const { getBilingualOrgNames } = require('./get-bilingual-org-names') + +module.exports = { getOrgAdmins, getAllOrgKeys, getNewAuditLogs, getBilingualOrgNames } diff --git a/services/org-footprint/src/index.js b/services/org-footprint/src/index.js new file mode 100644 index 0000000000..fb7893d7af --- /dev/null +++ b/services/org-footprint/src/index.js @@ -0,0 +1,5 @@ +const { orgFootprintService } = require('./org-footprint-service') + +module.exports = { + orgFootprintService, +} diff --git a/services/org-footprint/src/notify/index.js b/services/org-footprint/src/notify/index.js new file mode 100644 index 0000000000..bef4dbdf1a --- /dev/null +++ b/services/org-footprint/src/notify/index.js @@ -0,0 +1,2 @@ +const { sendOrgFootprintEmail } = require('./notify-send-org-footprint-email') +module.exports = { sendOrgFootprintEmail } diff --git a/services/org-footprint/src/notify/notify-send-org-footprint-email.js b/services/org-footprint/src/notify/notify-send-org-footprint-email.js new file mode 100644 index 0000000000..e1be2cf13b --- /dev/null +++ b/services/org-footprint/src/notify/notify-send-org-footprint-email.js @@ -0,0 +1,62 @@ +const { NOTIFICATION_ORG_FOOTPRINT_EN, NOTIFICATION_ORG_FOOTPRINT_FR } = process.env + +const sendOrgFootprintEmail = async ({ notifyClient, user, auditLogs, orgNames }) => { + let templateId = NOTIFICATION_ORG_FOOTPRINT_EN + if (user.preferredLang === 'french') { + templateId = NOTIFICATION_ORG_FOOTPRINT_FR + } + + // Get stats for user changes + const usersAdded = auditLogs.filter((log) => log.action === 'add' && log.target.resourceType === 'user') + const usersUpdated = auditLogs.filter((log) => log.action === 'update' && log.target.resourceType === 'user') + const usersRemoved = auditLogs.filter((log) => log.action === 'remove' && log.target.resourceType === 'user') + + // Get stats for domain changes + const domainsAdded = auditLogs.filter((log) => log.action === 'add' && log.target.resourceType === 'domain') + const domainsUpdated = auditLogs.filter((log) => log.action === 'update' && log.target.resourceType === 'domain') + const domainsRemoved = auditLogs.filter((log) => log.action === 'remove' && log.target.resourceType === 'domain') + + let addDomainsList = '' + let updateDomainsList = '' + let removeDomainsList = '' + + // Get list of domains added + if (domainsAdded.length > 0) { + addDomainsList = '\t' + domainsAdded.map((log) => log.target.resource).join(', ') + } + // Get list of domains updated + if (domainsUpdated.length > 0) { + updateDomainsList = '\t' + domainsUpdated.map((log) => log.target.resource).join(', ') + } + // Get list of domains removed + if (domainsRemoved.length > 0) { + removeDomainsList = '\t' + domainsRemoved.map((log) => log.target.resource).join(', ') + } + + const exportsToCsv = auditLogs.filter((log) => log.action === 'export') + + try { + await notifyClient.sendEmail(templateId, user.userName, { + personalisation: { + display_name: user.displayName, + organization_name: user.preferredLang === 'french' ? orgNames.fr : orgNames.en, + add_users_count: usersAdded.length, + update_users_count: usersUpdated.length, + remove_users_count: usersRemoved.length, + add_domains_count: domainsAdded.length, + add_domains_list: addDomainsList, + update_domains_count: domainsUpdated.length, + update_domains_list: updateDomainsList, + remove_domains_count: domainsRemoved.length, + remove_domains_list: removeDomainsList, + export_count: exportsToCsv.length, + }, + }) + } catch (err) { + console.error(`Error occurred when sending org footprint changes via email for ${user._key}: ${err}`) + } +} + +module.exports = { + sendOrgFootprintEmail, +} diff --git a/services/org-footprint/src/org-footprint-service.js b/services/org-footprint/src/org-footprint-service.js new file mode 100644 index 0000000000..2fa4a8c898 --- /dev/null +++ b/services/org-footprint/src/org-footprint-service.js @@ -0,0 +1,29 @@ +const { getOrgAdmins, getAllOrgKeys, getNewAuditLogs, getBilingualOrgNames } = require('./database') +const { sendOrgFootprintEmail } = require('./notify') + +const orgFootprintService = async ({ query, log, notifyClient }) => { + // get list of all orgs + const orgKeys = await getAllOrgKeys({ query }) + for (const orgKey of orgKeys) { + // check for new audit logs + const auditLogs = await getNewAuditLogs({ query, orgKey }) + // if new audit logs exist + if (auditLogs.length > 0) { + // get list of org admins + const orgAdmins = await getOrgAdmins({ query, orgKey }) + // if org admins exist + if (orgAdmins.length > 0) { + log(`Sending recent activity email to admins of org: ${orgKey}`) + const orgNames = await getBilingualOrgNames({ query, orgKey }) + // send email to org admins + for (const user of orgAdmins) { + await sendOrgFootprintEmail({ notifyClient, user, auditLogs, orgNames }) + } + } + } + } +} + +module.exports = { + orgFootprintService, +} diff --git a/services/org-footprint/src/setupEnv.js b/services/org-footprint/src/setupEnv.js new file mode 100644 index 0000000000..487e141b83 --- /dev/null +++ b/services/org-footprint/src/setupEnv.js @@ -0,0 +1,4 @@ +require('dotenv-safe').config({ + example: './.env.example', + path: './test.env', +}) From 982b139267d8f53db3a11b27846f43bb9d0fd9ef Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 26 May 2023 19:07:36 +0000 Subject: [PATCH 063/113] [ci skip] gcr.io/track-compliance/api-js:master-6426b98-1685127971 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 7aee7aacb1..e8d31953fe 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-e6b75e5-1685036257 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6426b98-1685127971 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index cfd4e88a2e..95adcd31cb 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-e6b75e5-1685036257 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-6426b98-1685127971 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From c994800eff1fa016af881cfa8e39f6f7fc622a13 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 26 May 2023 19:15:58 +0000 Subject: [PATCH 064/113] [ci skip] gcr.io/track-compliance/frontend:master-6426b98-1685128395 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index de18d828b1..28f44d984e 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-e6b75e5-1685036341 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6426b98-1685128395 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index d0e09e6b20..d2b0b4ef42 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-e6b75e5-1685036341 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6426b98-1685128395 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 8d91653973a5f05a9d9c6ccf93253d7978a1268e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 11:11:15 -0300 Subject: [PATCH 065/113] Bump requests from 2.26.0 to 2.31.0 in /scanners/log4shell-scanner (#4554) Bumps [requests](https://github.com/psf/requests) from 2.26.0 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.26.0...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- scanners/log4shell-scanner/Pipfile.lock | 306 ++++++++++++-------- scanners/log4shell-scanner/requirements.txt | 8 +- 2 files changed, 194 insertions(+), 120 deletions(-) diff --git a/scanners/log4shell-scanner/Pipfile.lock b/scanners/log4shell-scanner/Pipfile.lock index f1f6bb0f70..06d2221aef 100644 --- a/scanners/log4shell-scanner/Pipfile.lock +++ b/scanners/log4shell-scanner/Pipfile.lock @@ -23,26 +23,99 @@ }, "certifi": { "hashes": [ - "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", - "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", + "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" ], "markers": "python_version >= '3.6'", - "version": "==2022.12.7" + "version": "==2023.5.7" }, "charset-normalizer": { "hashes": [ - "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", - "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", + "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", + "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", + "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", + "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", + "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", + "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", + "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", + "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", + "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", + "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" ], - "markers": "python_version >= '3'", - "version": "==2.0.12" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.1.0" }, "idna": { "hashes": [ "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], - "markers": "python_version >= '3'", + "markers": "python_version >= '3.5'", "version": "==3.4" }, "python-dotenv": { @@ -55,19 +128,19 @@ }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.31.0" }, "urllib3": { "hashes": [ - "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc", - "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8" + "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", + "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.13" + "markers": "python_full_version >= '3.7.0'", + "version": "==2.0.2" } }, "develop": { @@ -86,120 +159,121 @@ }, "mypy-extensions": { "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" ], - "version": "==0.4.3" + "markers": "python_version >= '3.5'", + "version": "==1.0.0" }, "pathspec": { "hashes": [ - "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6", - "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6" + "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", + "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" ], "markers": "python_version >= '3.7'", - "version": "==0.10.3" + "version": "==0.11.1" }, "platformdirs": { "hashes": [ - "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca", - "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e" + "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f", + "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5" ], "markers": "python_version >= '3.7'", - "version": "==2.6.0" + "version": "==3.5.1" }, "regex": { "hashes": [ - "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad", - "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4", - "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd", - "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc", - "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d", - "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066", - "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec", - "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9", - "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e", - "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8", - "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e", - "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783", - "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6", - "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1", - "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c", - "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4", - "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1", - "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1", - "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7", - "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8", - "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe", - "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d", - "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b", - "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8", - "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c", - "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af", - "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49", - "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714", - "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542", - "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318", - "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e", - "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5", - "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc", - "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144", - "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453", - "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5", - "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61", - "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11", - "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a", - "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54", - "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73", - "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc", - "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347", - "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c", - "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66", - "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c", - "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93", - "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443", - "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc", - "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1", - "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892", - "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8", - "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001", - "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa", - "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90", - "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c", - "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0", - "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692", - "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4", - "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5", - "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690", - "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83", - "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66", - "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f", - "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f", - "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4", - "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee", - "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81", - "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95", - "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9", - "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff", - "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e", - "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5", - "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6", - "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7", - "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1", - "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394", - "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6", - "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742", - "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57", - "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b", - "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7", - "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b", - "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244", - "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af", - "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185", - "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8", - "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5" + "sha256:02f4541550459c08fdd6f97aa4e24c6f1932eec780d58a2faa2068253df7d6ff", + "sha256:0a69cf0c00c4d4a929c6c7717fd918414cab0d6132a49a6d8fc3ded1988ed2ea", + "sha256:0bbd5dcb19603ab8d2781fac60114fb89aee8494f4505ae7ad141a3314abb1f9", + "sha256:10250a093741ec7bf74bcd2039e697f519b028518f605ff2aa7ac1e9c9f97423", + "sha256:10374c84ee58c44575b667310d5bbfa89fb2e64e52349720a0182c0017512f6c", + "sha256:1189fbbb21e2c117fda5303653b61905aeeeea23de4a94d400b0487eb16d2d60", + "sha256:1307aa4daa1cbb23823d8238e1f61292fd07e4e5d8d38a6efff00b67a7cdb764", + "sha256:144b5b017646b5a9392a5554a1e5db0000ae637be4971c9747566775fc96e1b2", + "sha256:171c52e320fe29260da550d81c6b99f6f8402450dc7777ef5ced2e848f3b6f8f", + "sha256:18196c16a584619c7c1d843497c069955d7629ad4a3fdee240eb347f4a2c9dbe", + "sha256:18f05d14f14a812fe9723f13afafefe6b74ca042d99f8884e62dbd34dcccf3e2", + "sha256:1ecf3dcff71f0c0fe3e555201cbe749fa66aae8d18f80d2cc4de8e66df37390a", + "sha256:21e90a288e6ba4bf44c25c6a946cb9b0f00b73044d74308b5e0afd190338297c", + "sha256:23d86ad2121b3c4fc78c58f95e19173790e22ac05996df69b84e12da5816cb17", + "sha256:256f7f4c6ba145f62f7a441a003c94b8b1af78cee2cccacfc1e835f93bc09426", + "sha256:290fd35219486dfbc00b0de72f455ecdd63e59b528991a6aec9fdfc0ce85672e", + "sha256:2e9c4f778514a560a9c9aa8e5538bee759b55f6c1dcd35613ad72523fd9175b8", + "sha256:338994d3d4ca4cf12f09822e025731a5bdd3a37aaa571fa52659e85ca793fb67", + "sha256:33d430a23b661629661f1fe8395be2004006bc792bb9fc7c53911d661b69dd7e", + "sha256:385992d5ecf1a93cb85adff2f73e0402dd9ac29b71b7006d342cc920816e6f32", + "sha256:3d45864693351c15531f7e76f545ec35000d50848daa833cead96edae1665559", + "sha256:40005cbd383438aecf715a7b47fe1e3dcbc889a36461ed416bdec07e0ef1db66", + "sha256:4035d6945cb961c90c3e1c1ca2feb526175bcfed44dfb1cc77db4fdced060d3e", + "sha256:445d6f4fc3bd9fc2bf0416164454f90acab8858cd5a041403d7a11e3356980e8", + "sha256:48c9ec56579d4ba1c88f42302194b8ae2350265cb60c64b7b9a88dcb7fbde309", + "sha256:4a5059bd585e9e9504ef9c07e4bc15b0a621ba20504388875d66b8b30a5c4d18", + "sha256:4a6e4b0e0531223f53bad07ddf733af490ba2b8367f62342b92b39b29f72735a", + "sha256:4b870b6f632fc74941cadc2a0f3064ed8409e6f8ee226cdfd2a85ae50473aa94", + "sha256:50fd2d9b36938d4dcecbd684777dd12a407add4f9f934f235c66372e630772b0", + "sha256:53e22e4460f0245b468ee645156a4f84d0fc35a12d9ba79bd7d79bdcd2f9629d", + "sha256:586a011f77f8a2da4b888774174cd266e69e917a67ba072c7fc0e91878178a80", + "sha256:59597cd6315d3439ed4b074febe84a439c33928dd34396941b4d377692eca810", + "sha256:59e4b729eae1a0919f9e4c0fc635fbcc9db59c74ad98d684f4877be3d2607dd6", + "sha256:5a0f874ee8c0bc820e649c900243c6d1e6dc435b81da1492046716f14f1a2a96", + "sha256:5ac2b7d341dc1bd102be849d6dd33b09701223a851105b2754339e390be0627a", + "sha256:5e3f4468b8c6fd2fd33c218bbd0a1559e6a6fcf185af8bb0cc43f3b5bfb7d636", + "sha256:6164d4e2a82f9ebd7752a06bd6c504791bedc6418c0196cd0a23afb7f3e12b2d", + "sha256:6893544e06bae009916a5658ce7207e26ed17385149f35a3125f5259951f1bbe", + "sha256:690a17db524ee6ac4a27efc5406530dd90e7a7a69d8360235323d0e5dafb8f5b", + "sha256:6b8d0c153f07a953636b9cdb3011b733cadd4178123ef728ccc4d5969e67f3c2", + "sha256:72a28979cc667e5f82ef433db009184e7ac277844eea0f7f4d254b789517941d", + "sha256:72aa4746993a28c841e05889f3f1b1e5d14df8d3daa157d6001a34c98102b393", + "sha256:732176f5427e72fa2325b05c58ad0b45af341c459910d766f814b0584ac1f9ac", + "sha256:7918a1b83dd70dc04ab5ed24c78ae833ae8ea228cef84e08597c408286edc926", + "sha256:7923470d6056a9590247ff729c05e8e0f06bbd4efa6569c916943cb2d9b68b91", + "sha256:7d76a8a1fc9da08296462a18f16620ba73bcbf5909e42383b253ef34d9d5141e", + "sha256:811040d7f3dd9c55eb0d8b00b5dcb7fd9ae1761c454f444fd9f37fe5ec57143a", + "sha256:821a88b878b6589c5068f4cc2cfeb2c64e343a196bc9d7ac68ea8c2a776acd46", + "sha256:84397d3f750d153ebd7f958efaa92b45fea170200e2df5e0e1fd4d85b7e3f58a", + "sha256:844671c9c1150fcdac46d43198364034b961bd520f2c4fdaabfc7c7d7138a2dd", + "sha256:890a09cb0a62198bff92eda98b2b507305dd3abf974778bae3287f98b48907d3", + "sha256:8f08276466fedb9e36e5193a96cb944928301152879ec20c2d723d1031cd4ddd", + "sha256:8f5e06df94fff8c4c85f98c6487f6636848e1dc85ce17ab7d1931df4a081f657", + "sha256:921473a93bcea4d00295799ab929522fc650e85c6b9f27ae1e6bb32a790ea7d3", + "sha256:941b3f1b2392f0bcd6abf1bc7a322787d6db4e7457be6d1ffd3a693426a755f2", + "sha256:9b320677521aabf666cdd6e99baee4fb5ac3996349c3b7f8e7c4eee1c00dfe3a", + "sha256:9c3efee9bb53cbe7b285760c81f28ac80dc15fa48b5fe7e58b52752e642553f1", + "sha256:9fda3e50abad8d0f48df621cf75adc73c63f7243cbe0e3b2171392b445401550", + "sha256:a4c5da39bca4f7979eefcbb36efea04471cd68db2d38fcbb4ee2c6d440699833", + "sha256:a56c18f21ac98209da9c54ae3ebb3b6f6e772038681d6cb43b8d53da3b09ee81", + "sha256:a623564d810e7a953ff1357f7799c14bc9beeab699aacc8b7ab7822da1e952b8", + "sha256:a8906669b03c63266b6a7693d1f487b02647beb12adea20f8840c1a087e2dfb5", + "sha256:a99757ad7fe5c8a2bb44829fc57ced11253e10f462233c1255fe03888e06bc19", + "sha256:aa7d032c1d84726aa9edeb6accf079b4caa87151ca9fabacef31fa028186c66d", + "sha256:aad5524c2aedaf9aa14ef1bc9327f8abd915699dea457d339bebbe2f0d218f86", + "sha256:afb1c70ec1e594a547f38ad6bf5e3d60304ce7539e677c1429eebab115bce56e", + "sha256:b6365703e8cf1644b82104cdd05270d1a9f043119a168d66c55684b1b557d008", + "sha256:b8b942d8b3ce765dbc3b1dad0a944712a89b5de290ce8f72681e22b3c55f3cc8", + "sha256:ba73a14e9c8f9ac409863543cde3290dba39098fc261f717dc337ea72d3ebad2", + "sha256:bd7b68fd2e79d59d86dcbc1ccd6e2ca09c505343445daaa4e07f43c8a9cc34da", + "sha256:bd966475e963122ee0a7118ec9024388c602d12ac72860f6eea119a3928be053", + "sha256:c2ce65bdeaf0a386bb3b533a28de3994e8e13b464ac15e1e67e4603dd88787fa", + "sha256:c64d5abe91a3dfe5ff250c6bb267ef00dbc01501518225b45a5f9def458f31fb", + "sha256:c8c143a65ce3ca42e54d8e6fcaf465b6b672ed1c6c90022794a802fb93105d22", + "sha256:cd46f30e758629c3ee91713529cfbe107ac50d27110fdcc326a42ce2acf4dafc", + "sha256:ced02e3bd55e16e89c08bbc8128cff0884d96e7f7a5633d3dc366b6d95fcd1d6", + "sha256:cf123225945aa58b3057d0fba67e8061c62d14cc8a4202630f8057df70189051", + "sha256:d19e57f888b00cd04fc38f5e18d0efbd91ccba2d45039453ab2236e6eec48d4d", + "sha256:d1cbe6b5be3b9b698d8cc4ee4dee7e017ad655e83361cd0ea8e653d65e469468", + "sha256:db09e6c18977a33fea26fe67b7a842f706c67cf8bda1450974d0ae0dd63570df", + "sha256:de2f780c3242ea114dd01f84848655356af4dd561501896c751d7b885ea6d3a1", + "sha256:e2205a81f815b5bb17e46e74cc946c575b484e5f0acfcb805fb252d67e22938d", + "sha256:e645c757183ee0e13f0bbe56508598e2d9cd42b8abc6c0599d53b0d0b8dd1479", + "sha256:f2910502f718828cecc8beff004917dcf577fc5f8f5dd40ffb1ea7612124547b", + "sha256:f764e4dfafa288e2eba21231f455d209f4709436baeebb05bdecfb5d8ddc3d35", + "sha256:f83fe9e10f9d0b6cf580564d4d23845b9d692e4c91bd8be57733958e4c602956", + "sha256:fb2b495dd94b02de8215625948132cc2ea360ae84fe6634cd19b6567709c8ae2", + "sha256:fee0016cc35a8a91e8cc9312ab26a6fe638d484131a7afa79e1ce6165328a135" ], "markers": "python_version >= '3.6'", - "version": "==2022.10.31" + "version": "==2023.5.5" }, "tomli": { "hashes": [ @@ -211,11 +285,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", - "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "sha256:06006244c70ac8ee83fa8282cb188f697b8db25bc8b4df07be1873c43897060c", + "sha256:3a8b36f13dd5fdc5d1b16fe317f5668545de77fa0b8e02006381fd49d731ab98" ], "markers": "python_version >= '3.7'", - "version": "==4.4.0" + "version": "==4.6.2" } } } diff --git a/scanners/log4shell-scanner/requirements.txt b/scanners/log4shell-scanner/requirements.txt index 73699d08f4..a27e669a99 100644 --- a/scanners/log4shell-scanner/requirements.txt +++ b/scanners/log4shell-scanner/requirements.txt @@ -7,9 +7,9 @@ -i https://pypi.org/simple/ asyncio-nats-client==0.11.4 -certifi==2022.12.7; python_version >= '3.6' -charset-normalizer==2.0.12; python_version >= '3' -idna==3.4; python_version >= '3' +certifi==2023.5.7; python_version >= '3.6' +charset-normalizer==3.1.0; python_full_version >= '3.7.0' +idna==3.4; python_version >= '3.5' python-dotenv==0.19.2 requests==2.31.0 -urllib3==1.26.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +urllib3==2.0.2; python_full_version >= '3.7.0' From e9d5cd31353c656ff647211e402009248600cb8f Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Mon, 29 May 2023 10:12:08 -0400 Subject: [PATCH 066/113] Upgrade base image (#4559) --- scanners/web-processor/Dockerfile | 4 ++-- scanners/web-scanner/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scanners/web-processor/Dockerfile b/scanners/web-processor/Dockerfile index 5c99dced9f..dd03a9244a 100644 --- a/scanners/web-processor/Dockerfile +++ b/scanners/web-processor/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.7-slim-bullseye AS python-builder +FROM python:3.10.11-slim-bullseye AS python-builder # Copy local code to the container image. ENV PYTHONUNBUFFERED 1 @@ -23,7 +23,7 @@ RUN pip3 install --prefix=/working/install -r /requirements.txt #=============================================================================================== -FROM python:3.10.7-slim-bullseye +FROM python:3.10.11-slim-bullseye # Copy local code to the container image. ENV PYTHONWARNINGS ignore diff --git a/scanners/web-scanner/Dockerfile b/scanners/web-scanner/Dockerfile index 313b2541fe..22bb5ef163 100644 --- a/scanners/web-scanner/Dockerfile +++ b/scanners/web-scanner/Dockerfile @@ -4,7 +4,7 @@ RUN cargo install --git https://github.com/mozilla/crlite rust-query-crlite --re #=============================================================================================== #=============================================================================================== -FROM python:3.10.7-slim-bullseye AS python-builder +FROM python:3.10.11-slim-bullseye AS python-builder # Copy local code to the container image. ENV PYTHONUNBUFFERED 1 @@ -26,7 +26,7 @@ RUN pip3 install --prefix=/working/install -r /requirements.txt #=============================================================================================== #=============================================================================================== -FROM python:3.10.7-slim-bullseye +FROM python:3.10.11-slim-bullseye # Copy local code to the container image. ENV PYTHONUNBUFFERED 1 From f6ef30d83f5c3f66a7fa18a08832862a32385be5 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 29 May 2023 14:13:03 +0000 Subject: [PATCH 067/113] [ci skip] gcr.io/track-compliance/log4shell-scanner:master-8d91653-1685369491 --- app/gke/log4shell-scanner-deployment.yaml | 2 +- k8s/apps/bases/scanners/log4shell-scanner/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/gke/log4shell-scanner-deployment.yaml b/app/gke/log4shell-scanner-deployment.yaml index 8c71c99b99..8dd1fa1ed5 100644 --- a/app/gke/log4shell-scanner-deployment.yaml +++ b/app/gke/log4shell-scanner-deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: log4shell-scanner - image: gcr.io/track-compliance/log4shell-scanner:master-0dd5035-1684843927 # {"$imagepolicy": "flux-system:log4shell-scanner"} + image: gcr.io/track-compliance/log4shell-scanner:master-8d91653-1685369491 # {"$imagepolicy": "flux-system:log4shell-scanner"} env: - name: PYTHONWARNINGS value: ignore diff --git a/k8s/apps/bases/scanners/log4shell-scanner/deployment.yaml b/k8s/apps/bases/scanners/log4shell-scanner/deployment.yaml index 63c25ab566..c913b16e6f 100644 --- a/k8s/apps/bases/scanners/log4shell-scanner/deployment.yaml +++ b/k8s/apps/bases/scanners/log4shell-scanner/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: log4shell-scanner - image: gcr.io/track-compliance/log4shell-scanner:master-0dd5035-1684843927 # {"$imagepolicy": "flux-system:log4shell-scanner"} + image: gcr.io/track-compliance/log4shell-scanner:master-8d91653-1685369491 # {"$imagepolicy": "flux-system:log4shell-scanner"} env: - name: PYTHONWARNINGS value: ignore From 9df7b9ef0e952afb6ba931a398a91758f17c9cca Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 29 May 2023 14:15:21 +0000 Subject: [PATCH 068/113] [ci skip] gcr.io/track-compliance/web-processor:master-e9d5cd3-1685369546 --- k8s/apps/bases/scanners/web-processor/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/apps/bases/scanners/web-processor/deployment.yaml b/k8s/apps/bases/scanners/web-processor/deployment.yaml index d405740cfe..71e957e1e9 100644 --- a/k8s/apps/bases/scanners/web-processor/deployment.yaml +++ b/k8s/apps/bases/scanners/web-processor/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: web-processor - image: gcr.io/track-compliance/web-processor:master-46bdb68-1684842550 # {"$imagepolicy": "flux-system:web-processor"} + image: gcr.io/track-compliance/web-processor:master-e9d5cd3-1685369546 # {"$imagepolicy": "flux-system:web-processor"} env: - name: DB_NAME value: track_dmarc From 4f80dda62b0618255d481469331150c9dced13ac Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Tue, 30 May 2023 09:24:51 -0400 Subject: [PATCH 069/113] Update base image summaries (#4560) * Upgrade base image * Upgrade base image --- services/summaries/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/summaries/Dockerfile b/services/summaries/Dockerfile index 656d0cd084..729468fbe0 100644 --- a/services/summaries/Dockerfile +++ b/services/summaries/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.2-slim-bullseye +FROM python:3.11.3-slim-bullseye ENV PYTHONUNBUFFERED 1 ENV PYTHONDONTWRITEBYTECODE 1 From 266e796d6829a10cbdaa39cae5bf92b9877f3f7f Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 13:31:11 +0000 Subject: [PATCH 070/113] [ci skip] gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 --- app/bases/summaries-cronjob.yaml | 2 +- k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml | 2 +- services/summaries/summaries-job.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml index 7ac3129c1f..83b4fc8187 100644 --- a/app/bases/summaries-cronjob.yaml +++ b/app/bases/summaries-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-0927748-1684843850 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml index 7ac3129c1f..83b4fc8187 100644 --- a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-0927748-1684843850 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/services/summaries/summaries-job.yaml b/services/summaries/summaries-job.yaml index d83ff42885..34c25f4ca4 100644 --- a/services/summaries/summaries-job.yaml +++ b/services/summaries/summaries-job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-0927748-1684843850 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: From 927f20cffefc74d50ba8db4118c9019bd4aadaf6 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Tue, 30 May 2023 11:05:25 -0400 Subject: [PATCH 071/113] Pin cloudbuild arango version (#4562) * update notify org footprint test * Pin cloudbuild arango version --------- Co-authored-by: lcampbell --- api/cloudbuild.yaml | 2 +- scanners/domain-dispatcher/cloudbuild.yaml | 2 +- scripts/domain-loader/cloudbuild.yaml | 2 +- services/dmarc-report/cloudbuild.yaml | 2 +- services/org-footprint/cloudbuild.yaml | 2 +- .../src/__tests__/notify-send-org-footprint-email.test.js | 6 ++++++ .../src/notify/notify-send-org-footprint-email.js | 6 +++--- services/summaries/cloudbuild.yaml | 2 +- services/super-admin/cloudbuild.yaml | 2 +- 9 files changed, 16 insertions(+), 10 deletions(-) diff --git a/api/cloudbuild.yaml b/api/cloudbuild.yaml index 4bc8cce3b8..479326a33f 100644 --- a/api/cloudbuild.yaml +++ b/api/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for diff --git a/scanners/domain-dispatcher/cloudbuild.yaml b/scanners/domain-dispatcher/cloudbuild.yaml index a7a7b49e8b..a0410c0d10 100644 --- a/scanners/domain-dispatcher/cloudbuild.yaml +++ b/scanners/domain-dispatcher/cloudbuild.yaml @@ -6,7 +6,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for diff --git a/scripts/domain-loader/cloudbuild.yaml b/scripts/domain-loader/cloudbuild.yaml index a97845babb..5a54899298 100644 --- a/scripts/domain-loader/cloudbuild.yaml +++ b/scripts/domain-loader/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for diff --git a/services/dmarc-report/cloudbuild.yaml b/services/dmarc-report/cloudbuild.yaml index 0aa77ebec6..dcbf6e146b 100644 --- a/services/dmarc-report/cloudbuild.yaml +++ b/services/dmarc-report/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for diff --git a/services/org-footprint/cloudbuild.yaml b/services/org-footprint/cloudbuild.yaml index a4333c813f..40e4b08ef1 100644 --- a/services/org-footprint/cloudbuild.yaml +++ b/services/org-footprint/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for diff --git a/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js b/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js index 491422ee27..87c78dc012 100644 --- a/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js +++ b/services/org-footprint/src/__tests__/notify-send-org-footprint-email.test.js @@ -38,12 +38,14 @@ describe('given the sendOrgFootprintEmail function', () => { { action: 'remove', target: { + resource: 'domain1', resourceType: 'domain', }, }, { action: 'update', target: { + resource: 'domain2', resourceType: 'domain', }, }, @@ -67,8 +69,12 @@ describe('given the sendOrgFootprintEmail function', () => { update_users_count: 0, remove_users_count: 0, add_domains_count: 0, + add_domains_list: '', update_domains_count: 1, + update_domains_list: 'domain2', remove_domains_count: 1, + remove_domains_list: 'domain1', + export_count: 0, }, }) }) diff --git a/services/org-footprint/src/notify/notify-send-org-footprint-email.js b/services/org-footprint/src/notify/notify-send-org-footprint-email.js index e1be2cf13b..37ae6aa765 100644 --- a/services/org-footprint/src/notify/notify-send-org-footprint-email.js +++ b/services/org-footprint/src/notify/notify-send-org-footprint-email.js @@ -22,15 +22,15 @@ const sendOrgFootprintEmail = async ({ notifyClient, user, auditLogs, orgNames } // Get list of domains added if (domainsAdded.length > 0) { - addDomainsList = '\t' + domainsAdded.map((log) => log.target.resource).join(', ') + addDomainsList = domainsAdded.map((log) => log.target.resource).join(', ') } // Get list of domains updated if (domainsUpdated.length > 0) { - updateDomainsList = '\t' + domainsUpdated.map((log) => log.target.resource).join(', ') + updateDomainsList = domainsUpdated.map((log) => log.target.resource).join(', ') } // Get list of domains removed if (domainsRemoved.length > 0) { - removeDomainsList = '\t' + domainsRemoved.map((log) => log.target.resource).join(', ') + removeDomainsList = domainsRemoved.map((log) => log.target.resource).join(', ') } const exportsToCsv = auditLogs.filter((log) => log.action === 'export') diff --git a/services/summaries/cloudbuild.yaml b/services/summaries/cloudbuild.yaml index 38116c1f13..e28d878524 100644 --- a/services/summaries/cloudbuild.yaml +++ b/services/summaries/cloudbuild.yaml @@ -2,7 +2,7 @@ steps: - name: 'gcr.io/cloud-builders/docker' id: start_testdb entrypoint: /bin/sh - args: ["-c", "docker run --net cloudbuild --name testdb -e ARANGO_NO_AUTH=1 -d -p 127.0.0.1:8529:8529 arangodb"] + args: ["-c", "docker run --net cloudbuild --name testdb -e ARANGO_NO_AUTH=1 -d -p 127.0.0.1:8529:8529 arangodb/arangodb:3.10.4"] - name: mikewilliamson/wait-for id: wait_testdb diff --git a/services/super-admin/cloudbuild.yaml b/services/super-admin/cloudbuild.yaml index 3a2cbd6022..35a65b28d3 100644 --- a/services/super-admin/cloudbuild.yaml +++ b/services/super-admin/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', ] - name: mikewilliamson/wait-for From 450122c3bcafec9bc5697c9b55a91a1ba3be783a Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:07:42 +0000 Subject: [PATCH 072/113] [ci skip] gcr.io/track-compliance/dmarc-report:master-927f20c-1685459197 --- app/bases/dmarc-report-cronjob.yaml | 2 +- k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/dmarc-report-cronjob.yaml b/app/bases/dmarc-report-cronjob.yaml index ec6022c045..caf3492199 100644 --- a/app/bases/dmarc-report-cronjob.yaml +++ b/app/bases/dmarc-report-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: dmarc-report - image: gcr.io/track-compliance/dmarc-report:master-770f2e2-1674589724 # {"$imagepolicy": "flux-system:dmarc-report"} + image: gcr.io/track-compliance/dmarc-report:master-927f20c-1685459197 # {"$imagepolicy": "flux-system:dmarc-report"} env: - name: DB_NAME valueFrom: diff --git a/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml index ec6022c045..caf3492199 100644 --- a/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: dmarc-report - image: gcr.io/track-compliance/dmarc-report:master-770f2e2-1674589724 # {"$imagepolicy": "flux-system:dmarc-report"} + image: gcr.io/track-compliance/dmarc-report:master-927f20c-1685459197 # {"$imagepolicy": "flux-system:dmarc-report"} env: - name: DB_NAME valueFrom: From 22237b912605cd83e69d943deeab35380e744b68 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Tue, 30 May 2023 12:09:47 -0300 Subject: [PATCH 073/113] Reveal hidden domains to admins (#4553) * allow org admins and owners to view hidden domains * hidden domains affect landing page summaries * move hidden tags out of B stream * allow affiliated users to see hidden domains * update cloudbuild arango image --- ...-domain-connections-by-organizations-id.js | 2 +- frontend/src/admin/AdminDomainCard.js | 33 ++++---- frontend/src/admin/AdminDomainModal.js | 79 +++++++++---------- frontend/src/domains/DomainCard.js | 33 ++++---- services/summaries/summaries.py | 3 +- 5 files changed, 67 insertions(+), 83 deletions(-) diff --git a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js index 689855f9f6..df6014772e 100644 --- a/api/src/domain/loaders/load-domain-connections-by-organizations-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-organizations-id.js @@ -430,7 +430,7 @@ export const loadDomainConnectionsByOrgId = showArchivedDomains = aql`` } let showHiddenDomains = aql`FILTER e.hidden != true` - if (['super_admin'].includes(permission)) { + if (['super_admin', 'owner', 'admin', 'user'].includes(permission)) { showHiddenDomains = aql`` } diff --git a/frontend/src/admin/AdminDomainCard.js b/frontend/src/admin/AdminDomainCard.js index c472c353b7..cad20ca49d 100644 --- a/frontend/src/admin/AdminDomainCard.js +++ b/frontend/src/admin/AdminDomainCard.js @@ -3,7 +3,6 @@ import { Trans } from '@lingui/macro' import { array, bool, string } from 'prop-types' import { Flex, Grid, Link, ListItem, Stack, Tag, TagLabel, Text } from '@chakra-ui/react' import { ExternalLinkIcon } from '@chakra-ui/icons' -import { ABTestVariant, ABTestingWrapper } from '../app/ABTestWrapper' import { sanitizeUrl } from '../utilities/sanitizeUrl' @@ -36,24 +35,20 @@ export function AdminDomainCard({ url, tags, isHidden, isArchived, ...rest }) { ) })} - - - {isHidden && ( - - - Hidden - - - )} - {isArchived && ( - - - Archived - - - )} - - + {isHidden && ( + + + Hidden + + + )} + {isArchived && ( + + + Archived + + + )} diff --git a/frontend/src/admin/AdminDomainModal.js b/frontend/src/admin/AdminDomainModal.js index ed16e8d7a9..8111b7795c 100644 --- a/frontend/src/admin/AdminDomainModal.js +++ b/frontend/src/admin/AdminDomainModal.js @@ -37,7 +37,6 @@ import { useMutation } from '@apollo/client' import { DomainField } from '../components/fields/DomainField' import { CREATE_DOMAIN, UPDATE_DOMAIN } from '../graphql/mutations' -import { ABTestVariant, ABTestingWrapper } from '../app/ABTestWrapper' export function AdminDomainModal({ isOpen, onClose, validationSchema, orgId, ...props }) { const { @@ -327,62 +326,58 @@ export function AdminDomainModal({ isOpen, onClose, validationSchema, orgId, ... )} /> - - + + + + + + + Hide domain + + + {permission === 'SUPER_ADMIN' && ( + - + - Hide domain + Archive domain - {permission === 'SUPER_ADMIN' && ( - - - - - - - - Archive domain - - - - {orgCount > 0 ? ( - Note: This will affect results for {orgCount} organizations - ) : ( - Note: This could affect results for multiple organizations - )} - - - )} - - Please allow up to 24 hours for summaries to reflect any changes. + + {orgCount > 0 ? ( + Note: This will affect results for {orgCount} organizations + ) : ( + Note: This could affect results for multiple organizations + )} - - + + )} + + Please allow up to 24 hours for summaries to reflect any changes. + diff --git a/frontend/src/domains/DomainCard.js b/frontend/src/domains/DomainCard.js index b4fd1639af..81f39971df 100644 --- a/frontend/src/domains/DomainCard.js +++ b/frontend/src/domains/DomainCard.js @@ -16,7 +16,6 @@ import { } from '@chakra-ui/react' import { Link as RouteLink, useLocation } from 'react-router-dom' import { array, bool, object, string } from 'prop-types' -import { ABTestingWrapper, ABTestVariant } from '../app/ABTestWrapper' import { StatusBadge } from './StatusBadge' import { ScanDomainButton } from './ScanDomainButton' import { StarIcon } from '@chakra-ui/icons' @@ -154,24 +153,20 @@ export function DomainCard({ ) })} - - - {isHidden && ( - - - HIDDEN - - - )} - {isArchived && ( - - - ARCHIVED - - - )}{' '} - - + {isHidden && ( + + + HIDDEN + + + )} + {isArchived && ( + + + ARCHIVED + + + )} diff --git a/services/summaries/summaries.py b/services/summaries/summaries.py index b5f18e5474..576b8cecfe 100644 --- a/services/summaries/summaries.py +++ b/services/summaries/summaries.py @@ -75,8 +75,7 @@ def update_chart_summaries(host=DB_URL, name=DB_NAME, user=DB_USER, password=DB_ for domain in db.collection("domains"): archived = domain.get("archived") - hidden = is_domain_hidden(domain, db) - if archived != True and hidden != True: + if archived != True: # Update chart summaries for chart_type in chartSummaries: chart = chartSummaries[chart_type] From 3c6a4d1f457f09900820f4f5ebe35d60b90d8518 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:10:13 +0000 Subject: [PATCH 074/113] [ci skip] gcr.io/track-compliance/api-js:master-927f20c-1685459321 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index e8d31953fe..487d1d075d 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6426b98-1685127971 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-927f20c-1685459321 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 95adcd31cb..eb625a1f4b 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-6426b98-1685127971 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-927f20c-1685459321 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From fdde1b50b2b0eb4d606d80bb9d5016ed352c8715 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:11:14 +0000 Subject: [PATCH 075/113] [ci skip] gcr.io/track-compliance/super-admin:master-927f20c-1685459193 gcr.io/track-compliance/domain-dispatcher:master-927f20c-1685459197 gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 --- app/bases/summaries-cronjob.yaml | 2 +- app/jobs/super-admin.yaml | 2 +- k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml | 2 +- k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml | 2 +- k8s/jobs/bases/super-admin/super-admin-job.yaml | 2 +- scanners/domain-dispatcher/domain-dispatcher-job.yaml | 2 +- scanners/spring4shell/domain-dispatcher-job.yaml | 2 +- services/summaries/summaries-job.yaml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml index 83b4fc8187..a5d0796b96 100644 --- a/app/bases/summaries-cronjob.yaml +++ b/app/bases/summaries-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/app/jobs/super-admin.yaml b/app/jobs/super-admin.yaml index dc5876def1..b9f13abe8e 100644 --- a/app/jobs/super-admin.yaml +++ b/app/jobs/super-admin.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: super-admin - image: gcr.io/track-compliance/super-admin:master-a802403-1680796755 # {"$imagepolicy": "flux-system:super-admin"} + image: gcr.io/track-compliance/super-admin:master-927f20c-1685459193 # {"$imagepolicy": "flux-system:super-admin"} env: - name: DB_PASS valueFrom: diff --git a/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml index adf79210f0..8631d6169f 100644 --- a/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml @@ -16,7 +16,7 @@ spec: spec: containers: - name: domain-dispatcher - image: gcr.io/track-compliance/domain-dispatcher:master-bdfe07c-1678454331 # {"$imagepolicy": "flux-system:domain-dispatcher"} + image: gcr.io/track-compliance/domain-dispatcher:master-927f20c-1685459197 # {"$imagepolicy": "flux-system:domain-dispatcher"} env: - name: DB_PASS valueFrom: diff --git a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml index 83b4fc8187..a5d0796b96 100644 --- a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/k8s/jobs/bases/super-admin/super-admin-job.yaml b/k8s/jobs/bases/super-admin/super-admin-job.yaml index 9540e02330..5c5bc801e2 100644 --- a/k8s/jobs/bases/super-admin/super-admin-job.yaml +++ b/k8s/jobs/bases/super-admin/super-admin-job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: super-admin - image: gcr.io/track-compliance/super-admin:master-a802403-1680796755 # {"$imagepolicy": "flux-system:super-admin"} + image: gcr.io/track-compliance/super-admin:master-927f20c-1685459193 # {"$imagepolicy": "flux-system:super-admin"} env: - name: DB_PASS valueFrom: diff --git a/scanners/domain-dispatcher/domain-dispatcher-job.yaml b/scanners/domain-dispatcher/domain-dispatcher-job.yaml index 0f1c6406cf..eda30a4794 100644 --- a/scanners/domain-dispatcher/domain-dispatcher-job.yaml +++ b/scanners/domain-dispatcher/domain-dispatcher-job.yaml @@ -12,7 +12,7 @@ spec: spec: containers: - name: domain-dispatcher - image: gcr.io/track-compliance/domain-dispatcher:master-bdfe07c-1678454331 # {"$imagepolicy": "flux-system:domain-dispatcher"} + image: gcr.io/track-compliance/domain-dispatcher:master-927f20c-1685459197 # {"$imagepolicy": "flux-system:domain-dispatcher"} env: - name: DB_PASS valueFrom: diff --git a/scanners/spring4shell/domain-dispatcher-job.yaml b/scanners/spring4shell/domain-dispatcher-job.yaml index 0f1c6406cf..eda30a4794 100644 --- a/scanners/spring4shell/domain-dispatcher-job.yaml +++ b/scanners/spring4shell/domain-dispatcher-job.yaml @@ -12,7 +12,7 @@ spec: spec: containers: - name: domain-dispatcher - image: gcr.io/track-compliance/domain-dispatcher:master-bdfe07c-1678454331 # {"$imagepolicy": "flux-system:domain-dispatcher"} + image: gcr.io/track-compliance/domain-dispatcher:master-927f20c-1685459197 # {"$imagepolicy": "flux-system:domain-dispatcher"} env: - name: DB_PASS valueFrom: diff --git a/services/summaries/summaries-job.yaml b/services/summaries/summaries-job.yaml index 34c25f4ca4..73393076de 100644 --- a/services/summaries/summaries-job.yaml +++ b/services/summaries/summaries-job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-4f80dda-1685453158 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: From fb5694a78f977c681ee575bf47a4117dd1e68824 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:14:14 +0000 Subject: [PATCH 076/113] [ci skip] gcr.io/track-compliance/api-js:master-22237b9-1685459568 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 487d1d075d..c5067d4cb3 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-927f20c-1685459321 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-22237b9-1685459568 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index eb625a1f4b..6424460a7d 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-927f20c-1685459321 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-22237b9-1685459568 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From aa7f465d61e201428557c1ee3f14287fea908f34 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:16:15 +0000 Subject: [PATCH 077/113] [ci skip] gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 --- app/bases/summaries-cronjob.yaml | 2 +- k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml | 2 +- services/summaries/summaries-job.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml index a5d0796b96..6410ae5756 100644 --- a/app/bases/summaries-cronjob.yaml +++ b/app/bases/summaries-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml index a5d0796b96..6410ae5756 100644 --- a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/services/summaries/summaries-job.yaml b/services/summaries/summaries-job.yaml index 73393076de..2ddaf1dd41 100644 --- a/services/summaries/summaries-job.yaml +++ b/services/summaries/summaries-job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-927f20c-1685459188 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: From 1630f599550a456477a8107b864c300917b6849f Mon Sep 17 00:00:00 2001 From: fluxbot Date: Tue, 30 May 2023 15:24:50 +0000 Subject: [PATCH 078/113] [ci skip] gcr.io/track-compliance/frontend:master-22237b9-1685460133 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 28f44d984e..b31313ae73 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-6426b98-1685128395 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-22237b9-1685460133 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index d2b0b4ef42..c8329b7319 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-6426b98-1685128395 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-22237b9-1685460133 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 151af2cde2c6a6a0f19616a5c90e4662b8013e8b Mon Sep 17 00:00:00 2001 From: lcampbell Date: Wed, 31 May 2023 09:39:39 -0300 Subject: [PATCH 079/113] fix hidden dmarc summary calculation --- api/src/organization/objects/organization-summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/organization/objects/organization-summary.js b/api/src/organization/objects/organization-summary.js index 8380965061..fcc7b52a8e 100644 --- a/api/src/organization/objects/organization-summary.js +++ b/api/src/organization/objects/organization-summary.js @@ -359,7 +359,7 @@ export const organizationSummaryType = new GraphQLObjectType({ resolve: ({ dmarc, hidden }, _) => { const pass = dmarc.pass + hidden.dmarc.pass const fail = dmarc.fail + hidden.dmarc.fail - const total = dmarc.total + hidden.https.total + const total = dmarc.total + hidden.dmarc.total let percentPass, percentageFail if (total <= 0) { From 0a4ac420ecc8ce0e0602c25fd1a992a6923eb210 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Wed, 31 May 2023 12:43:38 +0000 Subject: [PATCH 080/113] [ci skip] gcr.io/track-compliance/api-js:master-151af2c-1685536947 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index c5067d4cb3..f3b741bf4e 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-22237b9-1685459568 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-151af2c-1685536947 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 6424460a7d..7c551f2f7f 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-22237b9-1685459568 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-151af2c-1685536947 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From b5372dc6e4cbd0abaf212d1a976c3dd1ce9d45e9 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:27:01 -0300 Subject: [PATCH 081/113] Redirect org footprint emails to tracker (#4564) * add service account redirect option * send to service account if no admins * move comment --- services/org-footprint/.env.example | 5 ++- services/org-footprint/README.md | 6 +++- services/org-footprint/cloudbuild.yaml | 2 ++ .../src/org-footprint-service.js | 31 ++++++++++++++----- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/services/org-footprint/.env.example b/services/org-footprint/.env.example index fa50c6a2ff..004c818b65 100644 --- a/services/org-footprint/.env.example +++ b/services/org-footprint/.env.example @@ -5,4 +5,7 @@ DB_NAME= NOTIFICATION_API_KEY= NOTIFICATION_API_URL= NOTIFICATION_ORG_FOOTPRINT_EN= -NOTIFICATION_ORG_FOOTPRINT_FR= \ No newline at end of file +NOTIFICATION_ORG_FOOTPRINT_FR= + +SERVICE_ACCOUNT_EMAIL= +REDIRECT_TO_SERVICE_ACCOUNT_EMAIL= \ No newline at end of file diff --git a/services/org-footprint/README.md b/services/org-footprint/README.md index d8b962a278..a460e9c8a0 100644 --- a/services/org-footprint/README.md +++ b/services/org-footprint/README.md @@ -26,6 +26,8 @@ NOTIFICATION_API_KEY=asdf1234 NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca NOTIFICATION_ORG_FOOTPRINT_EN=test_id NOTIFICATION_ORG_FOOTPRINT_FR=test_id +SERVICE_ACCOUNT_EMAIL=test@email.ca +REDIRECT_TO_SERVICE_ACCOUNT_EMAIL=false ``` ## Testing @@ -54,6 +56,8 @@ NOTIFICATION_API_KEY=asdf1234 NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca NOTIFICATION_ORG_FOOTPRINT_EN=test_id NOTIFICATION_ORG_FOOTPRINT_FR=test_id +SERVICE_ACCOUNT_EMAIL=test@email.ca +REDIRECT_TO_SERVICE_ACCOUNT_EMAIL=false ``` ### Cloudbuild @@ -61,7 +65,7 @@ NOTIFICATION_ORG_FOOTPRINT_FR=test_id Cloudbuild is used for CI, and one of the reasons for that is that it has great tooling for running jobs locally which is very helpful for debugging. To see how this will behave in CI you can run something like the following to run the tests with cloud-build-local. ```bash -cloud-build-local --config cloudbuild.yaml --substitutions=BRANCH_NAME=foo,SHORT_SHA=asdf1234,_DB_PASS=test,_DB_URL=http://arangodb:8529,_DB_NAME=track_dmarc,NOTIFICATION_API_KEY=asdf1234, NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca, NOTIFICATION_ORG_FOOTPRINT_EN=test_id, NOTIFICATION_ORG_FOOTPRINT_FR=test_id --dryrun=false . +cloud-build-local --config cloudbuild.yaml --substitutions=BRANCH_NAME=foo,SHORT_SHA=asdf1234,_DB_PASS=test,_DB_URL=http://arangodb:8529,_DB_NAME=track_dmarc,NOTIFICATION_API_KEY=asdf1234, NOTIFICATION_API_URL=https://api.notification.alpha.canada.ca, NOTIFICATION_ORG_FOOTPRINT_EN=test_id, NOTIFICATION_ORG_FOOTPRINT_FR=test_id, SERVICE_ACCOUNT_EMAIL=test@email.ca, REDIRECT_TO_SERVICE_ACCOUNT_EMAIL=false --dryrun=false . ``` Because of the way the cloudbuild config spins up and detaches a copy of ArangoDB, you will need to run the following commands to clean up after. diff --git a/services/org-footprint/cloudbuild.yaml b/services/org-footprint/cloudbuild.yaml index 40e4b08ef1..f88bca1644 100644 --- a/services/org-footprint/cloudbuild.yaml +++ b/services/org-footprint/cloudbuild.yaml @@ -37,6 +37,8 @@ steps: - NOTIFICATION_API_URL=$_NOTIFICATION_API_URL - NOTIFICATION_ORG_FOOTPRINT_EN=$_NOTIFICATION_TEST_TEMPLATE_ID - NOTIFICATION_ORG_FOOTPRINT_FR=$_NOTIFICATION_TEST_TEMPLATE_ID + - SERVICE_ACCOUNT_EMAIL=$_SERVICE_ACCOUNT_EMAIL + - REDIRECT_TO_SERVICE_ACCOUNT_EMAIL=$_REDIRECT_TO_SERVICE_ACCOUNT_EMAIL - name: 'gcr.io/cloud-builders/docker' id: generate-image-name entrypoint: 'bash' diff --git a/services/org-footprint/src/org-footprint-service.js b/services/org-footprint/src/org-footprint-service.js index 2fa4a8c898..6f636aec7a 100644 --- a/services/org-footprint/src/org-footprint-service.js +++ b/services/org-footprint/src/org-footprint-service.js @@ -1,6 +1,8 @@ const { getOrgAdmins, getAllOrgKeys, getNewAuditLogs, getBilingualOrgNames } = require('./database') const { sendOrgFootprintEmail } = require('./notify') +const { SERVICE_ACCOUNT_EMAIL, REDIRECT_TO_SERVICE_ACCOUNT_EMAIL } = process.env + const orgFootprintService = async ({ query, log, notifyClient }) => { // get list of all orgs const orgKeys = await getAllOrgKeys({ query }) @@ -9,15 +11,28 @@ const orgFootprintService = async ({ query, log, notifyClient }) => { const auditLogs = await getNewAuditLogs({ query, orgKey }) // if new audit logs exist if (auditLogs.length > 0) { - // get list of org admins - const orgAdmins = await getOrgAdmins({ query, orgKey }) - // if org admins exist - if (orgAdmins.length > 0) { - log(`Sending recent activity email to admins of org: ${orgKey}`) + if (REDIRECT_TO_SERVICE_ACCOUNT_EMAIL) { const orgNames = await getBilingualOrgNames({ query, orgKey }) - // send email to org admins - for (const user of orgAdmins) { - await sendOrgFootprintEmail({ notifyClient, user, auditLogs, orgNames }) + log(`Sending recent activity email to service account: ${orgKey}`) + // send email to service account + const user = { + userName: SERVICE_ACCOUNT_EMAIL, + displayName: 'Service Account', + preferredLang: 'en', + _key: 'service-account', + } + await sendOrgFootprintEmail({ notifyClient, user, auditLogs, orgNames }) + } else { + // get list of org admins + const orgAdmins = await getOrgAdmins({ query, orgKey }) + // if org admins exist + if (orgAdmins.length > 0) { + const orgNames = await getBilingualOrgNames({ query, orgKey }) + log(`Sending recent activity email to admins of org: ${orgKey}`) + // send email to org admins + for (const user of orgAdmins) { + await sendOrgFootprintEmail({ notifyClient, user, auditLogs, orgNames }) + } } } } From e1ed8f1e830bf929772045c980143d2d2f194a2a Mon Sep 17 00:00:00 2001 From: lcampbell Date: Thu, 1 Jun 2023 12:49:39 -0300 Subject: [PATCH 082/113] gcr.io/track-compliance/services/org-footprint:master-b5372dc-1685629702 --- k8s/apps/bases/api/org-footprint-cronjob.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/apps/bases/api/org-footprint-cronjob.yaml b/k8s/apps/bases/api/org-footprint-cronjob.yaml index 3c992d8e39..441d984deb 100644 --- a/k8s/apps/bases/api/org-footprint-cronjob.yaml +++ b/k8s/apps/bases/api/org-footprint-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: org-footprint - image: gcr.io/track-compliance/services/org-footprint:master-001 # {"$imagepolicy": "flux-system:org-footprint"} + image: gcr.io/track-compliance/services/org-footprint:master-b5372dc-1685629702 # {"$imagepolicy": "flux-system:org-footprint"} env: - name: DB_PASS valueFrom: From 508bcfe24962fe29a5e6cd8fe704fbd3ffd6defd Mon Sep 17 00:00:00 2001 From: lcampbell Date: Thu, 1 Jun 2023 14:29:16 -0300 Subject: [PATCH 083/113] add service account secrets to org-footprint cloudbuild --- k8s/apps/bases/api/org-footprint-cronjob.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/k8s/apps/bases/api/org-footprint-cronjob.yaml b/k8s/apps/bases/api/org-footprint-cronjob.yaml index 441d984deb..ea997fc180 100644 --- a/k8s/apps/bases/api/org-footprint-cronjob.yaml +++ b/k8s/apps/bases/api/org-footprint-cronjob.yaml @@ -50,4 +50,14 @@ spec: secretKeyRef: name: api key: NOTIFICATION_ORG_FOOTPRINT_FR + - name: SERVICE_ACCOUNT_EMAIL + valueFrom: + secretKeyRef: + name: api + key: SERVICE_ACCOUNT_EMAIL + - name: REDIRECT_TO_SERVICE_ACCOUNT_EMAIL + valueFrom: + secretKeyRef: + name: api + key: REDIRECT_TO_SERVICE_ACCOUNT_EMAIL restartPolicy: OnFailure From b62b049ba578d45ff2fb84fdc63c8d322750f44a Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Thu, 1 Jun 2023 14:33:03 -0300 Subject: [PATCH 084/113] Allow removal of nxdomains (#4565) * allow admins to remove domains in verified orgs if nxdomain * show nxdomain tags on admin page * fix tests --- .../mutations/__tests__/remove-domain.test.js | 16 +-- api/src/domain/mutations/remove-domain.js | 15 ++- frontend/src/admin/AdminDomainCard.js | 8 +- frontend/src/admin/AdminDomains.js | 124 +++++------------- frontend/src/fixtures/orgDomainListData.js | 12 ++ frontend/src/graphql/queries.js | 1 + 6 files changed, 72 insertions(+), 104 deletions(-) diff --git a/api/src/domain/mutations/__tests__/remove-domain.test.js b/api/src/domain/mutations/__tests__/remove-domain.test.js index 24f8eba505..4ca5e3cac5 100644 --- a/api/src/domain/mutations/__tests__/remove-domain.test.js +++ b/api/src/domain/mutations/__tests__/remove-domain.test.js @@ -3424,7 +3424,7 @@ describe('removing a domain', () => { removeDomain: { result: { code: 403, - description: 'Permission Denied: Please contact super admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3432,7 +3432,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -3499,7 +3499,7 @@ describe('removing a domain', () => { removeDomain: { result: { code: 403, - description: 'Permission Denied: Please contact super admin for help with removing domain.', + description: 'Permission Denied: Please contact organization admin for help with removing domain.', }, }, }, @@ -3507,7 +3507,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -4572,7 +4572,7 @@ describe('removing a domain', () => { result: { code: 403, description: - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.", }, }, }, @@ -4580,7 +4580,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) @@ -4648,7 +4648,7 @@ describe('removing a domain', () => { result: { code: 403, description: - "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.", + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.", }, }, }, @@ -4656,7 +4656,7 @@ describe('removing a domain', () => { expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove domain.gc.ca in temp-org but does not have permission to remove a domain from a verified check org.`, + `User: 123 attempted to remove domain.gc.ca in temp-org however they do not have permission in that org.`, ]) }) }) diff --git a/api/src/domain/mutations/remove-domain.js b/api/src/domain/mutations/remove-domain.js index dffa9eb786..13098c91ff 100644 --- a/api/src/domain/mutations/remove-domain.js +++ b/api/src/domain/mutations/remove-domain.js @@ -84,26 +84,27 @@ export const removeDomain = new mutationWithClientMutationId({ // Get permission const permission = await checkPermission({ orgId: org._id }) - // Check to see if domain belongs to verified check org - if (org.verified && permission !== 'super_admin') { + if (permission !== 'super_admin' && permission !== 'admin') { console.warn( - `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} but does not have permission to remove a domain from a verified check org.`, + `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} however they do not have permission in that org.`, ) return { _type: 'error', code: 403, - description: i18n._(t`Permission Denied: Please contact super admin for help with removing domain.`), + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing domain.`), } } - if (permission !== 'super_admin' && permission !== 'admin') { + // Check to see if domain belongs to verified check org + // if domain returns NXDOMAIN, allow removal + if (org.verified && permission !== 'super_admin' && domain.rcode !== 'NXDOMAIN') { console.warn( - `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} however they do not have permission in that org.`, + `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} but does not have permission to remove a domain from a verified check org.`, ) return { _type: 'error', code: 403, - description: i18n._(t`Permission Denied: Please contact organization admin for help with removing domain.`), + description: i18n._(t`Permission Denied: Please contact super admin for help with removing domain.`), } } diff --git a/frontend/src/admin/AdminDomainCard.js b/frontend/src/admin/AdminDomainCard.js index cad20ca49d..aa89a109d1 100644 --- a/frontend/src/admin/AdminDomainCard.js +++ b/frontend/src/admin/AdminDomainCard.js @@ -6,7 +6,7 @@ import { ExternalLinkIcon } from '@chakra-ui/icons' import { sanitizeUrl } from '../utilities/sanitizeUrl' -export function AdminDomainCard({ url, tags, isHidden, isArchived, ...rest }) { +export function AdminDomainCard({ url, tags, isHidden, isArchived, rcode, ...rest }) { return ( @@ -35,6 +35,11 @@ export function AdminDomainCard({ url, tags, isHidden, isArchived, ...rest }) { ) })} + {rcode === 'NXDOMAIN' && ( + + NXDOMAIN + + )} {isHidden && ( @@ -59,4 +64,5 @@ AdminDomainCard.propTypes = { tags: array, isHidden: bool, isArchived: bool, + rcode: string, } diff --git a/frontend/src/admin/AdminDomains.js b/frontend/src/admin/AdminDomains.js index 6a86ca4c3c..00eded66f1 100644 --- a/frontend/src/admin/AdminDomains.js +++ b/frontend/src/admin/AdminDomains.js @@ -48,8 +48,12 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { const { i18n } = useLingui() const [newDomainUrl, setNewDomainUrl] = useState('') - const [selectedRemoveDomainUrl, setSelectedRemoveDomainUrl] = useState() - const [selectedRemoveDomainId, setSelectedRemoveDomainId] = useState() + const [selectedRemoveProps, setSelectedRemoveProps] = useState({ + domain: '', + domainId: '', + rcode: '', + }) + // const [selectedRemoveDomainId, setSelectedRemoveDomainId] = useState() const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('') const [modalProps, setModalProps] = useState({ hidden: false, @@ -61,34 +65,19 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { editingDomainUrl: '', }) - const { - isOpen: updateIsOpen, - onOpen: updateOnOpen, - onClose: updateOnClose, - } = useDisclosure() - const { - isOpen: removeIsOpen, - onOpen: removeOnOpen, - onClose: removeOnClose, - } = useDisclosure() + const { isOpen: updateIsOpen, onOpen: updateOnOpen, onClose: updateOnClose } = useDisclosure() + const { isOpen: removeIsOpen, onOpen: removeOnOpen, onClose: removeOnClose } = useDisclosure() - const { - loading, - isLoadingMore, - error, - nodes, - next, - previous, - hasNextPage, - hasPreviousPage, - } = usePaginatedCollection({ - fetchForward: FORWARD, - recordsPerPage: domainsPerPage, - variables: { orgSlug, search: debouncedSearchTerm }, - relayRoot: 'findOrganizationBySlug.domains', - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first', - }) + const { loading, isLoadingMore, error, nodes, next, previous, hasNextPage, hasPreviousPage } = usePaginatedCollection( + { + fetchForward: FORWARD, + recordsPerPage: domainsPerPage, + variables: { orgSlug, search: debouncedSearchTerm }, + relayRoot: 'findOrganizationBySlug.domains', + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first', + }, + ) const memoizedSetDebouncedSearchTermCallback = useCallback(() => { setDebouncedSearchTerm(newDomainUrl) @@ -157,26 +146,14 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { )} > - {( - { - id: domainId, - domain, - selectors, - claimTags, - hidden, - archived, - organizations, - }, - index, - ) => ( + {({ id: domainId, domain, selectors, claimTags, hidden, archived, rcode, organizations }, index) => ( { - setSelectedRemoveDomainUrl(domain) - setSelectedRemoveDomainId(domainId) + setSelectedRemoveProps({ domain, domainId, rcode }) removeOnOpen() }} variant="danger" @@ -210,6 +187,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { tags={claimTags} isHidden={hidden} isArchived={archived} + rcode={rcode} locale={i18n.locale} flexGrow={1} fontSize={{ base: '75%', sm: '100%' }} @@ -241,21 +219,10 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { }} > - + Search: - + @@ -267,12 +234,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { onChange={(e) => setNewDomainUrl(e.target.value)} /> - @@ -300,16 +262,12 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { permission={permission} /> - + { removeDomain({ variables: { - domainId: selectedRemoveDomainId, + domainId: selectedRemoveProps.selectedRemoveDomainId, orgId: orgId, reason: values.reason, }, }) }} > - {({ handleSubmit, isSubmitting, handleChange }) => ( + {({ values, handleSubmit, isSubmitting, handleChange }) => (
Remove Domain @@ -335,15 +293,13 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { Confirm removal of domain: - {selectedRemoveDomainUrl} + {selectedRemoveProps.selectedRemoveDomainUrl} - A domain may only be removed for one of the reasons - below. For a domain to no longer exist, it must be - removed from the DNS. If you need to remove this domain - for a different reason, please contact TBS Cyber - Security. + A domain may only be removed for one of the reasons below. For a domain to no longer exist, it + must be removed from the DNS. If you need to remove this domain for a different reason, please + contact TBS Cyber Security. @@ -353,23 +309,20 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { @@ -377,12 +330,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { - diff --git a/frontend/src/fixtures/orgDomainListData.js b/frontend/src/fixtures/orgDomainListData.js index 5a2085e5b6..e22a1b596a 100644 --- a/frontend/src/fixtures/orgDomainListData.js +++ b/frontend/src/fixtures/orgDomainListData.js @@ -13,6 +13,10 @@ export const rawOrgDomainListData = { claimTags: [], archived: false, hidden: false, + rcode: 'NOERROR', + organizations: { + totalCount: 1, + }, __typename: 'Domain', }, __typename: 'DomainEdge', @@ -26,6 +30,10 @@ export const rawOrgDomainListData = { claimTags: [], archived: false, hidden: false, + rcode: 'NOERROR', + organizations: { + totalCount: 1, + }, __typename: 'Domain', }, __typename: 'DomainEdge', @@ -39,6 +47,10 @@ export const rawOrgDomainListData = { claimTags: [], archived: false, hidden: false, + rcode: 'NOERROR', + organizations: { + totalCount: 1, + }, __typename: 'Domain', }, __typename: 'DomainEdge', diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 5c7560c3b2..8c22907c0d 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -189,6 +189,7 @@ export const PAGINATED_ORG_DOMAINS_ADMIN_PAGE = gql` claimTags hidden archived + rcode organizations(first: 1) { totalCount } From ee42b74ca6f8df7a542c451f826e15baf1421337 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 1 Jun 2023 17:37:42 +0000 Subject: [PATCH 085/113] [ci skip] gcr.io/track-compliance/api-js:master-b62b049-1685640973 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index f3b741bf4e..deb3bde16d 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-151af2c-1685536947 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-b62b049-1685640973 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 7c551f2f7f..62c5f72a92 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-151af2c-1685536947 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-b62b049-1685640973 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 4731bf6fdd010c5e5c919c4ebaef29fd3b96ff86 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 1 Jun 2023 17:40:51 +0000 Subject: [PATCH 086/113] [ci skip] gcr.io/track-compliance/frontend:master-b62b049-1685641093 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index b31313ae73..32d3378f02 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-22237b9-1685460133 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-b62b049-1685641093 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index c8329b7319..54f9ce8c92 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-22237b9-1685460133 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-b62b049-1685641093 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 6d1dbf9447bf28fab764f8c83293d333b147013d Mon Sep 17 00:00:00 2001 From: lcampbell Date: Fri, 2 Jun 2023 13:47:37 -0300 Subject: [PATCH 087/113] fix selected domain removal prop names --- frontend/src/admin/AdminDomains.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/admin/AdminDomains.js b/frontend/src/admin/AdminDomains.js index 00eded66f1..8340a83d40 100644 --- a/frontend/src/admin/AdminDomains.js +++ b/frontend/src/admin/AdminDomains.js @@ -275,7 +275,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { onSubmit={async (values) => { removeDomain({ variables: { - domainId: selectedRemoveProps.selectedRemoveDomainId, + domainId: selectedRemoveProps.domainId, orgId: orgId, reason: values.reason, }, @@ -293,7 +293,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId, permission }) { Confirm removal of domain: - {selectedRemoveProps.selectedRemoveDomainUrl} + {selectedRemoveProps.domain} From b6d1d40b6aaf1524c243a9412b4842b0b51bc425 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 2 Jun 2023 16:54:48 +0000 Subject: [PATCH 088/113] [ci skip] gcr.io/track-compliance/frontend:master-6d1dbf9-1685724756 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index 32d3378f02..7dc4709e22 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-b62b049-1685641093 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6d1dbf9-1685724756 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 54f9ce8c92..0ef66c54e9 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-b62b049-1685641093 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-6d1dbf9-1685724756 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: From 955c53b294eda53f6dc2272dc3121da812b0d765 Mon Sep 17 00:00:00 2001 From: Luke Campbell <64781228+lcampbell2@users.noreply.github.com> Date: Mon, 5 Jun 2023 09:50:19 -0300 Subject: [PATCH 089/113] use ignore_domain func to exclude domains from summaries (#4566) --- services/summaries/summaries.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/services/summaries/summaries.py b/services/summaries/summaries.py index 576b8cecfe..62eb621f93 100644 --- a/services/summaries/summaries.py +++ b/services/summaries/summaries.py @@ -44,6 +44,21 @@ def is_domain_hidden(domain, db): return False +def ignore_domain(domain): + """Check if a domain should be ignored + + :param domain: domain to check + :return: True if domain should be ignored, False otherwise + """ + if ( + domain.get("archived") is True + or domain.get("blocked") is True + or domain.get("rcode") == "NXDOMAIN" + ): + return True + return False + + def update_chart_summaries(host=DB_URL, name=DB_NAME, user=DB_USER, password=DB_PASS): logging.info(f"Updating chart summaries...") @@ -74,8 +89,7 @@ def update_chart_summaries(host=DB_URL, name=DB_NAME, user=DB_USER, password=DB_ maintain_count = 0 for domain in db.collection("domains"): - archived = domain.get("archived") - if archived != True: + if ignore_domain(domain) is False: # Update chart summaries for chart_type in chartSummaries: chart = chartSummaries[chart_type] @@ -213,9 +227,8 @@ def update_org_summaries(host=DB_URL, name=DB_NAME, user=DB_USER, password=DB_PA claims = db.collection("claims").find({"_from": org["_id"]}) for claim in claims: domain = db.collection("domains").get({"_id": claim["_to"]}) - archived = domain.get("archived") - hidden = claim.get("hidden") - if archived != True: + if ignore_domain(domain) is False: + hidden = claim.get("hidden") if hidden != True: domain_total = domain_total + 1 if domain.get("status", {}).get("dmarc") == "pass": From 9e2bda4994dfe4606a9f9bbe56234620bd88f0da Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 5 Jun 2023 12:53:13 +0000 Subject: [PATCH 090/113] [ci skip] gcr.io/track-compliance/services/summaries:master-955c53b-1685969471 --- app/bases/summaries-cronjob.yaml | 2 +- k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml | 2 +- services/summaries/summaries-job.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml index 6410ae5756..20910abaed 100644 --- a/app/bases/summaries-cronjob.yaml +++ b/app/bases/summaries-cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-955c53b-1685969471 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml index 6410ae5756..20910abaed 100644 --- a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml @@ -13,7 +13,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-955c53b-1685969471 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: diff --git a/services/summaries/summaries-job.yaml b/services/summaries/summaries-job.yaml index 2ddaf1dd41..dde3a984f6 100644 --- a/services/summaries/summaries-job.yaml +++ b/services/summaries/summaries-job.yaml @@ -8,7 +8,7 @@ spec: spec: containers: - name: summaries - image: gcr.io/track-compliance/services/summaries:master-22237b9-1685459445 # {"$imagepolicy": "flux-system:summaries"} + image: gcr.io/track-compliance/services/summaries:master-955c53b-1685969471 # {"$imagepolicy": "flux-system:summaries"} env: - name: DB_USER valueFrom: From 91db267e423eff503c687c2346314f5a9ad6f12f Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Wed, 7 Jun 2023 14:41:09 -0400 Subject: [PATCH 091/113] Optimize domain connections by user id loader (#4575) * Optimize domain connections by user id loader * Fix query --- .../load-domain-connections-by-user-id.js | 66 ++++++------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/api/src/domain/loaders/load-domain-connections-by-user-id.js b/api/src/domain/loaders/load-domain-connections-by-user-id.js index 01895e6722..f08d72b1cd 100644 --- a/api/src/domain/loaders/load-domain-connections-by-user-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -8,20 +8,12 @@ export const loadDomainConnectionsByUserId = const userDBId = `users/${userKey}` let ownershipOrgsOnly = aql` - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId claims - OPTIONS {order: "bfs"} - RETURN v._key - ) + FOR v, e IN 1..1 OUTBOUND org._id claims ` if (typeof ownership !== 'undefined') { if (ownership) { ownershipOrgsOnly = aql` - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId ownership - OPTIONS {order: "bfs"} - RETURN v._key - ) + FOR v, e IN 1..1 OUTBOUND org._id ownership ` } } @@ -322,7 +314,7 @@ export const loadDomainConnectionsByUserId = if (myTracker) { domainKeysQuery = aql` WITH favourites, users - LET domainKeys = ( + LET collectedDomains = ( FOR v, e IN 1..1 OUTBOUND ${userDBId} favourites OPTIONS {order: "bfs"} RETURN v._key @@ -331,50 +323,37 @@ export const loadDomainConnectionsByUserId = } else if (isSuperAdmin) { domainKeysQuery = aql` WITH affiliations, domains, organizations, users, domainSearch, claims, ownership - LET domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = (FOR org IN organizations RETURN org._id) - FOR orgId IN orgIds + LET collectedDomains = UNIQUE( + FOR org IN organizations ${ownershipOrgsOnly} - RETURN APPEND(keys, claimDomainKeys) - )) + RETURN v + ) ` } else if (!loginRequiredBool) { domainKeysQuery = aql` WITH affiliations, domains, organizations, users, domainSearch, claims, ownership - LET domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = (FOR org IN organizations RETURN org._id) - FOR orgId IN orgIds - LET claimDomainKeys = ( - FOR v, e IN 1..1 OUTBOUND orgId claims - OPTIONS {order: "bfs"} - FILTER v.archived != true - RETURN v._key - ) - RETURN APPEND(keys, claimDomainKeys) - )) + LET collectedDomains = UNIQUE( + FOR org IN organizations + ${ownershipOrgsOnly} + FILTER v.archived != true + RETURN v + ) ` } else { domainKeysQuery = aql` WITH affiliations, domains, organizations, users, domainSearch, claims, ownership - LET domainKeys = UNIQUE(FLATTEN( - LET keys = [] - LET orgIds = ( - FOR v, e IN 1..1 ANY ${userDBId} affiliations - OPTIONS {order: "bfs"} - RETURN e._from - ) - FOR orgId IN orgIds + LET collectedDomains = UNIQUE( + FOR org, affiliationEdge IN 1..1 ANY ${userDBId} affiliations + FILTER affiliationEdge.permission != "pending" ${ownershipOrgsOnly} - RETURN APPEND(keys, claimDomainKeys) - )) + RETURN v + ) ` } let domainQuery = aql`` - let loopString = aql`FOR domain IN domains` - let totalCount = aql`LENGTH(domainKeys)` + let loopString = aql`FOR domain IN collectedDomains` + let totalCount = aql`LENGTH(collectedDomains)` if (typeof search !== 'undefined' && search !== '') { search = cleanseInput(search) domainQuery = aql` @@ -384,7 +363,7 @@ export const loadDomainConnectionsByUserId = LET token = LOWER(tokenItem) FOR domain IN domainSearch SEARCH ANALYZER(domain.domain LIKE CONCAT("%", token, "%"), "space-delimiter-analyzer") - FILTER domain._key IN domainKeys + FILTER domain IN collectedDomains RETURN domain ) ` @@ -408,7 +387,6 @@ export const loadDomainConnectionsByUserId = LET retrievedDomains = ( ${loopString} - FILTER domain._key IN domainKeys ${showArchivedDomains} ${afterTemplate} ${beforeTemplate} @@ -420,7 +398,6 @@ export const loadDomainConnectionsByUserId = LET hasNextPage = (LENGTH( ${loopString} - FILTER domain._key IN domainKeys ${showArchivedDomains} ${hasNextPageFilter} SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 @@ -429,7 +406,6 @@ export const loadDomainConnectionsByUserId = LET hasPreviousPage = (LENGTH( ${loopString} - FILTER domain._key IN domainKeys ${showArchivedDomains} ${hasPreviousPageFilter} SORT ${sortByField} TO_NUMBER(domain._key) ${sortString} LIMIT 1 From 31a8b8ae96737d2fc70a2f545defcb5d6618682a Mon Sep 17 00:00:00 2001 From: fluxbot Date: Wed, 7 Jun 2023 18:46:31 +0000 Subject: [PATCH 092/113] [ci skip] gcr.io/track-compliance/api-js:master-91db267-1686163512 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index deb3bde16d..8ca1dd7bce 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-b62b049-1685640973 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-91db267-1686163512 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 62c5f72a92..eb157514ef 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-b62b049-1685640973 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-91db267-1686163512 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 77ec9709141e53f21dc1354ca2cc7f97d8d33277 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Thu, 8 Jun 2023 11:33:12 -0400 Subject: [PATCH 093/113] Clean up transaction calls (#4578) * Clean up transaction calls * Clean up transaction calls --- .../mutations/leave-organization.js | 18 ++--- .../mutations/remove-user-from-org.js | 9 ++- .../mutations/remove-organization.js | 73 ++++++------------- api/src/user/mutations/close-account.js | 14 ++-- 4 files changed, 41 insertions(+), 73 deletions(-) diff --git a/api/src/affiliation/mutations/leave-organization.js b/api/src/affiliation/mutations/leave-organization.js index b908f5616a..8d65e334e3 100644 --- a/api/src/affiliation/mutations/leave-organization.js +++ b/api/src/affiliation/mutations/leave-organization.js @@ -242,10 +242,9 @@ export const leaveOrganization = new mutationWithClientMutationId({ } try { - await Promise.all([ - trx.step( - () => - query` + await trx.step( + () => + query` WITH affiliations, organizations, users LET userEdges = ( FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations @@ -258,16 +257,15 @@ export const leaveOrganization = new mutationWithClientMutationId({ ) RETURN true `, - ), - trx.step( - () => - query` + ) + await trx.step( + () => + query` WITH organizations REMOVE ${org._key} IN organizations OPTIONS { waitForSync: true } `, - ), - ]) + ) } catch (err) { console.error( `Trx step error occurred while attempting to remove affiliations, and the org for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, diff --git a/api/src/affiliation/mutations/remove-user-from-org.js b/api/src/affiliation/mutations/remove-user-from-org.js index 971d7069fd..b31b07d9d1 100644 --- a/api/src/affiliation/mutations/remove-user-from-org.js +++ b/api/src/affiliation/mutations/remove-user-from-org.js @@ -139,16 +139,17 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ const trx = await transaction(collections) try { - await trx.step(async () => { - query` + await trx.step( + () => + query` WITH affiliations, organizations, users FOR aff IN affiliations FILTER aff._from == ${requestedOrg._id} FILTER aff._to == ${requestedUser._id} REMOVE aff IN affiliations RETURN true - ` - }) + `, + ) } catch (err) { console.error( `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, diff --git a/api/src/organization/mutations/remove-organization.js b/api/src/organization/mutations/remove-organization.js index c920e34f63..56eeda0b61 100644 --- a/api/src/organization/mutations/remove-organization.js +++ b/api/src/organization/mutations/remove-organization.js @@ -17,8 +17,7 @@ export const removeOrganization = new mutationWithClientMutationId({ outputFields: () => ({ result: { type: GraphQLNonNull(removeOrganizationUnion), - description: - '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', + description: '`RemoveOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -48,9 +47,7 @@ export const removeOrganization = new mutationWithClientMutationId({ // Check to see if org exists if (!organization) { - console.warn( - `User: ${userKey} attempted to remove org: ${orgId}, but there is no org associated with that id.`, - ) + console.warn(`User: ${userKey} attempted to remove org: ${orgId}, but there is no org associated with that id.`) return { _type: 'error', code: 400, @@ -82,9 +79,7 @@ export const removeOrganization = new mutationWithClientMutationId({ return { _type: 'error', code: 403, - description: i18n._( - t`Permission Denied: Please contact super admin for help with removing organization.`, - ), + description: i18n._(t`Permission Denied: Please contact super admin for help with removing organization.`), } } @@ -103,9 +98,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Database error occurred for user: ${userKey} while attempting to get dmarcSummaryInfo while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } let dmarcSummaryCheckList @@ -115,9 +108,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Cursor error occurred for user: ${userKey} while attempting to get dmarcSummaryInfo while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } for (const ownership of dmarcSummaryCheckList) { @@ -147,9 +138,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Trx step error occurred for user: ${userKey} while attempting to remove dmarc summaries while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } try { @@ -164,9 +153,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Trx step error occurred for user: ${userKey} while attempting to remove ownerships while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } } @@ -196,9 +183,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Database error occurred for user: ${userKey} while attempting to gather domain count while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } let domainInfo @@ -208,9 +193,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Cursor error occurred for user: ${userKey} while attempting to gather domain count while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } for (const domain of domainInfo) { @@ -284,9 +267,7 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Trx step error occurred for user: ${userKey} while attempting to remove domains while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } } } @@ -310,10 +291,9 @@ export const removeOrganization = new mutationWithClientMutationId({ } try { - await Promise.all([ - trx.step( - () => - query` + await trx.step( + () => + query` WITH affiliations, organizations, users LET userEdges = ( FOR v, e IN 1..1 OUTBOUND ${organization._id} affiliations @@ -326,23 +306,20 @@ export const removeOrganization = new mutationWithClientMutationId({ ) RETURN true `, - ), - trx.step( - () => - query` + ) + await trx.step( + () => + query` WITH organizations REMOVE ${organization._key} IN organizations OPTIONS { waitForSync: true } `, - ), - ]) + ) } catch (err) { console.error( `Trx step error occurred for user: ${userKey} while attempting to remove affiliations, and the org while removing org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } try { @@ -351,14 +328,10 @@ export const removeOrganization = new mutationWithClientMutationId({ console.error( `Trx commit error occurred for user: ${userKey} while attempting remove of org: ${organization._key}, ${err}`, ) - throw new Error( - i18n._(t`Unable to remove organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to remove organization. Please try again.`)) } - console.info( - `User: ${userKey} successfully removed org: ${organization._key}.`, - ) + console.info(`User: ${userKey} successfully removed org: ${organization._key}.`) await logActivity({ transaction, collections, @@ -380,9 +353,7 @@ export const removeOrganization = new mutationWithClientMutationId({ return { _type: 'result', - status: i18n._( - t`Successfully removed organization: ${organization.slug}.`, - ), + status: i18n._(t`Successfully removed organization: ${organization.slug}.`), organization, } }, diff --git a/api/src/user/mutations/close-account.js b/api/src/user/mutations/close-account.js index 78eec8d8c3..cd3a849770 100644 --- a/api/src/user/mutations/close-account.js +++ b/api/src/user/mutations/close-account.js @@ -313,9 +313,8 @@ export const closeAccount = new mutationWithClientMutationId({ // remove users affiliation try { - await Promise.all([ - trx.step( - () => query` + await trx.step( + () => query` WITH affiliations, organizations, users LET userEdges = ( FOR v, e IN 1..1 INBOUND ${affiliation._from} affiliations @@ -328,15 +327,14 @@ export const closeAccount = new mutationWithClientMutationId({ ) RETURN true `, - ), - trx.step( - () => query` + ) + await trx.step( + () => query` WITH organizations REMOVE PARSE_IDENTIFIER(${affiliation._from}).key IN organizations OPTIONS { waitForSync: true } `, - ), - ]) + ) } catch (err) { console.error( `Trx step error occurred when removing ownership org and users affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, From 07bb1bfabce5d92b47c18c2913cd3d6a41014a46 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Thu, 8 Jun 2023 15:38:03 +0000 Subject: [PATCH 094/113] [ci skip] gcr.io/track-compliance/api-js:master-77ec970-1686238568 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 8ca1dd7bce..b0fdc57120 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-91db267-1686163512 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-77ec970-1686238568 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index eb157514ef..37afe6036c 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-91db267-1686163512 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-77ec970-1686238568 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 47bbe8dc2899d18622e000049b75b2926d7b31c5 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Fri, 9 Jun 2023 07:40:55 -0400 Subject: [PATCH 095/113] Fix collected domains query for myTracker (#4579) --- api/src/domain/loaders/load-domain-connections-by-user-id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/domain/loaders/load-domain-connections-by-user-id.js b/api/src/domain/loaders/load-domain-connections-by-user-id.js index f08d72b1cd..a0c2fa90e0 100644 --- a/api/src/domain/loaders/load-domain-connections-by-user-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -313,7 +313,7 @@ export const loadDomainConnectionsByUserId = let domainKeysQuery if (myTracker) { domainKeysQuery = aql` - WITH favourites, users + WITH favourites, users, domains LET collectedDomains = ( FOR v, e IN 1..1 OUTBOUND ${userDBId} favourites OPTIONS {order: "bfs"} From ecdcb79b4467e1d7afc948117fab6b446bc6c329 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 9 Jun 2023 11:45:45 +0000 Subject: [PATCH 096/113] [ci skip] gcr.io/track-compliance/api-js:master-47bbe8d-1686311047 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index b0fdc57120..edcbe0a043 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-77ec970-1686238568 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-47bbe8d-1686311047 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 37afe6036c..9559c95956 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-77ec970-1686238568 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-47bbe8d-1686311047 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From a1df8f932717920b39b1fb1addeb1b030d3c9d16 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Fri, 9 Jun 2023 08:43:22 -0400 Subject: [PATCH 097/113] Return domain object for collected domains in myTracker view (#4581) --- api/src/domain/loaders/load-domain-connections-by-user-id.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/domain/loaders/load-domain-connections-by-user-id.js b/api/src/domain/loaders/load-domain-connections-by-user-id.js index a0c2fa90e0..9ccd692fca 100644 --- a/api/src/domain/loaders/load-domain-connections-by-user-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -317,7 +317,7 @@ export const loadDomainConnectionsByUserId = LET collectedDomains = ( FOR v, e IN 1..1 OUTBOUND ${userDBId} favourites OPTIONS {order: "bfs"} - RETURN v._key + RETURN v ) ` } else if (isSuperAdmin) { From 7ed514662d8eebc1c8b169f33b5b78dcedcc9058 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 9 Jun 2023 12:47:58 +0000 Subject: [PATCH 098/113] [ci skip] gcr.io/track-compliance/api-js:master-a1df8f9-1686314767 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index edcbe0a043..fbba4cc700 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-47bbe8d-1686311047 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-a1df8f9-1686314767 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index 9559c95956..af09adb2af 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-47bbe8d-1686311047 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-a1df8f9-1686314767 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From 7f87d836dcfce714481939d17b580cf34ac6bde9 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:16:47 -0400 Subject: [PATCH 099/113] Update k8s cronjob versions (#4580) --- app/aks/backup-cronjob.yaml | 2 +- app/bases/dmarc-report-cronjob.yaml | 2 +- app/bases/guidance-cronjob.yaml | 2 +- app/bases/scan-dispatcher-cronjob.yaml | 2 +- app/bases/summaries-cronjob.yaml | 2 +- app/gke/backup-cronjob.yaml | 2 +- app/production/backup-cronjob.yaml | 2 +- app/staging/backup-cronjob.yaml | 2 +- app/test/backup-cronjob.yaml | 2 +- k8s/apps/bases/api/org-footprint-cronjob.yaml | 2 +- k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml | 2 +- k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml | 2 +- k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml | 2 +- k8s/apps/overlays/gke/domain-dispatcher-suspend-patch.yaml | 2 +- k8s/apps/overlays/staging/domain-dispatcher-suspend-patch.yaml | 2 +- k8s/infrastructure/overlays/gke/backup-cronjob.yaml | 2 +- k8s/infrastructure/overlays/production/backup-cronjob.yaml | 2 +- k8s/infrastructure/overlays/staging/backup-cronjob.yaml | 2 +- k8s/infrastructure/overlays/test/backup-cronjob.yaml | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/aks/backup-cronjob.yaml b/app/aks/backup-cronjob.yaml index 66f75dc2e2..099a77167e 100644 --- a/app/aks/backup-cronjob.yaml +++ b/app/aks/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/app/bases/dmarc-report-cronjob.yaml b/app/bases/dmarc-report-cronjob.yaml index caf3492199..cd9611245a 100644 --- a/app/bases/dmarc-report-cronjob.yaml +++ b/app/bases/dmarc-report-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: dmarc-report diff --git a/app/bases/guidance-cronjob.yaml b/app/bases/guidance-cronjob.yaml index ab28d8a2b1..0b0c31fc9f 100755 --- a/app/bases/guidance-cronjob.yaml +++ b/app/bases/guidance-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: guidance diff --git a/app/bases/scan-dispatcher-cronjob.yaml b/app/bases/scan-dispatcher-cronjob.yaml index 57f28f3d23..c982010ed8 100644 --- a/app/bases/scan-dispatcher-cronjob.yaml +++ b/app/bases/scan-dispatcher-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: scan-dispatcher diff --git a/app/bases/summaries-cronjob.yaml b/app/bases/summaries-cronjob.yaml index 20910abaed..35ef98f9de 100644 --- a/app/bases/summaries-cronjob.yaml +++ b/app/bases/summaries-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: summaries diff --git a/app/gke/backup-cronjob.yaml b/app/gke/backup-cronjob.yaml index 25adfba158..8815a7b084 100644 --- a/app/gke/backup-cronjob.yaml +++ b/app/gke/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/app/production/backup-cronjob.yaml b/app/production/backup-cronjob.yaml index 66f75dc2e2..099a77167e 100644 --- a/app/production/backup-cronjob.yaml +++ b/app/production/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/app/staging/backup-cronjob.yaml b/app/staging/backup-cronjob.yaml index 66f75dc2e2..099a77167e 100644 --- a/app/staging/backup-cronjob.yaml +++ b/app/staging/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/app/test/backup-cronjob.yaml b/app/test/backup-cronjob.yaml index e86cb5ba37..5c4ae9b263 100644 --- a/app/test/backup-cronjob.yaml +++ b/app/test/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/k8s/apps/bases/api/org-footprint-cronjob.yaml b/k8s/apps/bases/api/org-footprint-cronjob.yaml index ea997fc180..46dfd9ea59 100644 --- a/k8s/apps/bases/api/org-footprint-cronjob.yaml +++ b/k8s/apps/bases/api/org-footprint-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: org-footprint diff --git a/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml index caf3492199..cd9611245a 100644 --- a/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/dmarc-report-cronjob/cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: dmarc-report diff --git a/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml index 8631d6169f..f4408db2ac 100644 --- a/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/domain-dispatcher-cronjob/cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: domain-dispatcher diff --git a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml index 20910abaed..35ef98f9de 100644 --- a/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml +++ b/k8s/apps/bases/scanners/summaries-cronjob/cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: summaries diff --git a/k8s/apps/overlays/gke/domain-dispatcher-suspend-patch.yaml b/k8s/apps/overlays/gke/domain-dispatcher-suspend-patch.yaml index 8a5968f08f..f54d1d5eab 100644 --- a/k8s/apps/overlays/gke/domain-dispatcher-suspend-patch.yaml +++ b/k8s/apps/overlays/gke/domain-dispatcher-suspend-patch.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: domain-dispatcher diff --git a/k8s/apps/overlays/staging/domain-dispatcher-suspend-patch.yaml b/k8s/apps/overlays/staging/domain-dispatcher-suspend-patch.yaml index 8a5968f08f..f54d1d5eab 100644 --- a/k8s/apps/overlays/staging/domain-dispatcher-suspend-patch.yaml +++ b/k8s/apps/overlays/staging/domain-dispatcher-suspend-patch.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: name: domain-dispatcher diff --git a/k8s/infrastructure/overlays/gke/backup-cronjob.yaml b/k8s/infrastructure/overlays/gke/backup-cronjob.yaml index 45e6cef7dd..06ce3410e2 100644 --- a/k8s/infrastructure/overlays/gke/backup-cronjob.yaml +++ b/k8s/infrastructure/overlays/gke/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/k8s/infrastructure/overlays/production/backup-cronjob.yaml b/k8s/infrastructure/overlays/production/backup-cronjob.yaml index 10b2e602bd..20c0620d50 100644 --- a/k8s/infrastructure/overlays/production/backup-cronjob.yaml +++ b/k8s/infrastructure/overlays/production/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/k8s/infrastructure/overlays/staging/backup-cronjob.yaml b/k8s/infrastructure/overlays/staging/backup-cronjob.yaml index 10b2e602bd..20c0620d50 100644 --- a/k8s/infrastructure/overlays/staging/backup-cronjob.yaml +++ b/k8s/infrastructure/overlays/staging/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: diff --git a/k8s/infrastructure/overlays/test/backup-cronjob.yaml b/k8s/infrastructure/overlays/test/backup-cronjob.yaml index e86cb5ba37..5c4ae9b263 100644 --- a/k8s/infrastructure/overlays/test/backup-cronjob.yaml +++ b/k8s/infrastructure/overlays/test/backup-cronjob.yaml @@ -1,4 +1,4 @@ -apiVersion: batch/v1beta1 +apiVersion: batch/v1 kind: CronJob metadata: labels: From 130492c7aa003ad9e014f6e6d8150b0b3d63c162 Mon Sep 17 00:00:00 2001 From: Kyle Sullivan <47400288+FestiveKyle@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:20:38 -0400 Subject: [PATCH 100/113] Ac update (#4561) * Change owner affiliation property * Change owner property to permission * Do not remove owner affiliations when verifying org * Only remove affiliation when leaving org - non-destructive for org and domains * Fix checkDomainPermission function (include pending filter) * Require org affiliation to request scan * Remove used variable * Audit log scan requests * Only allow affiliated users to download org domain list * Only allow super admins to export all orgs and domains * Log organization export * Fix userkey usage * Add domainKey filter * Allow permission to domains if part of verified org network * Allow permission to domain ownership if part of verified org network * Add permission check directly to domains * Remove check for loginRequired * Fix return value * Require affiliation permission to check some properties of domains * Check for user permission when showing scan and scan detail buttons * Only allow admin, owner, super_admin to create domain * Allow admin, owner, super_admin to remove domains from non-verified orgs * Fix typo * Allow admin, owner, super_admin to update domain * Update permissions required to invite users to org * Add owner role * Only protect owner and super_admin roles and deny "users" from updating users * Update required permissions * Allow owner role users to read audit logs * Fix tests * Rearrange permission check * Update translations * Fix tests * Fix tests * Fix tests * Include missing domainId parameter * Fix tests * Fix tests * Fix tests * Fix tests * Fix return value * Fix tests * Update translations * Update translations * Update translations * Require email validation for scan details * Pin arangodb cloudbuild version * Fix format * Remove userRequired call for userHasPermission * Remove userRequired call for hasDMARCReport * Fix return value * fix isLoggedIn func for request org invite btns * Fix _id reference * Fix test * Fix owner not seeing org connection * Add property to check if user has permission for org * Fix org connection loader * Clean up syntax * Clean up syntax * Fix dmarc summary connection loader, add verified network and pending filter * Require user log in for dmarc summaries query * Fix domain connections loader for verified org network * Fix query * Fix query * Update test * Clean up auth required for retrieving orgs * Fix test case expected return value * Fix test * Allow admins to downgrade other admins * Fix org loader when login required * Enable "verified" network for dmarc summary connections when login required * Hide export domains button for everybody but super admins * Fix and expand logging for domain scan and org exports * Remove unused variable * Fix test * Allow org ownership transfer for verified orgs * Move myTracker and dmarc summary links to login required area * Allow owners to query org users * Rearrange nav links * Handle errors for DMARC page * Update translations * Fix test * Add loader optimizations * Check for invalid invitation roles * Fix traversal direction for affiliations * Only remove affiliations and user when closing account, do not remove orgs/domains/scan data --------- Co-authored-by: lcampbell --- api/cloudbuild.yaml | 2 +- .../__tests__/leave-organization.test.js | 2977 +-------- .../__tests__/remove-user-from-org.test.js | 682 +- .../__tests__/transfer-org-ownership.test.js | 354 +- .../__tests__/update-user-role.test.js | 821 +-- .../mutations/invite-user-to-org.js | 10 +- .../mutations/leave-organization.js | 244 +- .../mutations/remove-user-from-org.js | 147 +- .../mutations/request-org-affiliation.js | 1 - .../mutations/transfer-org-ownership.js | 84 +- .../affiliation/mutations/update-user-role.js | 86 +- .../loaders/load-audit-logs-by-org-id.js | 40 +- api/src/audit-logs/queries/find-audit-logs.js | 25 +- .../__tests__/check-domain-ownership.test.js | 336 +- .../__tests__/check-domain-permission.test.js | 82 +- .../auth/__tests__/check-org-owner.test.js | 66 +- .../auth/__tests__/check-permission.test.js | 92 +- .../check-user-belongs-to-org.test.js | 45 +- api/src/auth/check-domain-ownership.js | 45 +- api/src/auth/check-domain-permission.js | 103 +- api/src/auth/check-org-owner.js | 52 +- api/src/auth/check-permission.js | 52 +- .../load-dmarc-sum-connections-by-user-id.js | 672 +- .../queries/find-my-dmarc-summaries.js | 16 +- ...load-domain-connections-by-user-id.test.js | 471 +- .../load-domain-connections-by-user-id.js | 18 +- .../mutations/__tests__/create-domain.test.js | 666 +- .../mutations/__tests__/update-domain.test.js | 320 +- api/src/domain/mutations/create-domain.js | 2 +- api/src/domain/mutations/remove-domain.js | 2 +- api/src/domain/mutations/request-scan.js | 86 +- api/src/domain/mutations/update-domain.js | 82 +- .../domain/objects/__tests__/domain.test.js | 192 +- api/src/domain/objects/domain.js | 114 +- .../queries/__tests__/find-my-domains.test.js | 65 +- api/src/enums/index.js | 1 + api/src/enums/invitation-roles.js | 21 + api/src/enums/roles.js | 5 + api/src/enums/user-action.js | 8 + api/src/initialize-loaders.js | 93 +- api/src/locale/en/messages.js | 2 +- api/src/locale/en/messages.po | 387 +- api/src/locale/fr/messages.js | 2 +- api/src/locale/fr/messages.po | 387 +- ...oad-organization-connections-by-user-id.js | 747 ++- .../__tests__/verify-organization.test.js | 83 +- .../mutations/create-organization.js | 67 +- .../mutations/verify-organization.js | 45 +- api/src/organization/objects/organization.js | 101 +- ...t-all-organization-domain-statuses.test.js | 85 +- .../queries/find-organization-by-slug.js | 50 +- .../get-all-organization-domain-statuses.js | 36 +- .../mutations/__tests__/close-account.test.js | 5504 +++-------------- api/src/user/mutations/close-account.js | 267 - api/src/user/mutations/sign-up.js | 1 - frontend/src/admin/AuditLogTable.js | 190 +- frontend/src/admin/UserListModal.js | 2 +- .../src/admin/__tests__/UserListModal.test.js | 10 +- frontend/src/app/App.js | 20 +- frontend/src/dmarc/DmarcReportPage.js | 341 +- .../dmarc/__tests__/DmarcReportPage.test.js | 126 +- frontend/src/domains/DomainCard.js | 33 +- frontend/src/domains/DomainsPage.js | 68 +- .../src/domains/__tests__/DomainsPage.test.js | 25 +- frontend/src/graphql/queries.js | 3 + frontend/src/locales/en.js | 2 +- frontend/src/locales/en.po | 598 +- frontend/src/locales/fr.js | 2 +- frontend/src/locales/fr.po | 604 +- .../OrganizationDetails.js | 26 +- .../OrganizationDomains.js | 15 +- frontend/src/organizations/Organizations.js | 2 +- 72 files changed, 5219 insertions(+), 13792 deletions(-) create mode 100644 api/src/enums/invitation-roles.js diff --git a/api/cloudbuild.yaml b/api/cloudbuild.yaml index 479326a33f..bed41c92c0 100644 --- a/api/cloudbuild.yaml +++ b/api/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: [ '-c', - 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4', + 'docker run -d --network=cloudbuild -p=8529:8529 -e ARANGO_ROOT_PASSWORD=$_DB_PASS --name=arangodb arangodb/arangodb:3.10.4' ] - name: mikewilliamson/wait-for diff --git a/api/src/affiliation/mutations/__tests__/leave-organization.test.js b/api/src/affiliation/mutations/__tests__/leave-organization.test.js index 2a639fe7f2..cb455c6d3b 100644 --- a/api/src/affiliation/mutations/__tests__/leave-organization.test.js +++ b/api/src/affiliation/mutations/__tests__/leave-organization.test.js @@ -1,33 +1,23 @@ -import {setupI18n} from '@lingui/core' -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {checkOrgOwner, userRequired, verifiedRequired} from '../../../auth' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' -import {cleanseInput} from '../../../validators' -import {createMutationSchema} from '../../../mutation' -import {createQuerySchema} from '../../../query' +import { checkOrgOwner, userRequired, verifiedRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { cleanseInput } from '../../../validators' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY} = process.env +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env describe('given a successful leave', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - org, - domain, - domain2 + let query, drop, truncate, schema, collections, transaction, i18n, user, org, domain, domain2 const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -45,8 +35,8 @@ describe('given a successful leave', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -56,7 +46,7 @@ describe('given a successful leave', () => { }) }) beforeEach(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -112,13 +102,13 @@ describe('given a successful leave', () => { _to: domain2._id, }) - const dns = await collections.dns.save({dns: true}) + const dns = await collections.dns.save({ dns: true }) await collections.domainsDNS.save({ _from: domain._id, _to: dns._id, }) - const web = await collections.web.save({web: true}) + const web = await collections.web.save({ web: true }) await collections.domainsWeb.save({ _from: domain._id, _to: web._id, @@ -151,330 +141,17 @@ describe('given a successful leave', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) describe('only one org claims the domains', () => { - describe('org is owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - }) - describe('org is not the owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - }) - it('removes all scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testWebScanCursor = - await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` - const testWebScan = await testWebScanCursor.next() - expect(testWebScan).toEqual(undefined) - - const testDNSCursor = - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` - const testDNS = await testDNSCursor.next() - expect(testDNS).toEqual(undefined) - - const testWebCursor = - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` - const testWeb = await testWebCursor.next() - expect(testWeb).toEqual(undefined) - }) - it('removes all domain, affiliation, and org data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -527,7 +204,7 @@ describe('given a successful leave', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -537,7 +214,7 @@ describe('given a successful leave', () => { userKey: user._key, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -545,17 +222,14 @@ describe('given a successful leave', () => { data: { leaveOrganization: { result: { - status: - 'Successfully left organization: treasury-board-secretariat', + status: 'Successfully left organization: treasury-board-secretariat', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) describe('users language is set to french', () => { @@ -563,8 +237,8 @@ describe('given a successful leave', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -617,7 +291,7 @@ describe('given a successful leave', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -627,7 +301,7 @@ describe('given a successful leave', () => { userKey: user._key, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -635,17 +309,14 @@ describe('given a successful leave', () => { data: { leaveOrganization: { result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", + status: "L'organisation a été quittée avec succès: treasury-board-secretariat", }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) }) @@ -681,35 +352,44 @@ describe('given a successful leave', () => { _to: domain._id, }) }) - describe('org is owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, + + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) }) - it('removes all dmarc summary data', async () => { - await graphql( + it('returns status success message', async () => { + const response = await graphql( schema, ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', org._key)}" + } + ) { + result { + ... on LeaveOrganizationResult { + status } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } + ... on AffiliationError { + code + description } } } - `, + } + `, null, { i18n, @@ -732,7 +412,7 @@ describe('given a successful leave', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -742,39 +422,41 @@ describe('given a successful leave', () => { userKey: user._key, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: 'Successfully left organization: treasury-board-secretariat', + }, + }, + }, + } - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) - describe('org is not the owner of dmarc data', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org2._id, - _to: domain._id, + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) }) - it('does not remove all dmarc summary data', async () => { - await graphql( + it('returns status success message', async () => { + const response = await graphql( schema, ` mutation { @@ -817,7 +499,7 @@ describe('given a successful leave', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -827,860 +509,24 @@ describe('given a successful leave', () => { userKey: user._key, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() + const expectedResult = { + data: { + leaveOrganization: { + result: { + status: "L'organisation a été quittée avec succès: treasury-board-secretariat", + }, + }, + }, + } - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User: ${user._key} successfully left org: treasury-board-secretariat.`]) }) }) - it('does not remove all scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testWebScanCursor = - await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` - const testWebScan = await testWebScanCursor.next() - expect(testWebScan).toEqual(true) - - const testDNSCursor = - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` - const testDNS = await testDNSCursor.next() - expect(testDNS).toEqual(true) - - const testWebCursor = - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` - const testWeb = await testWebCursor.next() - expect(testWeb).toEqual(true) - - }) - it('does not remove all domain', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes affiliation, and org data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - FILTER org.orgDetails.en.slug != "treasury-board-secretariat-2" - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - 'Successfully left organization: treasury-board-secretariat', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: false, - }) - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove all dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove scan data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testWebScanCursor = - await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan.webScan` - const testWebScan = await testWebScanCursor.next() - expect(testWebScan).toEqual(true) - - const testDNSCursor = - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult.dns` - const testDNS = await testDNSCursor.next() - expect(testDNS).toEqual(true) - - const testWebCursor = - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult.web` - const testWeb = await testWebCursor.next() - expect(testWeb).toEqual(true) - - }) - it('does not remove org and domain information', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testOrgCursor = - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - const testOrg = await testOrgCursor.next() - expect(testOrg).toBeDefined() - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes affiliation data', async () => { - await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - 'Successfully left organization: treasury-board-secretariat', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) - }) - describe('language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns status success message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({i18n, query, userKey: user._key}), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - status: - "L'organisation a été quittée avec succès: treasury-board-secretariat", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully left org: treasury-board-secretariat.`, - ]) - }) }) }) }) @@ -1699,778 +545,86 @@ describe('given an unsuccessful leave', () => { schema = new GraphQLSchema({ query: createQuerySchema(), mutation: createMutationSchema(), - }) - }) - afterEach(async () => { - consoleOutput.length = 0 - }) - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('org cannot be found', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn(), - collections: jest.fn(), - transaction: jest.fn(), - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - leaveOrganization: { - result: { - code: 400, - description: 'Unable to leave undefined organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User 123 attempted to leave undefined organization: 123`, - ]) - }) - }) - describe('user is an org owner', () => { - describe('database error occurs', () => { - describe('when querying dmarcSummaryCheck', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while checking for dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - describe('when querying domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockReturnValueOnce({ - all: jest.fn().mockReturnValue([]), - }) - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting dmarc summary info', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting dmarc summary info for org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when getting domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when removing dmarcSummary data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove ownership for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing web scan result data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } + }) + }) + afterEach(async () => { + consoleOutput.length = 0 + }) + describe('language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) + }) + describe('org cannot be found', () => { + it('returns an error message', async () => { + const response = await graphql( + schema, + ` + mutation { + leaveOrganization ( + input: { + orgId: "${toGlobalId('organizations', 123)}" } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove web data for test.gc.ca in org: undefined, Error: Step error occurred.`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } + ) { + result { + ... on LeaveOrganizationResult { + status } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove DNS data for test.gc.ca in org: undefined, error: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } + ... on AffiliationError { + code + description } } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, + } + } + `, + null, + { + i18n, + query: jest.fn(), + collections: jest.fn(), + transaction: jest.fn(), + userKey: '123', + auth: { + checkOrgOwner: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ + _key: '123', + emailValidated: true, + }), + verifiedRequired: verifiedRequired({ i18n }), + }, + loaders: { + loadOrgByKey: { + load: jest.fn().mockReturnValue(undefined), }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove domains for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing affiliation, and org data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) + }, + validators: { cleanseInput }, + }, + ) - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, + const expectedResult = { + data: { + leaveOrganization: { + result: { + code: 400, + description: 'Unable to leave undefined organization.', }, - ) - - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + }, + }, + } - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove affiliations, and the org for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) + expect(response).toEqual(expectedResult) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) }) }) describe('user is not an org owner', () => { @@ -2482,9 +636,7 @@ describe('given an unsuccessful leave', () => { }) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), }) const response = await graphql( @@ -2512,7 +664,7 @@ describe('given an unsuccessful leave', () => { { i18n, query: mockedQuery, - collections: jest.fn({property: 'string'}), + collections: jest.fn({ property: 'string' }), transaction: mockedTransaction, userKey: '123', auth: { @@ -2521,20 +673,18 @@ describe('given an unsuccessful leave', () => { _key: '123', emailValidated: true, }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + const error = [new GraphQLError('Unable leave organization. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2582,7 +732,7 @@ describe('given an unsuccessful leave', () => { { i18n, query: mockedQuery, - collections: jest.fn({property: 'string'}), + collections: jest.fn({ property: 'string' }), transaction: mockedTransaction, userKey: '123', auth: { @@ -2591,20 +741,18 @@ describe('given an unsuccessful leave', () => { _key: '123', emailValidated: true, }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError('Unable leave organization. Please try again.'), - ] + const error = [new GraphQLError('Unable leave organization. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2618,8 +766,8 @@ describe('given an unsuccessful leave', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2664,14 +812,14 @@ describe('given an unsuccessful leave', () => { _key: '123', emailValidated: true, }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: { load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2680,727 +828,14 @@ describe('given an unsuccessful leave', () => { leaveOrganization: { result: { code: 400, - description: - 'Impossible de quitter une organisation non définie.', + description: 'Impossible de quitter une organisation non définie.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User 123 attempted to leave undefined organization: 123`, - ]) - }) - }) - describe('user is an org owner', () => { - describe('database error occurs', () => { - describe('when querying dmarcSummaryCheck', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while checking for dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - describe('when querying domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockReturnValueOnce({ - all: jest.fn().mockReturnValue([]), - }) - .mockRejectedValue(new Error('Database error occurred.')) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Database error occurred.`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting dmarc summary info', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting dmarc summary info for org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - describe('when getting domainInfo', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockRejectedValue(new Error('Cursor error occurred.')), - }) - - const mockedTransaction = jest.fn() - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred while while gathering domainInfo org: 123, when user: 123 attempted to leave: Error: Cursor error occurred.`, - ]) - }) - }) - }) - describe('transaction step error occurs', () => { - describe('when removing dmarcSummary data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove dmarc summaries for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing ownership data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest.fn().mockReturnValue([{}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove ownership for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing web result data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove web data for undefined in org: undefined, Error: Step error occurred.`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: 123 attempted to remove DNS data for test.gc.ca in org: undefined, error: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing domain', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove domains for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) - describe('when removing affiliation, and org data', () => { - it('throws an error', async () => { - const mockedQuery = jest.fn().mockReturnValue({ - all: jest - .fn() - .mockReturnValueOnce([]) - .mockReturnValue([{count: 1, domain: "test.gc.ca"}]), - }) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step error occurred.')), - }) - - const response = await graphql( - schema, - ` - mutation { - leaveOrganization ( - input: { - orgId: "${toGlobalId('organizations', 123)}" - } - ) { - result { - ... on LeaveOrganizationResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: jest.fn({property: 'string'}), - transaction: mockedTransaction, - userKey: '123', - auth: { - checkOrgOwner: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - emailValidated: true, - }), - verifiedRequired: verifiedRequired({i18n}), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while attempting to remove affiliations, and the org for org: 123, when user: 123 attempted to leave: Error: Step error occurred.`, - ]) - }) - }) + expect(consoleOutput).toEqual([`User 123 attempted to leave undefined organization: 123`]) }) }) describe('user is not an org owner', () => { @@ -3412,9 +847,7 @@ describe('given an unsuccessful leave', () => { }) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('Step error occurred.')), + step: jest.fn().mockRejectedValue(new Error('Step error occurred.')), }) const response = await graphql( @@ -3442,7 +875,7 @@ describe('given an unsuccessful leave', () => { { i18n, query: mockedQuery, - collections: jest.fn({property: 'string'}), + collections: jest.fn({ property: 'string' }), transaction: mockedTransaction, userKey: '123', auth: { @@ -3451,22 +884,18 @@ describe('given an unsuccessful leave', () => { _key: '123', emailValidated: true, }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3514,7 +943,7 @@ describe('given an unsuccessful leave', () => { { i18n, query: mockedQuery, - collections: jest.fn({property: 'string'}), + collections: jest.fn({ property: 'string' }), transaction: mockedTransaction, userKey: '123', auth: { @@ -3523,22 +952,18 @@ describe('given an unsuccessful leave', () => { _key: '123', emailValidated: true, }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 123}), + load: jest.fn().mockReturnValue({ _key: 123 }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - "Impossible de quitter l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de quitter l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js index e70015f7a7..04baf71703 100644 --- a/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js +++ b/api/src/affiliation/mutations/__tests__/remove-user-from-org.test.js @@ -1,26 +1,21 @@ -import {setupI18n} from '@lingui/core' -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {createQuerySchema} from '../../../query' -import {createMutationSchema} from '../../../mutation' -import {cleanseInput} from '../../../validators' -import { - checkPermission, - userRequired, - verifiedRequired, - tfaRequired, -} from '../../../auth' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' -import {loadAffiliationByKey} from '../../loaders' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { loadAffiliationByKey } from '../../loaders' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env const orgOneData = { verified: true, @@ -117,18 +112,7 @@ const userData = { } describe('given the removeUserFromOrg mutation', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - orgOne, - orgTwo, - admin, - affiliation + let query, drop, truncate, schema, collections, transaction, i18n, user, orgOne, orgTwo, admin, affiliation const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -146,8 +130,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -162,7 +146,7 @@ describe('given the removeUserFromOrg mutation', () => { describe('given a successful mutation', () => { beforeEach(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -245,8 +229,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -261,7 +245,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -273,18 +257,18 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -342,8 +326,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -358,7 +342,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -376,10 +360,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -387,8 +371,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -446,8 +430,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -462,7 +446,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -470,8 +454,7 @@ describe('given the removeUserFromOrg mutation', () => { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -481,15 +464,15 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) describe('super admin can remove a user from any org', () => { - it.skip('removes the user from the org', async () => { + it('removes the user from the org', async () => { await graphql( schema, ` @@ -538,8 +521,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -554,7 +537,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -566,18 +549,18 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -635,8 +618,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -651,7 +634,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -669,10 +652,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -680,8 +663,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -739,8 +722,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -755,7 +738,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -763,8 +746,7 @@ describe('given the removeUserFromOrg mutation', () => { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -774,10 +756,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -849,8 +831,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -865,7 +847,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -877,18 +859,18 @@ describe('given the removeUserFromOrg mutation', () => { const data = await loader.load(affiliation._key) - expect(data).toEqual(undefined) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(data).toEqual(undefined) }) describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -946,8 +928,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -962,7 +944,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -980,10 +962,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('users language is set to french', () => { @@ -991,8 +973,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1050,8 +1032,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -1066,7 +1048,7 @@ describe('given the removeUserFromOrg mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1074,8 +1056,7 @@ describe('given the removeUserFromOrg mutation', () => { data: { removeUserFromOrg: { result: { - status: - "L'utilisateur a été retiré de l'organisation avec succès.", + status: "L'utilisateur a été retiré de l'organisation avec succès.", user: { id: toGlobalId('user', user._key), userName: 'test.account@istio.actually.exists', @@ -1085,10 +1066,10 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${admin._key} successfully removed user: ${user._key} from org: ${orgOne._key}.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -1099,8 +1080,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1161,7 +1142,7 @@ describe('given the removeUserFromOrg mutation', () => { load: jest.fn(), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1170,17 +1151,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove user from unknown organization.', + description: 'Unable to remove user from unknown organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no org with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is not an admin', () => { @@ -1236,7 +1216,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1244,7 +1224,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1253,16 +1233,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: 'Unable to remove user from organization.', + description: 'Permission Denied: Please contact organization admin for help with removing users.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is an admin for another org', () => { @@ -1318,7 +1298,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1326,7 +1306,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1335,16 +1315,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: 'Unable to remove user from organization.', + description: 'Permission Denied: Please contact organization admin for help with removing users.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requested user is not found', () => { @@ -1400,13 +1380,13 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1415,17 +1395,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove unknown user from organization.', + description: 'Unable to remove unknown user from organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no user with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requested user does not belong to this org', () => { @@ -1478,7 +1457,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1486,7 +1465,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1495,192 +1474,23 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: - 'Unable to remove a user that already does not belong to this organization.', + description: 'Unable to remove a user that already does not belong to this organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456, but they do not have any affiliations to org: 12345.`, ]) - }) - }) - describe('super admin attempts to remove another super admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'super_admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with removing users.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) - }) - }) - describe('admin attempts to remove another admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with removing users.', - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) }) }) }) describe('database error occurs', () => { describe('when checking requested users permission in requested org', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('database error')) const response = await graphql( schema, @@ -1725,7 +1535,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1733,20 +1543,16 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1802,7 +1608,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1810,20 +1616,16 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1832,7 +1634,7 @@ describe('given the removeUserFromOrg mutation', () => { it('throws an error', async () => { const mockedCursor = { count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) @@ -1883,7 +1685,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1891,20 +1693,16 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx step error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1912,7 +1710,7 @@ describe('given the removeUserFromOrg mutation', () => { it('throws an error', async () => { const mockedCursor = { count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) @@ -1964,7 +1762,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -1972,20 +1770,16 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to remove user from this organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to remove user from this organization. Please try again.')] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx commit error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1994,8 +1788,8 @@ describe('given the removeUserFromOrg mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2056,7 +1850,7 @@ describe('given the removeUserFromOrg mutation', () => { load: jest.fn(), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2065,17 +1859,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: - "Impossible de supprimer un utilisateur d'une organisation inconnue.", + description: "Impossible de supprimer un utilisateur d'une organisation inconnue.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no org with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is not an admin', () => { @@ -2131,7 +1924,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2139,7 +1932,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2149,16 +1942,16 @@ describe('given the removeUserFromOrg mutation', () => { result: { code: 400, description: - "Impossible de supprimer un utilisateur de l'organisation.", + "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user is an admin for another org', () => { @@ -2214,7 +2007,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2222,7 +2015,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2232,16 +2025,16 @@ describe('given the removeUserFromOrg mutation', () => { result: { code: 400, description: - "Impossible de supprimer un utilisateur de l'organisation.", + "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, however they do not have the permission to remove users.`, + `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, ]) + expect(response).toEqual(error) }) }) describe('requested user is not found', () => { @@ -2297,13 +2090,13 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2312,17 +2105,16 @@ describe('given the removeUserFromOrg mutation', () => { removeUserFromOrg: { result: { code: 400, - description: - "Impossible de supprimer un utilisateur inconnu de l'organisation.", + description: "Impossible de supprimer un utilisateur inconnu de l'organisation.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456 from org: 12345, however no user with that id could be found.`, ]) + expect(response).toEqual(error) }) }) describe('requested user does not belong to this org', () => { @@ -2375,7 +2167,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2383,7 +2175,7 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -2399,185 +2191,17 @@ describe('given the removeUserFromOrg mutation', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to remove user: 456, but they do not have any affiliations to org: 12345.`, ]) - }) - }) - describe('super admin attempts to remove another super admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'super_admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) - }) - }) - describe('admin attempts to remove another admin', () => { - it('returns an error message', async () => { - const mockedCursor = { - count: 1, - next: jest.fn().mockReturnValue({ - permission: 'admin', - }), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const response = await graphql( - schema, - ` - mutation { - removeUserFromOrg ( - input: { - userId: "${toGlobalId('users', 456)}" - orgId: "${toGlobalId('organizations', 12345)}" - } - ) { - result { - ... on RemoveUserFromOrgResult { - status - user { - id - userName - } - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - _key: '123', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), - }, - loadUserByKey: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = { - data: { - removeUserFromOrg: { - result: { - code: 400, - description: - "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.", - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to remove user: 456 from org: 12345, but they do not have the right permission.`, - ]) }) }) }) describe('database error occurs', () => { describe('when checking requested users permission in requested org', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('database error')) const response = await graphql( schema, @@ -2622,7 +2246,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2630,20 +2254,18 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2699,7 +2321,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2707,20 +2329,18 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to check the current permission of user: 456 to see if they could be removed: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2729,7 +2349,7 @@ describe('given the removeUserFromOrg mutation', () => { it('throws an error', async () => { const mockedCursor = { count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) @@ -2780,7 +2400,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2788,20 +2408,18 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx step error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2809,7 +2427,7 @@ describe('given the removeUserFromOrg mutation', () => { it('throws an error', async () => { const mockedCursor = { count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) @@ -2861,7 +2479,7 @@ describe('given the removeUserFromOrg mutation', () => { }, loaders: { loadOrgByKey: { - load: jest.fn().mockReturnValue({_key: 12345}), + load: jest.fn().mockReturnValue({ _key: 12345 }), }, loadUserByKey: { load: jest.fn().mockReturnValue({ @@ -2869,20 +2487,18 @@ describe('given the removeUserFromOrg mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer."), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Trx commit error occurred when user: 123 attempted to remove user: 456 from org: 12345, error: Error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) diff --git a/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js index 00db8589b8..bc3cbcf89c 100644 --- a/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js +++ b/api/src/affiliation/mutations/__tests__/transfer-org-ownership.test.js @@ -1,32 +1,23 @@ -import {setupI18n} from '@lingui/core' -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {checkOrgOwner, userRequired, verifiedRequired} from '../../../auth' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' -import {cleanseInput} from '../../../validators' -import {createMutationSchema} from '../../../mutation' -import {createQuerySchema} from '../../../query' +import { checkOrgOwner, userRequired, verifiedRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { cleanseInput } from '../../../validators' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY} = process.env +const { DB_PASS: rootPass, DB_URL: url, SIGN_IN_KEY } = process.env describe('given the transferOrgOwnership mutation', () => { - let query, - drop, - truncate, - schema, - collections, - transaction, - i18n, - user, - user2, - org + let query, drop, truncate, schema, collections, transaction, i18n, user, user2, org const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -44,8 +35,8 @@ describe('given the transferOrgOwnership mutation', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -59,7 +50,7 @@ describe('given the transferOrgOwnership mutation', () => { }) describe('given a successful transfer', () => { beforeAll(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -108,14 +99,12 @@ describe('given the transferOrgOwnership mutation', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) await collections.affiliations.save({ _from: org._id, _to: user2._id, permission: 'admin', - owner: false, }) }) afterEach(async () => { @@ -170,7 +159,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -179,9 +168,9 @@ describe('given the transferOrgOwnership mutation', () => { i18n, userKey: user._key, }), - loadUserByKey: loadUserByKey({query, userKey: user._key, i18n}), + loadUserByKey: loadUserByKey({ query, userKey: user._key, i18n }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -191,7 +180,7 @@ describe('given the transferOrgOwnership mutation', () => { RETURN aff ` const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toMatchObject({owner: false}) + expect(testAffiliation).toMatchObject({ permission: 'admin' }) }) it('sets owner field in the requested users to true', async () => { await graphql( @@ -238,7 +227,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -247,9 +236,9 @@ describe('given the transferOrgOwnership mutation', () => { i18n, userKey: user._key, }), - loadUserByKey: loadUserByKey({query, userKey: user._key, i18n}), + loadUserByKey: loadUserByKey({ query, userKey: user._key, i18n }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -259,14 +248,14 @@ describe('given the transferOrgOwnership mutation', () => { RETURN aff ` const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toMatchObject({owner: true}) + expect(testAffiliation).toMatchObject({ permission: 'owner' }) }) describe('users language is set to english', () => { beforeAll(() => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, + en: { plurals: {} }, }, locales: ['en'], messages: { @@ -319,7 +308,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -334,7 +323,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -360,7 +349,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - fr: {plurals: {}}, + fr: { plurals: {} }, }, locales: ['fr'], messages: { @@ -413,7 +402,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }), - verifiedRequired: verifiedRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -428,7 +417,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -457,8 +446,8 @@ describe('given the transferOrgOwnership mutation', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -519,7 +508,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -528,94 +517,14 @@ describe('given the transferOrgOwnership mutation', () => { transferOrgOwnership: { result: { code: 400, - description: - 'Unable to transfer ownership of undefined organization.', + description: 'Unable to transfer ownership of undefined organization.', }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer org ownership of an undefined org.`, - ]) - }) - }) - describe('requested org is verified', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - transferOrgOwnership ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - userId: "${toGlobalId('users', user2._key)}" - } - ) { - result { - ... on TransferOrgOwnershipResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - slug: 'mocked-org', - }), - }, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - transferOrgOwnership: { - result: { - code: 400, - description: - 'Unable to transfer ownership of a verified organization.', - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer ownership of a verified org: mocked-org.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to transfer org ownership of an undefined org.`]) }) }) describe('requesting user is not the org owner', () => { @@ -669,7 +578,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -678,8 +587,7 @@ describe('given the transferOrgOwnership mutation', () => { transferOrgOwnership: { result: { code: 400, - description: - 'Permission Denied: Please contact org owner to transfer ownership.', + description: 'Permission Denied: Please contact org owner to transfer ownership.', }, }, }, @@ -740,7 +648,7 @@ describe('given the transferOrgOwnership mutation', () => { load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -749,8 +657,7 @@ describe('given the transferOrgOwnership mutation', () => { transferOrgOwnership: { result: { code: 400, - description: - 'Unable to transfer ownership of an org to an undefined user.', + description: 'Unable to transfer ownership of an org to an undefined user.', }, }, }, @@ -789,7 +696,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: user._key, @@ -813,7 +720,7 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -887,15 +794,11 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -936,7 +839,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -960,15 +863,11 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -979,10 +878,7 @@ describe('given the transferOrgOwnership mutation', () => { describe('when adding ownership to requested user', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Step Error')), }) const response = await graphql( @@ -1010,7 +906,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -1034,15 +930,11 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1083,7 +975,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -1107,15 +999,11 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Unable to transfer organization ownership. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to transfer organization ownership. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1129,8 +1017,8 @@ describe('given the transferOrgOwnership mutation', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1191,7 +1079,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1200,94 +1088,14 @@ describe('given the transferOrgOwnership mutation', () => { transferOrgOwnership: { result: { code: 400, - description: - "Impossible de transférer la propriété d'une organisation non définie.", + description: "Impossible de transférer la propriété d'une organisation non définie.", }, }, }, } expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer org ownership of an undefined org.`, - ]) - }) - }) - describe('requested org is verified', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - transferOrgOwnership ( - input: { - orgId: "${toGlobalId('organizations', org._key)}" - userId: "${toGlobalId('users', user2._key)}" - } - ) { - result { - ... on TransferOrgOwnershipResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkOrgOwner: checkOrgOwner({ - i18n, - query, - userKey: user._key, - }), - userRequired: jest.fn().mockReturnValue({ - _key: 123, - }), - verifiedRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - verified: true, - slug: 'mocked-org', - }), - }, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResult = { - data: { - transferOrgOwnership: { - result: { - code: 400, - description: - "Impossible de transférer la propriété d'une organisation vérifiée.", - }, - }, - }, - } - - expect(response).toEqual(expectedResult) - expect(consoleOutput).toEqual([ - `User: 123 attempted to transfer ownership of a verified org: mocked-org.`, - ]) + expect(consoleOutput).toEqual([`User: 123 attempted to transfer org ownership of an undefined org.`]) }) }) describe('requesting user is not the org owner', () => { @@ -1341,7 +1149,7 @@ describe('given the transferOrgOwnership mutation', () => { i18n, }), }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1412,7 +1220,7 @@ describe('given the transferOrgOwnership mutation', () => { load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1421,8 +1229,7 @@ describe('given the transferOrgOwnership mutation', () => { transferOrgOwnership: { result: { code: 400, - description: - "Impossible de transférer la propriété d'un org à un utilisateur non défini.", + description: "Impossible de transférer la propriété d'un org à un utilisateur non défini.", }, }, }, @@ -1461,7 +1268,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: user._key, @@ -1485,7 +1292,7 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -1559,14 +1366,12 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1608,7 +1413,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -1632,14 +1437,12 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1651,10 +1454,7 @@ describe('given the transferOrgOwnership mutation', () => { describe('when adding ownership to requested user', () => { it('throws an error', async () => { const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('Step Error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('Step Error')), }) const response = await graphql( @@ -1682,7 +1482,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -1706,14 +1506,12 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) @@ -1755,7 +1553,7 @@ describe('given the transferOrgOwnership mutation', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: mockedTransaction, userKey: user._key, @@ -1779,14 +1577,12 @@ describe('given the transferOrgOwnership mutation', () => { }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) const error = [ - new GraphQLError( - "Impossible de transférer la propriété de l'organisation. Veuillez réessayer.", - ), + new GraphQLError("Impossible de transférer la propriété de l'organisation. Veuillez réessayer."), ] expect(response.errors).toEqual(error) diff --git a/api/src/affiliation/mutations/__tests__/update-user-role.test.js b/api/src/affiliation/mutations/__tests__/update-user-role.test.js index e9229c9768..523968a487 100644 --- a/api/src/affiliation/mutations/__tests__/update-user-role.test.js +++ b/api/src/affiliation/mutations/__tests__/update-user-role.test.js @@ -1,25 +1,20 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {createQuerySchema} from '../../../query' -import {createMutationSchema} from '../../../mutation' -import {cleanseInput} from '../../../validators' -import { - checkPermission, - userRequired, - verifiedRequired, - tfaRequired, -} from '../../../auth' -import {loadUserByUserName, loadUserByKey} from '../../../user/loaders' -import {loadOrgByKey} from '../../../organization/loaders' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired, tfaRequired } from '../../../auth' +import { loadUserByUserName, loadUserByKey } from '../../../user/loaders' +import { loadOrgByKey } from '../../../organization/loaders' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('update a users role', () => { let query, drop, truncate, schema, collections, transaction, i18n, user @@ -46,7 +41,7 @@ describe('update a users role', () => { describe('given a successful role update', () => { beforeAll(async () => { // Generate DB Items - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -77,8 +72,8 @@ describe('update a users role', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -177,15 +172,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -206,10 +201,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('to user', () => { @@ -254,15 +249,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -283,10 +278,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -340,15 +335,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -369,10 +364,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('to admin', () => { @@ -417,15 +412,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -446,10 +441,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -512,15 +507,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -541,10 +536,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -555,8 +550,8 @@ describe('update a users role', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -655,15 +650,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -675,8 +670,7 @@ describe('update a users role', () => { data: { updateUserRole: { result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", + status: "Le rôle de l'utilisateur a été mis à jour avec succès.", user: { displayName: 'Test Account', }, @@ -685,10 +679,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('to user', () => { @@ -733,15 +727,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -753,8 +747,7 @@ describe('update a users role', () => { data: { updateUserRole: { result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", + status: "Le rôle de l'utilisateur a été mis à jour avec succès.", user: { displayName: 'Test Account', }, @@ -763,10 +756,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to user in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -820,15 +813,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -840,8 +833,7 @@ describe('update a users role', () => { data: { updateUserRole: { result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", + status: "Le rôle de l'utilisateur a été mis à jour avec succès.", user: { displayName: 'Test Account', }, @@ -850,10 +842,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to super_admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) describe('to admin', () => { @@ -898,15 +890,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -918,8 +910,7 @@ describe('update a users role', () => { data: { updateUserRole: { result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", + status: "Le rôle de l'utilisateur a été mis à jour avec succès.", user: { displayName: 'Test Account', }, @@ -928,10 +919,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -994,15 +985,15 @@ describe('update a users role', () => { }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - verifiedRequired: verifiedRequired({i18n}), - tfaRequired: tfaRequired({i18n}), + verifiedRequired: verifiedRequired({ i18n }), + tfaRequired: tfaRequired({ i18n }), }, loaders: { - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), - loadUserByUserName: loadUserByUserName({query}), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), + loadUserByUserName: loadUserByUserName({ query }), }, validators: { cleanseInput, @@ -1014,8 +1005,7 @@ describe('update a users role', () => { data: { updateUserRole: { result: { - status: - "Le rôle de l'utilisateur a été mis à jour avec succès.", + status: "Le rôle de l'utilisateur a été mis à jour avec succès.", user: { displayName: 'Test Account', }, @@ -1024,10 +1014,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ `User: ${user._key} successful updated user: ${secondaryUser._key} role to admin in org: treasury-board-secretariat.`, ]) + expect(response).toEqual(expectedResponse) }) }) }) @@ -1040,8 +1030,8 @@ describe('update a users role', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1116,10 +1106,8 @@ describe('update a users role', () => { }, } + expect(consoleOutput).toEqual([`User: 123 attempted to update their own role in org: 123.`]) expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their own role in org: 123.`, - ]) }) }) describe('user attempts to update a user that does not exist', () => { @@ -1187,10 +1175,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: random@email.ca role in org: 123, however there is no user associated with that user name.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a users role in an org that does not exist', () => { @@ -1260,10 +1248,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: 1, however there is no org associated with that id.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user permission is user', () => { @@ -1336,10 +1324,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a users role in an org that the requesting user does not belong to', () => { @@ -1370,7 +1358,7 @@ describe('update a users role', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -1412,10 +1400,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a user that does not belong to the requested org', () => { @@ -1446,7 +1434,7 @@ describe('update a users role', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -1481,185 +1469,16 @@ describe('update a users role', () => { updateUserRole: { result: { code: 400, - description: - 'Unable to update role: user does not belong to organization.', + description: 'Unable to update role: user does not belong to organization.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however that user does not have an affiliation with that organization.`, ]) - }) - }) - describe('requesting users role is super admin', () => { - describe('user attempts to update users role to admin', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: admin.`, - ]) - }) - }) - }) - describe('user attempts to update users role to user', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) + expect(response).toEqual(error) }) }) describe('requesting users role is admin', () => { @@ -1693,9 +1512,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), + next: jest.fn().mockReturnValue({ permission: 'super_admin' }), }), collections: collectionNames, transaction: jest.fn(), @@ -1732,95 +1549,16 @@ describe('update a users role', () => { result: { code: 400, description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', + 'Permission Denied: Please contact organization admin for help with user role changes.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to update a super_admin.`, ]) - }) - }) - describe('requested users current role is admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({permission: 'admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - 'Permission Denied: Please contact organization admin for help with updating user roles.', - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from admin to: user.`, - ]) }) }) }) @@ -1884,16 +1622,12 @@ describe('update a users role', () => { }, ) - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to update a user's: 456 role, error: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -1959,16 +1693,12 @@ describe('update a users role', () => { }, ) - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to update a user's: 456 role, error: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2003,7 +1733,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ @@ -2036,16 +1766,12 @@ describe('update a users role', () => { }, ) - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Transaction step error occurred when user: 123 attempted to update a user's: 456 role, error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) describe('when committing transaction', () => { @@ -2078,7 +1804,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ @@ -2112,16 +1838,12 @@ describe('update a users role', () => { }, ) - const error = [ - new GraphQLError( - `Unable to update user's role. Please try again.`, - ), - ] + const error = [new GraphQLError(`Unable to update user's role. Please try again.`)] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Transaction commit error occurred when user: 123 attempted to update a user's: 456 role, error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -2131,8 +1853,8 @@ describe('update a users role', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2201,17 +1923,14 @@ describe('update a users role', () => { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour votre propre rôle.', + description: 'Impossible de mettre à jour votre propre rôle.', }, }, }, } + expect(consoleOutput).toEqual([`User: 123 attempted to update their own role in org: 123.`]) expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to update their own role in org: 123.`, - ]) }) }) describe('user attempts to update a user that does not exist', () => { @@ -2273,17 +1992,16 @@ describe('update a users role', () => { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour le rôle : utilisateur inconnu.', + description: 'Impossible de mettre à jour le rôle : utilisateur inconnu.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: random@email.ca role in org: 123, however there is no user associated with that user name.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a users role in an org that does not exist', () => { @@ -2347,17 +2065,16 @@ describe('update a users role', () => { updateUserRole: { result: { code: 400, - description: - 'Impossible de mettre à jour le rôle : organisation inconnue.', + description: 'Impossible de mettre à jour le rôle : organisation inconnue.', }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: 1, however there is no org associated with that id.`, ]) + expect(response).toEqual(error) }) }) describe('requesting user permission is user', () => { @@ -2430,10 +2147,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a users role in an org that the requesting user does not belong to', () => { @@ -2464,7 +2181,7 @@ describe('update a users role', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -2506,10 +2223,10 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to do so.`, ]) + expect(response).toEqual(error) }) }) describe('user attempts to update a user that does not belong to the requested org', () => { @@ -2540,7 +2257,7 @@ describe('update a users role', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -2582,180 +2299,13 @@ describe('update a users role', () => { }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however that user does not have an affiliation with that organization.`, ]) + expect(response).toEqual(error) }) }) - describe('requesting users role is super admin', () => { - describe('user attempts to update users role to admin', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: ADMIN - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: admin.`, - ]) - }) - }) - }) - describe('user attempts to update users role to user', () => { - describe('requested users current role is super admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('super_admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, - ]) - }) - }) - }) - }) describe('requesting users role is admin', () => { describe('requested users role is super admin', () => { it('returns an error message', async () => { @@ -2787,9 +2337,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest - .fn() - .mockReturnValue({permission: 'super_admin'}), + next: jest.fn().mockReturnValue({ permission: 'super_admin' }), }), collections: collectionNames, transaction: jest.fn(), @@ -2826,95 +2374,16 @@ describe('update a users role', () => { result: { code: 400, description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", + "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.", }, }, }, } - expect(response).toEqual(error) expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from super_admin to: user.`, + `User: 123 attempted to update a user: 456 role in org: treasury-board-secretariat, however they do not have permission to update a super_admin.`, ]) - }) - }) - describe('requested users current role is admin', () => { - it('returns an error message', async () => { - const response = await graphql( - schema, - ` - mutation { - updateUserRole ( - input: { - userName: "test@email.gc.ca" - orgId: "${toGlobalId('organizations', 123)}" - role: USER - } - ) { - result { - ... on UpdateUserRoleResult { - status - } - ... on AffiliationError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: jest.fn().mockReturnValue({ - count: 1, - next: jest.fn().mockReturnValue({permission: 'admin'}), - }), - collections: collectionNames, - transaction: jest.fn(), - userKey: 123, - auth: { - checkPermission: jest.fn().mockReturnValue('admin'), - userRequired: jest.fn().mockReturnValue({ - userName: 'test.account@istio.actually.exists', - }), - verifiedRequired: jest.fn(), - tfaRequired: jest.fn(), - }, - loaders: { - loadOrgByKey: { - load: jest.fn().mockReturnValue({ - slug: 'treasury-board-secretariat', - }), - }, - loadUserByUserName: { - load: jest.fn().mockReturnValue({ - _key: 456, - }), - }, - }, - validators: { - cleanseInput, - }, - }, - ) - - const error = { - data: { - updateUserRole: { - result: { - code: 400, - description: - "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.", - }, - }, - }, - } - expect(response).toEqual(error) - expect(consoleOutput).toEqual([ - `User: 123 attempted to lower user: 456 from admin to: user.`, - ]) }) }) }) @@ -2979,15 +2448,13 @@ describe('update a users role', () => { ) const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), + new GraphQLError(`Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Database error occurred when user: 123 attempted to update a user's: 456 role, error: Error: database error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -3054,15 +2521,13 @@ describe('update a users role', () => { ) const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), + new GraphQLError(`Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Cursor error occurred when user: 123 attempted to update a user's: 456 role, error: Error: cursor error`, ]) + expect(response.errors).toEqual(error) }) }) }) @@ -3097,7 +2562,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ @@ -3131,15 +2596,13 @@ describe('update a users role', () => { ) const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), + new GraphQLError(`Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Transaction step error occurred when user: 123 attempted to update a user's: 456 role, error: trx step error`, ]) + expect(response.errors).toEqual(error) }) }) describe('when committing transaction', () => { @@ -3172,7 +2635,7 @@ describe('update a users role', () => { i18n, query: jest.fn().mockReturnValue({ count: 1, - next: jest.fn().mockReturnValue({permission: 'user'}), + next: jest.fn().mockReturnValue({ permission: 'user' }), }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ @@ -3207,15 +2670,13 @@ describe('update a users role', () => { ) const error = [ - new GraphQLError( - `Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`, - ), + new GraphQLError(`Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.`), ] - expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ `Transaction commit error occurred when user: 123 attempted to update a user's: 456 role, error: trx commit error`, ]) + expect(response.errors).toEqual(error) }) }) }) diff --git a/api/src/affiliation/mutations/invite-user-to-org.js b/api/src/affiliation/mutations/invite-user-to-org.js index 525d37f7a0..6f3ff361e2 100644 --- a/api/src/affiliation/mutations/invite-user-to-org.js +++ b/api/src/affiliation/mutations/invite-user-to-org.js @@ -4,8 +4,8 @@ import { GraphQLEmailAddress } from 'graphql-scalars' import { t } from '@lingui/macro' import { inviteUserToOrgUnion } from '../unions' -import { RoleEnums } from '../../enums' import { logActivity } from '../../audit-logs/mutations/log-activity' +import { InvitationRoleEnums } from '../../enums' export const inviteUserToOrg = new mutationWithClientMutationId({ name: 'InviteUserToOrg', @@ -18,7 +18,7 @@ able to sign-up and be assigned to that organization in one mutation.`, description: 'Users email that you would like to invite to your org.', }, requestedRole: { - type: GraphQLNonNull(RoleEnums), + type: GraphQLNonNull(InvitationRoleEnums), description: 'The role which you would like this user to have.', }, orgId: { @@ -87,9 +87,8 @@ able to sign-up and be assigned to that organization in one mutation.`, const permission = await checkPermission({ orgId: org._id }) if ( - typeof permission === 'undefined' || - permission === 'user' || - (permission === 'admin' && requestedRole === 'super_admin') + (['user', 'admin'].includes(requestedRole) && !['admin', 'owner', 'super_admin'].includes(permission)) || + (requestedRole === 'super_admin' && permission !== 'super_admin') ) { console.warn( `User: ${userKey} attempted to invite user: ${userName} to org: ${org._key} with role: ${requestedRole} but does not have permission to do so.`, @@ -221,7 +220,6 @@ able to sign-up and be assigned to that organization in one mutation.`, _from: ${org._id}, _to: ${requestedUser._id}, permission: ${requestedRole}, - owner: false } INTO affiliations `, ) diff --git a/api/src/affiliation/mutations/leave-organization.js b/api/src/affiliation/mutations/leave-organization.js index 8d65e334e3..bcbd9aa16e 100644 --- a/api/src/affiliation/mutations/leave-organization.js +++ b/api/src/affiliation/mutations/leave-organization.js @@ -27,7 +27,7 @@ export const leaveOrganization = new mutationWithClientMutationId({ query, collections, transaction, - auth: { checkOrgOwner, userRequired, verifiedRequired }, + auth: { userRequired, verifiedRequired }, loaders: { loadOrgByKey }, validators: { cleanseInput }, }, @@ -49,247 +49,25 @@ export const leaveOrganization = new mutationWithClientMutationId({ } } - // check to see if org owner - const owner = await checkOrgOwner({ orgId: org._id }) - // Setup Trans action const trx = await transaction(collections) - if (owner) { - // check to see if org has any dmarc summaries - let dmarcSummaryCheckCursor - try { - dmarcSummaryCheckCursor = await query` - WITH domains, ownership, dmarcSummaries, organizations - FOR v, e IN 1..1 OUTBOUND ${org._id} ownership - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred while checking for dmarc summaries for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - let dmarcSummaryCheckList - try { - dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting dmarc summary info for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - for (const ownership of dmarcSummaryCheckList) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove dmarc summaries for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - REMOVE ${ownership._key} IN ownership - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove ownership for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - } - - // check to see if any other orgs are using this domain - let countCursor - try { - countCursor = await query` - WITH claims, domains, organizations - LET domainIds = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} claims - RETURN e._to - ) - FOR domain IN domains - FILTER domain._id IN domainIds - LET count = LENGTH( - FOR v, e IN 1..1 INBOUND domain._id claims - RETURN 1 - ) - RETURN { - _id: domain._id, - _key: domain._key, - domain: domain.domain, - count - } - ` - } catch (err) { - console.error( - `Database error occurred while while gathering domainInfo org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - let domainInfo - try { - domainInfo = await countCursor.all() - } catch (err) { - console.error( - `Cursor error occurred while while gathering domainInfo org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - - for (const domain of domainInfo) { - if (domain.count === 1) { - try { - // Remove web data - await trx.step(async () => { - await query` - WITH web, webScan, domains - FOR webV, domainsWebEdge IN 1..1 OUTBOUND ${domain._id} domainsWeb - FOR webScanV, webToWebScansV In 1..1 ANY webV._id webToWebScans - REMOVE webScanV IN webScan - REMOVE webToWebScansV IN webToWebScans - OPTIONS { waitForSync: true } - REMOVE webV IN web - REMOVE domainsWebEdge IN domainsWeb - OPTIONS { waitForSync: true } - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${user._key} attempted to remove web data for ${domain.domain} in org: ${org.slug}, ${err}`, - ) - throw new Error(i18n._(t`Unable to leave organization. Please try again.`)) - } - - try { - // Remove DNS data - await trx.step(async () => { - await query` - WITH dns, domains - FOR dnsV, domainsDNSEdge IN 1..1 OUTBOUND ${domain._id} domainsDNS - REMOVE dnsV IN dns - REMOVE domainsDNSEdge IN domainsDNS - OPTIONS { waitForSync: true } - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${user._key} attempted to remove DNS data for ${domain.domain} in org: ${org.slug}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to leave organization. Please try again.`)) - } - - try { - await trx.step( - () => - query` - WITH claims, domains, organizations - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} claims - FILTER e._to == ${domain._id} - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge in domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge in domainEdges - LET key = PARSE_IDENTIFIER(domainEdge.domainId).key - REMOVE key IN domains - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove domains for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - } - } - - try { - await trx.step( - () => - query` - WITH affiliations, organizations, users - LET userEdges = ( - FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations - RETURN { edgeKey: e._key, userKey: e._to } - ) - LET removeUserEdges = ( - FOR userEdge IN userEdges - REMOVE userEdge.edgeKey IN affiliations - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - await trx.step( - () => - query` - WITH organizations - REMOVE ${org._key} IN organizations - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred while attempting to remove affiliations, and the org for org: ${org._key}, when user: ${user._key} attempted to leave: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } - } else { - try { - await trx.step( - () => - query` + try { + await trx.step( + () => + query` WITH affiliations, organizations, users FOR v, e IN 1..1 OUTBOUND ${org._id} affiliations FILTER e._to == ${user._id} REMOVE { _key: e._key } IN affiliations OPTIONS { waitForSync: true } `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing user: ${user._key} affiliation with org: ${org._key}: ${err}`, - ) - throw new Error(i18n._(t`Unable leave organization. Please try again.`)) - } + ) + } catch (err) { + console.error( + `Trx step error occurred when removing user: ${user._key} affiliation with org: ${org._key}: ${err}`, + ) + throw new Error(i18n._(t`Unable leave organization. Please try again.`)) } try { diff --git a/api/src/affiliation/mutations/remove-user-from-org.js b/api/src/affiliation/mutations/remove-user-from-org.js index b31b07d9d1..4450653695 100644 --- a/api/src/affiliation/mutations/remove-user-from-org.js +++ b/api/src/affiliation/mutations/remove-user-from-org.js @@ -64,16 +64,6 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ // Check requesting users permission const permission = await checkPermission({ orgId: requestedOrg._id }) - if (permission === 'user' || typeof permission === 'undefined') { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUserKey} from org: ${requestedOrg._key}, however they do not have the permission to remove users.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Unable to remove user from organization.`), - } - } // Get requested user const requestedUser = await loadUserByKey.load(requestedUserKey) @@ -125,23 +115,36 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) } - let canRemove - if (permission === 'super_admin' && ['pending', 'user', 'admin'].includes(affiliation.permission)) { - canRemove = true - } else if (permission === 'admin' && ['pending', 'user'].includes(affiliation.permission)) { - canRemove = true - } else { - canRemove = false + // Only admins, owners, and super admins can remove users + if (['admin', 'owner', 'super_admin'].includes(permission) === false) { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), + } } - if (canRemove) { - // Setup Transaction - const trx = await transaction(collections) + // Only super admins can remove super admins and owners + if (['owner', 'super_admin'].includes(affiliation.permission) && permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), + } + } - try { - await trx.step( - () => - query` + // Setup Transaction + const trx = await transaction(collections) + + try { + await trx.step( () => + query` WITH affiliations, organizations, users FOR aff IN affiliations FILTER aff._from == ${requestedOrg._id} @@ -149,61 +152,51 @@ export const removeUserFromOrg = new mutationWithClientMutationId({ REMOVE aff IN affiliations RETURN true `, - ) - } catch (err) { - console.error( - `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) - } - - try { - await trx.commit() - } catch (err) { - console.error( - `Trx commit error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, - ) - throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) - } - - console.info(`User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`) - await logActivity({ - transaction, - collections, - query, - initiatedBy: { - id: user._key, - userName: user.userName, - role: permission, - }, - action: 'remove', - target: { - resource: requestedUser.userName, - organization: { - id: requestedOrg._key, - name: requestedOrg.name, - }, // name of resource being acted upon - resourceType: 'user', // user, org, domain - }, - }) + ) + } catch (err) { + console.error( + `Trx step error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } - return { - _type: 'regular', - status: i18n._(t`Successfully removed user from organization.`), - user: { - id: requestedUser.id, - userName: requestedUser.userName, - }, - } - } else { - console.warn( - `User: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, but they do not have the right permission.`, + try { + await trx.commit() + } catch (err) { + console.error( + `Trx commit error occurred when user: ${userKey} attempted to remove user: ${requestedUser._key} from org: ${requestedOrg._key}, error: ${err}`, ) - return { - _type: 'error', - code: 400, - description: i18n._(t`Permission Denied: Please contact organization admin for help with removing users.`), - } + throw new Error(i18n._(t`Unable to remove user from this organization. Please try again.`)) + } + + console.info(`User: ${userKey} successfully removed user: ${requestedUser._key} from org: ${requestedOrg._key}.`) + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + }, + action: 'remove', + target: { + resource: requestedUser.userName, + organization: { + id: requestedOrg._key, + name: requestedOrg.name, + }, // name of resource being acted upon + resourceType: 'user', // user, org, domain + }, + }) + + return { + _type: 'regular', + status: i18n._(t`Successfully removed user from organization.`), + user: { + id: requestedUser.id, + userName: requestedUser.userName, + }, } }, }) diff --git a/api/src/affiliation/mutations/request-org-affiliation.js b/api/src/affiliation/mutations/request-org-affiliation.js index ee9f733ba7..b974191eb6 100644 --- a/api/src/affiliation/mutations/request-org-affiliation.js +++ b/api/src/affiliation/mutations/request-org-affiliation.js @@ -110,7 +110,6 @@ export const requestOrgAffiliation = new mutationWithClientMutationId({ _from: ${org._id}, _to: ${user._id}, permission: "pending", - owner: false } INTO affiliations `, ) diff --git a/api/src/affiliation/mutations/transfer-org-ownership.js b/api/src/affiliation/mutations/transfer-org-ownership.js index 7ed9255e31..866eafded5 100644 --- a/api/src/affiliation/mutations/transfer-org-ownership.js +++ b/api/src/affiliation/mutations/transfer-org-ownership.js @@ -1,23 +1,20 @@ -import {t} from '@lingui/macro' -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {fromGlobalId, mutationWithClientMutationId} from 'graphql-relay' +import { t } from '@lingui/macro' +import { GraphQLID, GraphQLNonNull } from 'graphql' +import { fromGlobalId, mutationWithClientMutationId } from 'graphql-relay' -import {transferOrgOwnershipUnion} from '../unions' +import { transferOrgOwnershipUnion } from '../unions' export const transferOrgOwnership = new mutationWithClientMutationId({ name: 'TransferOrgOwnership', - description: - 'This mutation allows a user to transfer org ownership to another user in the given org.', + description: 'This mutation allows a user to transfer org ownership to another user in the given org.', inputFields: () => ({ orgId: { type: GraphQLNonNull(GraphQLID), - description: - 'Id of the organization the user is looking to transfer ownership of.', + description: 'Id of the organization the user is looking to transfer ownership of.', }, userId: { type: GraphQLNonNull(GraphQLID), - description: - 'Id of the user that the org ownership is being transferred to.', + description: 'Id of the user that the org ownership is being transferred to.', }, }), outputFields: () => ({ @@ -35,53 +32,36 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ query, collections, transaction, - auth: {checkOrgOwner, userRequired, verifiedRequired}, - loaders: {loadOrgByKey, loadUserByKey}, - validators: {cleanseInput}, + auth: { checkOrgOwner, userRequired, verifiedRequired }, + loaders: { loadOrgByKey, loadUserByKey }, + validators: { cleanseInput }, }, ) => { // cleanse inputs - const {id: orgKey} = fromGlobalId(cleanseInput(args.orgId)) - const {id: userTransferKey} = fromGlobalId(cleanseInput(args.userId)) + const { id: orgKey } = fromGlobalId(cleanseInput(args.orgId)) + const { id: userTransferKey } = fromGlobalId(cleanseInput(args.userId)) // protect mutation from un-authed users const requestingUser = await userRequired() // ensure that user has email verified their account - verifiedRequired({user: requestingUser}) + verifiedRequired({ user: requestingUser }) // load the requested org const org = await loadOrgByKey.load(orgKey) // ensure requested org is not undefined if (typeof org === 'undefined') { - console.warn( - `User: ${requestingUser._key} attempted to transfer org ownership of an undefined org.`, - ) + console.warn(`User: ${requestingUser._key} attempted to transfer org ownership of an undefined org.`) return { _type: 'error', code: 400, - description: i18n._( - t`Unable to transfer ownership of undefined organization.`, - ), - } - } - // ensure org is not verified - else if (org.verified) { - console.warn( - `User: ${requestingUser._key} attempted to transfer ownership of a verified org: ${org.slug}.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Unable to transfer ownership of a verified organization.`, - ), + description: i18n._(t`Unable to transfer ownership of undefined organization.`), } } // get org owner bool value - const owner = await checkOrgOwner({orgId: org._id}) + const owner = await checkOrgOwner({ orgId: org._id }) // check to see if requesting user is the org owner if (!owner) { @@ -91,9 +71,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Permission Denied: Please contact org owner to transfer ownership.`, - ), + description: i18n._(t`Permission Denied: Please contact org owner to transfer ownership.`), } } @@ -108,9 +86,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to transfer ownership of an org to an undefined user.`, - ), + description: i18n._(t`Unable to transfer ownership of an org to an undefined user.`), } } @@ -127,9 +103,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ console.error( `Database error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // check to see if requested user belongs to org @@ -159,7 +133,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ FILTER aff._from == ${org._id} FILTER aff._to == ${requestingUser._id} UPDATE { _key: aff._key } WITH { - owner: false, + permission: "admin", } IN affiliations RETURN aff `, @@ -168,9 +142,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ console.error( `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // set new org owner @@ -183,7 +155,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ FILTER aff._from == ${org._id} FILTER aff._to == ${requestedUser._id} UPDATE { _key: aff._key } WITH { - owner: true, + permission: "owner", } IN affiliations RETURN aff `, @@ -192,9 +164,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ console.error( `Trx step error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } // commit changes to the db @@ -204,9 +174,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ console.error( `Trx commit error occurred for user: ${requestingUser._key} when they were attempting to transfer org: ${org.slug} ownership to user: ${requestedUser._key}: ${err}`, ) - throw new Error( - i18n._(t`Unable to transfer organization ownership. Please try again.`), - ) + throw new Error(i18n._(t`Unable to transfer organization ownership. Please try again.`)) } console.info( @@ -214,9 +182,7 @@ export const transferOrgOwnership = new mutationWithClientMutationId({ ) return { _type: 'regular', - status: i18n._( - t`Successfully transferred org: ${org.slug} ownership to user: ${requestedUser.userName}`, - ), + status: i18n._(t`Successfully transferred org: ${org.slug} ownership to user: ${requestedUser.userName}`), } }, }) diff --git a/api/src/affiliation/mutations/update-user-role.js b/api/src/affiliation/mutations/update-user-role.js index 8850d51c54..8bb77b48fd 100644 --- a/api/src/affiliation/mutations/update-user-role.js +++ b/api/src/affiliation/mutations/update-user-role.js @@ -98,7 +98,8 @@ given organization.`, // Check requesting user's permission const permission = await checkPermission({ orgId: org._id }) - if (!['admin', 'super_admin'].includes(permission) || typeof permission === 'undefined') { + // Only admins, owners, and super admins can update a user's role + if (['admin', 'owner', 'super_admin'].includes(permission) === false) { console.warn( `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to do so.`, ) @@ -122,6 +123,7 @@ given organization.`, console.error( `Database error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } @@ -143,74 +145,44 @@ given organization.`, console.error( `Cursor error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } - // Setup Transaction - const trx = await transaction(collections) - - // Only super admins can create new super admins - let edge - if (role === 'super_admin' && permission === 'super_admin') { - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'super_admin', - } - } else if (role === 'admin' && ['admin', 'super_admin'].includes(permission)) { - // If requested user's permission is super admin, make sure they don't get downgraded - if (affiliation.permission === 'super_admin') { - console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: admin.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), - } - } - - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'admin', - } - } else if (role === 'user' && permission === 'super_admin') { - // If requested user's permission is super admin or admin, make sure they don't get downgraded - if ( - affiliation.permission === 'super_admin' || - (affiliation.permission === 'admin' && permission !== 'super_admin') - ) { - console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: user.`, - ) - return { - _type: 'error', - code: 400, - description: i18n._( - t`Permission Denied: Please contact organization admin for help with updating user roles.`, - ), - } + // Only super admins can update other super admins or owners + if (['owner', 'super_admin'].includes(affiliation.permission) && permission !== 'super_admin') { + console.warn( + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to update a ${affiliation.permission}.`, + ) + return { + _type: 'error', + code: 400, + description: i18n._(t`Permission Denied: Please contact organization admin for help with user role changes.`), } + } - edge = { - _from: org._id, - _to: requestedUser._id, - permission: 'user', - } - } else { + // Only super admins can make other users super admins or owners + if (['owner', 'super_admin'].includes(role) && permission !== 'super_admin') { console.warn( - `User: ${userKey} attempted to lower user: ${requestedUser._key} from ${affiliation.permission} to: ${role}.`, + `User: ${userKey} attempted to update a user: ${requestedUser._key} role in org: ${org.slug}, however they do not have permission to make a user a ${role}.`, ) return { _type: 'error', code: 400, - description: i18n._(t`Permission Denied: Please contact organization admin for help with updating user roles.`), + description: i18n._(t`Permission Denied: Please contact organization admin for help with user role changes.`), } } + // Only super admins can create new super admins + const edge = { + _from: org._id, + _to: requestedUser._id, + permission: role, + } + + // Setup Transaction + const trx = await transaction(collections) + try { await trx.step(async () => { await query` @@ -225,6 +197,7 @@ given organization.`, console.error( `Transaction step error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } @@ -234,6 +207,7 @@ given organization.`, console.warn( `Transaction commit error occurred when user: ${userKey} attempted to update a user's: ${requestedUser._key} role, error: ${err}`, ) + throw new Error(i18n._(t`Unable to update user's role. Please try again.`)) } diff --git a/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js b/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js index c41de02b2c..065157677f 100644 --- a/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js +++ b/api/src/audit-logs/loaders/load-audit-logs-by-org-id.js @@ -97,37 +97,23 @@ export const loadAuditLogsByOrgId = `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadAuditLogsByOrgId.`, ) throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Log\` connection.`, - ), + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Log\` connection.`), ) } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAuditLogsByOrgId.`, - ) + console.warn(`User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadAuditLogsByOrgId.`) throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Log\` connection is not supported.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Log\` connection is not supported.`), ) } else if (typeof first === 'number' || typeof last === 'number') { /* istanbul ignore else */ if (first < 0 || last < 0) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAuditLogsByOrgId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Log\` connection cannot be less than zero.`, - ), - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadAuditLogsByOrgId.`) + throw new Error(i18n._(t`\`${argSet}\` on the \`Log\` connection cannot be less than zero.`)) } else if (first > 100 || last > 100) { const argSet = typeof first !== 'undefined' ? 'first' : 'last' const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAuditLogsByOrgId.`, - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadAuditLogsByOrgId.`) throw new Error( i18n._( t`Requesting \`${amount}\` records on the \`Log\` connection exceeds the \`${argSet}\` limit of 100 records.`, @@ -141,12 +127,8 @@ export const loadAuditLogsByOrgId = } else { const argSet = typeof first !== 'undefined' ? 'first' : 'last' const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAuditLogsByOrgId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadAuditLogsByOrgId.`) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } let hasNextPageFilter = aql`FILTER TO_NUMBER(log._key) > TO_NUMBER(LAST(retrievedLogs)._key)` @@ -239,11 +221,7 @@ export const loadAuditLogsByOrgId = ) ` } else { - throw new Error( - i18n._( - t`Cannot query audit logs on organization without admin permission or higher.`, - ), - ) + throw new Error(i18n._(t`Cannot query audit logs on organization without admin permission or higher.`)) } let logQuery = aql`` diff --git a/api/src/audit-logs/queries/find-audit-logs.js b/api/src/audit-logs/queries/find-audit-logs.js index b50d2e0145..838c60a6c1 100644 --- a/api/src/audit-logs/queries/find-audit-logs.js +++ b/api/src/audit-logs/queries/find-audit-logs.js @@ -19,8 +19,7 @@ export const findAuditLogs = { }, search: { type: GraphQLString, - description: - 'String used to search for logs by initiant user or target resource.', + description: 'String used to search for logs by initiant user or target resource.', }, filters: { type: logFilters, @@ -48,19 +47,15 @@ export const findAuditLogs = { // Check to see if user belongs to org const permission = await checkPermission({ orgId: org?._id }) - if (permission === 'admin' || permission === 'super_admin') { - const auditLogCollection = await loadAuditLogsByOrgId({ - ...args, - orgId: org?._key, - permission, - }) - console.info(`User: ${userKey} successfully retrieved audit logs.`) - return auditLogCollection + if (['admin', 'owner', 'super_admin'].includes(permission) === false) { + throw new Error(i18n._(t`Cannot query audit logs on organization without admin permission or higher.`)) } - throw new Error( - i18n._( - t`Cannot query audit logs on organization without admin permission or higher.`, - ), - ) + const auditLogCollection = await loadAuditLogsByOrgId({ + ...args, + orgId: org?._key, + permission, + }) + console.info(`User: ${userKey} successfully retrieved audit logs.`) + return auditLogCollection }, } diff --git a/api/src/auth/__tests__/check-domain-ownership.test.js b/api/src/auth/__tests__/check-domain-ownership.test.js index 870ac2e4d2..122bfd0e42 100644 --- a/api/src/auth/__tests__/check-domain-ownership.test.js +++ b/api/src/auth/__tests__/check-domain-ownership.test.js @@ -1,15 +1,27 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' -import {checkDomainOwnership} from '../index' +import { checkDomainOwnership } from '../index' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import dbschema from '../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the check domain ownership function', () => { - let query, drop, truncate, collections, org, domain, i18n, user + let query, drop, truncate, collections, org, verifiedOrg, unverifiedOrg, domain, i18n, user + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) const consoleOutput = [] const mockedError = (output) => consoleOutput.push(output) @@ -20,70 +32,155 @@ describe('given the check domain ownership function', () => { consoleOutput.length = 0 }) - describe('given a successful domain ownership call', () => { - let permitted - beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ - variables: { - dbname: dbNameFromFile(__filename), - username: 'root', - rootPassword: rootPass, - password: rootPass, - url, - }, + beforeAll(async () => { + ;({ query, drop, truncate, collections } = await ensure({ + variables: { + dbname: dbNameFromFile(__filename), + username: 'root', + rootPassword: rootPass, + password: rootPass, + url, + }, - schema: dbschema, - })) + schema: dbschema, + })) + }) + beforeEach(async () => { + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + displayName: 'Test Account', + preferredLang: 'french', + tfaValidated: false, + emailValidated: false, }) - beforeEach(async () => { - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - displayName: 'Test Account', - preferredLang: 'french', - tfaValidated: false, - emailValidated: false, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - lastRan: null, - selectors: ['selector1', 'selector2'], - }) - await collections.ownership.save({ - _to: domain._id, - _from: org._id, - }) + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + verified: true, + }) + verifiedOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'other-org', + acronym: 'OO', + name: 'Other Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'autre-org', + acronym: 'AO', + name: 'Autre Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + verified: true, + }) + unverifiedOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'unverified-org', + acronym: 'UO', + name: 'Unverified Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'unverified-org', + acronym: 'UO', + name: 'Unverified Org', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, + }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', + lastRan: null, + selectors: ['selector1', 'selector2'], + }) + await collections.ownership.save({ + _to: domain._id, + _from: org._id, }) - afterEach(async () => { - await truncate() + }) + afterEach(async () => { + await truncate() + }) + afterAll(async () => { + await drop() + }) + + describe('given a successful domain ownership call', () => { + let permitted + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) - afterAll(async () => { - await drop() + + describe('if the user belongs to an org which is verified and the org in question is also verified', () => { + beforeEach(async () => { + await collections.affiliations.save({ + _from: verifiedOrg._id, + _to: user._id, + permission: 'user', + }) + }) + it('will return true', async () => { + const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, + query, + userKey: user._key, + }) + permitted = await testCheckDomainOwnerShip({ + domainId: domain._id, + }) + expect(permitted).toEqual(true) + }) }) + describe('if the user belongs to an org which has a ownership for a given organization', () => { afterEach(async () => { await query` @@ -109,7 +206,7 @@ describe('given the check domain ownership function', () => { const testCheckDomainOwnerShip = checkDomainOwnership({ query, userKey: user._key, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }) permitted = await testCheckDomainOwnerShip({ domainId: domain._id, @@ -128,9 +225,10 @@ describe('given the check domain ownership function', () => { }) it('will return true', async () => { const testCheckDomainOwnerShip = checkDomainOwnership({ + i18n, query, userKey: user._key, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }) permitted = await testCheckDomainOwnerShip({ domainId: domain._id, @@ -150,7 +248,7 @@ describe('given the check domain ownership function', () => { const testCheckDomainOwnerShip = checkDomainOwnership({ query, userKey: user._key, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }) permitted = await testCheckDomainOwnerShip({ domainId: domain._id, @@ -166,12 +264,10 @@ describe('given the check domain ownership function', () => { it('will return false', async () => { const testCheckDomainOwnerShip = checkDomainOwnership({ query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockReturnValue({superAdmin: true, domainOwnership: false}), + next: jest.fn().mockReturnValue({ superAdmin: true, domainOwnership: false }), }), userKey: 123, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }) const permitted = await testCheckDomainOwnerShip({ domainId: 'domains/123', @@ -182,18 +278,18 @@ describe('given the check domain ownership function', () => { }) describe('if the user does not belong to an org which has a ownership for a given domain', () => { let permitted + beforeEach(async () => { + await collections.affiliations.save({ + _from: unverifiedOrg._id, + _to: user._id, + permission: 'user', + }) + }) it('will return false', async () => { const testCheckDomainOwnerShip = checkDomainOwnership({ - query: jest - .fn() - .mockReturnValueOnce({ - next: jest.fn().mockReturnValue({superAdmin: false}), - }) - .mockReturnValue({ - next: jest.fn().mockReturnValue([]), - }), + i18n: i18n, + query: query, userKey: '123', - auth: {loginRequiredBool: true}, }) permitted = await testCheckDomainOwnerShip({ domainId: 'domains/123', @@ -206,8 +302,8 @@ describe('given the check domain ownership function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -220,15 +316,13 @@ describe('given the check domain ownership function', () => { let mockQuery it('returns an appropriate error message', async () => { const firstCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), } mockQuery = jest.fn().mockReturnValueOnce(firstCursor) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -236,11 +330,7 @@ describe('given the check domain ownership function', () => { domainId: domain._id, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Cursor error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, ]) @@ -257,18 +347,13 @@ describe('given the check domain ownership function', () => { }), } const secondCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), } - mockQuery = jest - .fn() - .mockReturnValueOnce(firstCursor) - .mockReturnValue(secondCursor) + mockQuery = jest.fn().mockReturnValueOnce(firstCursor).mockReturnValue(secondCursor) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -276,11 +361,7 @@ describe('given the check domain ownership function', () => { domainId: domain._id, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Cursor error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Cursor error occurred.`, ]) @@ -290,13 +371,11 @@ describe('given the check domain ownership function', () => { describe('if a database error is encountered during super admin check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -304,11 +383,7 @@ describe('given the check domain ownership function', () => { domainId: domain._id, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving super admin affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, ]) @@ -332,7 +407,7 @@ describe('given the check domain ownership function', () => { try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -340,11 +415,7 @@ describe('given the check domain ownership function', () => { domainId: domain._id, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Ownership check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Ownership check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving affiliated organization ownership for user: ${user._key} and domain: ${domain._id}: Error: Database error occurred.`, ]) @@ -357,8 +428,8 @@ describe('given the check domain ownership function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -371,15 +442,13 @@ describe('given the check domain ownership function', () => { let mockQuery it('returns an appropriate error message', async () => { const firstCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), } mockQuery = jest.fn().mockReturnValueOnce(firstCursor) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -408,18 +477,13 @@ describe('given the check domain ownership function', () => { }), } const secondCursor = { - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), } - mockQuery = jest - .fn() - .mockReturnValueOnce(firstCursor) - .mockReturnValue(secondCursor) + mockQuery = jest.fn().mockReturnValueOnce(firstCursor).mockReturnValue(secondCursor) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -441,13 +505,11 @@ describe('given the check domain ownership function', () => { describe('if a database error is encountered during super admin check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) @@ -483,7 +545,7 @@ describe('given the check domain ownership function', () => { try { const testCheckDomainOwnerShip = checkDomainOwnership({ i18n, - auth: {loginRequired: true}, + auth: { loginRequired: true }, query: mockQuery, userKey: user._key, }) diff --git a/api/src/auth/__tests__/check-domain-permission.test.js b/api/src/auth/__tests__/check-domain-permission.test.js index 931a27295f..d39b081675 100644 --- a/api/src/auth/__tests__/check-domain-permission.test.js +++ b/api/src/auth/__tests__/check-domain-permission.test.js @@ -1,12 +1,12 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' -import {checkDomainPermission} from '../index' +import { checkDomainPermission } from '../index' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import dbschema from '../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the check domain permission function', () => { let query, drop, truncate, collections, org, domain, i18n @@ -23,7 +23,7 @@ describe('given the check domain permission function', () => { describe('given a successful domain permission call', () => { let user, permitted beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -103,7 +103,7 @@ describe('given the check domain permission function', () => { query, userKey: user._key, }) - permitted = await testCheckDomainPermission({domainId: domain._id}) + permitted = await testCheckDomainPermission({ domainId: domain._id }) expect(permitted).toEqual(true) }) }) @@ -121,7 +121,7 @@ describe('given the check domain permission function', () => { query, userKey: user._key, }) - permitted = await testCheckDomainPermission({domainId: domain._id}) + permitted = await testCheckDomainPermission({ domainId: domain._id }) expect(permitted).toEqual(true) }) }) @@ -138,7 +138,7 @@ describe('given the check domain permission function', () => { query, userKey: user._key, }) - permitted = await testCheckDomainPermission({domainId: domain._id}) + permitted = await testCheckDomainPermission({ domainId: domain._id }) expect(permitted).toEqual(true) }) }) @@ -152,11 +152,11 @@ describe('given the check domain permission function', () => { const testCheckDomainPermission = checkDomainPermission({ query: jest .fn() - .mockReturnValueOnce({count: 0}) - .mockReturnValue({next: jest.fn().mockReturnValue([])}), + .mockReturnValueOnce({ count: 0 }) + .mockReturnValue({ next: jest.fn().mockReturnValue(false) }), userKey: 123, }) - permitted = await testCheckDomainPermission({domainId: 'domains/123'}) + permitted = await testCheckDomainPermission({ domainId: 'domains/123' }) expect(permitted).toEqual(false) }) }) @@ -165,8 +165,8 @@ describe('given the check domain permission function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -178,22 +178,16 @@ describe('given the check domain permission function', () => { describe('if a database error is encountered during super admin permission check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ i18n, query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: 'domains/123'}) + await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving super admin claims for user: 123 and domain: domains/123: Error: Database error occurred.`, ]) @@ -205,7 +199,7 @@ describe('given the check domain permission function', () => { it('returns an appropriate error message', async () => { mockQuery = jest .fn() - .mockReturnValueOnce({count: 0}) + .mockReturnValueOnce({ count: 0 }) .mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ @@ -213,13 +207,9 @@ describe('given the check domain permission function', () => { query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: 'domains/123'}) + await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Database error when retrieving affiliated organization claims for user: 123 and domain: domains/123: Error: Database error occurred.`, ]) @@ -234,23 +224,16 @@ describe('given the check domain permission function', () => { throw new Error('Cursor error occurred.') }, } - mockQuery = jest - .fn() - .mockReturnValueOnce({count: 0}) - .mockReturnValue(cursor) + mockQuery = jest.fn().mockReturnValueOnce({ count: 0 }).mockReturnValue(cursor) try { const testCheckDomainPermission = checkDomainPermission({ i18n, query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: 'domains/123'}) + await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { - expect(err).toEqual( - new Error( - 'Permission check error. Unable to request domain information.', - ), - ) + expect(err).toEqual(new Error('Permission check error. Unable to request domain information.')) expect(consoleOutput).toEqual([ `Cursor error when retrieving affiliated organization claims for user: 123 and domain: domains/123: Error: Cursor error occurred.`, ]) @@ -263,8 +246,8 @@ describe('given the check domain permission function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -276,16 +259,14 @@ describe('given the check domain permission function', () => { describe('if a database error is encountered during super admin permission check', () => { let mockQuery it('returns an appropriate error message', async () => { - mockQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + mockQuery = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ i18n, query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: 'domains/123'}) + await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { expect(err).toEqual( new Error( @@ -303,7 +284,7 @@ describe('given the check domain permission function', () => { it('returns an appropriate error message', async () => { mockQuery = jest .fn() - .mockReturnValueOnce({count: 0}) + .mockReturnValueOnce({ count: 0 }) .mockRejectedValue(new Error('Database error occurred.')) try { const testCheckDomainPermission = checkDomainPermission({ @@ -311,7 +292,7 @@ describe('given the check domain permission function', () => { query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: 'domains/123'}) + await testCheckDomainPermission({ domainId: 'domains/123' }) } catch (err) { expect(err).toEqual( new Error( @@ -332,17 +313,14 @@ describe('given the check domain permission function', () => { throw new Error('Cursor error occurred.') }, } - mockQuery = jest - .fn() - .mockReturnValueOnce({count: 1}) - .mockReturnValue(cursor) + mockQuery = jest.fn().mockReturnValueOnce({ count: 1 }).mockReturnValue(cursor) try { const testCheckDomainPermission = checkDomainPermission({ i18n, query: mockQuery, userKey: 123, }) - await testCheckDomainPermission({domainId: domain._id}) + await testCheckDomainPermission({ domainId: domain._id }) } catch (err) { expect(err).toEqual(new Error('todo')) expect(consoleOutput).toEqual([ diff --git a/api/src/auth/__tests__/check-org-owner.test.js b/api/src/auth/__tests__/check-org-owner.test.js index e4fc8c9b79..ade0b35e4c 100644 --- a/api/src/auth/__tests__/check-org-owner.test.js +++ b/api/src/auth/__tests__/check-org-owner.test.js @@ -1,18 +1,18 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' -import {checkOrgOwner} from '../check-org-owner' +import { checkOrgOwner } from '../check-org-owner' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import dbschema from '../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the checkOrgOwner function', () => { describe('given a successful check', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -68,14 +68,13 @@ describe('given the checkOrgOwner function', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) it('returns true', async () => { - const testCheckOrgOwner = checkOrgOwner({query, userKey: user._key}) + const testCheckOrgOwner = checkOrgOwner({ query, userKey: user._key }) - const result = await testCheckOrgOwner({orgId: org._id}) + const result = await testCheckOrgOwner({ orgId: org._id }) expect(result).toEqual(true) }) @@ -86,13 +85,12 @@ describe('given the checkOrgOwner function', () => { _from: org._id, _to: user._id, permission: 'admin', - owner: false, }) }) it('returns false', async () => { - const testCheckOrgOwner = checkOrgOwner({query, userKey: user._key}) + const testCheckOrgOwner = checkOrgOwner({ query, userKey: user._key }) - const result = await testCheckOrgOwner({orgId: org._id}) + const result = await testCheckOrgOwner({ orgId: org._id }) expect(result).toEqual(false) }) @@ -113,7 +111,7 @@ describe('given the checkOrgOwner function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, + en: { plurals: {} }, }, locales: ['en'], messages: { @@ -123,9 +121,7 @@ describe('given the checkOrgOwner function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckOrgOwner = checkOrgOwner({ i18n, @@ -134,11 +130,9 @@ describe('given the checkOrgOwner function', () => { }) try { - await testCheckOrgOwner({orgId: '123'}) + await testCheckOrgOwner({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error(`Unable to load owner information. Please try again.`), - ) + expect(err).toEqual(new Error(`Unable to load owner information. Please try again.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 is the owner of: 123: Error: Database error occurred`, @@ -148,9 +142,7 @@ describe('given the checkOrgOwner function', () => { describe('cursor error occurs', () => { it('throws an error', async () => { const mockedQuery = jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred')), }) const testCheckOrgOwner = checkOrgOwner({ @@ -160,11 +152,9 @@ describe('given the checkOrgOwner function', () => { }) try { - await testCheckOrgOwner({orgId: '123'}) + await testCheckOrgOwner({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error(`Unable to load owner information. Please try again.`), - ) + expect(err).toEqual(new Error(`Unable to load owner information. Please try again.`)) } expect(consoleOutput).toEqual([ `Cursor error when checking to see if user: 123 is the owner of: 123: Error: Cursor error occurred`, @@ -177,7 +167,7 @@ describe('given the checkOrgOwner function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - fr: {plurals: {}}, + fr: { plurals: {} }, }, locales: ['fr'], messages: { @@ -187,9 +177,7 @@ describe('given the checkOrgOwner function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckOrgOwner = checkOrgOwner({ i18n, @@ -198,12 +186,10 @@ describe('given the checkOrgOwner function', () => { }) try { - await testCheckOrgOwner({orgId: '123'}) + await testCheckOrgOwner({ orgId: '123' }) } catch (err) { expect(err).toEqual( - new Error( - `Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`, - ), + new Error(`Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`), ) } expect(consoleOutput).toEqual([ @@ -214,9 +200,7 @@ describe('given the checkOrgOwner function', () => { describe('cursor error occurs', () => { it('throws an error', async () => { const mockedQuery = jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred')), }) const testCheckOrgOwner = checkOrgOwner({ @@ -226,12 +210,10 @@ describe('given the checkOrgOwner function', () => { }) try { - await testCheckOrgOwner({orgId: '123'}) + await testCheckOrgOwner({ orgId: '123' }) } catch (err) { expect(err).toEqual( - new Error( - `Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`, - ), + new Error(`Impossible de charger les informations sur le propriétaire. Veuillez réessayer.`), ) } expect(consoleOutput).toEqual([ diff --git a/api/src/auth/__tests__/check-permission.test.js b/api/src/auth/__tests__/check-permission.test.js index b60a82ac6a..17c2a52964 100644 --- a/api/src/auth/__tests__/check-permission.test.js +++ b/api/src/auth/__tests__/check-permission.test.js @@ -1,12 +1,12 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' -import {checkPermission} from '..' +import { checkPermission } from '..' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import dbschema from '../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the check permission function', () => { let query, drop, truncate, collections, i18n @@ -22,7 +22,7 @@ describe('given the check permission function', () => { describe('given a successful permission call', () => { beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -102,7 +102,7 @@ describe('given the check permission function', () => { userKey: user._key, query, }) - const permission = await testCheckPermission({orgId: org._id}) + const permission = await testCheckPermission({ orgId: org._id }) expect(permission).toEqual('super_admin') }) }) @@ -135,7 +135,7 @@ describe('given the check permission function', () => { userKey: user._key, query, }) - const permission = await testCheckPermission({orgId: org._id}) + const permission = await testCheckPermission({ orgId: org._id }) expect(permission).toEqual('admin') }) }) @@ -168,7 +168,7 @@ describe('given the check permission function', () => { userKey: user._key, query, }) - const permission = await testCheckPermission({orgId: org._id}) + const permission = await testCheckPermission({ orgId: org._id }) expect(permission).toEqual('user') }) }) @@ -193,8 +193,8 @@ describe('given the check permission function', () => { userKey: user._key, query, }) - const permission = await testCheckPermission({orgId: org._id}) - expect(permission).toEqual(undefined) + const permission = await testCheckPermission({ orgId: org._id }) + expect(permission).toEqual(null) }) }) }) @@ -204,8 +204,8 @@ describe('given the check permission function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -217,9 +217,7 @@ describe('given the check permission function', () => { describe('database error occurs', () => { describe('when checking if super admin', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckPermission = checkPermission({ @@ -227,11 +225,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -256,11 +252,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Authentication error. Please sign in.'), - ) + expect(err).toEqual(new Error('Authentication error. Please sign in.')) } expect(consoleOutput).toEqual([ @@ -285,11 +279,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -319,11 +311,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error('Unable to check permission. Please try again.'), - ) + expect(err).toEqual(new Error('Unable to check permission. Please try again.')) } expect(consoleOutput).toEqual([ @@ -338,8 +328,8 @@ describe('given the check permission function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -351,9 +341,7 @@ describe('given the check permission function', () => { describe('database error occurs', () => { describe('when checking if super admin', () => { it('throws an error', async () => { - query = jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')) + query = jest.fn().mockRejectedValue(new Error('Database error occurred.')) try { const testCheckPermission = checkPermission({ @@ -361,13 +349,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Erreur d'authentification. Veuillez vous connecter.", - ), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ @@ -392,13 +376,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Erreur d'authentification. Veuillez vous connecter.", - ), - ) + expect(err).toEqual(new Error("Erreur d'authentification. Veuillez vous connecter.")) } expect(consoleOutput).toEqual([ @@ -423,13 +403,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -459,13 +435,9 @@ describe('given the check permission function', () => { userKey: '1', query, }) - await testCheckPermission({orgId: 'organizations/1'}) + await testCheckPermission({ orgId: 'organizations/1' }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible de vérifier l'autorisation. Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible de vérifier l'autorisation. Veuillez réessayer.")) } expect(consoleOutput).toEqual([ diff --git a/api/src/auth/__tests__/check-user-belongs-to-org.test.js b/api/src/auth/__tests__/check-user-belongs-to-org.test.js index 0300544a93..6957b05017 100644 --- a/api/src/auth/__tests__/check-user-belongs-to-org.test.js +++ b/api/src/auth/__tests__/check-user-belongs-to-org.test.js @@ -1,18 +1,18 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { setupI18n } from '@lingui/core' -import {checkUserBelongsToOrg} from '../check-user-belongs-to-org' +import { checkUserBelongsToOrg } from '../check-user-belongs-to-org' import englishMessages from '../../locale/en/messages' import frenchMessages from '../../locale/fr/messages' import dbschema from '../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the checkUserBelongsToOrg function', () => { describe('given a successful call', () => { let query, drop, truncate, collections, user, org beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -68,8 +68,7 @@ describe('given the checkUserBelongsToOrg function', () => { await collections.affiliations.save({ _from: org._id, _to: user._id, - permission: 'admin', - owner: true, + permission: 'owner', }) }) it('returns true', async () => { @@ -78,7 +77,7 @@ describe('given the checkUserBelongsToOrg function', () => { userKey: user._key, }) - const result = await testCheckUserBelongsToOrg({orgId: org._id}) + const result = await testCheckUserBelongsToOrg({ orgId: org._id }) expect(result).toEqual(true) }) @@ -90,7 +89,7 @@ describe('given the checkUserBelongsToOrg function', () => { userKey: user._key, }) - const result = await testCheckUserBelongsToOrg({orgId: org._id}) + const result = await testCheckUserBelongsToOrg({ orgId: org._id }) expect(result).toEqual(false) }) @@ -111,7 +110,7 @@ describe('given the checkUserBelongsToOrg function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, + en: { plurals: {} }, }, locales: ['en'], messages: { @@ -121,9 +120,7 @@ describe('given the checkUserBelongsToOrg function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckUserBelongsToOrg = checkUserBelongsToOrg({ i18n, @@ -132,13 +129,9 @@ describe('given the checkUserBelongsToOrg function', () => { }) try { - await testCheckUserBelongsToOrg({orgId: '123'}) + await testCheckUserBelongsToOrg({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error( - `Unable to load affiliation information. Please try again.`, - ), - ) + expect(err).toEqual(new Error(`Unable to load affiliation information. Please try again.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 belongs to org: 123: Error: Database error occurred`, @@ -151,7 +144,7 @@ describe('given the checkUserBelongsToOrg function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - fr: {plurals: {}}, + fr: { plurals: {} }, }, locales: ['fr'], messages: { @@ -161,9 +154,7 @@ describe('given the checkUserBelongsToOrg function', () => { }) describe('database error occurs', () => { it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const testCheckUserBelongsToOrg = checkUserBelongsToOrg({ i18n, @@ -172,13 +163,9 @@ describe('given the checkUserBelongsToOrg function', () => { }) try { - await testCheckUserBelongsToOrg({orgId: '123'}) + await testCheckUserBelongsToOrg({ orgId: '123' }) } catch (err) { - expect(err).toEqual( - new Error( - `Impossible de charger les informations d'affiliation. Veuillez réessayer.`, - ), - ) + expect(err).toEqual(new Error(`Impossible de charger les informations d'affiliation. Veuillez réessayer.`)) } expect(consoleOutput).toEqual([ `Database error when checking to see if user: 123 belongs to org: 123: Error: Database error occurred`, diff --git a/api/src/auth/check-domain-ownership.js b/api/src/auth/check-domain-ownership.js index efedb347be..982a78e693 100644 --- a/api/src/auth/check-domain-ownership.js +++ b/api/src/auth/check-domain-ownership.js @@ -1,13 +1,10 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkDomainOwnership = - ({i18n, - query, - userKey, - auth: {loginRequiredBool}}) => - async ({domainId}) => { - let userAffiliatedOwnership, ownership - const userKeyString = `users/${userKey}` + ({ i18n, query, userKey }) => + async ({ domainId }) => { + let userAffiliatedOwnership, ownership + const userKeyString = `users/${userKey}` // Check to see if the user is a super admin let superAdminAffiliationCursor @@ -29,9 +26,7 @@ export const checkDomainOwnership = console.error( `Database error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) } let superAdminAffiliation @@ -41,12 +36,10 @@ export const checkDomainOwnership = console.error( `Cursor error when retrieving super admin affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) } - if (superAdminAffiliation.superAdmin || !loginRequiredBool) { + if (superAdminAffiliation.superAdmin) { return !!superAdminAffiliation.domainOwnership } @@ -54,18 +47,22 @@ export const checkDomainOwnership = try { userAffiliatedOwnership = await query` WITH affiliations, domains, organizations, ownership, users - LET userAffiliations = (FOR v, e IN 1..1 ANY ${userKeyString} affiliations RETURN e._from) - LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN e._from) + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET domainOwnerships = (FOR v, e IN 1..1 ANY ${domainId} ownership RETURN v) + LET domainBelongsToVerifiedOrg = POSITION(domainOwnerships[*].verified, true) LET affiliatedOwnership = INTERSECTION(userAffiliations, domainOwnerships) - RETURN affiliatedOwnership + RETURN (domainBelongsToVerifiedOrg && hasVerifiedOrgAffiliation) || LENGTH(affiliatedOwnership) > 0 ` } catch (err) { console.error( `Database error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) } try { @@ -74,10 +71,8 @@ export const checkDomainOwnership = console.error( `Cursor error when retrieving affiliated organization ownership for user: ${userKey} and domain: ${domainId}: ${err}`, ) - throw new Error( - i18n._(t`Ownership check error. Unable to request domain information.`), - ) + throw new Error(i18n._(t`Ownership check error. Unable to request domain information.`)) } - return ownership[0] !== undefined + return ownership } diff --git a/api/src/auth/check-domain-permission.js b/api/src/auth/check-domain-permission.js index 24006b11aa..54d8e300e1 100644 --- a/api/src/auth/check-domain-permission.js +++ b/api/src/auth/check-domain-permission.js @@ -1,66 +1,65 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkDomainPermission = - ({i18n, query, userKey}) => - async ({domainId}) => { - let userAffiliatedClaims, claim - const userKeyString = `users/${userKey}` + ({ i18n, query, userKey }) => + async ({ domainId }) => { + let userAffiliatedClaims + const userKeyString = `users/${userKey}` - // Check to see if the user is a super admin - let superAdminAffiliationCursor - try { - superAdminAffiliationCursor = await query` + // Check to see if the user is a super admin + let superAdminAffiliationCursor + try { + superAdminAffiliationCursor = await query` WITH affiliations, organizations, users FOR v, e IN 1..1 ANY ${userKeyString} affiliations FILTER e.permission == 'super_admin' RETURN e.from ` - } catch (err) { - console.error( - `Database error when retrieving super admin claims for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), - ) - } + } catch (err) { + console.error( + `Database error when retrieving super admin claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } - if (superAdminAffiliationCursor.count > 0) { - return true - } + if (superAdminAffiliationCursor.count > 0) { + return true + } - // Retrieve user affiliations and affiliated organizations owning provided domain - try { - userAffiliatedClaims = await query` - WITH affiliations, claims, domains, organizations, users - LET userAffiliations = (FOR v, e IN 1..1 ANY ${userKeyString} affiliations RETURN e._from) - LET domainClaims = (FOR v, e IN 1..1 ANY ${domainId} claims RETURN e._from) - LET affiliatedClaims = INTERSECTION(userAffiliations, domainClaims) - RETURN affiliatedClaims - ` - } catch (err) { - console.error( - `Database error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, + // Retrieve user affiliations and affiliated organizations owning provided domain + try { + userAffiliatedClaims = await query` + WITH domains, users, organizations + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET domainOrgClaims = ( + FOR v, e IN 1..1 ANY ${domainId} claims + RETURN v ) - } + LET domainBelongsToVerifiedOrg = POSITION(domainOrgClaims[*].verified, true) + LET affiliatedClaims = INTERSECTION(userAffiliations, domainOrgClaims) + RETURN (domainBelongsToVerifiedOrg && hasVerifiedOrgAffiliation) || LENGTH(affiliatedClaims) > 0 + ` + } catch (err) { + console.error( + `Database error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) + } - try { - claim = await userAffiliatedClaims.next() - } catch (err) { - console.error( - `Cursor error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, - ) - throw new Error( - i18n._( - t`Permission check error. Unable to request domain information.`, - ), - ) - } - return claim[0] !== undefined + let affiliated + try { + affiliated = await userAffiliatedClaims.next() + } catch (err) { + console.error( + `Cursor error when retrieving affiliated organization claims for user: ${userKey} and domain: ${domainId}: ${err}`, + ) + throw new Error(i18n._(t`Permission check error. Unable to request domain information.`)) } + + return affiliated + } diff --git a/api/src/auth/check-org-owner.js b/api/src/auth/check-org-owner.js index f8baf74e0b..51fac07950 100644 --- a/api/src/auth/check-org-owner.js +++ b/api/src/auth/check-org-owner.js @@ -1,39 +1,31 @@ -import {t} from '@lingui/macro' +import { t } from '@lingui/macro' export const checkOrgOwner = - ({i18n, query, userKey}) => - async ({orgId}) => { - const userIdString = `users/${userKey}` + ({ i18n, query, userKey }) => + async ({ orgId }) => { + const userIdString = `users/${userKey}` - // find affiliation - let affiliationCursor - try { - affiliationCursor = await query` + // find affiliation + let affiliationCursor + try { + affiliationCursor = await query` WITH affiliations, organizations, users FOR v, e IN 1..1 OUTBOUND ${orgId} affiliations FILTER e._to == ${userIdString} - RETURN e.owner + RETURN e.permission == "owner" ` - } catch (err) { - console.error( - `Database error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load owner information. Please try again.`), - ) - } - - let isOrgOwner - try { - isOrgOwner = await affiliationCursor.next() - } catch (err) { - console.error( - `Cursor error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load owner information. Please try again.`), - ) - } + } catch (err) { + console.error(`Database error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) + } - return isOrgOwner + let isOrgOwner + try { + isOrgOwner = await affiliationCursor.next() + } catch (err) { + console.error(`Cursor error when checking to see if user: ${userKey} is the owner of: ${orgId}: ${err}`) + throw new Error(i18n._(t`Unable to load owner information. Please try again.`)) } + + return isOrgOwner + } diff --git a/api/src/auth/check-permission.js b/api/src/auth/check-permission.js index dad9a94eaf..33d68b47f9 100644 --- a/api/src/auth/check-permission.js +++ b/api/src/auth/check-permission.js @@ -11,7 +11,7 @@ export const checkPermission = WITH affiliations, organizations, users FOR v, e IN 1 INBOUND ${userKeyString} affiliations FILTER e.permission == "super_admin" - RETURN e.permission + RETURN e.permission ` } catch (err) { console.error(`Database error when checking to see if user: ${userKeyString} has super admin permission: ${err}`) @@ -28,26 +28,40 @@ export const checkPermission = if (permission === 'super_admin') { return permission - } else { - // Check for other permission level - try { - cursor = await query` + } + + // Check for other permission level + try { + cursor = await query` WITH affiliations, organizations, users - FOR v, e IN 1 INBOUND ${userKeyString} affiliations - FILTER e._from == ${orgId} - RETURN e.permission + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET org = DOCUMENT(${orgId}) + LET orgIsVerified = org.verified + LET userOrgAffiliation = FIRST( + FOR v, e IN 1..1 ANY ${userKeyString} affiliations + FILTER e._from == ${orgId} + RETURN e + ) + RETURN userOrgAffiliation.permission IN ["user", "admin", "owner", "super_admin"] ? userOrgAffiliation.permission + : (orgIsVerified && hasVerifiedOrgAffiliation) ? "user" + : userOrgAffiliation.permission == "pending" ? userOrgAffiliation + : null ` - } catch (err) { - console.error(`Database error occurred when checking ${userKeyString}'s permission: ${err}`) - throw new Error(i18n._(t`Authentication error. Please sign in.`)) - } + } catch (err) { + console.error(`Database error occurred when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Authentication error. Please sign in.`)) + } - try { - permission = await cursor.next() - } catch (err) { - console.error(`Cursor error when checking ${userKeyString}'s permission: ${err}`) - throw new Error(i18n._(t`Unable to check permission. Please try again.`)) - } - return permission + try { + permission = await cursor.next() + } catch (err) { + console.error(`Cursor error when checking ${userKeyString}'s permission: ${err}`) + throw new Error(i18n._(t`Unable to check permission. Please try again.`)) } + return permission } diff --git a/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js b/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js index 146b46ae5e..cab6a06a7b 100644 --- a/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js +++ b/api/src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js @@ -2,360 +2,325 @@ import { aql } from 'arangojs' import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' -export const loadDmarcSummaryConnectionsByUserId = ({ - query, - userKey, - cleanseInput, - i18n, - loadStartDateFromPeriod, - auth: { loginRequiredBool }, -}) => async ({ - after, - before, - first, - last, - period, - year, - orderBy, - isSuperAdmin, - search, -}) => { - const userDBId = `users/${userKey}` - - if (typeof period === 'undefined') { - console.warn( - `User: ${userKey} did not have \`period\` argument set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`period\` value to access the \`DmarcSummaries\` connection.`, - ), - ) - } - const cleansedPeriod = cleanseInput(period) +export const loadDmarcSummaryConnectionsByUserId = + ({ query, userKey, cleanseInput, i18n, loadStartDateFromPeriod }) => + async ({ after, before, first, last, period, year, orderBy, isSuperAdmin, search }) => { + const userDBId = `users/${userKey}` + + if (typeof period === 'undefined') { + console.warn(`User: ${userKey} did not have \`period\` argument set for: loadDmarcSummaryConnectionsByUserId.`) + throw new Error(i18n._(t`You must provide a \`period\` value to access the \`DmarcSummaries\` connection.`)) + } + const cleansedPeriod = cleanseInput(period) - if (typeof year === 'undefined') { - console.warn( - `User: ${userKey} did not have \`year\` argument set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`year\` value to access the \`DmarcSummaries\` connection.`, - ), - ) - } - const cleansedYear = cleanseInput(year) - - const startDate = loadStartDateFromPeriod({ - period: cleansedPeriod, - year: cleansedYear, - }) - - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(${afterId})` - } else { - let afterTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` + if (typeof year === 'undefined') { + console.warn(`User: ${userKey} did not have \`year\` argument set for: loadDmarcSummaryConnectionsByUserId.`) + throw new Error(i18n._(t`You must provide a \`year\` value to access the \`DmarcSummaries\` connection.`)) + } + const cleansedYear = cleanseInput(year) + + const startDate = loadStartDateFromPeriod({ + period: cleansedPeriod, + year: cleansedYear, + }) + + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(${afterId})` } else { - afterTemplateDirection = aql`<` - } - - afterVar = aql`LET afterVar = DOCUMENT(dmarcSummaries, ${afterId})` + let afterTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } else { + afterTemplateDirection = aql`<` + } - let documentField = aql`` - let summaryField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - documentField = aql`afterVar.categoryTotals.fail` - summaryField = aql`summary.categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - documentField = aql`afterVar.categoryTotals.pass` - summaryField = aql`summary.categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - documentField = aql`afterVar.categoryTotals.passDkimOnly` - summaryField = aql`summary.categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - documentField = aql`afterVar.categoryTotals.passSpfOnly` - summaryField = aql`summary.categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - documentField = aql`afterVar.categoryPercentages.fail` - summaryField = aql`summary.categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - documentField = aql`afterVar.categoryPercentages.pass` - summaryField = aql`summary.categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - documentField = aql`afterVar.categoryPercentages.passDkimOnly` - summaryField = aql`summary.categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - documentField = aql`afterVar.categoryPercentages.passSpfOnly` - summaryField = aql`summary.categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - documentField = aql`afterVar.totalMessages` - summaryField = aql`summary.totalMessages` - } else if (orderBy.field === 'domain') { - documentField = aql` + afterVar = aql`LET afterVar = DOCUMENT(dmarcSummaries, ${afterId})` + + let documentField = aql`` + let summaryField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + documentField = aql`afterVar.categoryTotals.fail` + summaryField = aql`summary.categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + documentField = aql`afterVar.categoryTotals.pass` + summaryField = aql`summary.categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + documentField = aql`afterVar.categoryTotals.passDkimOnly` + summaryField = aql`summary.categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + documentField = aql`afterVar.categoryTotals.passSpfOnly` + summaryField = aql`summary.categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + documentField = aql`afterVar.categoryPercentages.fail` + summaryField = aql`summary.categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + documentField = aql`afterVar.categoryPercentages.pass` + summaryField = aql`summary.categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + documentField = aql`afterVar.categoryPercentages.passDkimOnly` + summaryField = aql`summary.categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + documentField = aql`afterVar.categoryPercentages.passSpfOnly` + summaryField = aql`summary.categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + documentField = aql`afterVar.totalMessages` + summaryField = aql`summary.totalMessages` + } else if (orderBy.field === 'domain') { + documentField = aql` FIRST( FOR v, e IN 1..1 ANY afterVar._id domainsToDmarcSummaries RETURN v.domain ) ` - summaryField = aql`domain.domain` - } + summaryField = aql`domain.domain` + } - afterTemplate = aql` + afterTemplate = aql` FILTER ${summaryField} ${afterTemplateDirection} ${documentField} OR (${summaryField} == ${documentField} AND TO_NUMBER(summary._key) > TO_NUMBER(${afterId})) ` + } } - } - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(${beforeId})` } else { - beforeTemplateDirection = aql`>` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(dmarcSummaries, ${beforeId})` + let beforeTemplateDirection = aql`` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } else { + beforeTemplateDirection = aql`>` + } - let documentField = aql`` - let summaryField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - documentField = aql`beforeVar.categoryTotals.fail` - summaryField = aql`summary.categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - documentField = aql`beforeVar.categoryTotals.pass` - summaryField = aql`summary.categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - documentField = aql`beforeVar.categoryTotals.passDkimOnly` - summaryField = aql`summary.categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - documentField = aql`beforeVar.categoryTotals.passSpfOnly` - summaryField = aql`summary.categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - documentField = aql`beforeVar.categoryPercentages.fail` - summaryField = aql`summary.categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - documentField = aql`beforeVar.categoryPercentages.pass` - summaryField = aql`summary.categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - documentField = aql`beforeVar.categoryPercentages.passDkimOnly` - summaryField = aql`summary.categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - documentField = aql`beforeVar.categoryPercentages.passSpfOnly` - summaryField = aql`summary.categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - documentField = aql`beforeVar.totalMessages` - summaryField = aql`summary.totalMessages` - } else if (orderBy.field === 'domain') { - documentField = aql` + beforeVar = aql`LET beforeVar = DOCUMENT(dmarcSummaries, ${beforeId})` + + let documentField = aql`` + let summaryField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + documentField = aql`beforeVar.categoryTotals.fail` + summaryField = aql`summary.categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + documentField = aql`beforeVar.categoryTotals.pass` + summaryField = aql`summary.categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + documentField = aql`beforeVar.categoryTotals.passDkimOnly` + summaryField = aql`summary.categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + documentField = aql`beforeVar.categoryTotals.passSpfOnly` + summaryField = aql`summary.categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + documentField = aql`beforeVar.categoryPercentages.fail` + summaryField = aql`summary.categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + documentField = aql`beforeVar.categoryPercentages.pass` + summaryField = aql`summary.categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + documentField = aql`beforeVar.categoryPercentages.passDkimOnly` + summaryField = aql`summary.categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + documentField = aql`beforeVar.categoryPercentages.passSpfOnly` + summaryField = aql`summary.categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + documentField = aql`beforeVar.totalMessages` + summaryField = aql`summary.totalMessages` + } else if (orderBy.field === 'domain') { + documentField = aql` FIRST( FOR v, e IN 1..1 ANY beforeVar._id domainsToDmarcSummaries RETURN v.domain ) ` - summaryField = aql`domain.domain` - } + summaryField = aql`domain.domain` + } - beforeTemplate = aql` + beforeTemplate = aql` FILTER ${summaryField} ${beforeTemplateDirection} ${documentField} OR (${summaryField} == ${documentField} AND TO_NUMBER(summary._key) < TO_NUMBER(${beforeId})) ` + } } - } - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`DmarcSummaries\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`DmarcSummaries\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, ) throw new Error( i18n._( - t`\`${argSet}\` on the \`DmarcSummaries\` connection cannot be less than zero.`, + t`You must provide a \`first\` or \`last\` value to properly paginate the \`DmarcSummaries\` connection.`, ), ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcSummaryConnectionsByUserId.`, + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadDmarcSummaryConnectionsByUserId.`, ) throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`DmarcSummaries\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`DmarcSummaries\` connection is not supported.`), ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(summary._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(summary._key) DESC LIMIT TO_NUMBER(${last})` - } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcSummaryConnectionsByUserId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - - let hasNextPageFilter = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`` - let hasPreviousPageDirection = aql`` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadDmarcSummaryConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` on the \`DmarcSummaries\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set to ${amount} for: loadDmarcSummaryConnectionsByUserId.`, + ) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`DmarcSummaries\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(summary._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(summary._key) DESC LIMIT TO_NUMBER(${last})` + } } else { - hasNextPageDirection = aql`<` - hasPreviousPageDirection = aql`>` + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadDmarcSummaryConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) } - let hasNextPageDocumentField = aql`` - let summaryField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - summaryField = aql`summary.categoryTotals.fail` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.fail` - } else if (orderBy.field === 'pass-count') { - summaryField = aql`summary.categoryTotals.pass` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.pass` - } else if (orderBy.field === 'pass-dkim-count') { - summaryField = aql`summary.categoryTotals.passDkimOnly` - hasNextPageDocumentField = aql` LAST(retrievedSummaries).categoryTotals.passDkimOnly` - hasPreviousPageDocumentField = aql` FIRST(retrievedSummaries).categoryTotals.passDkimOnly` - } else if (orderBy.field === 'pass-spf-count') { - summaryField = aql`summary.categoryTotals.passSpfOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.passSpfOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.passSpfOnly` - } else if (orderBy.field === 'fail-percentage') { - summaryField = aql`summary.categoryPercentages.fail` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.fail` - } else if (orderBy.field === 'pass-percentage') { - summaryField = aql`summary.categoryPercentages.pass` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.pass` - } else if (orderBy.field === 'pass-dkim-percentage') { - summaryField = aql`summary.categoryPercentages.passDkimOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passDkimOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passDkimOnly` - } else if (orderBy.field === 'pass-spf-percentage') { - summaryField = aql`summary.categoryPercentages.passSpfOnly` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passSpfOnly` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passSpfOnly` - } else if (orderBy.field === 'total-messages') { - summaryField = aql`summary.totalMessages` - hasNextPageDocumentField = aql`LAST(retrievedSummaries).totalMessages` - hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).totalMessages` - } else if (orderBy.field === 'domain') { - summaryField = aql`domain.domain` - hasNextPageDocumentField = aql` + let hasNextPageFilter = aql`FILTER TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`` + let hasPreviousPageDirection = aql`` + if (orderBy.direction === 'ASC') { + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` + } else { + hasNextPageDirection = aql`<` + hasPreviousPageDirection = aql`>` + } + + let hasNextPageDocumentField = aql`` + let summaryField = aql`` + let hasPreviousPageDocumentField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + summaryField = aql`summary.categoryTotals.fail` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.fail` + } else if (orderBy.field === 'pass-count') { + summaryField = aql`summary.categoryTotals.pass` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.pass` + } else if (orderBy.field === 'pass-dkim-count') { + summaryField = aql`summary.categoryTotals.passDkimOnly` + hasNextPageDocumentField = aql` LAST(retrievedSummaries).categoryTotals.passDkimOnly` + hasPreviousPageDocumentField = aql` FIRST(retrievedSummaries).categoryTotals.passDkimOnly` + } else if (orderBy.field === 'pass-spf-count') { + summaryField = aql`summary.categoryTotals.passSpfOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryTotals.passSpfOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryTotals.passSpfOnly` + } else if (orderBy.field === 'fail-percentage') { + summaryField = aql`summary.categoryPercentages.fail` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.fail` + } else if (orderBy.field === 'pass-percentage') { + summaryField = aql`summary.categoryPercentages.pass` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.pass` + } else if (orderBy.field === 'pass-dkim-percentage') { + summaryField = aql`summary.categoryPercentages.passDkimOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passDkimOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passDkimOnly` + } else if (orderBy.field === 'pass-spf-percentage') { + summaryField = aql`summary.categoryPercentages.passSpfOnly` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).categoryPercentages.passSpfOnly` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).categoryPercentages.passSpfOnly` + } else if (orderBy.field === 'total-messages') { + summaryField = aql`summary.totalMessages` + hasNextPageDocumentField = aql`LAST(retrievedSummaries).totalMessages` + hasPreviousPageDocumentField = aql`FIRST(retrievedSummaries).totalMessages` + } else if (orderBy.field === 'domain') { + summaryField = aql`domain.domain` + hasNextPageDocumentField = aql` FIRST( FOR v, e IN 1..1 ANY LAST(retrievedSummaries)._id domainsToDmarcSummaries RETURN v.domain ) ` - hasPreviousPageDocumentField = aql` + hasPreviousPageDocumentField = aql` FIRST( FOR v, e IN 1..1 ANY FIRST(retrievedSummaries)._id domainsToDmarcSummaries RETURN v.domain ) ` - } + } - hasNextPageFilter = aql` + hasNextPageFilter = aql` FILTER ${summaryField} ${hasNextPageDirection} ${hasNextPageDocumentField} OR (${summaryField} == ${hasNextPageDocumentField} AND TO_NUMBER(summary._key) > TO_NUMBER(LAST(retrievedSummaries)._key)) ` - hasPreviousPageFilter = aql` + hasPreviousPageFilter = aql` FILTER ${summaryField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} OR (${summaryField} == ${hasPreviousPageDocumentField} AND TO_NUMBER(summary._key) < TO_NUMBER(FIRST(retrievedSummaries)._key)) ` - } + } - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'fail-count') { - sortByField = aql`summary.categoryTotals.fail ${orderBy.direction},` - } else if (orderBy.field === 'pass-count') { - sortByField = aql`summary.categoryTotals.pass ${orderBy.direction},` - } else if (orderBy.field === 'pass-dkim-count') { - sortByField = aql`summary.categoryTotals.passDkimOnly ${orderBy.direction},` - } else if (orderBy.field === 'pass-spf-count') { - sortByField = aql`summary.categoryTotals.passSpfOnly ${orderBy.direction},` - } else if (orderBy.field === 'fail-percentage') { - sortByField = aql`summary.categoryPercentages.fail ${orderBy.direction},` - } else if (orderBy.field === 'pass-percentage') { - sortByField = aql`summary.categoryPercentages.pass ${orderBy.direction},` - } else if (orderBy.field === 'pass-dkim-percentage') { - sortByField = aql`summary.categoryPercentages.passDkimOnly ${orderBy.direction},` - } else if (orderBy.field === 'pass-spf-percentage') { - sortByField = aql`summary.categoryPercentages.passSpfOnly ${orderBy.direction},` - } else if (orderBy.field === 'total-messages') { - sortByField = aql`summary.totalMessages ${orderBy.direction},` - } else if (orderBy.field === 'domain') { - sortByField = aql`domain.domain ${orderBy.direction},` + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { + /* istanbul ignore else */ + if (orderBy.field === 'fail-count') { + sortByField = aql`summary.categoryTotals.fail ${orderBy.direction},` + } else if (orderBy.field === 'pass-count') { + sortByField = aql`summary.categoryTotals.pass ${orderBy.direction},` + } else if (orderBy.field === 'pass-dkim-count') { + sortByField = aql`summary.categoryTotals.passDkimOnly ${orderBy.direction},` + } else if (orderBy.field === 'pass-spf-count') { + sortByField = aql`summary.categoryTotals.passSpfOnly ${orderBy.direction},` + } else if (orderBy.field === 'fail-percentage') { + sortByField = aql`summary.categoryPercentages.fail ${orderBy.direction},` + } else if (orderBy.field === 'pass-percentage') { + sortByField = aql`summary.categoryPercentages.pass ${orderBy.direction},` + } else if (orderBy.field === 'pass-dkim-percentage') { + sortByField = aql`summary.categoryPercentages.passDkimOnly ${orderBy.direction},` + } else if (orderBy.field === 'pass-spf-percentage') { + sortByField = aql`summary.categoryPercentages.passSpfOnly ${orderBy.direction},` + } else if (orderBy.field === 'total-messages') { + sortByField = aql`summary.totalMessages ${orderBy.direction},` + } else if (orderBy.field === 'domain') { + sortByField = aql`domain.domain ${orderBy.direction},` + } } - } - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` + } - let domainQuery = aql`` - let searchDomainFilter = aql`` - if (typeof search !== 'undefined') { - search = cleanseInput(search) - domainQuery = aql` + let domainQuery = aql`` + let searchDomainFilter = aql`` + if (typeof search !== 'undefined') { + search = cleanseInput(search) + domainQuery = aql` LET tokenArr = TOKENS(${search}, "space-delimiter-analyzer") LET searchedDomains = ( FOR token IN tokenArr @@ -364,12 +329,12 @@ export const loadDmarcSummaryConnectionsByUserId = ({ RETURN domain._id ) ` - searchDomainFilter = aql`FILTER domainId IN searchedDomains` - } + searchDomainFilter = aql`FILTER domainId IN searchedDomains` + } - let domainIdQueries - if (isSuperAdmin || !loginRequiredBool) { - domainIdQueries = aql` + let domainIdQueries + if (isSuperAdmin) { + domainIdQueries = aql` WITH affiliations, dmarcSummaries, domains, domainsToDmarcSummaries, organizations, ownership, users, domainSearch LET domainIds = UNIQUE(FLATTEN( LET ids = [] @@ -379,22 +344,27 @@ export const loadDmarcSummaryConnectionsByUserId = ({ RETURN APPEND(ids, claimDomainIds) )) ` - } else { - domainIdQueries = aql` + } else { + domainIdQueries = aql` WITH affiliations, dmarcSummaries, domains, domainsToDmarcSummaries, organizations, ownership, users, domainSearch - LET domainIds = UNIQUE(FLATTEN( - LET ids = [] - LET orgIds = (FOR v, e IN 1..1 ANY ${userDBId} affiliations RETURN e._from) - FOR orgId IN orgIds - LET claimDomainIds = (FOR v, e IN 1..1 OUTBOUND orgId ownership RETURN v._id) - RETURN APPEND(ids, claimDomainIds) - )) + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + LET domainIds = UNIQUE( + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || (hasVerifiedOrgAffiliation == true && org.verified == true) + FOR v, e IN 1..1 OUTBOUND org._id ownership + RETURN v._id + ) ` - } + } - let requestedSummaryInfo - try { - requestedSummaryInfo = await query` + let requestedSummaryInfo + try { + requestedSummaryInfo = await query` ${domainIdQueries} ${domainQuery} @@ -469,56 +439,52 @@ export const loadDmarcSummaryConnectionsByUserId = ({ "endKey": LAST(retrievedSummaries)._key } ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC summary data. Please try again.`), - ) - } + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load DMARC summary data. Please try again.`)) + } - let summariesInfo - try { - summariesInfo = await requestedSummaryInfo.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load DMARC summary data. Please try again.`), - ) - } + let summariesInfo + try { + summariesInfo = await requestedSummaryInfo.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather dmarc summaries in loadDmarcSummaryConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load DMARC summary data. Please try again.`)) + } - if (summariesInfo.summaries.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, + if (summariesInfo.summaries.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = summariesInfo.summaries.map((summary) => { - summary.startDate = startDate + const edges = summariesInfo.summaries.map((summary) => { + summary.startDate = startDate + return { + cursor: toGlobalId('dmarcSummary', summary.id), + node: summary, + } + }) + return { - cursor: toGlobalId('dmarcSummary', summary.id), - node: summary, + edges, + totalCount: summariesInfo.totalCount, + pageInfo: { + hasNextPage: summariesInfo.hasNextPage, + hasPreviousPage: summariesInfo.hasPreviousPage, + startCursor: toGlobalId('dmarcSummary', summariesInfo.startKey), + endCursor: toGlobalId('dmarcSummary', summariesInfo.endKey), + }, } - }) - - return { - edges, - totalCount: summariesInfo.totalCount, - pageInfo: { - hasNextPage: summariesInfo.hasNextPage, - hasPreviousPage: summariesInfo.hasPreviousPage, - startCursor: toGlobalId('dmarcSummary', summariesInfo.startKey), - endCursor: toGlobalId('dmarcSummary', summariesInfo.endKey), - }, } -} diff --git a/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js b/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js index d1259ad88f..385222c1fd 100644 --- a/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js +++ b/api/src/dmarc-summaries/queries/find-my-dmarc-summaries.js @@ -24,8 +24,7 @@ export const findMyDmarcSummaries = { }, search: { type: GraphQLString, - description: - 'An optional string used to filter the results based on domains.', + description: 'An optional string used to filter the results based on domains.', }, ...connectionArgs, }, @@ -34,19 +33,12 @@ export const findMyDmarcSummaries = { args, { userKey, - auth: { - checkSuperAdmin, - userRequired, - verifiedRequired, - loginRequiredBool, - }, + auth: { checkSuperAdmin, userRequired, verifiedRequired }, loaders: { loadDmarcSummaryConnectionsByUserId }, }, ) => { - if (loginRequiredBool) { - const user = await userRequired() - verifiedRequired({ user }) - } + const user = await userRequired() + verifiedRequired({ user }) const isSuperAdmin = await checkSuperAdmin() diff --git a/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js b/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js index 6c27006d79..e0871384e3 100644 --- a/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js +++ b/api/src/domain/loaders/__tests__/load-domain-connections-by-user-id.test.js @@ -1,15 +1,15 @@ -import {stringify} from 'jest-matcher-utils' -import {ensure, dbNameFromFile} from 'arango-tools' -import {toGlobalId} from 'graphql-relay' -import {setupI18n} from '@lingui/core' +import { stringify } from 'jest-matcher-utils' +import { ensure, dbNameFromFile } from 'arango-tools' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {cleanseInput} from '../../../validators' -import {loadDomainConnectionsByUserId, loadDomainByKey} from '../index' +import { cleanseInput } from '../../../validators' +import { loadDomainConnectionsByUserId, loadDomainByKey } from '../index' import dbschema from '../../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the load domain connections by user id function', () => { let query, drop, truncate, collections, org, i18n, user, domainOne, domainTwo @@ -28,7 +28,7 @@ describe('given the load domain connections by user id function', () => { describe('given a successful load', () => { beforeAll(async () => { - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -123,14 +123,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -139,7 +136,7 @@ describe('given the load domain connections by user id function', () => { first: 10, after: toGlobalId('domain', expectedDomains[0].id), } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -168,14 +165,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -184,7 +178,7 @@ describe('given the load domain connections by user id function', () => { first: 10, before: toGlobalId('domain', expectedDomains[1].id), } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -213,14 +207,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -228,7 +219,7 @@ describe('given the load domain connections by user id function', () => { const connectionArgs = { first: 1, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -257,14 +248,11 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -272,7 +260,7 @@ describe('given the load domain connections by user id function', () => { const connectionArgs = { last: 1, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -310,10 +298,10 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) + const domainLoader = loadDomainByKey({ query }) const expectedDomain = await domainLoader.load(domainOne._key) const connectionArgs = { @@ -321,7 +309,7 @@ describe('given the load domain connections by user id function', () => { search: 'test1.gc.ca', } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -367,19 +355,17 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainThree._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainThree._key]) const connectionArgs = { first: 1, ownership: true, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -408,21 +394,17 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - domainThree._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key, domainThree._key]) const connectionArgs = { first: 3, ownership: false, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -463,11 +445,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DOMAIN', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -484,9 +463,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -511,11 +490,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -532,9 +508,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -561,11 +537,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DKIM_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -582,9 +555,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -609,11 +582,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -630,9 +600,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -659,11 +629,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DMARC_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -680,9 +647,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -707,11 +674,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -728,9 +692,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -757,11 +721,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on HTTPS_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -778,9 +739,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -805,11 +766,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -826,9 +784,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -855,11 +813,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on SPF_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -876,9 +831,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -903,11 +858,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -924,9 +876,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -955,11 +907,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DOMAIN', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -976,9 +925,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1003,11 +952,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1024,9 +970,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1053,11 +999,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DKIM_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1074,9 +1017,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1101,11 +1044,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1122,9 +1062,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1151,11 +1091,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on DMARC_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1172,9 +1109,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1199,11 +1136,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1220,9 +1154,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1249,11 +1183,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on HTTPS_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1270,9 +1201,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1297,11 +1228,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1318,9 +1246,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1347,11 +1275,8 @@ describe('given the load domain connections by user id function', () => { describe('ordering on SPF_STATUS', () => { describe('order direction is ASC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1368,9 +1293,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1395,11 +1320,8 @@ describe('given the load domain connections by user id function', () => { }) describe('order direction is DESC', () => { it('returns domains in order', async () => { - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1416,9 +1338,9 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [ @@ -1450,20 +1372,17 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) const connectionArgs = { first: 10, isSuperAdmin: true, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) - const domainLoader = loadDomainByKey({query}) - const expectedDomains = await domainLoader.loadMany([ - domainOne._key, - domainTwo._key, - ]) + const domainLoader = loadDomainByKey({ query }) + const expectedDomains = await domainLoader.loadMany([domainOne._key, domainTwo._key]) expectedDomains[0].id = expectedDomains[0]._key expectedDomains[1].id = expectedDomains[1]._key @@ -1504,13 +1423,13 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }) const connectionArgs = { first: 10, } - const domains = await connectionLoader({...connectionArgs}) + const domains = await connectionLoader({ ...connectionArgs }) const expectedStructure = { edges: [], @@ -1532,8 +1451,8 @@ describe('given the load domain connections by user id function', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1549,7 +1468,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1577,7 +1496,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1591,9 +1510,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`, - ), + new Error(`Passing both \`first\` and \`last\` to paginate the \`Domain\` connection is not supported.`), ) } @@ -1609,7 +1526,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1639,7 +1556,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1671,7 +1588,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1683,11 +1600,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -1701,7 +1614,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1713,11 +1626,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` on the \`Domain\` connection cannot be less than zero.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` on the \`Domain\` connection cannot be less than zero.`)) } expect(consoleOutput).toEqual([ @@ -1729,14 +1638,12 @@ describe('given the load domain connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1749,11 +1656,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`first\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -1765,14 +1668,12 @@ describe('given the load domain connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1785,11 +1686,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - `\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`, - ), - ) + expect(err).toEqual(new Error(`\`last\` must be of type \`number\` not \`${typeof invalidInput}\`.`)) } expect(consoleOutput).toEqual([ `User: ${ @@ -1804,17 +1701,13 @@ describe('given the load domain connections by user id function', () => { describe('given a database error', () => { describe('while querying for domain information', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query domains. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query domains. Please try again.')) const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1826,9 +1719,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to query domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to query domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1851,7 +1742,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1863,9 +1754,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error('Unable to load domain(s). Please try again.'), - ) + expect(err).toEqual(new Error('Unable to load domain(s). Please try again.')) } expect(consoleOutput).toEqual([ @@ -1880,8 +1769,8 @@ describe('given the load domain connections by user id function', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1897,7 +1786,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1925,7 +1814,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1939,9 +1828,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.", - ), + new Error("Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté."), ) } @@ -1957,7 +1844,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -1987,7 +1874,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2019,7 +1906,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2031,11 +1918,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`first` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`first` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2049,7 +1932,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2061,11 +1944,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - '`last` sur la connexion `Domain` ne peut être inférieur à zéro.', - ), - ) + expect(err).toEqual(new Error('`last` sur la connexion `Domain` ne peut être inférieur à zéro.')) } expect(consoleOutput).toEqual([ @@ -2077,14 +1956,12 @@ describe('given the load domain connections by user id function', () => { describe('limits are not set to numbers', () => { describe('first limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when first set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when first set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2098,9 +1975,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`first\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2113,14 +1988,12 @@ describe('given the load domain connections by user id function', () => { }) describe('last limit is set', () => { ;['123', {}, [], null, true].forEach((invalidInput) => { - it(`returns an error when last set to ${stringify( - invalidInput, - )}`, async () => { + it(`returns an error when last set to ${stringify(invalidInput)}`, async () => { const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2134,9 +2007,7 @@ describe('given the load domain connections by user id function', () => { }) } catch (err) { expect(err).toEqual( - new Error( - `\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`, - ), + new Error(`\`last\` doit être de type \`number\` et non \`${typeof invalidInput}\`.`), ) } expect(consoleOutput).toEqual([ @@ -2152,17 +2023,13 @@ describe('given the load domain connections by user id function', () => { describe('given a database error', () => { describe('while querying domains', () => { it('returns an error message', async () => { - const query = jest - .fn() - .mockRejectedValue( - new Error('Unable to query domains. Please try again.'), - ) + const query = jest.fn().mockRejectedValue(new Error('Unable to query domains. Please try again.')) const connectionLoader = loadDomainConnectionsByUserId({ query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2174,11 +2041,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - "Impossible d'interroger le(s) domaine(s). Veuillez réessayer.", - ), - ) + expect(err).toEqual(new Error("Impossible d'interroger le(s) domaine(s). Veuillez réessayer.")) } expect(consoleOutput).toEqual([ @@ -2201,7 +2064,7 @@ describe('given the load domain connections by user id function', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }) @@ -2213,11 +2076,7 @@ describe('given the load domain connections by user id function', () => { ...connectionArgs, }) } catch (err) { - expect(err).toEqual( - new Error( - 'Impossible de charger le(s) domaine(s). Veuillez réessayer.', - ), - ) + expect(err).toEqual(new Error('Impossible de charger le(s) domaine(s). Veuillez réessayer.')) } expect(consoleOutput).toEqual([ diff --git a/api/src/domain/loaders/load-domain-connections-by-user-id.js b/api/src/domain/loaders/load-domain-connections-by-user-id.js index 9ccd692fca..cf51e8e7ab 100644 --- a/api/src/domain/loaders/load-domain-connections-by-user-id.js +++ b/api/src/domain/loaders/load-domain-connections-by-user-id.js @@ -333,7 +333,13 @@ export const loadDomainConnectionsByUserId = domainKeysQuery = aql` WITH affiliations, domains, organizations, users, domainSearch, claims, ownership LET collectedDomains = UNIQUE( + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || org.verified == true ${ownershipOrgsOnly} FILTER v.archived != true RETURN v @@ -343,9 +349,17 @@ export const loadDomainConnectionsByUserId = domainKeysQuery = aql` WITH affiliations, domains, organizations, users, domainSearch, claims, ownership LET collectedDomains = UNIQUE( - FOR org, affiliationEdge IN 1..1 ANY ${userDBId} affiliations - FILTER affiliationEdge.permission != "pending" + LET userAffiliations = ( + FOR v, e IN 1..1 INBOUND ${userDBId} affiliations + FILTER e.permission != "pending" + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) + + FOR org IN organizations + FILTER org._key IN userAffiliations[*]._key || (hasVerifiedOrgAffiliation == true && org.verified == true) ${ownershipOrgsOnly} + FILTER v.archived != true RETURN v ) ` diff --git a/api/src/domain/mutations/__tests__/create-domain.test.js b/api/src/domain/mutations/__tests__/create-domain.test.js index 1ee487a253..529934d060 100644 --- a/api/src/domain/mutations/__tests__/create-domain.test.js +++ b/api/src/domain/mutations/__tests__/create-domain.test.js @@ -1,13 +1,13 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' -import {createQuerySchema} from '../../../query' -import {createMutationSchema} from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {cleanseInput, slugify} from '../../../validators' +import { cleanseInput, slugify } from '../../../validators' import { checkPermission, userRequired, @@ -15,17 +15,15 @@ import { saltedHash, verifiedRequired, tfaRequired, + checkDomainPermission, } from '../../../auth' -import {loadDomainByDomain} from '../../loaders' -import { - loadOrgByKey, - loadOrgConnectionsByDomainId, -} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' +import { loadDomainByDomain } from '../../loaders' +import { loadOrgByKey, loadOrgConnectionsByDomainId } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url, HASHING_SECRET} = process.env +const { DB_PASS: rootPass, DB_URL: url, HASHING_SECRET } = process.env describe('create a domain', () => { let query, drop, truncate, schema, collections, transaction, user, org @@ -48,8 +46,20 @@ describe('create a domain', () => { consoleOutput.length = 0 }) describe('given a successful domain creation', () => { + const i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) beforeAll(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -160,29 +170,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequiredBool: true}, + auth: { loginRequiredBool: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -314,29 +329,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -445,159 +465,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), - saltedHash: saltedHash(HASHING_SECRET), - userRequired: userRequired({ + checkDomainPermission: checkDomainPermission({ + i18n, userKey: user._key, - loadUserByKey: loadUserByKey({query}), - }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), - verifiedRequired: verifiedRequired({}), - tfaRequired: tfaRequired({}), - }, - loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, - language: 'en', - userKey: user._key, - cleanseInput, - auth: {loginRequired: true}, }), - loadUserByKey: loadUserByKey({query}), - }, - validators: {cleanseInput, slugify}, - }, - ) - - const domainCursor = await query` - FOR domain IN domains - RETURN domain - ` - const domain = await domainCursor.next() - - const expectedResponse = { - data: { - createDomain: { - result: { - id: toGlobalId('domain', domain._key), - domain: 'test.gc.ca', - lastRan: null, - selectors: ['selector1', 'selector2'], - status: { - dkim: null, - dmarc: null, - https: null, - spf: null, - ssl: null, - }, - organizations: { - edges: [ - { - node: { - id: toGlobalId('organization', org._key), - name: 'Treasury Board of Canada Secretariat', - }, - }, - ], - }, - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully created ${domain.domain} in org: treasury-board-secretariat.`, - ]) - }) - }) - describe('user has user permission level', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - }) - }) - it('returns the domain', async () => { - const response = await graphql( - schema, - ` - mutation { - createDomain( - input: { - orgId: "${toGlobalId('organization', org._key)}" - domain: "test.gc.ca" - selectors: ["selector1", "selector2"] - } - ) { - result { - ... on Domain { - id - domain - lastRan - selectors - status { - dkim - dmarc - https - spf - ssl - } - organizations(first: 5) { - edges { - node { - id - name - } - } - } - } - ... on DomainError { - code - description - } - } - } - } - `, - null, - { - request: { - language: 'en', - }, - query, - collections: collectionNames, - transaction, - userKey: user._key, - publish: jest.fn(), - auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -748,29 +643,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -895,29 +795,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1042,29 +947,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1081,12 +991,7 @@ describe('create a domain', () => { id: toGlobalId('domain', domain._key), domain: 'test.gc.ca', lastRan: null, - selectors: [ - 'selector1', - 'selector2', - 'selector3', - 'selector4', - ], + selectors: ['selector1', 'selector2', 'selector3', 'selector4'], status: { dkim: null, dmarc: null, @@ -1194,29 +1099,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1341,29 +1251,34 @@ describe('create a domain', () => { userKey: user._key, publish: jest.fn(), auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), saltedHash: saltedHash(HASHING_SECRET), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), - checkSuperAdmin: checkSuperAdmin({userKey: user._key, query}), + checkSuperAdmin: checkSuperAdmin({ userKey: user._key, query }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), }, loaders: { - loadDomainByDomain: loadDomainByDomain({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), + loadDomainByDomain: loadDomainByDomain({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), loadOrgConnectionsByDomainId: loadOrgConnectionsByDomainId({ query, language: 'en', userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1425,8 +1340,8 @@ describe('create a domain', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1442,11 +1357,7 @@ describe('create a domain', () => { ` mutation { createDomain( - input: { - orgId: "b3JnYW5pemF0aW9uOjE=" - domain: "test.gc.ca" - selectors: ["selector1", "selector2"] - } + input: { orgId: "b3JnYW5pemF0aW9uOjE=", domain: "test.gc.ca", selectors: ["selector1", "selector2"] } ) { result { ... on Domain { @@ -1490,6 +1401,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn(), saltedHash: jest.fn(), userRequired: jest.fn(), @@ -1508,7 +1424,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1517,8 +1433,7 @@ describe('create a domain', () => { createDomain: { result: { code: 400, - description: - 'Unable to create domain in unknown organization.', + description: 'Unable to create domain in unknown organization.', }, }, }, @@ -1585,6 +1500,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -1605,7 +1525,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1614,8 +1534,7 @@ describe('create a domain', () => { createDomain: { result: { code: 400, - description: - 'Permission Denied: Please contact organization user for help with creating domain.', + description: 'Permission Denied: Please contact organization user for help with creating domain.', }, }, }, @@ -1684,6 +1603,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -1704,7 +1628,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -1713,8 +1637,7 @@ describe('create a domain', () => { createDomain: { result: { code: 400, - description: - 'Unable to create domain, organization has already claimed it.', + description: 'Unable to create domain, organization has already claimed it.', }, }, }, @@ -1776,14 +1699,17 @@ describe('create a domain', () => { request: { language: 'en', }, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), collections: collectionNames, transaction, userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -1804,13 +1730,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1870,15 +1794,18 @@ describe('create a domain', () => { language: 'en', }, query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), }), collections: collectionNames, transaction, userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -1899,13 +1826,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1968,14 +1893,17 @@ describe('create a domain', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValueOnce({ - next: jest - .fn() - .mockRejectedValue(new Error('cursor error')), + next: jest.fn().mockRejectedValue(new Error('cursor error')), }), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -1996,13 +1924,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2067,13 +1993,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2094,13 +2023,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2172,6 +2099,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2192,13 +2124,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2262,13 +2192,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2294,13 +2227,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2362,14 +2293,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2395,13 +2328,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2467,13 +2398,16 @@ describe('create a domain', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2499,13 +2433,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError('Unable to create domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to create domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2520,8 +2452,8 @@ describe('create a domain', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -2537,11 +2469,7 @@ describe('create a domain', () => { ` mutation { createDomain( - input: { - orgId: "b3JnYW5pemF0aW9uOjE=" - domain: "test.gc.ca" - selectors: ["selector1", "selector2"] - } + input: { orgId: "b3JnYW5pemF0aW9uOjE=", domain: "test.gc.ca", selectors: ["selector1", "selector2"] } ) { result { ... on Domain { @@ -2585,6 +2513,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn(), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2603,7 +2536,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -2612,8 +2545,7 @@ describe('create a domain', () => { createDomain: { result: { code: 400, - description: - 'Impossible de créer un domaine dans une organisation inconnue.', + description: 'Impossible de créer un domaine dans une organisation inconnue.', }, }, }, @@ -2680,6 +2612,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue(undefined), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2700,7 +2637,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -2779,6 +2716,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2799,7 +2741,7 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) @@ -2808,8 +2750,7 @@ describe('create a domain', () => { createDomain: { result: { code: 400, - description: - "Impossible de créer le domaine, l'organisation l'a déjà réclamé.", + description: "Impossible de créer le domaine, l'organisation l'a déjà réclamé.", }, }, }, @@ -2871,14 +2812,17 @@ describe('create a domain', () => { request: { language: 'en', }, - query: jest - .fn() - .mockRejectedValue(new Error('Database error occurred.')), + query: jest.fn().mockRejectedValue(new Error('Database error occurred.')), collections: collectionNames, transaction, userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2899,15 +2843,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -2967,15 +2907,18 @@ describe('create a domain', () => { language: 'en', }, query: jest.fn().mockReturnValue({ - next: jest - .fn() - .mockRejectedValue(new Error('Cursor error occurred.')), + next: jest.fn().mockRejectedValue(new Error('Cursor error occurred.')), }), collections: collectionNames, transaction, userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -2996,15 +2939,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3067,14 +3006,17 @@ describe('create a domain', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValueOnce({ - next: jest - .fn() - .mockRejectedValue(new Error('cursor error')), + next: jest.fn().mockRejectedValue(new Error('cursor error')), }), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3095,15 +3037,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3168,13 +3106,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3195,15 +3136,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3275,6 +3212,11 @@ describe('create a domain', () => { userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3295,15 +3237,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3367,13 +3305,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3399,15 +3340,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3469,14 +3406,16 @@ describe('create a domain', () => { }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3502,15 +3441,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -3576,13 +3511,16 @@ describe('create a domain', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, publish: jest.fn(), auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkPermission: jest.fn().mockReturnValue('admin'), userRequired: jest.fn(), saltedHash: jest.fn(), @@ -3608,15 +3546,11 @@ describe('create a domain', () => { load: jest.fn(), }, }, - validators: {cleanseInput, slugify}, + validators: { cleanseInput, slugify }, }, ) - const error = [ - new GraphQLError( - 'Impossible de créer un domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de créer un domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/domain/mutations/__tests__/update-domain.test.js b/api/src/domain/mutations/__tests__/update-domain.test.js index d25bedc353..b5dd6b1280 100644 --- a/api/src/domain/mutations/__tests__/update-domain.test.js +++ b/api/src/domain/mutations/__tests__/update-domain.test.js @@ -1,26 +1,21 @@ -import {setupI18n} from '@lingui/core' -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' -import {createQuerySchema} from '../../../query' -import {createMutationSchema} from '../../../mutation' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {cleanseInput, slugify} from '../../../validators' -import { - checkPermission, - userRequired, - verifiedRequired, - tfaRequired, -} from '../../../auth' -import {loadDomainByKey} from '../../loaders' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' +import { cleanseInput, slugify } from '../../../validators' +import { checkPermission, userRequired, verifiedRequired, tfaRequired, checkDomainPermission } from '../../../auth' +import { loadDomainByKey } from '../../loaders' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('updating a domain', () => { let query, drop, truncate, schema, collections, transaction, user @@ -45,9 +40,21 @@ describe('updating a domain', () => { describe('given a successful domain update', () => { let org, domain + const i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, + }) beforeAll(async () => { // Generate DB Items - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -144,10 +151,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -157,9 +169,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -178,9 +190,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates selectors', () => { @@ -217,10 +227,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -230,9 +245,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -251,9 +266,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates domain and selectors', () => { @@ -291,10 +304,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -304,9 +322,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -325,9 +343,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -370,10 +386,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -383,9 +404,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -404,9 +425,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates selectors', () => { @@ -443,10 +462,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -456,9 +480,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -477,9 +501,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates domain and selectors', () => { @@ -517,10 +539,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -530,9 +557,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -551,9 +578,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -596,10 +621,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -609,9 +639,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -630,9 +660,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates selectors', () => { @@ -669,10 +697,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -682,9 +715,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -703,9 +736,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) describe('user updates domain and selectors', () => { @@ -743,10 +774,15 @@ describe('updating a domain', () => { transaction, userKey: user._key, auth: { - checkPermission: checkPermission({userKey: user._key, query}), + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), + checkPermission: checkPermission({ userKey: user._key, query }), userRequired: userRequired({ userKey: user._key, - loadUserByKey: loadUserByKey({query}), + loadUserByKey: loadUserByKey({ query }), }), verifiedRequired: verifiedRequired({}), tfaRequired: tfaRequired({}), @@ -756,9 +792,9 @@ describe('updating a domain', () => { slugify, }, loaders: { - loadDomainByKey: loadDomainByKey({query}), - loadOrgByKey: loadOrgByKey({query, language: 'en'}), - loadUserByKey: loadUserByKey({query}), + loadDomainByKey: loadDomainByKey({ query }), + loadOrgByKey: loadOrgByKey({ query, language: 'en' }), + loadUserByKey: loadUserByKey({ query }), }, }, ) @@ -777,9 +813,7 @@ describe('updating a domain', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully updated domain: ${domain._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully updated domain: ${domain._key}.`]) }) }) }) @@ -791,8 +825,8 @@ describe('updating a domain', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -857,7 +891,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn(), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -935,7 +969,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue(undefined), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1013,7 +1047,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1071,7 +1105,7 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -1092,7 +1126,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1102,8 +1136,7 @@ describe('updating a domain', () => { updateDomain: { result: { code: 400, - description: - 'Unable to update domain that does not belong to the given organization.', + description: 'Unable to update domain that does not belong to the given organization.', }, }, }, @@ -1172,14 +1205,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to update domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1224,12 +1255,10 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -1249,14 +1278,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to update domain. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1300,13 +1327,11 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, auth: { @@ -1326,14 +1351,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError('Unable to update domain. Please try again.'), - ] + const error = [new GraphQLError('Unable to update domain. Please try again.')] expect(response.errors).toEqual(error) }) @@ -1344,8 +1367,8 @@ describe('updating a domain', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -1410,7 +1433,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn(), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1420,8 +1443,7 @@ describe('updating a domain', () => { updateDomain: { result: { code: 400, - description: - 'Impossible de mettre à jour un domaine inconnu.', + description: 'Impossible de mettre à jour un domaine inconnu.', }, }, }, @@ -1489,7 +1511,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue(undefined), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1499,8 +1521,7 @@ describe('updating a domain', () => { updateDomain: { result: { code: 400, - description: - 'Impossible de mettre à jour le domaine dans un org inconnu.', + description: 'Impossible de mettre à jour le domaine dans un org inconnu.', }, }, }, @@ -1568,7 +1589,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1626,7 +1647,7 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 0}), + query: jest.fn().mockReturnValue({ count: 0 }), collections: collectionNames, transaction, userKey: 123, @@ -1647,7 +1668,7 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) @@ -1657,8 +1678,7 @@ describe('updating a domain', () => { updateDomain: { result: { code: 400, - description: - "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", + description: "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.", }, }, }, @@ -1727,16 +1747,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1781,12 +1797,10 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -1806,16 +1820,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -1859,13 +1869,11 @@ describe('updating a domain', () => { null, { i18n, - query: jest.fn().mockReturnValue({count: 1}), + query: jest.fn().mockReturnValue({ count: 1 }), collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, auth: { @@ -1885,16 +1893,12 @@ describe('updating a domain', () => { loadOrgByKey: { load: jest.fn().mockReturnValue({}), }, - loadUserByKey: {load: jest.fn()}, + loadUserByKey: { load: jest.fn() }, }, }, ) - const error = [ - new GraphQLError( - 'Impossible de mettre à jour le domaine. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de mettre à jour le domaine. Veuillez réessayer.')] expect(response.errors).toEqual(error) }) diff --git a/api/src/domain/mutations/create-domain.js b/api/src/domain/mutations/create-domain.js index 819cb33c35..595c13c1bc 100644 --- a/api/src/domain/mutations/create-domain.js +++ b/api/src/domain/mutations/create-domain.js @@ -111,7 +111,7 @@ export const createDomain = new mutationWithClientMutationId({ // Check to see if user belongs to org const permission = await checkPermission({ orgId: org._id }) - if (permission !== 'user' && permission !== 'admin' && permission !== 'super_admin') { + if (!['admin', 'owner', 'super_admin'].includes(permission)) { console.warn( `User: ${userKey} attempted to create a domain in: ${org.slug}, however they do not have permission to do so.`, ) diff --git a/api/src/domain/mutations/remove-domain.js b/api/src/domain/mutations/remove-domain.js index 13098c91ff..f8a0e4a1c4 100644 --- a/api/src/domain/mutations/remove-domain.js +++ b/api/src/domain/mutations/remove-domain.js @@ -84,7 +84,7 @@ export const removeDomain = new mutationWithClientMutationId({ // Get permission const permission = await checkPermission({ orgId: org._id }) - if (permission !== 'super_admin' && permission !== 'admin') { + if (['admin', 'owner', 'super_admin'].includes(permission) === false) { console.warn( `User: ${userKey} attempted to remove ${domain.domain} in ${org.slug} however they do not have permission in that org.`, ) diff --git a/api/src/domain/mutations/request-scan.js b/api/src/domain/mutations/request-scan.js index 2d31967e66..68c5444ec8 100644 --- a/api/src/domain/mutations/request-scan.js +++ b/api/src/domain/mutations/request-scan.js @@ -3,10 +3,11 @@ import { GraphQLString } from 'graphql' import { mutationWithClientMutationId } from 'graphql-relay' import { Domain } from '../../scalars' +import { logActivity } from '../../audit-logs' export const requestScan = new mutationWithClientMutationId({ name: 'RequestScan', - description: 'This mutation is used to step a manual scan on a requested domain.', + description: 'This mutation is used to start a manual scan on a requested domain.', inputFields: () => ({ domain: { type: Domain, @@ -23,10 +24,13 @@ export const requestScan = new mutationWithClientMutationId({ mutateAndGetPayload: async ( args, { + query, + collections, + transaction, i18n, userKey, publish, - auth: { checkDomainPermission, userRequired, verifiedRequired, loginRequiredBool }, + auth: { checkDomainPermission, userRequired, verifiedRequired }, loaders: { loadDomainByDomain, loadWebConnectionsByDomainId, loadWebScansByWebId }, validators: { cleanseInput }, }, @@ -43,23 +47,54 @@ export const requestScan = new mutationWithClientMutationId({ if (typeof domain === 'undefined') { console.warn( - `User: ${userKey} attempted to step a one time scan on: ${domainInput} however domain cannot be found.`, + `User: ${userKey} attempted to start a one time scan on: ${domainInput} however domain cannot be found.`, ) throw new Error(i18n._(t`Unable to request a one time scan on an unknown domain.`)) } - if (loginRequiredBool) { - // Check to see if user has access to domain - const permission = await checkDomainPermission({ domainId: domain._id }) + // Check to see if user has access to domain + const permission = await checkDomainPermission({ domainId: domain._id }) - if (!permission) { - console.warn( - `User: ${userKey} attempted to step a one time scan on: ${domain.domain} however they do not have permission to do so.`, + if (!permission) { + console.warn( + `User: ${userKey} attempted to start a one time scan on: ${domain.domain} however they do not have permission to do so.`, + ) + throw new Error( + i18n._(t`Permission Denied: Please contact organization user for help with scanning this domain.`), + ) + } + + let orgsClaimingDomainQuery + try { + orgsClaimingDomainQuery = await query` + WITH domains, users, organizations + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${user._id} affiliations + FILTER e.permission != "pending" + RETURN v ) - throw new Error( - i18n._(t`Permission Denied: Please contact organization user for help with scanning this domain.`), + LET domainOrgClaims = ( + FOR v, e IN 1..1 ANY ${domain._id} claims + RETURN v ) - } + LET orgsClaimingDomain = UNIQUE(domainOrgClaims[* FILTER CURRENT.verified == true || CURRENT IN userAffiliations]) + RETURN orgsClaimingDomain + ` + } catch (err) { + console.error( + `Database error when retrieving organizations claiming domain: ${userKey} and domain: ${domain._id}: ${err}`, + ) + throw new Error(i18n._(t`Error while requesting scan. Please try again.`)) + } + + let orgsClaimingDomain + try { + orgsClaimingDomain = await orgsClaimingDomainQuery.next() + } catch (err) { + console.error( + `Cursor error when retrieving organizations claiming domain: ${userKey} and domain: ${domain._id}: ${err}`, + ) + throw new Error(i18n._(t`Error while requesting scan. Please try again.`)) } // Check to see if a scan is already pending @@ -77,7 +112,7 @@ export const requestScan = new mutationWithClientMutationId({ const timeDifferenceInMinutes = (Date.now() - new Date(webConnection.timestamp).getTime()) / 1000 / 60 if (result.status.toUpperCase() === 'PENDING' && timeDifferenceInMinutes < 30) { console.warn( - `User: ${userKey} attempted to step a one time scan on: ${domain.domain} however a scan is already pending.`, + `User: ${userKey} attempted to start a one time scan on: ${domain.domain} however a scan is already pending.`, ) throw new Error(i18n._(t`Unable to request a one time scan on a domain that already has a pending scan.`)) } @@ -85,7 +120,7 @@ export const requestScan = new mutationWithClientMutationId({ } } catch (err) { console.error( - `Error occurred when user: ${userKey} attempted to step a one time scan on: ${domain.domain}, error: ${err}`, + `Error occurred when user: ${userKey} attempted to start a one time scan on: ${domain.domain}, error: ${err}`, ) throw new Error(i18n._(t`Unable to request a one time scan. Please try again.`)) } @@ -102,6 +137,29 @@ export const requestScan = new mutationWithClientMutationId({ }, }) + // Logs scan request activity for each org claiming domain + for (const orgClaimingDomain of orgsClaimingDomain) { + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + }, + action: 'scan', + target: { + resource: domain.domain, + organization: { + id: orgClaimingDomain._key, + name: orgClaimingDomain.orgDetails.en.name, + }, // name of resource being acted upon + resourceType: 'domain', // user, org, domain + }, + }) + } + console.info(`User: ${userKey} successfully dispatched a one time scan on domain: ${domain.domain}.`) return { diff --git a/api/src/domain/mutations/update-domain.js b/api/src/domain/mutations/update-domain.js index 6383786449..923af0e7b5 100644 --- a/api/src/domain/mutations/update-domain.js +++ b/api/src/domain/mutations/update-domain.js @@ -1,16 +1,15 @@ -import {GraphQLID, GraphQLNonNull, GraphQLList, GraphQLBoolean} from 'graphql' -import {mutationWithClientMutationId, fromGlobalId} from 'graphql-relay' -import {t} from '@lingui/macro' +import { GraphQLID, GraphQLNonNull, GraphQLList, GraphQLBoolean } from 'graphql' +import { mutationWithClientMutationId, fromGlobalId } from 'graphql-relay' +import { t } from '@lingui/macro' -import {updateDomainUnion} from '../unions' -import {Domain, Selectors} from '../../scalars' +import { updateDomainUnion } from '../unions' +import { Domain, Selectors } from '../../scalars' import { logActivity } from '../../audit-logs/mutations/log-activity' import { inputTag } from '../inputs/domain-tag' export const updateDomain = new mutationWithClientMutationId({ name: 'UpdateDomain', - description: - 'Mutation allows the modification of domains if domain is updated through out its life-cycle', + description: 'Mutation allows the modification of domains if domain is updated through out its life-cycle', inputFields: () => ({ domainId: { type: GraphQLNonNull(GraphQLID), @@ -18,8 +17,7 @@ export const updateDomain = new mutationWithClientMutationId({ }, orgId: { type: GraphQLNonNull(GraphQLID), - description: - 'The global ID of the organization used for permission checks.', + description: 'The global ID of the organization used for permission checks.', }, domain: { type: Domain, @@ -27,29 +25,25 @@ export const updateDomain = new mutationWithClientMutationId({ }, selectors: { type: new GraphQLList(Selectors), - description: - 'The updated DKIM selector strings corresponding to this domain.', + description: 'The updated DKIM selector strings corresponding to this domain.', }, tags: { description: 'List of labelled tags users have applied to the domain.', type: new GraphQLList(inputTag), }, hidden: { - description: - "Value that determines if the domain is excluded from an organization's score.", + description: "Value that determines if the domain is excluded from an organization's score.", type: GraphQLBoolean, }, archived: { - description: - 'Value that determines if the domain is excluded from the scanning process.', + description: 'Value that determines if the domain is excluded from the scanning process.', type: GraphQLBoolean, }, }), outputFields: () => ({ result: { type: updateDomainUnion, - description: - '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', + description: '`UpdateDomainUnion` returning either a `Domain`, or `DomainError` object.', resolve: (payload) => payload, }, }), @@ -61,19 +55,19 @@ export const updateDomain = new mutationWithClientMutationId({ collections, transaction, userKey, - auth: {checkPermission, userRequired, verifiedRequired, tfaRequired}, - validators: {cleanseInput}, - loaders: {loadDomainByKey, loadOrgByKey}, + auth: { checkPermission, userRequired, verifiedRequired, tfaRequired }, + validators: { cleanseInput }, + loaders: { loadDomainByKey, loadOrgByKey }, }, ) => { // Get User const user = await userRequired() - verifiedRequired({user}) - tfaRequired({user}) + verifiedRequired({ user }) + tfaRequired({ user }) - const {id: domainId} = fromGlobalId(cleanseInput(args.domainId)) - const {id: orgId} = fromGlobalId(cleanseInput(args.orgId)) + const { id: domainId } = fromGlobalId(cleanseInput(args.domainId)) + const { id: orgId } = fromGlobalId(cleanseInput(args.orgId)) const updatedDomain = cleanseInput(args.domain) let selectors @@ -133,22 +127,16 @@ export const updateDomain = new mutationWithClientMutationId({ } // Check permission - const permission = await checkPermission({orgId: org._id}) + const permission = await checkPermission({ orgId: org._id }) - if ( - permission !== 'user' && - permission !== 'admin' && - permission !== 'super_admin' - ) { + if (!['admin', 'owner', 'super_admin'].includes(permission)) { console.warn( `User: ${userKey} attempted to update domain: ${domainId} for org: ${orgId}, however they do not have permission in that org.`, ) return { _type: 'error', code: 403, - description: i18n._( - t`Permission Denied: Please contact organization user for help with updating this domain.`, - ), + description: i18n._(t`Permission Denied: Please contact organization user for help with updating this domain.`), } } @@ -175,9 +163,7 @@ export const updateDomain = new mutationWithClientMutationId({ return { _type: 'error', code: 400, - description: i18n._( - t`Unable to update domain that does not belong to the given organization.`, - ), + description: i18n._(t`Unable to update domain that does not belong to the given organization.`), } } @@ -219,17 +205,13 @@ export const updateDomain = new mutationWithClientMutationId({ RETURN MERGE({ id: claim._key, _type: "claim" }, claim) ` } catch (err) { - console.error( - `Database error occurred when user: ${userKey} running loadDomainByKey: ${err}`, - ) + console.error(`Database error occurred when user: ${userKey} running loadDomainByKey: ${err}`) } let claim try { claim = await claimCursor.next() } catch (err) { - console.error( - `Cursor error occurred when user: ${userKey} running loadDomainByKey: ${err}`, - ) + console.error(`Cursor error occurred when user: ${userKey} running loadDomainByKey: ${err}`) } const claimToInsert = { @@ -252,9 +234,7 @@ export const updateDomain = new mutationWithClientMutationId({ console.error( `Transaction step error occurred when user: ${userKey} attempted to update domain edge, error: ${err}`, ) - throw new Error( - i18n._(t`Unable to update domain edge. Please try again.`), - ) + throw new Error(i18n._(t`Unable to update domain edge. Please try again.`)) } // Commit transaction @@ -283,8 +263,7 @@ export const updateDomain = new mutationWithClientMutationId({ } if ( typeof selectors !== 'undefined' && - JSON.stringify(domainToInsert.selectors) !== - JSON.stringify(domain.selectors) + JSON.stringify(domainToInsert.selectors) !== JSON.stringify(domain.selectors) ) { updatedProperties.push({ name: 'selectors', @@ -293,10 +272,7 @@ export const updateDomain = new mutationWithClientMutationId({ }) } - if ( - typeof tags !== 'undefined' && - JSON.stringify(claim.tags) !== JSON.stringify(tags) - ) { + if (typeof tags !== 'undefined' && JSON.stringify(claim.tags) !== JSON.stringify(tags)) { updatedProperties.push({ name: 'tags', oldValue: claim.tags, @@ -349,9 +325,7 @@ export const updateDomain = new mutationWithClientMutationId({ target: { resource: domain.domain, resourceType: 'domain', // user, org, domain - updatedProperties: [ - { name: 'archived', oldValue: domain.archived, newValue: archived }, - ], + updatedProperties: [{ name: 'archived', oldValue: domain.archived, newValue: archived }], }, }) } diff --git a/api/src/domain/objects/__tests__/domain.test.js b/api/src/domain/objects/__tests__/domain.test.js index c3235b8f0c..851b4effce 100644 --- a/api/src/domain/objects/__tests__/domain.test.js +++ b/api/src/domain/objects/__tests__/domain.test.js @@ -1,23 +1,17 @@ -import { - GraphQLNonNull, - GraphQLID, - GraphQLList, - GraphQLString, - GraphQLBoolean, -} from 'graphql' -import {toGlobalId} from 'graphql-relay' -import {setupI18n} from '@lingui/core' - -import {tokenize} from '../../../auth' -import {organizationConnection} from '../../../organization' -import {domainStatus} from '../domain-status' -import {dmarcSummaryType} from '../../../dmarc-summaries' -import {webConnection} from '../../../web-scan' -import {domainType} from '../../index' -import {Domain, Selectors} from '../../../scalars' +import { GraphQLNonNull, GraphQLID, GraphQLList, GraphQLString, GraphQLBoolean } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' + +import { tokenize } from '../../../auth' +import { organizationConnection } from '../../../organization' +import { domainStatus } from '../domain-status' +import { dmarcSummaryType } from '../../../dmarc-summaries' +import { webConnection } from '../../../web-scan' +import { domainType } from '../../index' +import { Domain, Selectors } from '../../../scalars' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {dnsScanConnection} from "../../../dns-scan" +import { dnsScanConnection } from '../../../dns-scan' describe('given the domain object', () => { describe('testing its field definitions', () => { @@ -67,9 +61,7 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('organizations') - expect(demoType.organizations.type).toMatchObject( - organizationConnection.connectionType, - ) + expect(demoType.organizations.type).toMatchObject(organizationConnection.connectionType) }) it('has an dnsScan field', () => { const demoType = domainType.getFields() @@ -93,9 +85,7 @@ describe('given the domain object', () => { const demoType = domainType.getFields() expect(demoType).toHaveProperty('yearlyDmarcSummaries') - expect(demoType.yearlyDmarcSummaries.type).toMatchObject( - GraphQLList(dmarcSummaryType), - ) + expect(demoType.yearlyDmarcSummaries.type).toMatchObject(GraphQLList(dmarcSummaryType)) }) }) describe('testing the field resolvers', () => { @@ -113,27 +103,21 @@ describe('given the domain object', () => { it('returns the resolved value', () => { const demoType = domainType.getFields() - expect(demoType.id.resolve({id: '1'})).toEqual( - toGlobalId('domain', 1), - ) + expect(demoType.id.resolve({ id: '1' })).toEqual(toGlobalId('domain', 1)) }) }) describe('testing the domain resolver', () => { it('returns the resolved value', () => { const demoType = domainType.getFields() - expect(demoType.domain.resolve({domain: 'test.gc.ca'})).toEqual( - 'test.gc.ca', - ) + expect(demoType.domain.resolve({ domain: 'test.gc.ca' })).toEqual('test.gc.ca') }) }) describe('testing the dmarcPhase resolver', () => { it('returns the resolved value', () => { const demoType = domainType.getFields() - expect( - demoType.dmarcPhase.resolve({phase: 'not implemented'}), - ).toEqual('not implemented') + expect(demoType.dmarcPhase.resolve({ phase: 'not implemented' })).toEqual('not implemented') }) }) describe('testing the hasDMARCReport resolver', () => { @@ -146,7 +130,7 @@ describe('given the domain object', () => { await expect( demoType.hasDMARCReport.resolve( - {_id: 1}, + { _id: 1 }, {}, { auth: { @@ -167,7 +151,7 @@ describe('given the domain object', () => { await expect( demoType.hasDMARCReport.resolve( - {_id: 1}, + { _id: 1 }, {}, { auth: { @@ -184,9 +168,7 @@ describe('given the domain object', () => { it('returns the resolved value', () => { const demoType = domainType.getFields() - expect( - demoType.lastRan.resolve({lastRan: '2020-10-02T12:43:39Z'}), - ).toEqual('2020-10-02T12:43:39Z') + expect(demoType.lastRan.resolve({ lastRan: '2020-10-02T12:43:39Z' })).toEqual('2020-10-02T12:43:39Z') }) }) describe('testing the selectors resolver', () => { @@ -195,10 +177,18 @@ describe('given the domain object', () => { const selectors = ['selector1', 'selector2'] - expect(demoType.selectors.resolve({selectors})).toEqual([ - 'selector1', - 'selector2', - ]) + expect( + demoType.selectors.resolve( + { selectors }, + {}, + { + auth: { + userRequired: jest.fn().mockReturnValue(true), + checkDomainPermission: jest.fn().mockReturnValue(true), + }, + }, + ), + ).resolves.toEqual(['selector1', 'selector2']) }) }) describe('testing the status resolver', () => { @@ -213,7 +203,7 @@ describe('given the domain object', () => { ssl: 'fail', } - expect(demoType.status.resolve({status})).toEqual({ + expect(demoType.status.resolve({ status })).toEqual({ dkim: 'pass', dmarc: 'pass', https: 'info', @@ -272,13 +262,11 @@ describe('given the domain object', () => { await expect( demoType.organizations.resolve( - {_id: '1'}, - {first: 1}, + { _id: '1' }, + { first: 1 }, { loaders: { - loadOrgConnectionsByDomainId: jest - .fn() - .mockReturnValue(expectedResult), + loadOrgConnectionsByDomainId: jest.fn().mockReturnValue(expectedResult), }, auth: { checkSuperAdmin: jest.fn().mockReturnValue(false), @@ -293,19 +281,19 @@ describe('given the domain object', () => { const demoType = domainType.getFields() const response = await demoType.web.resolve( - {_id: '1'}, - {limit: 1}, - { - loaders: { - loadWebConnectionsByDomainId: jest - .fn() - .mockReturnValue({"_id": "1", "_key": "1"}), - }, - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - }, + { _id: '1' }, + { limit: 1 }, + { + loaders: { + loadWebConnectionsByDomainId: jest.fn().mockReturnValue({ _id: '1', _key: '1' }), + }, + auth: { + checkDomainPermission: jest.fn().mockReturnValue(true), + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue(true), }, - ) + }, + ) expect(response).toEqual({ _id: '1', @@ -318,19 +306,19 @@ describe('given the domain object', () => { const demoType = domainType.getFields() const response = await demoType.dnsScan.resolve( - {_id: '1'}, - {limit: 1}, - { - loaders: { - loadDnsConnectionsByDomainId: jest - .fn() - .mockReturnValue({"_id": "1", "_key": "1"}), - }, - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - }, + { _id: '1' }, + { limit: 1 }, + { + loaders: { + loadDnsConnectionsByDomainId: jest.fn().mockReturnValue({ _id: '1', _key: '1' }), }, - ) + auth: { + checkDomainPermission: jest.fn().mockReturnValue(true), + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue(true), + }, + }, + ) expect(response).toEqual({ _id: '1', @@ -360,14 +348,10 @@ describe('given the domain object', () => { { userKey: '1', loaders: { - loadDmarcSummaryEdgeByDomainIdAndPeriod: jest - .fn() - .mockReturnValue({ - _to: 'dmarcSummaries/1', - }), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn().mockReturnValue({ + _to: 'dmarcSummaries/1', + }), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(true), @@ -388,8 +372,8 @@ describe('given the domain object', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -417,9 +401,7 @@ describe('given the domain object', () => { userKey: '1', loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(false), @@ -428,11 +410,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) + ).rejects.toEqual(new Error('Unable to retrieve DMARC report information for: test1.gc.ca')) expect(consoleOutput).toEqual([ `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, @@ -445,8 +423,8 @@ describe('given the domain object', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -474,9 +452,7 @@ describe('given the domain object', () => { userKey: '1', loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod: jest.fn(), - loadStartDateFromPeriod: jest - .fn() - .mockReturnValue('2021-01-01'), + loadStartDateFromPeriod: jest.fn().mockReturnValue('2021-01-01'), }, auth: { checkDomainOwnership: jest.fn().mockReturnValue(false), @@ -485,11 +461,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) + ).rejects.toEqual(new Error('Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca')) expect(consoleOutput).toEqual([ `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, @@ -544,8 +516,8 @@ describe('given the domain object', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -584,11 +556,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Unable to retrieve DMARC report information for: test1.gc.ca', - ), - ) + ).rejects.toEqual(new Error('Unable to retrieve DMARC report information for: test1.gc.ca')) expect(consoleOutput).toEqual([ `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, ]) @@ -600,8 +568,8 @@ describe('given the domain object', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -637,11 +605,7 @@ describe('given the domain object', () => { }, }, ), - ).rejects.toEqual( - new Error( - 'Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca', - ), - ) + ).rejects.toEqual(new Error('Impossible de récupérer les informations du rapport DMARC pour : test1.gc.ca')) expect(consoleOutput).toEqual([ `User: 1 attempted to access dmarc report period data for 1, but does not belong to an org with ownership.`, ]) diff --git a/api/src/domain/objects/domain.js b/api/src/domain/objects/domain.js index ab19a9cc3c..63f3947c72 100644 --- a/api/src/domain/objects/domain.js +++ b/api/src/domain/objects/domain.js @@ -32,13 +32,10 @@ export const domainType = new GraphQLObjectType({ hasDMARCReport: { type: GraphQLBoolean, description: 'Whether or not the domain has a aggregate dmarc report.', - resolve: async ({ _id }, _, { auth: { checkDomainOwnership, userRequired, loginRequiredBool } }) => { - if (loginRequiredBool) await userRequired() - const hasDMARCReport = await checkDomainOwnership({ + resolve: async ({ _id }, _, { auth: { checkDomainOwnership } }) => { + return await checkDomainOwnership({ domainId: _id, }) - - return hasDMARCReport }, }, lastRan: { @@ -53,7 +50,16 @@ export const domainType = new GraphQLObjectType({ selectors: { type: new GraphQLList(Selectors), description: 'Domain Keys Identified Mail (DKIM) selector strings associated with domain.', - resolve: ({ selectors }) => selectors, + resolve: async ({ _id, selectors }, _, { userKey, auth: { checkDomainPermission, userRequired } }) => { + await userRequired() + const permitted = await checkDomainPermission({ domainId: _id }) + if (!permitted) { + console.warn(`User: ${userKey} attempted to access selectors for ${_id}, but does not have permission.`) + throw new Error(t`Cannot query domain selectors without permission.`) + } + + return selectors + }, }, status: { type: domainStatus, @@ -100,12 +106,11 @@ export const domainType = new GraphQLObjectType({ resolve: async ({ _id }, args, { auth: { checkSuperAdmin }, loaders: { loadOrgConnectionsByDomainId } }) => { const isSuperAdmin = await checkSuperAdmin() - const orgs = await loadOrgConnectionsByDomainId({ + return await loadOrgConnectionsByDomainId({ domainId: _id, isSuperAdmin, ...args, }) - return orgs }, }, dnsScan: { @@ -130,7 +135,20 @@ export const domainType = new GraphQLObjectType({ ...connectionArgs, }, description: `DNS scan results.`, - resolve: async ({ _id }, args, { loaders: { loadDnsConnectionsByDomainId } }) => { + resolve: async ( + { _id }, + args, + { userKey, auth: { checkDomainPermission, userRequired }, loaders: { loadDnsConnectionsByDomainId } }, + ) => { + await userRequired() + const permitted = await checkDomainPermission({ domainId: _id }) + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dns scan results for ${_id}, but does not have permission.`, + ) + throw new Error(t`Cannot query dns scan results without permission.`) + } + return await loadDnsConnectionsByDomainId({ domainId: _id, ...args, @@ -163,7 +181,20 @@ export const domainType = new GraphQLObjectType({ }, ...connectionArgs, }, - resolve: async ({ _id }, args, { loaders: { loadWebConnectionsByDomainId } }) => { + resolve: async ( + { _id }, + args, + { userKey, auth: { checkDomainPermission, userRequired }, loaders: { loadWebConnectionsByDomainId } }, + ) => { + await userRequired() + const permitted = await checkDomainPermission({ domainId: _id }) + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access web scan results for ${_id}, but does not have permission.`, + ) + throw new Error(t`Cannot query web scan results without permission.`) + } + return await loadWebConnectionsByDomainId({ domainId: _id, ...args, @@ -190,21 +221,19 @@ export const domainType = new GraphQLObjectType({ i18n, userKey, loaders: { loadDmarcSummaryEdgeByDomainIdAndPeriod, loadStartDateFromPeriod }, - auth: { checkDomainOwnership, userRequired, loginRequiredBool }, + auth: { checkDomainOwnership, userRequired }, }, ) => { - if (loginRequiredBool) { - await userRequired() - const permitted = await checkDomainOwnership({ - domainId: _id, - }) + await userRequired() + const permitted = await checkDomainOwnership({ + domainId: _id, + }) - if (!permitted) { - console.warn( - `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, - ) - throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) - } + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, + ) + throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) } const startDate = loadStartDateFromPeriod({ period: month, year }) @@ -227,39 +256,30 @@ export const domainType = new GraphQLObjectType({ resolve: async ( { _id, _key, domain }, __, - { - i18n, - userKey, - loaders: { loadDmarcYearlySumEdge }, - auth: { checkDomainOwnership, userRequired, loginRequiredBool }, - }, + { i18n, userKey, loaders: { loadDmarcYearlySumEdge }, auth: { checkDomainOwnership, userRequired } }, ) => { - if (loginRequiredBool) { - await userRequired() + await userRequired() - const permitted = await checkDomainOwnership({ - domainId: _id, - }) + const permitted = await checkDomainOwnership({ + domainId: _id, + }) - if (!permitted) { - console.warn( - `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, - ) - throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) - } + if (!permitted) { + console.warn( + `User: ${userKey} attempted to access dmarc report period data for ${_key}, but does not belong to an org with ownership.`, + ) + throw new Error(i18n._(t`Unable to retrieve DMARC report information for: ${domain}`)) } const dmarcSummaryEdges = await loadDmarcYearlySumEdge({ domainId: _id, }) - const edges = dmarcSummaryEdges.map((edge) => ({ + return dmarcSummaryEdges.map((edge) => ({ domainKey: _key, _id: edge._to, startDate: edge.startDate, })) - - return edges }, }, claimTags: { @@ -272,6 +292,16 @@ export const domainType = new GraphQLObjectType({ type: GraphQLBoolean, resolve: ({ hidden }) => hidden, }, + userHasPermission: { + description: + 'Value that determines if a user is affiliated with a domain, whether through organization affiliation, verified organization network affiliation, or through super admin status.', + type: GraphQLBoolean, + resolve: async ({ _id }, __, { auth: { checkDomainPermission } }) => { + return await checkDomainPermission({ + domainId: _id, + }) + }, + }, }), interfaces: [nodeInterface], description: 'Domain object containing information for a given domain.', diff --git a/api/src/domain/queries/__tests__/find-my-domains.test.js b/api/src/domain/queries/__tests__/find-my-domains.test.js index 8155adac20..3310d86982 100644 --- a/api/src/domain/queries/__tests__/find-my-domains.test.js +++ b/api/src/domain/queries/__tests__/find-my-domains.test.js @@ -1,19 +1,19 @@ -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' -import {setupI18n} from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' +import { setupI18n } from '@lingui/core' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {createQuerySchema} from '../../../query' -import {createMutationSchema} from '../../../mutation' -import {cleanseInput} from '../../../validators' -import {checkSuperAdmin, userRequired, verifiedRequired} from '../../../auth' -import {loadDomainConnectionsByUserId} from '../../loaders' -import {loadUserByKey} from '../../../user' +import { createQuerySchema } from '../../../query' +import { createMutationSchema } from '../../../mutation' +import { cleanseInput } from '../../../validators' +import { checkDomainPermission, checkSuperAdmin, userRequired, verifiedRequired } from '../../../auth' +import { loadDomainConnectionsByUserId } from '../../loaders' +import { loadUserByKey } from '../../../user' import dbschema from '../../../../database.json' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given findMyDomainsQuery', () => { let query, drop, truncate, schema, collections, org, i18n, user @@ -39,7 +39,7 @@ describe('given findMyDomainsQuery', () => { let domainOne, domainTwo beforeAll(async () => { // Generate DB Items - ;({query, drop, truncate, collections} = await ensure({ + ;({ query, drop, truncate, collections } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -158,6 +158,11 @@ describe('given findMyDomainsQuery', () => { i18n, userKey: user._key, auth: { + checkDomainPermission: checkDomainPermission({ + i18n, + userKey: user._key, + query, + }), checkSuperAdmin: checkSuperAdmin({ i18n, userKey: user._key, @@ -179,7 +184,7 @@ describe('given findMyDomainsQuery', () => { query, userKey: user._key, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, }), }, }, @@ -219,9 +224,7 @@ describe('given findMyDomainsQuery', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully retrieved their domains.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key} successfully retrieved their domains.`]) }) }) }) @@ -230,8 +233,8 @@ describe('given findMyDomainsQuery', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -243,9 +246,7 @@ describe('given findMyDomainsQuery', () => { describe('given an error thrown during retrieving domains', () => { describe('user queries for their domains', () => { it('returns domains', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const response = await graphql( schema, @@ -285,16 +286,14 @@ describe('given findMyDomainsQuery', () => { query: mockedQuery, userKey: 1, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }), }, }, ) - const error = [ - new GraphQLError(`Unable to query domain(s). Please try again.`), - ] + const error = [new GraphQLError(`Unable to query domain(s). Please try again.`)] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -309,8 +308,8 @@ describe('given findMyDomainsQuery', () => { i18n = setupI18n({ locale: 'fr', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -322,9 +321,7 @@ describe('given findMyDomainsQuery', () => { describe('given an error thrown during retrieving domains', () => { describe('user queries for their domains', () => { it('returns domains', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('Database error occurred')) + const mockedQuery = jest.fn().mockRejectedValue(new Error('Database error occurred')) const response = await graphql( schema, @@ -364,18 +361,14 @@ describe('given findMyDomainsQuery', () => { query: mockedQuery, userKey: 1, cleanseInput, - auth: {loginRequired: true}, + auth: { loginRequired: true }, i18n, }), }, }, ) - const error = [ - new GraphQLError( - "Impossible d'interroger le(s) domaine(s). Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible d'interroger le(s) domaine(s). Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/enums/index.js b/api/src/enums/index.js index f85ec5e9a9..0d36ba8e5f 100644 --- a/api/src/enums/index.js +++ b/api/src/enums/index.js @@ -11,6 +11,7 @@ export * from './domain-tag-label' export * from './filter-comparisons' export * from './guidance-tag-order-field' export * from './https-order-field' +export * from './invitation-roles' export * from './languages' export * from './log-order-field' export * from './order-direction' diff --git a/api/src/enums/invitation-roles.js b/api/src/enums/invitation-roles.js new file mode 100644 index 0000000000..e2ed9e0169 --- /dev/null +++ b/api/src/enums/invitation-roles.js @@ -0,0 +1,21 @@ +import { GraphQLEnumType } from 'graphql' + +export const InvitationRoleEnums = new GraphQLEnumType({ + name: 'InvitationRoleEnums', + values: { + USER: { + value: 'user', + description: 'A user who has been given access to view an organization.', + }, + ADMIN: { + value: 'admin', + description: + 'A user who has the same access as a user write account, but can define new user read/write accounts.', + }, + SUPER_ADMIN: { + value: 'super_admin', + description: 'A user who has the same access as an admin, but can define new admins.', + }, + }, + description: 'An enum used when inviting users to an organization to assign their role.', +}) diff --git a/api/src/enums/roles.js b/api/src/enums/roles.js index d6ae2420f1..61080e6fbd 100644 --- a/api/src/enums/roles.js +++ b/api/src/enums/roles.js @@ -16,6 +16,11 @@ export const RoleEnums = new GraphQLEnumType({ description: 'A user who has the same access as a user write account, but can define new user read/write accounts.', }, + OWNER: { + value: 'owner', + description: + 'A user who has the same access as an admin, but can define new admins, and delete the organization.', + }, SUPER_ADMIN: { value: 'super_admin', description: 'A user who has the same access as an admin, but can define new admins.', diff --git a/api/src/enums/user-action.js b/api/src/enums/user-action.js index 8c3819d9b1..e5160ffc85 100644 --- a/api/src/enums/user-action.js +++ b/api/src/enums/user-action.js @@ -23,6 +23,14 @@ export const UserActionEnums = new GraphQLEnumType({ value: 'remove', description: 'An affiliation between resources was deleted.', }, + SCAN: { + value: 'scan', + description: 'A scan was requested on a resource.', + }, + EXPORT: { + value: 'export', + description: 'A resource was exported.', + }, }, description: 'Describes actions performed by users to modify resources.', }) diff --git a/api/src/initialize-loaders.js b/api/src/initialize-loaders.js index 9f0118a2d0..adbcfbd082 100644 --- a/api/src/initialize-loaders.js +++ b/api/src/initialize-loaders.js @@ -45,13 +45,8 @@ import { loadAllOrganizationDomainStatuses, loadOrganizationDomainStatuses, } from './organization/loaders' -import { - loadMyTrackerByUserId, - loadUserByUserName, - loadUserByKey, - loadUserConnectionsByUserId, -} from './user/loaders' -import { loadWebConnectionsByDomainId, loadWebScansByWebId} from './web-scan/loaders' +import { loadMyTrackerByUserId, loadUserByUserName, loadUserByKey, loadUserConnectionsByUserId } from './user/loaders' +import { loadWebConnectionsByDomainId, loadWebScansByWebId } from './web-scan/loaders' import { loadVerifiedDomainsById, loadVerifiedDomainByKey, @@ -65,18 +60,9 @@ import { loadVerifiedOrgConnections, } from './verified-organizations/loaders' import { loadChartSummaryByKey } from './summaries/loaders' -import {loadDnsConnectionsByDomainId} from "./dns-scan" +import { loadDnsConnectionsByDomainId } from './dns-scan' -export function initializeLoaders({ - query, - db, - userKey, - i18n, - language, - cleanseInput, - loginRequiredBool, - moment, -}) { +export function initializeLoaders({ query, db, userKey, i18n, language, cleanseInput, loginRequiredBool, moment }) { return { loadChartSummaryByKey: loadChartSummaryByKey({ query, userKey, i18n }), loadAggregateGuidanceTagByTagId: loadAggregateGuidanceTagByTagId({ @@ -85,14 +71,13 @@ export function initializeLoaders({ i18n, language, }), - loadAggregateGuidanceTagConnectionsByTagId: - loadAggregateGuidanceTagConnectionsByTagId({ - query, - userKey, - i18n, - cleanseInput, - language, - }), + loadAggregateGuidanceTagConnectionsByTagId: loadAggregateGuidanceTagConnectionsByTagId({ + query, + userKey, + i18n, + cleanseInput, + language, + }), loadAuditLogsByOrgId: loadAuditLogsByOrgId({ query, userKey, @@ -123,12 +108,11 @@ export function initializeLoaders({ i18n, }), }), - loadDmarcSummaryEdgeByDomainIdAndPeriod: - loadDmarcSummaryEdgeByDomainIdAndPeriod({ - query, - userKey, - i18n, - }), + loadDmarcSummaryEdgeByDomainIdAndPeriod: loadDmarcSummaryEdgeByDomainIdAndPeriod({ + query, + userKey, + i18n, + }), loadDmarcSummaryByKey: loadDmarcSummaryByKey({ query, userKey, i18n }), loadFullPassConnectionsBySumId: loadFullPassConnectionsBySumId({ query, @@ -193,42 +177,39 @@ export function initializeLoaders({ i18n, language, }), - loadDkimGuidanceTagConnectionsByTagId: - loadDkimGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), + loadDkimGuidanceTagConnectionsByTagId: loadDkimGuidanceTagConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }), loadDmarcGuidanceTagByTagId: loadDmarcGuidanceTagByTagId({ query, userKey, i18n, language, }), - loadDmarcGuidanceTagConnectionsByTagId: - loadDmarcGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), + loadDmarcGuidanceTagConnectionsByTagId: loadDmarcGuidanceTagConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }), loadHttpsGuidanceTagByTagId: loadHttpsGuidanceTagByTagId({ query, userKey, i18n, language, }), - loadHttpsGuidanceTagConnectionsByTagId: - loadHttpsGuidanceTagConnectionsByTagId({ - query, - userKey, - cleanseInput, - i18n, - language, - }), + loadHttpsGuidanceTagConnectionsByTagId: loadHttpsGuidanceTagConnectionsByTagId({ + query, + userKey, + cleanseInput, + i18n, + language, + }), loadSpfGuidanceTagByTagId: loadSpfGuidanceTagByTagId({ query, userKey, diff --git a/api/src/locale/en/messages.js b/api/src/locale/en/messages.js index deef109e74..9fd71f2be0 100644 --- a/api/src/locale/en/messages.js +++ b/api/src/locale/en/messages.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Authentication error. Please sign in.","Cannot query affiliations on organization without admin permission or higher.":"Cannot query affiliations on organization without admin permission or higher.","Cannot query audit logs on organization without admin permission or higher.":"Cannot query audit logs on organization without admin permission or higher.","Email already in use.":"Email already in use.","If an account with this username is found, a password reset link will be found in your inbox.":"If an account with this username is found, a password reset link will be found in your inbox.","If an account with this username is found, an email verification link will be found in your inbox.":"If an account with this username is found, an email verification link will be found in your inbox.","Incorrect TFA code. Please sign in again.":"Incorrect TFA code. Please sign in again.","Incorrect token value. Please request a new email.":"Incorrect token value. Please request a new email.","Incorrect username or password. Please try again.":"Incorrect username or password. Please try again.","Invalid token, please sign in.":"Invalid token, please sign in.","New passwords do not match.":"New passwords do not match.","No organization with the provided slug could be found.":"No organization with the provided slug could be found.","No verified domain with the provided domain could be found.":"No verified domain with the provided domain could be found.","No verified organization with the provided slug could be found.":"No verified organization with the provided slug could be found.","Organization has already been verified.":"Organization has already been verified.","Organization name already in use, please choose another and try again.":"Organization name already in use, please choose another and try again.","Organization name already in use. Please try again with a different name.":"Organization name already in use. Please try again with a different name.","Ownership check error. Unable to request domain information.":"Ownership check error. Unable to request domain information.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIM` connection is not supported.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passing both `first` and `last` to paginate the `DMARC` connection is not supported.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passing both `first` and `last` to paginate the `Domain` connection is not supported.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passing both `first` and `last` to paginate the `Log` connection is not supported.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passing both `first` and `last` to paginate the `Organization` connection is not supported.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passing both `first` and `last` to paginate the `SPF` connection is not supported.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passing both `first` and `last` to paginate the `SSL` connection is not supported.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passing both `first` and `last` to paginate the `User` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.","Password does not meet requirements.":"Password does not meet requirements.","Password was successfully reset.":"Password was successfully reset.","Password was successfully updated.":"Password was successfully updated.","Passwords do not match.":"Passwords do not match.","Permission Denied: Could not retrieve specified organization.":"Permission Denied: Could not retrieve specified organization.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission Denied: Multi-factor authentication is required for admin accounts","Permission Denied: Please contact org owner to transfer ownership.":"Permission Denied: Please contact org owner to transfer ownership.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission Denied: Please contact organization admin for help with archiving domains.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission Denied: Please contact organization admin for help with removing domain.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission Denied: Please contact organization admin for help with removing domains.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission Denied: Please contact organization admin for help with removing organization.","Permission Denied: Please contact organization admin for help with removing users.":"Permission Denied: Please contact organization admin for help with removing users.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission Denied: Please contact organization admin for help with updating organization.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission Denied: Please contact organization admin for help with updating user roles.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission Denied: Please contact organization admin for help with user invitations.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission Denied: Please contact organization admin for help with user role changes.","Permission Denied: Please contact organization user for help with creating domain.":"Permission Denied: Please contact organization user for help with creating domain.","Permission Denied: Please contact organization user for help with creating domains.":"Permission Denied: Please contact organization user for help with creating domains.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission Denied: Please contact organization user for help with retrieving this domain.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission Denied: Please contact organization user for help with scanning this domain.","Permission Denied: Please contact organization user for help with updating this domain.":"Permission Denied: Please contact organization user for help with updating this domain.","Permission Denied: Please contact super admin for help with removing domain.":"Permission Denied: Please contact super admin for help with removing domain.","Permission Denied: Please contact super admin for help with removing organization.":"Permission Denied: Please contact super admin for help with removing organization.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission Denied: Please contact super admin for help with scanning this domain.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission Denied: Please contact super admin for help with verifying this organization.","Permission check error. Unable to request domain information.":"Permission check error. Unable to request domain information.","Permission error, not an admin for this user.":"Permission error, not an admin for this user.","Permission error: Unable to close other user's account.":"Permission error: Unable to close other user's account.","Permissions error. You do not have sufficient permissions to access this data.":"Permissions error. You do not have sufficient permissions to access this data.","Phone number has been successfully removed.":"Phone number has been successfully removed.","Phone number has been successfully set, you will receive a verification text message shortly.":"Phone number has been successfully set, you will receive a verification text message shortly.","Profile successfully updated.":"Profile successfully updated.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Affiliation` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DkimFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcSummaries` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Domain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `FullPassTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `GuidanceTag` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Log` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Organization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `SpfFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `User` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedDomain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedOrganization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIMResults` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIM` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DMARC` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `HTTPS` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SPF` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SSL` connection exceeds the `",["argSet"],"` limit of 100 records."],"Successfully added {domainCount} domain(s) to {0}.":["Successfully added ",["domainCount"]," domain(s) to ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Successfully added ",["domainCount"]," domains to ",["0"],"."],"Successfully closed account.":"Successfully closed account.","Successfully dispatched one time scan.":"Successfully dispatched one time scan.","Successfully email verified account, and set TFA send method to email.":"Successfully email verified account, and set TFA send method to email.","Successfully invited user to organization, and sent notification email.":"Successfully invited user to organization, and sent notification email.","Successfully left organization: {0}":["Successfully left organization: ",["0"]],"Successfully removed domain: {0} from favourites.":["Successfully removed domain: ",["0"]," from favourites."],"Successfully removed domain: {0} from {1}.":["Successfully removed domain: ",["0"]," from ",["1"],"."],"Successfully removed organization: {0}.":["Successfully removed organization: ",["0"],"."],"Successfully removed user from organization.":"Successfully removed user from organization.","Successfully removed {domainCount} domain(s) from {0}.":["Successfully removed ",["domainCount"]," domain(s) from ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Successfully removed ",["domainCount"]," domains from ",["0"],"."],"Successfully sent invitation to service, and organization email.":"Successfully sent invitation to service, and organization email.","Successfully signed out.":"Successfully signed out.","Successfully transferred org: {0} ownership to user: {1}":["Successfully transferred org: ",["0"]," ownership to user: ",["1"]],"Successfully verified organization: {0}.":["Successfully verified organization: ",["0"],"."],"Successfully verified phone number, and set TFA send method to text.":"Successfully verified phone number, and set TFA send method to text.","Token value incorrect, please sign in again.":"Token value incorrect, please sign in again.","Too many failed login attempts, please reset your password, and try again.":"Too many failed login attempts, please reset your password, and try again.","Two factor code is incorrect. Please try again.":"Two factor code is incorrect. Please try again.","Two factor code length is incorrect. Please try again.":"Two factor code length is incorrect. Please try again.","Unable leave organization. Please try again.":"Unable leave organization. Please try again.","Unable to add domains in unknown organization.":"Unable to add domains in unknown organization.","Unable to authenticate. Please try again.":"Unable to authenticate. Please try again.","Unable to check permission. Please try again.":"Unable to check permission. Please try again.","Unable to close account of an undefined user.":"Unable to close account of an undefined user.","Unable to close account. Please try again.":"Unable to close account. Please try again.","Unable to create domain in unknown organization.":"Unable to create domain in unknown organization.","Unable to create domain, organization has already claimed it.":"Unable to create domain, organization has already claimed it.","Unable to create domain. Please try again.":"Unable to create domain. Please try again.","Unable to create domains. Please try again.":"Unable to create domains. Please try again.","Unable to create organization. Please try again.":"Unable to create organization. Please try again.","Unable to dispatch one time scan. Please try again.":"Unable to dispatch one time scan. Please try again.","Unable to favourite domain, user has already favourited it.":"Unable to favourite domain, user has already favourited it.","Unable to favourite domain. Please try again.":"Unable to favourite domain. Please try again.","Unable to favourite unknown domain.":"Unable to favourite unknown domain.","Unable to find Aggregate guidance tag(s). Please try again.":"Unable to find Aggregate guidance tag(s). Please try again.","Unable to find DKIM guidance tag(s). Please try again.":"Unable to find DKIM guidance tag(s). Please try again.","Unable to find DKIM result(s). Please try again.":"Unable to find DKIM result(s). Please try again.","Unable to find DKIM scan(s). Please try again.":"Unable to find DKIM scan(s). Please try again.","Unable to find DMARC guidance tag(s). Please try again.":"Unable to find DMARC guidance tag(s). Please try again.","Unable to find DMARC scan(s). Please try again.":"Unable to find DMARC scan(s). Please try again.","Unable to find DMARC summary data. Please try again.":"Unable to find DMARC summary data. Please try again.","Unable to find DNS scan(s). Please try again.":"Unable to find DNS scan(s). Please try again.","Unable to find HTTPS guidance tag(s). Please try again.":"Unable to find HTTPS guidance tag(s). Please try again.","Unable to find HTTPS scan(s). Please try again.":"Unable to find HTTPS scan(s). Please try again.","Unable to find SPF guidance tag(s). Please try again.":"Unable to find SPF guidance tag(s). Please try again.","Unable to find SPF scan(s). Please try again.":"Unable to find SPF scan(s). Please try again.","Unable to find SSL guidance tag(s). Please try again.":"Unable to find SSL guidance tag(s). Please try again.","Unable to find SSL scan(s). Please try again.":"Unable to find SSL scan(s). Please try again.","Unable to find the requested domain.":"Unable to find the requested domain.","Unable to find user affiliation(s). Please try again.":"Unable to find user affiliation(s). Please try again.","Unable to find verified organization(s). Please try again.":"Unable to find verified organization(s). Please try again.","Unable to invite user to organization. Please try again.":"Unable to invite user to organization. Please try again.","Unable to invite user to organization. User is already affiliated with organization.":"Unable to invite user to organization. User is already affiliated with organization.","Unable to invite user to unknown organization.":"Unable to invite user to unknown organization.","Unable to invite user. Please try again.":"Unable to invite user. Please try again.","Unable to invite yourself to an org.":"Unable to invite yourself to an org.","Unable to leave organization. Please try again.":"Unable to leave organization. Please try again.","Unable to leave undefined organization.":"Unable to leave undefined organization.","Unable to load Aggregate guidance tag(s). Please try again.":"Unable to load Aggregate guidance tag(s). Please try again.","Unable to load DKIM failure data. Please try again.":"Unable to load DKIM failure data. Please try again.","Unable to load DKIM guidance tag(s). Please try again.":"Unable to load DKIM guidance tag(s). Please try again.","Unable to load DKIM result(s). Please try again.":"Unable to load DKIM result(s). Please try again.","Unable to load DKIM scan(s). Please try again.":"Unable to load DKIM scan(s). Please try again.","Unable to load DMARC failure data. Please try again.":"Unable to load DMARC failure data. Please try again.","Unable to load DMARC guidance tag(s). Please try again.":"Unable to load DMARC guidance tag(s). Please try again.","Unable to load DMARC phase summary. Please try again.":"Unable to load DMARC phase summary. Please try again.","Unable to load DMARC scan(s). Please try again.":"Unable to load DMARC scan(s). Please try again.","Unable to load DMARC summary data. Please try again.":"Unable to load DMARC summary data. Please try again.","Unable to load DNS scan(s). Please try again.":"Unable to load DNS scan(s). Please try again.","Unable to load HTTPS guidance tag(s). Please try again.":"Unable to load HTTPS guidance tag(s). Please try again.","Unable to load HTTPS scan(s). Please try again.":"Unable to load HTTPS scan(s). Please try again.","Unable to load HTTPS summary. Please try again.":"Unable to load HTTPS summary. Please try again.","Unable to load SPF failure data. Please try again.":"Unable to load SPF failure data. Please try again.","Unable to load SPF guidance tag(s). Please try again.":"Unable to load SPF guidance tag(s). Please try again.","Unable to load SPF scan(s). Please try again.":"Unable to load SPF scan(s). Please try again.","Unable to load SSL guidance tag(s). Please try again.":"Unable to load SSL guidance tag(s). Please try again.","Unable to load SSL scan(s). Please try again.":"Unable to load SSL scan(s). Please try again.","Unable to load affiliation information. Please try again.":"Unable to load affiliation information. Please try again.","Unable to load affiliation(s). Please try again.":"Unable to load affiliation(s). Please try again.","Unable to load all organization domain statuses. Please try again.":"Unable to load all organization domain statuses. Please try again.","Unable to load domain(s). Please try again.":"Unable to load domain(s). Please try again.","Unable to load domain. Please try again.":"Unable to load domain. Please try again.","Unable to load full pass data. Please try again.":"Unable to load full pass data. Please try again.","Unable to load log(s). Please try again.":"Unable to load log(s). Please try again.","Unable to load log. Please try again.":"Unable to load log. Please try again.","Unable to load mail summary. Please try again.":"Unable to load mail summary. Please try again.","Unable to load organization domain statuses. Please try again.":"Unable to load organization domain statuses. Please try again.","Unable to load organization(s). Please try again.":"Unable to load organization(s). Please try again.","Unable to load owner information. Please try again.":"Unable to load owner information. Please try again.","Unable to load summary. Please try again.":"Unable to load summary. Please try again.","Unable to load tags(s). Please try again.":"Unable to load tags(s). Please try again.","Unable to load user(s). Please try again.":"Unable to load user(s). Please try again.","Unable to load verified domain(s). Please try again.":"Unable to load verified domain(s). Please try again.","Unable to load verified organization(s). Please try again.":"Unable to load verified organization(s). Please try again.","Unable to load web scan(s). Please try again.":"Unable to load web scan(s). Please try again.","Unable to load web summary. Please try again.":"Unable to load web summary. Please try again.","Unable to query affiliation(s). Please try again.":"Unable to query affiliation(s). Please try again.","Unable to query domain(s). Please try again.":"Unable to query domain(s). Please try again.","Unable to query log(s). Please try again.":"Unable to query log(s). Please try again.","Unable to query user(s). Please try again.":"Unable to query user(s). Please try again.","Unable to refresh tokens, please sign in.":"Unable to refresh tokens, please sign in.","Unable to remove a user that already does not belong to this organization.":"Unable to remove a user that already does not belong to this organization.","Unable to remove domain from unknown organization.":"Unable to remove domain from unknown organization.","Unable to remove domain. Domain is not part of organization.":"Unable to remove domain. Domain is not part of organization.","Unable to remove domain. Please try again.":"Unable to remove domain. Please try again.","Unable to remove domains from unknown organization.":"Unable to remove domains from unknown organization.","Unable to remove organization. Please try again.":"Unable to remove organization. Please try again.","Unable to remove phone number. Please try again.":"Unable to remove phone number. Please try again.","Unable to remove unknown domain.":"Unable to remove unknown domain.","Unable to remove unknown organization.":"Unable to remove unknown organization.","Unable to remove unknown user from organization.":"Unable to remove unknown user from organization.","Unable to remove user from organization.":"Unable to remove user from organization.","Unable to remove user from this organization. Please try again.":"Unable to remove user from this organization. Please try again.","Unable to remove user from unknown organization.":"Unable to remove user from unknown organization.","Unable to request a one time scan on a domain that already has a pending scan.":"Unable to request a one time scan on a domain that already has a pending scan.","Unable to request a one time scan on an unknown domain.":"Unable to request a one time scan on an unknown domain.","Unable to request a one time scan. Please try again.":"Unable to request a one time scan. Please try again.","Unable to reset password. Please request a new email.":"Unable to reset password. Please request a new email.","Unable to reset password. Please try again.":"Unable to reset password. Please try again.","Unable to retrieve DMARC report information for: {domain}":["Unable to retrieve DMARC report information for: ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Unable to select DMARC report(s) for this period and year.","Unable to send authentication email. Please try again.":"Unable to send authentication email. Please try again.","Unable to send authentication text message. Please try again.":"Unable to send authentication text message. Please try again.","Unable to send org invite email. Please try again.":"Unable to send org invite email. Please try again.","Unable to send password reset email. Please try again.":"Unable to send password reset email. Please try again.","Unable to send two factor authentication message. Please try again.":"Unable to send two factor authentication message. Please try again.","Unable to send verification email. Please try again.":"Unable to send verification email. Please try again.","Unable to set phone number, please try again.":"Unable to set phone number, please try again.","Unable to sign in, please try again.":"Unable to sign in, please try again.","Unable to sign up, please contact org admin for a new invite.":"Unable to sign up, please contact org admin for a new invite.","Unable to sign up. Please try again.":"Unable to sign up. Please try again.","Unable to transfer organization ownership. Please try again.":"Unable to transfer organization ownership. Please try again.","Unable to transfer ownership of a verified organization.":"Unable to transfer ownership of a verified organization.","Unable to transfer ownership of an org to an undefined user.":"Unable to transfer ownership of an org to an undefined user.","Unable to transfer ownership of undefined organization.":"Unable to transfer ownership of undefined organization.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Unable to transfer ownership to a user outside the org. Please invite the user and try again.","Unable to two factor authenticate. Please try again.":"Unable to two factor authenticate. Please try again.","Unable to unfavourite domain, domain is not favourited.":"Unable to unfavourite domain, domain is not favourited.","Unable to unfavourite domain. Please try again.":"Unable to unfavourite domain. Please try again.","Unable to unfavourite unknown domain.":"Unable to unfavourite unknown domain.","Unable to update domain edge. Please try again.":"Unable to update domain edge. Please try again.","Unable to update domain in an unknown org.":"Unable to update domain in an unknown org.","Unable to update domain that does not belong to the given organization.":"Unable to update domain that does not belong to the given organization.","Unable to update domain. Please try again.":"Unable to update domain. Please try again.","Unable to update organization. Please try again.":"Unable to update organization. Please try again.","Unable to update password, current password does not match. Please try again.":"Unable to update password, current password does not match. Please try again.","Unable to update password, new passwords do not match. Please try again.":"Unable to update password, new passwords do not match. Please try again.","Unable to update password, passwords do not match requirements. Please try again.":"Unable to update password, passwords do not match requirements. Please try again.","Unable to update password. Please try again.":"Unable to update password. Please try again.","Unable to update profile. Please try again.":"Unable to update profile. Please try again.","Unable to update role: organization unknown.":"Unable to update role: organization unknown.","Unable to update role: user does not belong to organization.":"Unable to update role: user does not belong to organization.","Unable to update role: user unknown.":"Unable to update role: user unknown.","Unable to update unknown domain.":"Unable to update unknown domain.","Unable to update unknown organization.":"Unable to update unknown organization.","Unable to update user's role. Please try again.":"Unable to update user's role. Please try again.","Unable to update your own role.":"Unable to update your own role.","Unable to verify account. Please request a new email.":"Unable to verify account. Please request a new email.","Unable to verify account. Please try again.":"Unable to verify account. Please try again.","Unable to verify if user is a super admin, please try again.":"Unable to verify if user is a super admin, please try again.","Unable to verify if user is an admin, please try again.":"Unable to verify if user is an admin, please try again.","Unable to verify organization. Please try again.":"Unable to verify organization. Please try again.","Unable to verify unknown organization.":"Unable to verify unknown organization.","User could not be queried.":"User could not be queried.","User role was updated successfully.":"User role was updated successfully.","Username not available, please try another.":"Username not available, please try another.","Verification error. Please activate multi-factor authentication to access content.":"Verification error. Please activate multi-factor authentication to access content.","Verification error. Please verify your account via email to access content.":"Verification error. Please verify your account via email to access content.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIM` connection.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"You must provide a `first` or `last` value to properly paginate the `DMARC` connection.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"You must provide a `first` or `last` value to properly paginate the `Domain` connection.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"You must provide a `first` or `last` value to properly paginate the `Log` connection.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"You must provide a `first` or `last` value to properly paginate the `Organization` connection.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"You must provide a `first` or `last` value to properly paginate the `SPF` connection.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"You must provide a `first` or `last` value to properly paginate the `SSL` connection.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"You must provide a `first` or `last` value to properly paginate the `User` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.","You must provide a `limit` value to properly paginate the `DNS` connection.":"You must provide a `limit` value to properly paginate the `DNS` connection.","You must provide a `limit` value to properly paginate the `web` connection.":"You must provide a `limit` value to properly paginate the `web` connection.","You must provide a `period` value to access the `DmarcSummaries` connection.":"You must provide a `period` value to access the `DmarcSummaries` connection.","You must provide a `year` value to access the `DmarcSummaries` connection.":"You must provide a `year` value to access the `DmarcSummaries` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` must be of type `number` not `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` on the `Affiliation` connection cannot be less than zero."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIMResults` connection cannot be less than zero."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIM` connection cannot be less than zero."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` on the `DMARC` connection cannot be less than zero."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DkimFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcSummaries` connection cannot be less than zero."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` on the `Domain` connection cannot be less than zero."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` on the `FullPassTable` connection cannot be less than zero."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` on the `GuidanceTag` connection cannot be less than zero."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` on the `HTTPS` connection cannot be less than zero."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` on the `Log` connection cannot be less than zero."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` on the `Organization` connection cannot be less than zero."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` on the `SPF` connection cannot be less than zero."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` on the `SSL` connection cannot be less than zero."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `SpfFailureTable` connection cannot be less than zero."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` on the `User` connection cannot be less than zero."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedDomain` connection cannot be less than zero."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedOrganization` connection cannot be less than zero."]}}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Authentication error. Please sign in.","Cannot query affiliations on organization without admin permission or higher.":"Cannot query affiliations on organization without admin permission or higher.","Cannot query audit logs on organization without admin permission or higher.":"Cannot query audit logs on organization without admin permission or higher.","Cannot query dns scan results without permission.":"Cannot query dns scan results without permission.","Cannot query domain selectors without permission.":"Cannot query domain selectors without permission.","Cannot query web scan results without permission.":"Cannot query web scan results without permission.","Email already in use.":"Email already in use.","Error while requesting scan. Please try again.":"Error while requesting scan. Please try again.","If an account with this username is found, a password reset link will be found in your inbox.":"If an account with this username is found, a password reset link will be found in your inbox.","If an account with this username is found, an email verification link will be found in your inbox.":"If an account with this username is found, an email verification link will be found in your inbox.","Incorrect TFA code. Please sign in again.":"Incorrect TFA code. Please sign in again.","Incorrect token value. Please request a new email.":"Incorrect token value. Please request a new email.","Incorrect username or password. Please try again.":"Incorrect username or password. Please try again.","Invalid token, please sign in.":"Invalid token, please sign in.","New passwords do not match.":"New passwords do not match.","No organization with the provided slug could be found.":"No organization with the provided slug could be found.","No verified domain with the provided domain could be found.":"No verified domain with the provided domain could be found.","No verified organization with the provided slug could be found.":"No verified organization with the provided slug could be found.","Organization has already been verified.":"Organization has already been verified.","Organization name already in use, please choose another and try again.":"Organization name already in use, please choose another and try again.","Organization name already in use. Please try again with a different name.":"Organization name already in use. Please try again with a different name.","Ownership check error. Unable to request domain information.":"Ownership check error. Unable to request domain information.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passing both `first` and `last` to paginate the `DKIM` connection is not supported.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passing both `first` and `last` to paginate the `DMARC` connection is not supported.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passing both `first` and `last` to paginate the `Domain` connection is not supported.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passing both `first` and `last` to paginate the `Log` connection is not supported.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passing both `first` and `last` to paginate the `Organization` connection is not supported.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passing both `first` and `last` to paginate the `SPF` connection is not supported.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passing both `first` and `last` to paginate the `SSL` connection is not supported.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passing both `first` and `last` to paginate the `User` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.","Password does not meet requirements.":"Password does not meet requirements.","Password was successfully reset.":"Password was successfully reset.","Password was successfully updated.":"Password was successfully updated.","Passwords do not match.":"Passwords do not match.","Permission Denied: Could not retrieve specified organization.":"Permission Denied: Could not retrieve specified organization.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission Denied: Multi-factor authentication is required for admin accounts","Permission Denied: Please contact org owner to transfer ownership.":"Permission Denied: Please contact org owner to transfer ownership.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission Denied: Please contact organization admin for help with archiving domains.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission Denied: Please contact organization admin for help with removing domain.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission Denied: Please contact organization admin for help with removing domains.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission Denied: Please contact organization admin for help with removing organization.","Permission Denied: Please contact organization admin for help with removing users.":"Permission Denied: Please contact organization admin for help with removing users.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission Denied: Please contact organization admin for help with updating organization.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission Denied: Please contact organization admin for help with updating user roles.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission Denied: Please contact organization admin for help with user invitations.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission Denied: Please contact organization admin for help with user role changes.","Permission Denied: Please contact organization user for help with creating domain.":"Permission Denied: Please contact organization user for help with creating domain.","Permission Denied: Please contact organization user for help with creating domains.":"Permission Denied: Please contact organization user for help with creating domains.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission Denied: Please contact organization user for help with retrieving this domain.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission Denied: Please contact organization user for help with scanning this domain.","Permission Denied: Please contact organization user for help with updating this domain.":"Permission Denied: Please contact organization user for help with updating this domain.","Permission Denied: Please contact super admin for help with archiving organization.":"Permission Denied: Please contact super admin for help with archiving organization.","Permission Denied: Please contact super admin for help with removing domain.":"Permission Denied: Please contact super admin for help with removing domain.","Permission Denied: Please contact super admin for help with removing organization.":"Permission Denied: Please contact super admin for help with removing organization.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission Denied: Please contact super admin for help with scanning this domain.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission Denied: Please contact super admin for help with verifying this organization.","Permission check error. Unable to request domain information.":"Permission check error. Unable to request domain information.","Permission error, not an admin for this user.":"Permission error, not an admin for this user.","Permission error: Unable to close other user's account.":"Permission error: Unable to close other user's account.","Permissions error. You do not have sufficient permissions to access this data.":"Permissions error. You do not have sufficient permissions to access this data.","Phone number has been successfully removed.":"Phone number has been successfully removed.","Phone number has been successfully set, you will receive a verification text message shortly.":"Phone number has been successfully set, you will receive a verification text message shortly.","Profile successfully updated.":"Profile successfully updated.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Affiliation` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DkimFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `DmarcSummaries` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Domain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `FullPassTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `GuidanceTag` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Log` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `Organization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `SpfFailureTable` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `User` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedDomain` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["Requesting `",["amount"],"` records on the `VerifiedOrganization` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIMResults` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DKIM` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `DMARC` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `HTTPS` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SPF` connection exceeds the `",["argSet"],"` limit of 100 records."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["Requesting ",["amount"]," records on the `SSL` connection exceeds the `",["argSet"],"` limit of 100 records."],"Successfully added {domainCount} domain(s) to {0}.":["Successfully added ",["domainCount"]," domain(s) to ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Successfully added ",["domainCount"]," domains to ",["0"],"."],"Successfully archived organization: {0}.":["Successfully archived organization: ",["0"],"."],"Successfully closed account.":"Successfully closed account.","Successfully dispatched one time scan.":"Successfully dispatched one time scan.","Successfully email verified account, and set TFA send method to email.":"Successfully email verified account, and set TFA send method to email.","Successfully invited user to organization, and sent notification email.":"Successfully invited user to organization, and sent notification email.","Successfully left organization: {0}":["Successfully left organization: ",["0"]],"Successfully removed domain: {0} from favourites.":["Successfully removed domain: ",["0"]," from favourites."],"Successfully removed domain: {0} from {1}.":["Successfully removed domain: ",["0"]," from ",["1"],"."],"Successfully removed organization: {0}.":["Successfully removed organization: ",["0"],"."],"Successfully removed user from organization.":"Successfully removed user from organization.","Successfully removed {domainCount} domain(s) from {0}.":["Successfully removed ",["domainCount"]," domain(s) from ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Successfully removed ",["domainCount"]," domains from ",["0"],"."],"Successfully requested invite to organization, and sent notification email.":"Successfully requested invite to organization, and sent notification email.","Successfully sent invitation to service, and organization email.":"Successfully sent invitation to service, and organization email.","Successfully signed out.":"Successfully signed out.","Successfully transferred org: {0} ownership to user: {1}":["Successfully transferred org: ",["0"]," ownership to user: ",["1"]],"Successfully verified organization: {0}.":["Successfully verified organization: ",["0"],"."],"Successfully verified phone number, and set TFA send method to text.":"Successfully verified phone number, and set TFA send method to text.","Token value incorrect, please sign in again.":"Token value incorrect, please sign in again.","Too many failed login attempts, please reset your password, and try again.":"Too many failed login attempts, please reset your password, and try again.","Two factor code is incorrect. Please try again.":"Two factor code is incorrect. Please try again.","Two factor code length is incorrect. Please try again.":"Two factor code length is incorrect. Please try again.","Unable leave organization. Please try again.":"Unable leave organization. Please try again.","Unable to add domains in unknown organization.":"Unable to add domains in unknown organization.","Unable to archive organization. Please try again.":"Unable to archive organization. Please try again.","Unable to archive unknown organization.":"Unable to archive unknown organization.","Unable to authenticate. Please try again.":"Unable to authenticate. Please try again.","Unable to check permission. Please try again.":"Unable to check permission. Please try again.","Unable to close account of an undefined user.":"Unable to close account of an undefined user.","Unable to close account. Please try again.":"Unable to close account. Please try again.","Unable to create domain in unknown organization.":"Unable to create domain in unknown organization.","Unable to create domain, organization has already claimed it.":"Unable to create domain, organization has already claimed it.","Unable to create domain. Please try again.":"Unable to create domain. Please try again.","Unable to create domains. Please try again.":"Unable to create domains. Please try again.","Unable to create organization. Please try again.":"Unable to create organization. Please try again.","Unable to dispatch one time scan. Please try again.":"Unable to dispatch one time scan. Please try again.","Unable to export organization. Please try again.":"Unable to export organization. Please try again.","Unable to favourite domain, user has already favourited it.":"Unable to favourite domain, user has already favourited it.","Unable to favourite domain. Please try again.":"Unable to favourite domain. Please try again.","Unable to favourite unknown domain.":"Unable to favourite unknown domain.","Unable to find Aggregate guidance tag(s). Please try again.":"Unable to find Aggregate guidance tag(s). Please try again.","Unable to find DKIM guidance tag(s). Please try again.":"Unable to find DKIM guidance tag(s). Please try again.","Unable to find DKIM result(s). Please try again.":"Unable to find DKIM result(s). Please try again.","Unable to find DKIM scan(s). Please try again.":"Unable to find DKIM scan(s). Please try again.","Unable to find DMARC guidance tag(s). Please try again.":"Unable to find DMARC guidance tag(s). Please try again.","Unable to find DMARC scan(s). Please try again.":"Unable to find DMARC scan(s). Please try again.","Unable to find DMARC summary data. Please try again.":"Unable to find DMARC summary data. Please try again.","Unable to find DNS scan(s). Please try again.":"Unable to find DNS scan(s). Please try again.","Unable to find HTTPS guidance tag(s). Please try again.":"Unable to find HTTPS guidance tag(s). Please try again.","Unable to find HTTPS scan(s). Please try again.":"Unable to find HTTPS scan(s). Please try again.","Unable to find SPF guidance tag(s). Please try again.":"Unable to find SPF guidance tag(s). Please try again.","Unable to find SPF scan(s). Please try again.":"Unable to find SPF scan(s). Please try again.","Unable to find SSL guidance tag(s). Please try again.":"Unable to find SSL guidance tag(s). Please try again.","Unable to find SSL scan(s). Please try again.":"Unable to find SSL scan(s). Please try again.","Unable to find the requested domain.":"Unable to find the requested domain.","Unable to find user affiliation(s). Please try again.":"Unable to find user affiliation(s). Please try again.","Unable to find verified organization(s). Please try again.":"Unable to find verified organization(s). Please try again.","Unable to invite user to organization. Please try again.":"Unable to invite user to organization. Please try again.","Unable to invite user to organization. User is already affiliated with organization.":"Unable to invite user to organization. User is already affiliated with organization.","Unable to invite user to unknown organization.":"Unable to invite user to unknown organization.","Unable to invite user. Please try again.":"Unable to invite user. Please try again.","Unable to invite yourself to an org.":"Unable to invite yourself to an org.","Unable to leave organization. Please try again.":"Unable to leave organization. Please try again.","Unable to leave undefined organization.":"Unable to leave undefined organization.","Unable to load Aggregate guidance tag(s). Please try again.":"Unable to load Aggregate guidance tag(s). Please try again.","Unable to load DKIM failure data. Please try again.":"Unable to load DKIM failure data. Please try again.","Unable to load DKIM guidance tag(s). Please try again.":"Unable to load DKIM guidance tag(s). Please try again.","Unable to load DKIM result(s). Please try again.":"Unable to load DKIM result(s). Please try again.","Unable to load DKIM scan(s). Please try again.":"Unable to load DKIM scan(s). Please try again.","Unable to load DKIM summary. Please try again.":"Unable to load DKIM summary. Please try again.","Unable to load DMARC failure data. Please try again.":"Unable to load DMARC failure data. Please try again.","Unable to load DMARC guidance tag(s). Please try again.":"Unable to load DMARC guidance tag(s). Please try again.","Unable to load DMARC phase summary. Please try again.":"Unable to load DMARC phase summary. Please try again.","Unable to load DMARC scan(s). Please try again.":"Unable to load DMARC scan(s). Please try again.","Unable to load DMARC summary data. Please try again.":"Unable to load DMARC summary data. Please try again.","Unable to load DMARC summary. Please try again.":"Unable to load DMARC summary. Please try again.","Unable to load DNS scan(s). Please try again.":"Unable to load DNS scan(s). Please try again.","Unable to load HTTPS guidance tag(s). Please try again.":"Unable to load HTTPS guidance tag(s). Please try again.","Unable to load HTTPS scan(s). Please try again.":"Unable to load HTTPS scan(s). Please try again.","Unable to load HTTPS summary. Please try again.":"Unable to load HTTPS summary. Please try again.","Unable to load SPF failure data. Please try again.":"Unable to load SPF failure data. Please try again.","Unable to load SPF guidance tag(s). Please try again.":"Unable to load SPF guidance tag(s). Please try again.","Unable to load SPF scan(s). Please try again.":"Unable to load SPF scan(s). Please try again.","Unable to load SPF summary. Please try again.":"Unable to load SPF summary. Please try again.","Unable to load SSL guidance tag(s). Please try again.":"Unable to load SSL guidance tag(s). Please try again.","Unable to load SSL scan(s). Please try again.":"Unable to load SSL scan(s). Please try again.","Unable to load SSL summary. Please try again.":"Unable to load SSL summary. Please try again.","Unable to load affiliation information. Please try again.":"Unable to load affiliation information. Please try again.","Unable to load affiliation(s). Please try again.":"Unable to load affiliation(s). Please try again.","Unable to load all organization domain statuses. Please try again.":"Unable to load all organization domain statuses. Please try again.","Unable to load domain(s). Please try again.":"Unable to load domain(s). Please try again.","Unable to load domain. Please try again.":"Unable to load domain. Please try again.","Unable to load full pass data. Please try again.":"Unable to load full pass data. Please try again.","Unable to load log(s). Please try again.":"Unable to load log(s). Please try again.","Unable to load log. Please try again.":"Unable to load log. Please try again.","Unable to load mail summary. Please try again.":"Unable to load mail summary. Please try again.","Unable to load organization domain statuses. Please try again.":"Unable to load organization domain statuses. Please try again.","Unable to load organization(s). Please try again.":"Unable to load organization(s). Please try again.","Unable to load owner information. Please try again.":"Unable to load owner information. Please try again.","Unable to load summary. Please try again.":"Unable to load summary. Please try again.","Unable to load tags(s). Please try again.":"Unable to load tags(s). Please try again.","Unable to load user(s). Please try again.":"Unable to load user(s). Please try again.","Unable to load verified domain(s). Please try again.":"Unable to load verified domain(s). Please try again.","Unable to load verified organization(s). Please try again.":"Unable to load verified organization(s). Please try again.","Unable to load web connections summary. Please try again.":"Unable to load web connections summary. Please try again.","Unable to load web scan(s). Please try again.":"Unable to load web scan(s). Please try again.","Unable to load web summary. Please try again.":"Unable to load web summary. Please try again.","Unable to query affiliation(s). Please try again.":"Unable to query affiliation(s). Please try again.","Unable to query domain(s). Please try again.":"Unable to query domain(s). Please try again.","Unable to query log(s). Please try again.":"Unable to query log(s). Please try again.","Unable to query user(s). Please try again.":"Unable to query user(s). Please try again.","Unable to refresh tokens, please sign in.":"Unable to refresh tokens, please sign in.","Unable to remove a user that already does not belong to this organization.":"Unable to remove a user that already does not belong to this organization.","Unable to remove domain from unknown organization.":"Unable to remove domain from unknown organization.","Unable to remove domain. Domain is not part of organization.":"Unable to remove domain. Domain is not part of organization.","Unable to remove domain. Please try again.":"Unable to remove domain. Please try again.","Unable to remove domains from unknown organization.":"Unable to remove domains from unknown organization.","Unable to remove organization. Please try again.":"Unable to remove organization. Please try again.","Unable to remove phone number. Please try again.":"Unable to remove phone number. Please try again.","Unable to remove unknown domain.":"Unable to remove unknown domain.","Unable to remove unknown organization.":"Unable to remove unknown organization.","Unable to remove unknown user from organization.":"Unable to remove unknown user from organization.","Unable to remove user from organization.":"Unable to remove user from organization.","Unable to remove user from this organization. Please try again.":"Unable to remove user from this organization. Please try again.","Unable to remove user from unknown organization.":"Unable to remove user from unknown organization.","Unable to request a one time scan on a domain that already has a pending scan.":"Unable to request a one time scan on a domain that already has a pending scan.","Unable to request a one time scan on an unknown domain.":"Unable to request a one time scan on an unknown domain.","Unable to request a one time scan. Please try again.":"Unable to request a one time scan. Please try again.","Unable to request invite to organization with which you are already affiliated.":"Unable to request invite to organization with which you are already affiliated.","Unable to request invite to organization with which you have already requested to join.":"Unable to request invite to organization with which you have already requested to join.","Unable to request invite to unknown organization.":"Unable to request invite to unknown organization.","Unable to request invite. Please try again.":"Unable to request invite. Please try again.","Unable to reset password. Please request a new email.":"Unable to reset password. Please request a new email.","Unable to reset password. Please try again.":"Unable to reset password. Please try again.","Unable to retrieve DMARC report information for: {domain}":["Unable to retrieve DMARC report information for: ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Unable to select DMARC report(s) for this period and year.","Unable to send authentication email. Please try again.":"Unable to send authentication email. Please try again.","Unable to send authentication text message. Please try again.":"Unable to send authentication text message. Please try again.","Unable to send org invite email. Please try again.":"Unable to send org invite email. Please try again.","Unable to send org invite request email. Please try again.":"Unable to send org invite request email. Please try again.","Unable to send password reset email. Please try again.":"Unable to send password reset email. Please try again.","Unable to send two factor authentication message. Please try again.":"Unable to send two factor authentication message. Please try again.","Unable to send verification email. Please try again.":"Unable to send verification email. Please try again.","Unable to set phone number, please try again.":"Unable to set phone number, please try again.","Unable to sign in, please try again.":"Unable to sign in, please try again.","Unable to sign up, please contact org admin for a new invite.":"Unable to sign up, please contact org admin for a new invite.","Unable to sign up. Please try again.":"Unable to sign up. Please try again.","Unable to transfer organization ownership. Please try again.":"Unable to transfer organization ownership. Please try again.","Unable to transfer ownership of a verified organization.":"Unable to transfer ownership of a verified organization.","Unable to transfer ownership of an org to an undefined user.":"Unable to transfer ownership of an org to an undefined user.","Unable to transfer ownership of undefined organization.":"Unable to transfer ownership of undefined organization.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Unable to transfer ownership to a user outside the org. Please invite the user and try again.","Unable to two factor authenticate. Please try again.":"Unable to two factor authenticate. Please try again.","Unable to unfavourite domain, domain is not favourited.":"Unable to unfavourite domain, domain is not favourited.","Unable to unfavourite domain. Please try again.":"Unable to unfavourite domain. Please try again.","Unable to unfavourite unknown domain.":"Unable to unfavourite unknown domain.","Unable to update domain edge. Please try again.":"Unable to update domain edge. Please try again.","Unable to update domain in an unknown org.":"Unable to update domain in an unknown org.","Unable to update domain that does not belong to the given organization.":"Unable to update domain that does not belong to the given organization.","Unable to update domain. Please try again.":"Unable to update domain. Please try again.","Unable to update organization. Please try again.":"Unable to update organization. Please try again.","Unable to update password, current password does not match. Please try again.":"Unable to update password, current password does not match. Please try again.","Unable to update password, new passwords do not match. Please try again.":"Unable to update password, new passwords do not match. Please try again.","Unable to update password, passwords do not match requirements. Please try again.":"Unable to update password, passwords do not match requirements. Please try again.","Unable to update password. Please try again.":"Unable to update password. Please try again.","Unable to update profile. Please try again.":"Unable to update profile. Please try again.","Unable to update role: organization unknown.":"Unable to update role: organization unknown.","Unable to update role: user does not belong to organization.":"Unable to update role: user does not belong to organization.","Unable to update role: user unknown.":"Unable to update role: user unknown.","Unable to update unknown domain.":"Unable to update unknown domain.","Unable to update unknown organization.":"Unable to update unknown organization.","Unable to update user's role. Please try again.":"Unable to update user's role. Please try again.","Unable to update your own role.":"Unable to update your own role.","Unable to verify account. Please request a new email.":"Unable to verify account. Please request a new email.","Unable to verify account. Please try again.":"Unable to verify account. Please try again.","Unable to verify if user is a super admin, please try again.":"Unable to verify if user is a super admin, please try again.","Unable to verify if user is an admin, please try again.":"Unable to verify if user is an admin, please try again.","Unable to verify organization. Please try again.":"Unable to verify organization. Please try again.","Unable to verify unknown organization.":"Unable to verify unknown organization.","User could not be queried.":"User could not be queried.","User role was updated successfully.":"User role was updated successfully.","Username not available, please try another.":"Username not available, please try another.","Verification error. Please activate multi-factor authentication to access content.":"Verification error. Please activate multi-factor authentication to access content.","Verification error. Please verify your account via email to access content.":"Verification error. Please verify your account via email to access content.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"You must provide a `first` or `last` value to properly paginate the `DKIM` connection.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"You must provide a `first` or `last` value to properly paginate the `DMARC` connection.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"You must provide a `first` or `last` value to properly paginate the `Domain` connection.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"You must provide a `first` or `last` value to properly paginate the `Log` connection.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"You must provide a `first` or `last` value to properly paginate the `Organization` connection.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"You must provide a `first` or `last` value to properly paginate the `SPF` connection.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"You must provide a `first` or `last` value to properly paginate the `SSL` connection.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"You must provide a `first` or `last` value to properly paginate the `User` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.","You must provide a `limit` value to properly paginate the `DNS` connection.":"You must provide a `limit` value to properly paginate the `DNS` connection.","You must provide a `limit` value to properly paginate the `web` connection.":"You must provide a `limit` value to properly paginate the `web` connection.","You must provide a `period` value to access the `DmarcSummaries` connection.":"You must provide a `period` value to access the `DmarcSummaries` connection.","You must provide a `year` value to access the `DmarcSummaries` connection.":"You must provide a `year` value to access the `DmarcSummaries` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` must be of type `number` not `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` on the `Affiliation` connection cannot be less than zero."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIMResults` connection cannot be less than zero."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` on the `DKIM` connection cannot be less than zero."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` on the `DMARC` connection cannot be less than zero."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DkimFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcFailureTable` connection cannot be less than zero."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` on the `DmarcSummaries` connection cannot be less than zero."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` on the `Domain` connection cannot be less than zero."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` on the `FullPassTable` connection cannot be less than zero."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` on the `GuidanceTag` connection cannot be less than zero."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` on the `HTTPS` connection cannot be less than zero."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` on the `Log` connection cannot be less than zero."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` on the `Organization` connection cannot be less than zero."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` on the `SPF` connection cannot be less than zero."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` on the `SSL` connection cannot be less than zero."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` on the `SpfFailureTable` connection cannot be less than zero."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` on the `User` connection cannot be less than zero."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedDomain` connection cannot be less than zero."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` on the `VerifiedOrganization` connection cannot be less than zero."]}}; \ No newline at end of file diff --git a/api/src/locale/en/messages.po b/api/src/locale/en/messages.po index c777614f32..17cf3b9ad0 100644 --- a/api/src/locale/en/messages.po +++ b/api/src/locale/en/messages.po @@ -12,26 +12,43 @@ msgstr "" "Plural-Forms: \n" #: src/auth/check-permission.js:18 -#: src/auth/check-permission.js:42 +#: src/auth/check-permission.js:57 #: src/auth/user-required.js:10 #: src/auth/user-required.js:21 #: src/auth/user-required.js:28 msgid "Authentication error. Please sign in." msgstr "Authentication error. Please sign in." -#: src/organization/objects/organization.js:161 +#: src/organization/objects/organization.js:241 msgid "Cannot query affiliations on organization without admin permission or higher." msgstr "Cannot query affiliations on organization without admin permission or higher." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:244 -#: src/audit-logs/queries/find-audit-logs.js:62 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:224 +#: src/audit-logs/queries/find-audit-logs.js:51 msgid "Cannot query audit logs on organization without admin permission or higher." msgstr "Cannot query audit logs on organization without admin permission or higher." +#: src/domain/objects/domain.js:149 +msgid "Cannot query dns scan results without permission." +msgstr "Cannot query dns scan results without permission." + +#: src/domain/objects/domain.js:58 +msgid "Cannot query domain selectors without permission." +msgstr "Cannot query domain selectors without permission." + +#: src/domain/objects/domain.js:195 +msgid "Cannot query web scan results without permission." +msgstr "Cannot query web scan results without permission." + #: src/user/mutations/sign-up.js:106 msgid "Email already in use." msgstr "Email already in use." +#: src/domain/mutations/request-scan.js:87 +#: src/domain/mutations/request-scan.js:97 +msgid "Error while requesting scan. Please try again." +msgstr "Error while requesting scan. Please try again." + #: src/user/mutations/send-password-reset.js:62 msgid "If an account with this username is found, a password reset link will be found in your inbox." msgstr "If an account with this username is found, a password reset link will be found in your inbox." @@ -61,7 +78,7 @@ msgstr "Invalid token, please sign in." msgid "New passwords do not match." msgstr "New passwords do not match." -#: src/organization/queries/find-organization-by-slug.js:49 +#: src/organization/queries/find-organization-by-slug.js:41 #: src/user/queries/find-my-tracker.js:29 msgid "No organization with the provided slug could be found." msgstr "No organization with the provided slug could be found." @@ -74,7 +91,7 @@ msgstr "No verified domain with the provided domain could be found." msgid "No verified organization with the provided slug could be found." msgstr "No verified organization with the provided slug could be found." -#: src/organization/mutations/verify-organization.js:82 +#: src/organization/mutations/verify-organization.js:81 msgid "Organization has already been verified." msgstr "Organization has already been verified." @@ -82,14 +99,14 @@ msgstr "Organization has already been verified." msgid "Organization name already in use, please choose another and try again." msgstr "Organization name already in use, please choose another and try again." -#: src/organization/mutations/create-organization.js:139 +#: src/organization/mutations/create-organization.js:124 msgid "Organization name already in use. Please try again with a different name." msgstr "Organization name already in use. Please try again with a different name." -#: src/auth/check-domain-ownership.js:33 -#: src/auth/check-domain-ownership.js:45 -#: src/auth/check-domain-ownership.js:67 -#: src/auth/check-domain-ownership.js:78 +#: src/auth/check-domain-ownership.js:29 +#: src/auth/check-domain-ownership.js:39 +#: src/auth/check-domain-ownership.js:65 +#: src/auth/check-domain-ownership.js:74 msgid "Ownership check error. Unable to request domain information." msgstr "Ownership check error. Unable to request domain information." @@ -119,12 +136,12 @@ msgstr "Passing both `first` and `last` to paginate the `DkimFailureTable` conne msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:198 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:170 msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 -#: src/domain/loaders/load-domain-connections-by-user-id.js:168 +#: src/domain/loaders/load-domain-connections-by-user-id.js:154 msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `Domain` connection is not supported." @@ -145,12 +162,12 @@ msgstr "Passing both `first` and `last` to paginate the `GuidanceTag` connection #~ msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." #~ msgstr "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:110 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:105 msgid "Passing both `first` and `last` to paginate the `Log` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `Log` connection is not supported." #: src/organization/loaders/load-organization-connections-by-domain-id.js:192 -#: src/organization/loaders/load-organization-connections-by-user-id.js:191 +#: src/organization/loaders/load-organization-connections-by-user-id.js:173 #: src/organization/loaders/load-web-check-connections-by-user-id.js:98 msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." msgstr "Passing both `first` and `last` to paginate the `Organization` connection is not supported." @@ -198,16 +215,16 @@ msgstr "Password was successfully updated." msgid "Passwords do not match." msgstr "Passwords do not match." -#: src/organization/queries/find-organization-by-slug.js:61 -#: src/organization/queries/find-organization-by-slug.js:74 +#: src/organization/queries/find-organization-by-slug.js:50 +#: src/organization/queries/find-organization-by-slug.js:55 msgid "Permission Denied: Could not retrieve specified organization." msgstr "Permission Denied: Could not retrieve specified organization." -#: src/user/mutations/update-user-profile.js:115 +#: src/user/mutations/update-user-profile.js:110 msgid "Permission Denied: Multi-factor authentication is required for admin accounts" msgstr "Permission Denied: Multi-factor authentication is required for admin accounts" -#: src/affiliation/mutations/transfer-org-ownership.js:95 +#: src/affiliation/mutations/transfer-org-ownership.js:74 msgid "Permission Denied: Please contact org owner to transfer ownership." msgstr "Permission Denied: Please contact org owner to transfer ownership." @@ -215,7 +232,7 @@ msgstr "Permission Denied: Please contact org owner to transfer ownership." msgid "Permission Denied: Please contact organization admin for help with archiving domains." msgstr "Permission Denied: Please contact organization admin for help with archiving domains." -#: src/domain/mutations/remove-domain.js:106 +#: src/domain/mutations/remove-domain.js:94 msgid "Permission Denied: Please contact organization admin for help with removing domain." msgstr "Permission Denied: Please contact organization admin for help with removing domain." @@ -227,7 +244,8 @@ msgstr "Permission Denied: Please contact organization admin for help with remov msgid "Permission Denied: Please contact organization admin for help with removing organization." msgstr "Permission Denied: Please contact organization admin for help with removing organization." -#: src/affiliation/mutations/remove-user-from-org.js:204 +#: src/affiliation/mutations/remove-user-from-org.js:126 +#: src/affiliation/mutations/remove-user-from-org.js:138 msgid "Permission Denied: Please contact organization admin for help with removing users." msgstr "Permission Denied: Please contact organization admin for help with removing users." @@ -238,14 +256,16 @@ msgstr "Permission Denied: Please contact organization admin for help with updat #: src/affiliation/mutations/update-user-role.js:170 #: src/affiliation/mutations/update-user-role.js:193 #: src/affiliation/mutations/update-user-role.js:210 -msgid "Permission Denied: Please contact organization admin for help with updating user roles." -msgstr "Permission Denied: Please contact organization admin for help with updating user roles." +#~ msgid "Permission Denied: Please contact organization admin for help with updating user roles." +#~ msgstr "Permission Denied: Please contact organization admin for help with updating user roles." -#: src/affiliation/mutations/invite-user-to-org.js:105 +#: src/affiliation/mutations/invite-user-to-org.js:99 msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission Denied: Please contact organization admin for help with user invitations." -#: src/affiliation/mutations/update-user-role.js:108 +#: src/affiliation/mutations/update-user-role.js:109 +#: src/affiliation/mutations/update-user-role.js:160 +#: src/affiliation/mutations/update-user-role.js:172 msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission Denied: Please contact organization admin for help with user role changes." @@ -258,18 +278,23 @@ msgid "Permission Denied: Please contact organization user for help with creatin msgstr "Permission Denied: Please contact organization user for help with creating domains." #: src/domain/queries/find-domain-by-domain.js:57 +#: src/organization/objects/organization.js:98 msgid "Permission Denied: Please contact organization user for help with retrieving this domain." msgstr "Permission Denied: Please contact organization user for help with retrieving this domain." -#: src/domain/mutations/request-scan.js:60 +#: src/domain/mutations/request-scan.js:63 msgid "Permission Denied: Please contact organization user for help with scanning this domain." msgstr "Permission Denied: Please contact organization user for help with scanning this domain." -#: src/domain/mutations/update-domain.js:150 +#: src/domain/mutations/update-domain.js:139 msgid "Permission Denied: Please contact organization user for help with updating this domain." msgstr "Permission Denied: Please contact organization user for help with updating this domain." -#: src/domain/mutations/remove-domain.js:95 +#: src/organization/mutations/archive-organization.js:68 +msgid "Permission Denied: Please contact super admin for help with archiving organization." +msgstr "Permission Denied: Please contact super admin for help with archiving organization." + +#: src/domain/mutations/remove-domain.js:107 #: src/domain/mutations/remove-organizations-domains.js:105 msgid "Permission Denied: Please contact super admin for help with removing domain." msgstr "Permission Denied: Please contact super admin for help with removing domain." @@ -282,12 +307,12 @@ msgstr "Permission Denied: Please contact super admin for help with removing org #~ msgid "Permission Denied: Please contact super admin for help with scanning this domain." #~ msgstr "Permission Denied: Please contact super admin for help with scanning this domain." -#: src/organization/mutations/verify-organization.js:69 +#: src/organization/mutations/verify-organization.js:68 msgid "Permission Denied: Please contact super admin for help with verifying this organization." msgstr "Permission Denied: Please contact super admin for help with verifying this organization." -#: src/auth/check-domain-permission.js:24 -#: src/auth/check-domain-permission.js:48 +#: src/auth/check-domain-permission.js:22 +#: src/auth/check-domain-permission.js:51 #: src/auth/check-domain-permission.js:61 msgid "Permission check error. Unable to request domain information." msgstr "Permission check error. Unable to request domain information." @@ -304,7 +329,7 @@ msgid "Permission error: Unable to close other user's account." msgstr "Permission error: Unable to close other user's account." #: src/auth/super-admin-required.js:15 -#: src/organization/queries/get-all-organization-domain-statuses.js:36 +#: src/organization/queries/get-all-organization-domain-statuses.js:27 msgid "Permissions error. You do not have sufficient permissions to access this data." msgstr "Permissions error. You do not have sufficient permissions to access this data." @@ -316,7 +341,7 @@ msgstr "Phone number has been successfully removed." msgid "Phone number has been successfully set, you will receive a verification text message shortly." msgstr "Phone number has been successfully set, you will receive a verification text message shortly." -#: src/user/mutations/update-user-profile.js:198 +#: src/user/mutations/update-user-profile.js:184 msgid "Profile successfully updated." msgstr "Profile successfully updated." @@ -333,12 +358,12 @@ msgstr "Requesting `{amount}` records on the `DkimFailureTable` connection excee msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:221 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:188 msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 -#: src/domain/loaders/load-domain-connections-by-user-id.js:186 +#: src/domain/loaders/load-domain-connections-by-user-id.js:172 msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." @@ -355,12 +380,12 @@ msgstr "Requesting `{amount}` records on the `FullPassTable` connection exceeds msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:133 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:119 msgid "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." #: src/organization/loaders/load-organization-connections-by-domain-id.js:215 -#: src/organization/loaders/load-organization-connections-by-user-id.js:214 +#: src/organization/loaders/load-organization-connections-by-user-id.js:187 #: src/organization/loaders/load-web-check-connections-by-user-id.js:121 msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." msgstr "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." @@ -415,11 +440,15 @@ msgstr "Successfully added {domainCount} domain(s) to {0}." #~ msgid "Successfully added {domainCount} domains to {0}." #~ msgstr "Successfully added {domainCount} domains to {0}." +#: src/organization/mutations/archive-organization.js:188 +msgid "Successfully archived organization: {0}." +msgstr "Successfully archived organization: {0}." + #: src/user/mutations/close-account.js:405 msgid "Successfully closed account." msgstr "Successfully closed account." -#: src/domain/mutations/request-scan.js:108 +#: src/domain/mutations/request-scan.js:166 msgid "Successfully dispatched one time scan." msgstr "Successfully dispatched one time scan." @@ -427,11 +456,11 @@ msgstr "Successfully dispatched one time scan." msgid "Successfully email verified account, and set TFA send method to email." msgstr "Successfully email verified account, and set TFA send method to email." -#: src/affiliation/mutations/invite-user-to-org.js:259 +#: src/affiliation/mutations/invite-user-to-org.js:280 msgid "Successfully invited user to organization, and sent notification email." msgstr "Successfully invited user to organization, and sent notification email." -#: src/affiliation/mutations/leave-organization.js:308 +#: src/affiliation/mutations/leave-organization.js:84 msgid "Successfully left organization: {0}" msgstr "Successfully left organization: {0}" @@ -439,7 +468,7 @@ msgstr "Successfully left organization: {0}" msgid "Successfully removed domain: {0} from favourites." msgstr "Successfully removed domain: {0} from favourites." -#: src/domain/mutations/remove-domain.js:325 +#: src/domain/mutations/remove-domain.js:326 msgid "Successfully removed domain: {0} from {1}." msgstr "Successfully removed domain: {0} from {1}." @@ -447,7 +476,7 @@ msgstr "Successfully removed domain: {0} from {1}." msgid "Successfully removed organization: {0}." msgstr "Successfully removed organization: {0}." -#: src/affiliation/mutations/remove-user-from-org.js:191 +#: src/affiliation/mutations/remove-user-from-org.js:195 msgid "Successfully removed user from organization." msgstr "Successfully removed user from organization." @@ -455,15 +484,15 @@ msgstr "Successfully removed user from organization." msgid "Successfully removed {domainCount} domain(s) from {0}." msgstr "Successfully removed {domainCount} domain(s) from {0}." -#: src/affiliation/mutations/request-org-affiliation.js:201 -msgid "Successfully requested invite to organization, and sent notification email." -msgstr "Successfully requested invite to organization, and sent notification email." - #: src/domain/mutations/remove-organizations-domains.js:530 #~ msgid "Successfully removed {domainCount} domains from {0}." #~ msgstr "Successfully removed {domainCount} domains from {0}." -#: src/affiliation/mutations/invite-user-to-org.js:150 +#: src/affiliation/mutations/request-org-affiliation.js:197 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "Successfully requested invite to organization, and sent notification email." + +#: src/affiliation/mutations/invite-user-to-org.js:172 msgid "Successfully sent invitation to service, and organization email." msgstr "Successfully sent invitation to service, and organization email." @@ -471,11 +500,11 @@ msgstr "Successfully sent invitation to service, and organization email." msgid "Successfully signed out." msgstr "Successfully signed out." -#: src/affiliation/mutations/transfer-org-ownership.js:218 +#: src/affiliation/mutations/transfer-org-ownership.js:185 msgid "Successfully transferred org: {0} ownership to user: {1}" msgstr "Successfully transferred org: {0} ownership to user: {1}" -#: src/organization/mutations/verify-organization.js:151 +#: src/organization/mutations/verify-organization.js:119 msgid "Successfully verified organization: {0}." msgstr "Successfully verified organization: {0}." @@ -499,16 +528,8 @@ msgstr "Two factor code is incorrect. Please try again." msgid "Two factor code length is incorrect. Please try again." msgstr "Two factor code length is incorrect. Please try again." -#: src/affiliation/mutations/leave-organization.js:71 -#: src/affiliation/mutations/leave-organization.js:81 -#: src/affiliation/mutations/leave-organization.js:111 -#: src/affiliation/mutations/leave-organization.js:126 -#: src/affiliation/mutations/leave-organization.js:156 -#: src/affiliation/mutations/leave-organization.js:166 -#: src/affiliation/mutations/leave-organization.js:239 -#: src/affiliation/mutations/leave-organization.js:275 -#: src/affiliation/mutations/leave-organization.js:293 -#: src/affiliation/mutations/leave-organization.js:301 +#: src/affiliation/mutations/leave-organization.js:70 +#: src/affiliation/mutations/leave-organization.js:77 msgid "Unable leave organization. Please try again." msgstr "Unable leave organization. Please try again." @@ -516,6 +537,18 @@ msgstr "Unable leave organization. Please try again." msgid "Unable to add domains in unknown organization." msgstr "Unable to add domains in unknown organization." +#: src/organization/mutations/archive-organization.js:101 +#: src/organization/mutations/archive-organization.js:111 +#: src/organization/mutations/archive-organization.js:129 +#: src/organization/mutations/archive-organization.js:146 +#: src/organization/mutations/archive-organization.js:155 +msgid "Unable to archive organization. Please try again." +msgstr "Unable to archive organization. Please try again." + +#: src/organization/mutations/archive-organization.js:54 +msgid "Unable to archive unknown organization." +msgstr "Unable to archive unknown organization." + #: src/user/mutations/authenticate.js:77 #: src/user/mutations/authenticate.js:117 #: src/user/mutations/authenticate.js:126 @@ -523,7 +556,7 @@ msgid "Unable to authenticate. Please try again." msgstr "Unable to authenticate. Please try again." #: src/auth/check-permission.js:26 -#: src/auth/check-permission.js:49 +#: src/auth/check-permission.js:64 #: src/auth/check-super-admin.js:20 #: src/auth/check-super-admin.js:30 msgid "Unable to check permission. Please try again." @@ -575,9 +608,9 @@ msgstr "Unable to create domain. Please try again." msgid "Unable to create domains. Please try again." msgstr "Unable to create domains. Please try again." -#: src/organization/mutations/create-organization.js:201 -#: src/organization/mutations/create-organization.js:224 -#: src/organization/mutations/create-organization.js:235 +#: src/organization/mutations/create-organization.js:182 +#: src/organization/mutations/create-organization.js:202 +#: src/organization/mutations/create-organization.js:211 msgid "Unable to create organization. Please try again." msgstr "Unable to create organization. Please try again." @@ -587,6 +620,11 @@ msgstr "Unable to create organization. Please try again." #~ msgid "Unable to dispatch one time scan. Please try again." #~ msgstr "Unable to dispatch one time scan. Please try again." +#: src/organization/objects/organization.js:139 +#: src/organization/objects/organization.js:149 +msgid "Unable to export organization. Please try again." +msgstr "Unable to export organization. Please try again." + #: src/domain/mutations/favourite-domain.js:94 msgid "Unable to favourite domain, user has already favourited it." msgstr "Unable to favourite domain, user has already favourited it." @@ -689,32 +727,33 @@ msgstr "Unable to find user affiliation(s). Please try again." msgid "Unable to find verified organization(s). Please try again." msgstr "Unable to find verified organization(s). Please try again." -#: src/affiliation/mutations/invite-user-to-org.js:170 +#: src/affiliation/mutations/invite-user-to-org.js:117 +#: src/affiliation/mutations/invite-user-to-org.js:127 +#: src/affiliation/mutations/invite-user-to-org.js:192 msgid "Unable to invite user to organization. Please try again." msgstr "Unable to invite user to organization. Please try again." -#: src/affiliation/mutations/invite-user-to-org.js:182 +#: src/affiliation/mutations/invite-user-to-org.js:204 msgid "Unable to invite user to organization. User is already affiliated with organization." msgstr "Unable to invite user to organization. User is already affiliated with organization." -#: src/affiliation/mutations/invite-user-to-org.js:87 +#: src/affiliation/mutations/invite-user-to-org.js:82 msgid "Unable to invite user to unknown organization." msgstr "Unable to invite user to unknown organization." -#: src/affiliation/mutations/invite-user-to-org.js:194 -#: src/affiliation/mutations/invite-user-to-org.js:209 -#: src/affiliation/mutations/request-org-affiliation.js:134 +#: src/affiliation/mutations/invite-user-to-org.js:233 +#: src/affiliation/mutations/invite-user-to-org.js:252 msgid "Unable to invite user. Please try again." msgstr "Unable to invite user. Please try again." -#: src/affiliation/mutations/invite-user-to-org.js:73 +#: src/affiliation/mutations/invite-user-to-org.js:68 msgid "Unable to invite yourself to an org." msgstr "Unable to invite yourself to an org." #: src/affiliation/mutations/leave-organization.js:190 #: src/affiliation/mutations/leave-organization.js:208 -msgid "Unable to leave organization. Please try again." -msgstr "Unable to leave organization. Please try again." +#~ msgid "Unable to leave organization. Please try again." +#~ msgstr "Unable to leave organization. Please try again." #: src/affiliation/mutations/leave-organization.js:48 msgid "Unable to leave undefined organization." @@ -746,6 +785,10 @@ msgstr "Unable to load DKIM guidance tag(s). Please try again." #~ msgid "Unable to load DKIM scan(s). Please try again." #~ msgstr "Unable to load DKIM scan(s). Please try again." +#: src/summaries/queries/dkim-summary.js:12 +msgid "Unable to load DKIM summary. Please try again." +msgstr "Unable to load DKIM summary. Please try again." + #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 @@ -766,8 +809,8 @@ msgstr "Unable to load DMARC phase summary. Please try again." #~ msgid "Unable to load DMARC scan(s). Please try again." #~ msgstr "Unable to load DMARC scan(s). Please try again." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:477 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:489 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:446 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:456 #: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 #: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 #: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 @@ -775,6 +818,10 @@ msgstr "Unable to load DMARC phase summary. Please try again." msgid "Unable to load DMARC summary data. Please try again." msgstr "Unable to load DMARC summary data. Please try again." +#: src/summaries/queries/dmarc-summary.js:12 +msgid "Unable to load DMARC summary. Please try again." +msgstr "Unable to load DMARC summary. Please try again." + #: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 #: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 msgid "Unable to load DNS scan(s). Please try again." @@ -811,6 +858,10 @@ msgstr "Unable to load SPF guidance tag(s). Please try again." #~ msgid "Unable to load SPF scan(s). Please try again." #~ msgstr "Unable to load SPF scan(s). Please try again." +#: src/summaries/queries/spf-summary.js:12 +msgid "Unable to load SPF summary. Please try again." +msgstr "Unable to load SPF summary. Please try again." + #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 msgid "Unable to load SSL guidance tag(s). Please try again." @@ -821,6 +872,10 @@ msgstr "Unable to load SSL guidance tag(s). Please try again." #~ msgid "Unable to load SSL scan(s). Please try again." #~ msgstr "Unable to load SSL scan(s). Please try again." +#: src/summaries/queries/ssl-summary.js:12 +msgid "Unable to load SSL summary. Please try again." +msgstr "Unable to load SSL summary. Please try again." + #: src/auth/check-user-belongs-to-org.js:20 msgid "Unable to load affiliation information. Please try again." msgstr "Unable to load affiliation information. Please try again." @@ -834,11 +889,11 @@ msgstr "Unable to load affiliation(s). Please try again." msgid "Unable to load all organization domain statuses. Please try again." msgstr "Unable to load all organization domain statuses. Please try again." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:534 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:544 -#: src/domain/loaders/load-domain-connections-by-user-id.js:462 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:549 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:559 +#: src/domain/loaders/load-domain-connections-by-user-id.js:448 #: src/domain/loaders/load-domain-tags-by-org-id.js:27 -#: src/user/loaders/load-my-tracker-by-user-id.js:43 +#: src/user/loaders/load-my-tracker-by-user-id.js:44 msgid "Unable to load domain(s). Please try again." msgstr "Unable to load domain(s). Please try again." @@ -855,7 +910,7 @@ msgstr "Unable to load domain. Please try again." msgid "Unable to load full pass data. Please try again." msgstr "Unable to load full pass data. Please try again." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:340 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:318 msgid "Unable to load log(s). Please try again." msgstr "Unable to load log(s). Please try again." @@ -878,15 +933,15 @@ msgstr "Unable to load organization domain statuses. Please try again." #: src/organization/loaders/load-organization-by-slug.js:51 #: src/organization/loaders/load-organization-connections-by-domain-id.js:533 #: src/organization/loaders/load-organization-connections-by-domain-id.js:545 -#: src/organization/loaders/load-organization-connections-by-user-id.js:520 -#: src/organization/loaders/load-organization-connections-by-user-id.js:532 +#: src/organization/loaders/load-organization-connections-by-user-id.js:505 +#: src/organization/loaders/load-organization-connections-by-user-id.js:515 #: src/organization/loaders/load-web-check-connections-by-user-id.js:331 #: src/organization/loaders/load-web-check-connections-by-user-id.js:343 msgid "Unable to load organization(s). Please try again." msgstr "Unable to load organization(s). Please try again." -#: src/auth/check-org-owner.js:22 -#: src/auth/check-org-owner.js:34 +#: src/auth/check-org-owner.js:19 +#: src/auth/check-org-owner.js:27 msgid "Unable to load owner information. Please try again." msgstr "Unable to load owner information. Please try again." @@ -925,6 +980,10 @@ msgstr "Unable to load verified domain(s). Please try again." msgid "Unable to load verified organization(s). Please try again." msgstr "Unable to load verified organization(s). Please try again." +#: src/summaries/queries/web-connections-summary.js:12 +msgid "Unable to load web connections summary. Please try again." +msgstr "Unable to load web connections summary. Please try again." + #: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 #: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 #: src/web-scan/loaders/load-web-scans-by-web-id.js:9 @@ -942,12 +1001,12 @@ msgstr "Unable to load web summary. Please try again." msgid "Unable to query affiliation(s). Please try again." msgstr "Unable to query affiliation(s). Please try again." -#: src/domain/loaders/load-domain-connections-by-user-id.js:452 -#: src/user/loaders/load-my-tracker-by-user-id.js:33 +#: src/domain/loaders/load-domain-connections-by-user-id.js:438 +#: src/user/loaders/load-my-tracker-by-user-id.js:34 msgid "Unable to query domain(s). Please try again." msgstr "Unable to query domain(s). Please try again." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:330 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:308 msgid "Unable to query log(s). Please try again." msgstr "Unable to query log(s). Please try again." @@ -965,7 +1024,7 @@ msgstr "Unable to query user(s). Please try again." msgid "Unable to refresh tokens, please sign in." msgstr "Unable to refresh tokens, please sign in." -#: src/affiliation/mutations/remove-user-from-org.js:114 +#: src/affiliation/mutations/remove-user-from-org.js:104 msgid "Unable to remove a user that already does not belong to this organization." msgstr "Unable to remove a user that already does not belong to this organization." @@ -973,19 +1032,19 @@ msgstr "Unable to remove a user that already does not belong to this organizatio msgid "Unable to remove domain from unknown organization." msgstr "Unable to remove domain from unknown organization." -#: src/domain/mutations/remove-domain.js:135 +#: src/domain/mutations/remove-domain.js:136 msgid "Unable to remove domain. Domain is not part of organization." msgstr "Unable to remove domain. Domain is not part of organization." -#: src/domain/mutations/remove-domain.js:122 -#: src/domain/mutations/remove-domain.js:151 -#: src/domain/mutations/remove-domain.js:184 -#: src/domain/mutations/remove-domain.js:203 -#: src/domain/mutations/remove-domain.js:229 -#: src/domain/mutations/remove-domain.js:247 -#: src/domain/mutations/remove-domain.js:264 -#: src/domain/mutations/remove-domain.js:287 -#: src/domain/mutations/remove-domain.js:298 +#: src/domain/mutations/remove-domain.js:123 +#: src/domain/mutations/remove-domain.js:152 +#: src/domain/mutations/remove-domain.js:185 +#: src/domain/mutations/remove-domain.js:204 +#: src/domain/mutations/remove-domain.js:230 +#: src/domain/mutations/remove-domain.js:248 +#: src/domain/mutations/remove-domain.js:265 +#: src/domain/mutations/remove-domain.js:288 +#: src/domain/mutations/remove-domain.js:299 msgid "Unable to remove domain. Please try again." msgstr "Unable to remove domain. Please try again." @@ -1020,18 +1079,18 @@ msgstr "Unable to remove unknown domain." msgid "Unable to remove unknown organization." msgstr "Unable to remove unknown organization." -#: src/affiliation/mutations/remove-user-from-org.js:87 +#: src/affiliation/mutations/remove-user-from-org.js:77 msgid "Unable to remove unknown user from organization." msgstr "Unable to remove unknown user from organization." #: src/affiliation/mutations/remove-user-from-org.js:74 -msgid "Unable to remove user from organization." -msgstr "Unable to remove user from organization." +#~ msgid "Unable to remove user from organization." +#~ msgstr "Unable to remove user from organization." -#: src/affiliation/mutations/remove-user-from-org.js:104 -#: src/affiliation/mutations/remove-user-from-org.js:125 -#: src/affiliation/mutations/remove-user-from-org.js:156 -#: src/affiliation/mutations/remove-user-from-org.js:165 +#: src/affiliation/mutations/remove-user-from-org.js:94 +#: src/affiliation/mutations/remove-user-from-org.js:115 +#: src/affiliation/mutations/remove-user-from-org.js:160 +#: src/affiliation/mutations/remove-user-from-org.js:169 msgid "Unable to remove user from this organization. Please try again." msgstr "Unable to remove user from this organization. Please try again." @@ -1039,23 +1098,23 @@ msgstr "Unable to remove user from this organization. Please try again." msgid "Unable to remove user from unknown organization." msgstr "Unable to remove user from unknown organization." -#: src/domain/mutations/request-scan.js:82 +#: src/domain/mutations/request-scan.js:117 msgid "Unable to request a one time scan on a domain that already has a pending scan." msgstr "Unable to request a one time scan on a domain that already has a pending scan." -#: src/domain/mutations/request-scan.js:48 +#: src/domain/mutations/request-scan.js:52 msgid "Unable to request a one time scan on an unknown domain." msgstr "Unable to request a one time scan on an unknown domain." -#: src/domain/mutations/request-scan.js:90 +#: src/domain/mutations/request-scan.js:125 msgid "Unable to request a one time scan. Please try again." msgstr "Unable to request a one time scan. Please try again." -#: src/affiliation/mutations/request-org-affiliation.js:109 +#: src/affiliation/mutations/request-org-affiliation.js:95 msgid "Unable to request invite to organization with which you are already affiliated." msgstr "Unable to request invite to organization with which you are already affiliated." -#: src/affiliation/mutations/request-org-affiliation.js:83 +#: src/affiliation/mutations/request-org-affiliation.js:85 msgid "Unable to request invite to organization with which you have already requested to join." msgstr "Unable to request invite to organization with which you have already requested to join." @@ -1063,10 +1122,11 @@ msgstr "Unable to request invite to organization with which you have already req msgid "Unable to request invite to unknown organization." msgstr "Unable to request invite to unknown organization." -#: src/affiliation/mutations/request-org-affiliation.js:73 -#: src/affiliation/mutations/request-org-affiliation.js:99 -#: src/affiliation/mutations/request-org-affiliation.js:150 -#: src/affiliation/mutations/request-org-affiliation.js:169 +#: src/affiliation/mutations/request-org-affiliation.js:72 +#: src/affiliation/mutations/request-org-affiliation.js:120 +#: src/affiliation/mutations/request-org-affiliation.js:136 +#: src/affiliation/mutations/request-org-affiliation.js:146 +#: src/affiliation/mutations/request-org-affiliation.js:165 msgid "Unable to request invite. Please try again." msgstr "Unable to request invite. Please try again." @@ -1080,8 +1140,8 @@ msgstr "Unable to reset password. Please request a new email." msgid "Unable to reset password. Please try again." msgstr "Unable to reset password. Please try again." -#: src/domain/objects/domain.js:206 -#: src/domain/objects/domain.js:248 +#: src/domain/objects/domain.js:236 +#: src/domain/objects/domain.js:271 msgid "Unable to retrieve DMARC report information for: {domain}" msgstr "Unable to retrieve DMARC report information for: {domain}" @@ -1098,7 +1158,7 @@ msgstr "Unable to send authentication email. Please try again." msgid "Unable to send authentication text message. Please try again." msgstr "Unable to send authentication text message. Please try again." -#: src/notify/notify-send-org-invite-create-account.js:31 +#: src/notify/notify-send-org-invite-create-account.js:19 #: src/notify/notify-send-org-invite-email.js:26 msgid "Unable to send org invite email. Please try again." msgstr "Unable to send org invite email. Please try again." @@ -1134,38 +1194,38 @@ msgstr "Unable to set phone number, please try again." msgid "Unable to sign in, please try again." msgstr "Unable to sign in, please try again." -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:192 +#: src/user/mutations/sign-up.js:183 +#: src/user/mutations/sign-up.js:193 msgid "Unable to sign up, please contact org admin for a new invite." msgstr "Unable to sign up, please contact org admin for a new invite." -#: src/user/mutations/sign-up.js:155 -#: src/user/mutations/sign-up.js:163 +#: src/user/mutations/sign-up.js:156 +#: src/user/mutations/sign-up.js:164 #: src/user/mutations/sign-up.js:213 #: src/user/mutations/sign-up.js:221 msgid "Unable to sign up. Please try again." msgstr "Unable to sign up. Please try again." -#: src/affiliation/mutations/transfer-org-ownership.js:131 -#: src/affiliation/mutations/transfer-org-ownership.js:172 -#: src/affiliation/mutations/transfer-org-ownership.js:196 -#: src/affiliation/mutations/transfer-org-ownership.js:208 +#: src/affiliation/mutations/transfer-org-ownership.js:106 +#: src/affiliation/mutations/transfer-org-ownership.js:145 +#: src/affiliation/mutations/transfer-org-ownership.js:167 +#: src/affiliation/mutations/transfer-org-ownership.js:177 msgid "Unable to transfer organization ownership. Please try again." msgstr "Unable to transfer organization ownership. Please try again." -#: src/affiliation/mutations/transfer-org-ownership.js:78 -msgid "Unable to transfer ownership of a verified organization." -msgstr "Unable to transfer ownership of a verified organization." +#: src/affiliation/mutations/transfer-org-ownership.js:68 +#~ msgid "Unable to transfer ownership of a verified organization." +#~ msgstr "Unable to transfer ownership of a verified organization." -#: src/affiliation/mutations/transfer-org-ownership.js:112 +#: src/affiliation/mutations/transfer-org-ownership.js:89 msgid "Unable to transfer ownership of an org to an undefined user." msgstr "Unable to transfer ownership of an org to an undefined user." -#: src/affiliation/mutations/transfer-org-ownership.js:65 +#: src/affiliation/mutations/transfer-org-ownership.js:59 msgid "Unable to transfer ownership of undefined organization." msgstr "Unable to transfer ownership of undefined organization." -#: src/affiliation/mutations/transfer-org-ownership.js:144 +#: src/affiliation/mutations/transfer-org-ownership.js:118 msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." msgstr "Unable to transfer ownership to a user outside the org. Please invite the user and try again." @@ -1187,21 +1247,21 @@ msgstr "Unable to unfavourite domain. Please try again." msgid "Unable to unfavourite unknown domain." msgstr "Unable to unfavourite unknown domain." -#: src/domain/mutations/update-domain.js:256 +#: src/domain/mutations/update-domain.js:237 msgid "Unable to update domain edge. Please try again." msgstr "Unable to update domain edge. Please try again." -#: src/domain/mutations/update-domain.js:131 +#: src/domain/mutations/update-domain.js:125 msgid "Unable to update domain in an unknown org." msgstr "Unable to update domain in an unknown org." -#: src/domain/mutations/update-domain.js:179 +#: src/domain/mutations/update-domain.js:166 msgid "Unable to update domain that does not belong to the given organization." msgstr "Unable to update domain that does not belong to the given organization." -#: src/domain/mutations/update-domain.js:168 -#: src/domain/mutations/update-domain.js:210 -#: src/domain/mutations/update-domain.js:267 +#: src/domain/mutations/update-domain.js:156 +#: src/domain/mutations/update-domain.js:196 +#: src/domain/mutations/update-domain.js:247 msgid "Unable to update domain. Please try again." msgstr "Unable to update domain. Please try again." @@ -1230,8 +1290,8 @@ msgstr "Unable to update password, passwords do not match requirements. Please t msgid "Unable to update password. Please try again." msgstr "Unable to update password. Please try again." -#: src/user/mutations/update-user-profile.js:172 -#: src/user/mutations/update-user-profile.js:181 +#: src/user/mutations/update-user-profile.js:160 +#: src/user/mutations/update-user-profile.js:167 msgid "Unable to update profile. Please try again." msgstr "Unable to update profile. Please try again." @@ -1239,7 +1299,7 @@ msgstr "Unable to update profile. Please try again." msgid "Unable to update role: organization unknown." msgstr "Unable to update role: organization unknown." -#: src/affiliation/mutations/update-user-role.js:135 +#: src/affiliation/mutations/update-user-role.js:137 msgid "Unable to update role: user does not belong to organization." msgstr "Unable to update role: user does not belong to organization." @@ -1247,7 +1307,7 @@ msgstr "Unable to update role: user does not belong to organization." msgid "Unable to update role: user unknown." msgstr "Unable to update role: user unknown." -#: src/domain/mutations/update-domain.js:117 +#: src/domain/mutations/update-domain.js:111 msgid "Unable to update unknown domain." msgstr "Unable to update unknown domain." @@ -1255,10 +1315,10 @@ msgstr "Unable to update unknown domain." msgid "Unable to update unknown organization." msgstr "Unable to update unknown organization." -#: src/affiliation/mutations/update-user-role.js:125 -#: src/affiliation/mutations/update-user-role.js:146 -#: src/affiliation/mutations/update-user-role.js:228 -#: src/affiliation/mutations/update-user-role.js:237 +#: src/affiliation/mutations/update-user-role.js:127 +#: src/affiliation/mutations/update-user-role.js:149 +#: src/affiliation/mutations/update-user-role.js:201 +#: src/affiliation/mutations/update-user-role.js:211 msgid "Unable to update user's role. Please try again." msgstr "Unable to update user's role. Please try again." @@ -1280,18 +1340,17 @@ msgstr "Unable to verify account. Please try again." msgid "Unable to verify if user is a super admin, please try again." msgstr "Unable to verify if user is a super admin, please try again." -#: src/user/mutations/update-user-profile.js:103 +#: src/user/mutations/update-user-profile.js:100 #: src/user/queries/is-user-admin.js:59 msgid "Unable to verify if user is an admin, please try again." msgstr "Unable to verify if user is an admin, please try again." -#: src/organization/mutations/verify-organization.js:109 -#: src/organization/mutations/verify-organization.js:131 -#: src/organization/mutations/verify-organization.js:142 +#: src/organization/mutations/verify-organization.js:105 +#: src/organization/mutations/verify-organization.js:112 msgid "Unable to verify organization. Please try again." msgstr "Unable to verify organization. Please try again." -#: src/organization/mutations/verify-organization.js:54 +#: src/organization/mutations/verify-organization.js:53 msgid "Unable to verify unknown organization." msgstr "Unable to verify unknown organization." @@ -1299,7 +1358,7 @@ msgstr "Unable to verify unknown organization." msgid "User could not be queried." msgstr "User could not be queried." -#: src/affiliation/mutations/update-user-role.js:270 +#: src/affiliation/mutations/update-user-role.js:244 msgid "User role was updated successfully." msgstr "User role was updated successfully." @@ -1340,12 +1399,12 @@ msgstr "You must provide a `first` or `last` value to properly paginate the `Dki msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:189 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:162 msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 -#: src/domain/loaders/load-domain-connections-by-user-id.js:161 +#: src/domain/loaders/load-domain-connections-by-user-id.js:147 msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `Domain` connection." @@ -1366,12 +1425,12 @@ msgstr "You must provide a `first` or `last` value to properly paginate the `Gui #~ msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." #~ msgstr "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:101 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:100 msgid "You must provide a `first` or `last` value to properly paginate the `Log` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `Log` connection." #: src/organization/loaders/load-organization-connections-by-domain-id.js:183 -#: src/organization/loaders/load-organization-connections-by-user-id.js:182 +#: src/organization/loaders/load-organization-connections-by-user-id.js:166 #: src/organization/loaders/load-web-check-connections-by-user-id.js:89 msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." msgstr "You must provide a `first` or `last` value to properly paginate the `Organization` connection." @@ -1418,11 +1477,11 @@ msgstr "You must provide a `limit` value to properly paginate the `DNS` connecti msgid "You must provide a `limit` value to properly paginate the `web` connection." msgstr "You must provide a `limit` value to properly paginate the `web` connection." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:31 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:12 msgid "You must provide a `period` value to access the `DmarcSummaries` connection." msgstr "You must provide a `period` value to access the `DmarcSummaries` connection." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:43 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:18 msgid "You must provide a `year` value to access the `DmarcSummaries` connection." msgstr "You must provide a `year` value to access the `DmarcSummaries` connection." @@ -1436,14 +1495,14 @@ msgstr "You must provide at most one pagination method (`before`, `after`, `offs #: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:109 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:148 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:131 #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:236 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:202 #: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 #: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 -#: src/domain/loaders/load-domain-connections-by-user-id.js:200 +#: src/domain/loaders/load-domain-connections-by-user-id.js:186 #: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 #: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 @@ -1451,7 +1510,7 @@ msgstr "You must provide at most one pagination method (`before`, `after`, `offs #: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 #: src/organization/loaders/load-organization-connections-by-domain-id.js:230 -#: src/organization/loaders/load-organization-connections-by-user-id.js:229 +#: src/organization/loaders/load-organization-connections-by-user-id.js:201 #: src/organization/loaders/load-web-check-connections-by-user-id.js:136 #: src/user/loaders/load-user-connections-by-user-id.js:154 #: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 @@ -1486,12 +1545,12 @@ msgstr "`{argSet}` on the `DkimFailureTable` connection cannot be less than zero msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." msgstr "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:210 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:179 msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." msgstr "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 -#: src/domain/loaders/load-domain-connections-by-user-id.js:177 +#: src/domain/loaders/load-domain-connections-by-user-id.js:163 msgid "`{argSet}` on the `Domain` connection cannot be less than zero." msgstr "`{argSet}` on the `Domain` connection cannot be less than zero." @@ -1512,12 +1571,12 @@ msgstr "`{argSet}` on the `GuidanceTag` connection cannot be less than zero." #~ msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." #~ msgstr "`{argSet}` on the `HTTPS` connection cannot be less than zero." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:122 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:112 msgid "`{argSet}` on the `Log` connection cannot be less than zero." msgstr "`{argSet}` on the `Log` connection cannot be less than zero." #: src/organization/loaders/load-organization-connections-by-domain-id.js:204 -#: src/organization/loaders/load-organization-connections-by-user-id.js:203 +#: src/organization/loaders/load-organization-connections-by-user-id.js:180 #: src/organization/loaders/load-web-check-connections-by-user-id.js:110 msgid "`{argSet}` on the `Organization` connection cannot be less than zero." msgstr "`{argSet}` on the `Organization` connection cannot be less than zero." diff --git a/api/src/locale/fr/messages.js b/api/src/locale/fr/messages.js index b59e68a72e..d77e0afa4a 100644 --- a/api/src/locale/fr/messages.js +++ b/api/src/locale/fr/messages.js @@ -1 +1 @@ -/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Erreur d'authentification. Veuillez vous connecter.","Cannot query affiliations on organization without admin permission or higher.":"Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.","Cannot query audit logs on organization without admin permission or higher.":"Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus.","Email already in use.":"Courriel déjà utilisé.","If an account with this username is found, a password reset link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.","If an account with this username is found, an email verification link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.","Incorrect TFA code. Please sign in again.":"Code TFA incorrect. Veuillez vous reconnecter.","Incorrect token value. Please request a new email.":"La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.","Incorrect username or password. Please try again.":"Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.","Invalid token, please sign in.":"Jeton invalide, veuillez vous connecter.","New passwords do not match.":"Les nouveaux mots de passe ne correspondent pas.","No organization with the provided slug could be found.":"Aucune organisation avec le slug fourni n'a pu être trouvée.","No verified domain with the provided domain could be found.":"Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.","No verified organization with the provided slug could be found.":"Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.","Organization has already been verified.":"L'organisation a déjà été vérifiée.","Organization name already in use, please choose another and try again.":"Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.","Organization name already in use. Please try again with a different name.":"Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.","Ownership check error. Unable to request domain information.":"Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.","Password does not meet requirements.":"Le mot de passe ne répond pas aux exigences.","Password was successfully reset.":"Le mot de passe a été réinitialisé avec succès.","Password was successfully updated.":"Le mot de passe a été mis à jour avec succès.","Passwords do not match.":"Les mots de passe ne correspondent pas.","Permission Denied: Could not retrieve specified organization.":"Permission refusée : Impossible de récupérer l'organisation spécifiée.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission refusée : L'authentification multifactorielle est requise pour les comptes admin.","Permission Denied: Please contact org owner to transfer ownership.":"Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.","Permission Denied: Please contact organization admin for help with removing users.":"Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.","Permission Denied: Please contact organization user for help with creating domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.","Permission Denied: Please contact organization user for help with creating domains.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact organization user for help with updating this domain.":"Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.","Permission check error. Unable to request domain information.":"Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.","Permission error, not an admin for this user.":"Erreur de permission, pas d'administrateur pour cet utilisateur.","Permission error: Unable to close other user's account.":"Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.","Permissions error. You do not have sufficient permissions to access this data.":"Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données.","Phone number has been successfully removed.":"Le numéro de téléphone a été supprimé avec succès.","Phone number has been successfully set, you will receive a verification text message shortly.":"Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.","Profile successfully updated.":"Le profil a été mis à jour avec succès.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Affiliation` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DmarcSummaries` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Domain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `FullPassTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `GuidanceTag` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Log` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Organization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `SpfFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `User` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedDomain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedOrganization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIMResults` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIM` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DMARC` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `HTTPS` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SPF` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SSL` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Successfully added {domainCount} domain(s) to {0}.":["Ajouté avec succès le(s) domaine(s) ",["domainCount"]," à ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Ajouté avec succès les domaines ",["domainCount"]," à ",["0"],"."],"Successfully closed account.":"Le compte a été fermé avec succès.","Successfully dispatched one time scan.":"Un seul balayage a été effectué avec succès.","Successfully email verified account, and set TFA send method to email.":"Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.","Successfully invited user to organization, and sent notification email.":"L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.","Successfully left organization: {0}":["L'organisation a été quittée avec succès: ",["0"]],"Successfully removed domain: {0} from favourites.":["A réussi à supprimer le domaine : ",["0"]," des favoris."],"Successfully removed domain: {0} from {1}.":["A réussi à supprimer le domaine : ",["0"]," de ",["1"],"."],"Successfully removed organization: {0}.":["A réussi à supprimer l'organisation : ",["0"],"."],"Successfully removed user from organization.":"L'utilisateur a été retiré de l'organisation avec succès.","Successfully removed {domainCount} domain(s) from {0}.":["Supprimé avec succès le(s) domaine(s) ",["domainCount"]," de ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Suppression réussie des domaines ",["domainCount"]," de ",["0"],"."],"Successfully sent invitation to service, and organization email.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully signed out.":"J'ai réussi à me déconnecter.","Successfully transferred org: {0} ownership to user: {1}":["A réussi à transférer la propriété de org: ",["0"]," à l'utilisateur: ",["1"]],"Successfully verified organization: {0}.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully verified phone number, and set TFA send method to text.":"Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.","Token value incorrect, please sign in again.":"La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.","Too many failed login attempts, please reset your password, and try again.":"Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.","Two factor code is incorrect. Please try again.":"Le code à deux facteurs est incorrect. Veuillez réessayer.","Two factor code length is incorrect. Please try again.":"La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.","Unable leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to add domains in unknown organization.":"Impossible d'ajouter des domaines dans une organisation inconnue.","Unable to authenticate. Please try again.":"Impossible de s'authentifier. Veuillez réessayer.","Unable to check permission. Please try again.":"Impossible de vérifier l'autorisation. Veuillez réessayer.","Unable to close account of an undefined user.":"Impossible de fermer le compte d'un utilisateur non défini.","Unable to close account. Please try again.":"Impossible de fermer le compte. Veuillez réessayer.","Unable to create domain in unknown organization.":"Impossible de créer un domaine dans une organisation inconnue.","Unable to create domain, organization has already claimed it.":"Impossible de créer le domaine, l'organisation l'a déjà réclamé.","Unable to create domain. Please try again.":"Impossible de créer un domaine. Veuillez réessayer.","Unable to create domains. Please try again.":"Impossible de créer des domaines. Veuillez réessayer.","Unable to create organization. Please try again.":"Impossible de créer une organisation. Veuillez réessayer.","Unable to dispatch one time scan. Please try again.":"Impossible d'envoyer un scan unique. Veuillez réessayer.","Unable to favourite domain, user has already favourited it.":"Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé.","Unable to favourite domain. Please try again.":"Impossible d'accéder au domaine favori. Veuillez réessayer.","Unable to favourite unknown domain.":"Impossible de favoriser le domaine inconnu.","Unable to find Aggregate guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to find DKIM guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to find DKIM result(s). Please try again.":"Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to find DKIM scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.","Unable to find DMARC guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to find DMARC scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.","Unable to find DMARC summary data. Please try again.":"Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.","Unable to find DNS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer.","Unable to find HTTPS guidance tag(s). Please try again.":"Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to find HTTPS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to find SPF guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to find SPF scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.","Unable to find SSL guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to find SSL scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.","Unable to find the requested domain.":"Impossible de trouver le domaine demandé.","Unable to find user affiliation(s). Please try again.":"Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.","Unable to find verified organization(s). Please try again.":"Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.","Unable to invite user to organization. Please try again.":"Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer.","Unable to invite user to organization. User is already affiliated with organization.":"Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation.","Unable to invite user to unknown organization.":"Impossible d'inviter un utilisateur à une organisation inconnue.","Unable to invite user. Please try again.":"Impossible d'inviter un utilisateur. Veuillez réessayer.","Unable to invite yourself to an org.":"Impossible de s'inviter à un org.","Unable to leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to leave undefined organization.":"Impossible de quitter une organisation non définie.","Unable to load Aggregate guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to load DKIM failure data. Please try again.":"Impossible de charger les données d'échec DKIM. Veuillez réessayer.","Unable to load DKIM guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to load DKIM result(s). Please try again.":"Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to load DKIM scan(s). Please try again.":"Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.","Unable to load DMARC failure data. Please try again.":"Impossible de charger les données d'échec DMARC. Veuillez réessayer.","Unable to load DMARC guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to load DMARC phase summary. Please try again.":"Impossible de charger le résumé DMARC. Veuillez réessayer.","Unable to load DMARC scan(s). Please try again.":"Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.","Unable to load DMARC summary data. Please try again.":"Impossible de charger les données de synthèse DMARC. Veuillez réessayer.","Unable to load DNS scan(s). Please try again.":"Impossible de charger le(s) scan(s) DNS. Veuillez réessayer.","Unable to load HTTPS guidance tag(s). Please try again.":"Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to load HTTPS scan(s). Please try again.":"Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to load HTTPS summary. Please try again.":"Impossible de charger le résumé HTTPS. Veuillez réessayer.","Unable to load SPF failure data. Please try again.":"Impossible de charger les données d'échec SPF. Veuillez réessayer.","Unable to load SPF guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to load SPF scan(s). Please try again.":"Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.","Unable to load SSL guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to load SSL scan(s). Please try again.":"Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.","Unable to load affiliation information. Please try again.":"Impossible de charger les informations d'affiliation. Veuillez réessayer.","Unable to load affiliation(s). Please try again.":"Impossible de charger l'affiliation (s). Veuillez réessayer.","Unable to load all organization domain statuses. Please try again.":"Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer.","Unable to load domain(s). Please try again.":"Impossible de charger le(s) domaine(s). Veuillez réessayer.","Unable to load domain. Please try again.":"Impossible de charger le domaine. Veuillez réessayer.","Unable to load full pass data. Please try again.":"Impossible de charger les données complètes de la passe. Veuillez réessayer.","Unable to load log(s). Please try again.":"Impossible de charger le(s) journal(s). Veuillez réessayer.","Unable to load log. Please try again.":"Impossible de charger le journal. Veuillez réessayer.","Unable to load mail summary. Please try again.":"Impossible de charger le résumé du courrier. Veuillez réessayer.","Unable to load organization domain statuses. Please try again.":"Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer.","Unable to load organization(s). Please try again.":"Impossible de charger l'organisation (s). Veuillez réessayer.","Unable to load owner information. Please try again.":"Impossible de charger les informations sur le propriétaire. Veuillez réessayer.","Unable to load summary. Please try again.":"Impossible de charger le résumé. Veuillez réessayer.","Unable to load tags(s). Please try again.":"Impossible de charger les balises. Veuillez réessayer.","Unable to load user(s). Please try again.":"Impossible de charger le(s) utilisateur(s). Veuillez réessayer.","Unable to load verified domain(s). Please try again.":"Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.","Unable to load verified organization(s). Please try again.":"Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.","Unable to load web scan(s). Please try again.":"Impossible de charger le(s) scan(s) web. Veuillez réessayer.","Unable to load web summary. Please try again.":"Impossible de charger le résumé web. Veuillez réessayer.","Unable to query affiliation(s). Please try again.":"Impossible de demander l'affiliation (s). Veuillez réessayer.","Unable to query domain(s). Please try again.":"Impossible d'interroger le(s) domaine(s). Veuillez réessayer.","Unable to query log(s). Please try again.":"Impossible d'interroger le(s) journal(s). Veuillez réessayer.","Unable to query user(s). Please try again.":"Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer.","Unable to refresh tokens, please sign in.":"Impossible de rafraîchir les jetons, veuillez vous connecter.","Unable to remove a user that already does not belong to this organization.":"Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.","Unable to remove domain from unknown organization.":"Impossible de supprimer le domaine d'une organisation inconnue.","Unable to remove domain. Domain is not part of organization.":"Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation.","Unable to remove domain. Please try again.":"Impossible de supprimer le domaine. Veuillez réessayer.","Unable to remove domains from unknown organization.":"Impossible de supprimer les domaines d'une organisation inconnue.","Unable to remove organization. Please try again.":"Impossible de supprimer l'organisation. Veuillez réessayer.","Unable to remove phone number. Please try again.":"Impossible de supprimer le numéro de téléphone. Veuillez réessayer.","Unable to remove unknown domain.":"Impossible de supprimer un domaine inconnu.","Unable to remove unknown organization.":"Impossible de supprimer une organisation inconnue.","Unable to remove unknown user from organization.":"Impossible de supprimer un utilisateur inconnu de l'organisation.","Unable to remove user from organization.":"Impossible de supprimer un utilisateur de l'organisation.","Unable to remove user from this organization. Please try again.":"Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.","Unable to remove user from unknown organization.":"Impossible de supprimer un utilisateur d'une organisation inconnue.","Unable to request a one time scan on a domain that already has a pending scan.":"Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours.","Unable to request a one time scan on an unknown domain.":"Impossible de demander un scan unique sur un domaine inconnu.","Unable to request a one time scan. Please try again.":"Impossible de demander une analyse unique. Veuillez réessayer.","Unable to reset password. Please request a new email.":"Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.","Unable to reset password. Please try again.":"Impossible de réinitialiser le mot de passe. Veuillez réessayer.","Unable to retrieve DMARC report information for: {domain}":["Impossible de récupérer les informations du rapport DMARC pour : ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.","Unable to send authentication email. Please try again.":"Impossible d'envoyer l'email d'authentification. Veuillez réessayer.","Unable to send authentication text message. Please try again.":"Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.","Unable to send org invite email. Please try again.":"Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.","Unable to send password reset email. Please try again.":"Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.","Unable to send two factor authentication message. Please try again.":"Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.","Unable to send verification email. Please try again.":"Impossible d'envoyer l'email de vérification. Veuillez réessayer.","Unable to set phone number, please try again.":"Impossible de définir le numéro de téléphone, veuillez réessayer.","Unable to sign in, please try again.":"Impossible de se connecter, veuillez réessayer.","Unable to sign up, please contact org admin for a new invite.":"Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.","Unable to sign up. Please try again.":"Impossible de s'inscrire. Veuillez réessayer.","Unable to transfer organization ownership. Please try again.":"Impossible de transférer la propriété de l'organisation. Veuillez réessayer.","Unable to transfer ownership of a verified organization.":"Impossible de transférer la propriété d'une organisation vérifiée.","Unable to transfer ownership of an org to an undefined user.":"Impossible de transférer la propriété d'un org à un utilisateur non défini.","Unable to transfer ownership of undefined organization.":"Impossible de transférer la propriété d'une organisation non définie.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.","Unable to two factor authenticate. Please try again.":"Impossible de s'authentifier par deux facteurs. Veuillez réessayer.","Unable to unfavourite domain, domain is not favourited.":"Impossible de désactiver le domaine, le domaine n'est pas favorisé.","Unable to unfavourite domain. Please try again.":"Impossible de défavoriser le domaine. Veuillez réessayer.","Unable to unfavourite unknown domain.":"Impossible de défavoriser un domaine inconnu.","Unable to update domain edge. Please try again.":"Impossible de mettre à jour le bord du domaine. Veuillez réessayer.","Unable to update domain in an unknown org.":"Impossible de mettre à jour le domaine dans un org inconnu.","Unable to update domain that does not belong to the given organization.":"Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.","Unable to update domain. Please try again.":"Impossible de mettre à jour le domaine. Veuillez réessayer.","Unable to update organization. Please try again.":"Impossible de mettre à jour l'organisation. Veuillez réessayer.","Unable to update password, current password does not match. Please try again.":"Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.","Unable to update password, new passwords do not match. Please try again.":"Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.","Unable to update password, passwords do not match requirements. Please try again.":"Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.","Unable to update password. Please try again.":"Impossible de mettre à jour le mot de passe. Veuillez réessayer.","Unable to update profile. Please try again.":"Impossible de mettre à jour le profil. Veuillez réessayer.","Unable to update role: organization unknown.":"Impossible de mettre à jour le rôle : organisation inconnue.","Unable to update role: user does not belong to organization.":"Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.","Unable to update role: user unknown.":"Impossible de mettre à jour le rôle : utilisateur inconnu.","Unable to update unknown domain.":"Impossible de mettre à jour un domaine inconnu.","Unable to update unknown organization.":"Impossible de mettre à jour une organisation inconnue.","Unable to update user's role. Please try again.":"Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.","Unable to update your own role.":"Impossible de mettre à jour votre propre rôle.","Unable to verify account. Please request a new email.":"Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.","Unable to verify account. Please try again.":"Impossible de vérifier le compte. Veuillez réessayer.","Unable to verify if user is a super admin, please try again.":"Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.","Unable to verify if user is an admin, please try again.":"Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.","Unable to verify organization. Please try again.":"Impossible de vérifier l'organisation. Veuillez réessayer.","Unable to verify unknown organization.":"Impossible de vérifier une organisation inconnue.","User could not be queried.":"L'utilisateur n'a pas pu être interrogé.","User role was updated successfully.":"Le rôle de l'utilisateur a été mis à jour avec succès.","Username not available, please try another.":"Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.","Verification error. Please activate multi-factor authentication to access content.":"Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.","Verification error. Please verify your account via email to access content.":"Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`.","You must provide a `limit` value to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`.","You must provide a `limit` value to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`.","You must provide a `period` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.","You must provide a `year` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` doit être de type `number` et non `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Affiliation` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIMResults` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIM` ne peut être inférieur à zéro."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DMARC` ne peut être inférieur à zéro."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Domain` ne peut être inférieur à zéro."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `FullPassTable` ne peut être inférieur à zéro."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `GuidanceTag` ne peut être inférieure à zéro."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `HTTPS` ne peut être inférieur à zéro."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Log` ne peut être inférieur à zéro."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Organization` ne peut être inférieure à zéro."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SPF` ne peut être inférieure à zéro."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SSL` ne peut être inférieur à zéro."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `User` ne peut être inférieure à zéro."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro."]}}; \ No newline at end of file +/*eslint-disable*/module.exports={messages:{"Authentication error. Please sign in.":"Erreur d'authentification. Veuillez vous connecter.","Cannot query affiliations on organization without admin permission or higher.":"Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus.","Cannot query audit logs on organization without admin permission or higher.":"Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus.","Cannot query dns scan results without permission.":"Impossible d'interroger les résultats de l'analyse DNS sans autorisation.","Cannot query domain selectors without permission.":"Impossible d'interroger les sélecteurs de domaine sans autorisation.","Cannot query web scan results without permission.":"Impossible d'interroger les résultats de l'analyse web sans autorisation.","Email already in use.":"Courriel déjà utilisé.","Error while requesting scan. Please try again.":"Erreur lors de la demande d'analyse. Veuillez réessayer.","If an account with this username is found, a password reset link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception.","If an account with this username is found, an email verification link will be found in your inbox.":"Si un compte avec ce nom d'utilisateur est trouvé, un lien de vérification par e-mail sera trouvé dans votre boîte de réception.","Incorrect TFA code. Please sign in again.":"Code TFA incorrect. Veuillez vous reconnecter.","Incorrect token value. Please request a new email.":"La valeur du jeton est incorrecte. Veuillez demander un nouvel e-mail.","Incorrect username or password. Please try again.":"Le nom d'utilisateur ou le mot de passe est incorrect. Veuillez réessayer.","Invalid token, please sign in.":"Jeton invalide, veuillez vous connecter.","New passwords do not match.":"Les nouveaux mots de passe ne correspondent pas.","No organization with the provided slug could be found.":"Aucune organisation avec le slug fourni n'a pu être trouvée.","No verified domain with the provided domain could be found.":"Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé.","No verified organization with the provided slug could be found.":"Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée.","Organization has already been verified.":"L'organisation a déjà été vérifiée.","Organization name already in use, please choose another and try again.":"Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer.","Organization name already in use. Please try again with a different name.":"Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent.","Ownership check error. Unable to request domain information.":"Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine.","Passing both `first` and `last` to paginate the `Affiliation` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Affiliation` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIMResults` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DKIM` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DKIMResults` n'est pas supporté.","Passing both `first` and `last` to paginate the `DMARC` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DMARC` n'est pas supporté.","Passing both `first` and `last` to paginate the `DkimFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DkimFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté.","Passing both `first` and `last` to paginate the `Domain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté.","Passing both `first` and `last` to paginate the `FullPassTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `FullPassTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `GuidanceTag` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `GuidanceTag` n'est pas supporté.","Passing both `first` and `last` to paginate the `HTTPS` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté.","Passing both `first` and `last` to paginate the `Log` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté.","Passing both `first` and `last` to paginate the `Organization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté.","Passing both `first` and `last` to paginate the `SPF` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SPF` n'est pas supporté.","Passing both `first` and `last` to paginate the `SSL` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SSL` n'est pas supporté.","Passing both `first` and `last` to paginate the `SpfFailureTable` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `SpfFailureTable` n'est pas supporté.","Passing both `first` and `last` to paginate the `User` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `User` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedDomain` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedDomain` n'est pas supporté.","Passing both `first` and `last` to paginate the `VerifiedOrganization` connection is not supported.":"Passer à la fois `first` et `last` pour paginer la connexion `VerifiedOrganization` n'est pas supporté.","Password does not meet requirements.":"Le mot de passe ne répond pas aux exigences.","Password was successfully reset.":"Le mot de passe a été réinitialisé avec succès.","Password was successfully updated.":"Le mot de passe a été mis à jour avec succès.","Passwords do not match.":"Les mots de passe ne correspondent pas.","Permission Denied: Could not retrieve specified organization.":"Permission refusée : Impossible de récupérer l'organisation spécifiée.","Permission Denied: Multi-factor authentication is required for admin accounts":"Permission refusée : L'authentification multifactorielle est requise pour les comptes admin.","Permission Denied: Please contact org owner to transfer ownership.":"Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété.","Permission Denied: Please contact organization admin for help with archiving domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines.","Permission Denied: Please contact organization admin for help with removing domain.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine.","Permission Denied: Please contact organization admin for help with removing domains.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des domaines.","Permission Denied: Please contact organization admin for help with removing organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation.","Permission Denied: Please contact organization admin for help with removing users.":"Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating organization.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs.","Permission Denied: Please contact organization admin for help with updating user roles.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs.","Permission Denied: Please contact organization admin for help with user invitations.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs.","Permission Denied: Please contact organization admin for help with user role changes.":"Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs.","Permission Denied: Please contact organization user for help with creating domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création du domaine.","Permission Denied: Please contact organization user for help with creating domains.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines.","Permission Denied: Please contact organization user for help with retrieving this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine.","Permission Denied: Please contact organization user for help with scanning this domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact organization user for help with updating this domain.":"Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with archiving organization.":"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'organisation de l'archivage.","Permission Denied: Please contact super admin for help with removing domain.":"Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine.","Permission Denied: Please contact super admin for help with removing organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à supprimer l'organisation.","Permission Denied: Please contact super admin for help with scanning this domain.":"Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine.","Permission Denied: Please contact super admin for help with verifying this organization.":"Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation.","Permission check error. Unable to request domain information.":"Erreur de vérification des permissions. Impossible de demander des informations sur le domaine.","Permission error, not an admin for this user.":"Erreur de permission, pas d'administrateur pour cet utilisateur.","Permission error: Unable to close other user's account.":"Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.","Permissions error. You do not have sufficient permissions to access this data.":"Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données.","Phone number has been successfully removed.":"Le numéro de téléphone a été supprimé avec succès.","Phone number has been successfully set, you will receive a verification text message shortly.":"Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification.","Profile successfully updated.":"Le profil a été mis à jour avec succès.","Requesting `{amount}` records on the `Affiliation` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Affiliation` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DkimFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DkimFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `DmarcSummaries` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Domain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `FullPassTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `FullPassTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `GuidanceTag` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Log` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `Organization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `SpfFailureTable` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `SpfFailureTable` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `User` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `User` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedDomain` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedDomain` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting `{amount}` records on the `VerifiedOrganization` connection exceeds the `{argSet}` limit of 100 records.":["La demande d'enregistrements `",["amount"],"` sur la connexion `VerifiedOrganization` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIMResults` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIMResults` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DKIM` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DKIM` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `DMARC` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `DMARC` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `HTTPS` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `HTTPS` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SPF` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SPF` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Requesting {amount} records on the `SSL` connection exceeds the `{argSet}` limit of 100 records.":["La demande de ",["amount"]," enregistrements sur la connexion `SSL` dépasse la limite `",["argSet"],"` de 100 enregistrements."],"Successfully added {domainCount} domain(s) to {0}.":["Ajouté avec succès le(s) domaine(s) ",["domainCount"]," à ",["0"],"."],"Successfully added {domainCount} domains to {0}.":["Ajouté avec succès les domaines ",["domainCount"]," à ",["0"],"."],"Successfully archived organization: {0}.":["Organisation archivée avec succès : ",["0"],"."],"Successfully closed account.":"Le compte a été fermé avec succès.","Successfully dispatched one time scan.":"Un seul balayage a été effectué avec succès.","Successfully email verified account, and set TFA send method to email.":"Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email.","Successfully invited user to organization, and sent notification email.":"L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé.","Successfully left organization: {0}":["L'organisation a été quittée avec succès: ",["0"]],"Successfully removed domain: {0} from favourites.":["A réussi à supprimer le domaine : ",["0"]," des favoris."],"Successfully removed domain: {0} from {1}.":["A réussi à supprimer le domaine : ",["0"]," de ",["1"],"."],"Successfully removed organization: {0}.":["A réussi à supprimer l'organisation : ",["0"],"."],"Successfully removed user from organization.":"L'utilisateur a été retiré de l'organisation avec succès.","Successfully removed {domainCount} domain(s) from {0}.":["Supprimé avec succès le(s) domaine(s) ",["domainCount"]," de ",["0"],"."],"Successfully removed {domainCount} domains from {0}.":["Suppression réussie des domaines ",["domainCount"]," de ",["0"],"."],"Successfully requested invite to organization, and sent notification email.":"La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé.","Successfully sent invitation to service, and organization email.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully signed out.":"J'ai réussi à me déconnecter.","Successfully transferred org: {0} ownership to user: {1}":["A réussi à transférer la propriété de org: ",["0"]," à l'utilisateur: ",["1"]],"Successfully verified organization: {0}.":"Envoi réussi de l'invitation au service, et de l'email de l'organisation.","Successfully verified phone number, and set TFA send method to text.":"Le numéro de téléphone a été vérifié avec succès, et la méthode d'envoi de la TFA a été réglée sur le texte.","Token value incorrect, please sign in again.":"La valeur du jeton est incorrecte, veuillez vous connecter à nouveau.","Too many failed login attempts, please reset your password, and try again.":"Trop de tentatives de connexion ont échoué, veuillez réinitialiser votre mot de passe et réessayer.","Two factor code is incorrect. Please try again.":"Le code à deux facteurs est incorrect. Veuillez réessayer.","Two factor code length is incorrect. Please try again.":"La longueur du code à deux facteurs est incorrecte. Veuillez réessayer.","Unable leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to add domains in unknown organization.":"Impossible d'ajouter des domaines dans une organisation inconnue.","Unable to archive organization. Please try again.":"Impossible d'archiver l'organisation. Veuillez réessayer.","Unable to archive unknown organization.":"Impossible d'archiver une organisation inconnue.","Unable to authenticate. Please try again.":"Impossible de s'authentifier. Veuillez réessayer.","Unable to check permission. Please try again.":"Impossible de vérifier l'autorisation. Veuillez réessayer.","Unable to close account of an undefined user.":"Impossible de fermer le compte d'un utilisateur non défini.","Unable to close account. Please try again.":"Impossible de fermer le compte. Veuillez réessayer.","Unable to create domain in unknown organization.":"Impossible de créer un domaine dans une organisation inconnue.","Unable to create domain, organization has already claimed it.":"Impossible de créer le domaine, l'organisation l'a déjà réclamé.","Unable to create domain. Please try again.":"Impossible de créer un domaine. Veuillez réessayer.","Unable to create domains. Please try again.":"Impossible de créer des domaines. Veuillez réessayer.","Unable to create organization. Please try again.":"Impossible de créer une organisation. Veuillez réessayer.","Unable to dispatch one time scan. Please try again.":"Impossible d'envoyer un scan unique. Veuillez réessayer.","Unable to export organization. Please try again.":"Impossible d'exporter l'organisation. Veuillez réessayer.","Unable to favourite domain, user has already favourited it.":"Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé.","Unable to favourite domain. Please try again.":"Impossible d'accéder au domaine favori. Veuillez réessayer.","Unable to favourite unknown domain.":"Impossible de favoriser le domaine inconnu.","Unable to find Aggregate guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to find DKIM guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to find DKIM result(s). Please try again.":"Impossible de trouver le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to find DKIM scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DKIM. Veuillez réessayer.","Unable to find DMARC guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to find DMARC scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DMARC. Veuillez réessayer.","Unable to find DMARC summary data. Please try again.":"Impossible de trouver les données de synthèse DMARC. Veuillez réessayer.","Unable to find DNS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) DNS. Veuillez réessayer.","Unable to find HTTPS guidance tag(s). Please try again.":"Impossible de trouver la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to find HTTPS scan(s). Please try again.":"Impossible de trouver le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to find SPF guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to find SPF scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SPF. Veuillez réessayer.","Unable to find SSL guidance tag(s). Please try again.":"Impossible de trouver le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to find SSL scan(s). Please try again.":"Impossible de trouver le(s) scan(s) SSL. Veuillez réessayer.","Unable to find the requested domain.":"Impossible de trouver le domaine demandé.","Unable to find user affiliation(s). Please try again.":"Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez réessayer.","Unable to find verified organization(s). Please try again.":"Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer.","Unable to invite user to organization. Please try again.":"Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer.","Unable to invite user to organization. User is already affiliated with organization.":"Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation.","Unable to invite user to unknown organization.":"Impossible d'inviter un utilisateur à une organisation inconnue.","Unable to invite user. Please try again.":"Impossible d'inviter un utilisateur. Veuillez réessayer.","Unable to invite yourself to an org.":"Impossible de s'inviter à un org.","Unable to leave organization. Please try again.":"Impossible de quitter l'organisation. Veuillez réessayer.","Unable to leave undefined organization.":"Impossible de quitter une organisation non définie.","Unable to load Aggregate guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation des agrégats. Veuillez réessayer.","Unable to load DKIM failure data. Please try again.":"Impossible de charger les données d'échec DKIM. Veuillez réessayer.","Unable to load DKIM guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessayer.","Unable to load DKIM result(s). Please try again.":"Impossible de charger le(s) résultat(s) DKIM. Veuillez réessayer.","Unable to load DKIM scan(s). Please try again.":"Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer.","Unable to load DKIM summary. Please try again.":"Impossible de charger le résumé DKIM. Veuillez réessayer.","Unable to load DMARC failure data. Please try again.":"Impossible de charger les données d'échec DMARC. Veuillez réessayer.","Unable to load DMARC guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation DMARC. Veuillez réessayer.","Unable to load DMARC phase summary. Please try again.":"Impossible de charger le résumé DMARC. Veuillez réessayer.","Unable to load DMARC scan(s). Please try again.":"Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer.","Unable to load DMARC summary data. Please try again.":"Impossible de charger les données de synthèse DMARC. Veuillez réessayer.","Unable to load DMARC summary. Please try again.":"Impossible de charger le résumé DMARC. Veuillez réessayer.","Unable to load DNS scan(s). Please try again.":"Impossible de charger le(s) scan(s) DNS. Veuillez réessayer.","Unable to load HTTPS guidance tag(s). Please try again.":"Impossible de charger la ou les balises d'orientation HTTPS. Veuillez réessayer.","Unable to load HTTPS scan(s). Please try again.":"Impossible de charger le(s) scan(s) HTTPS. Veuillez réessayer.","Unable to load HTTPS summary. Please try again.":"Impossible de charger le résumé HTTPS. Veuillez réessayer.","Unable to load SPF failure data. Please try again.":"Impossible de charger les données d'échec SPF. Veuillez réessayer.","Unable to load SPF guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessayer.","Unable to load SPF scan(s). Please try again.":"Impossible de charger le(s) scan(s) SPF. Veuillez réessayer.","Unable to load SPF summary. Please try again.":"Impossible de charger le résumé SPF. Veuillez réessayer.","Unable to load SSL guidance tag(s). Please try again.":"Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessayer.","Unable to load SSL scan(s). Please try again.":"Impossible de charger le(s) scan(s) SSL. Veuillez réessayer.","Unable to load SSL summary. Please try again.":"Impossible de charger le résumé SSL. Veuillez réessayer.","Unable to load affiliation information. Please try again.":"Impossible de charger les informations d'affiliation. Veuillez réessayer.","Unable to load affiliation(s). Please try again.":"Impossible de charger l'affiliation (s). Veuillez réessayer.","Unable to load all organization domain statuses. Please try again.":"Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer.","Unable to load domain(s). Please try again.":"Impossible de charger le(s) domaine(s). Veuillez réessayer.","Unable to load domain. Please try again.":"Impossible de charger le domaine. Veuillez réessayer.","Unable to load full pass data. Please try again.":"Impossible de charger les données complètes de la passe. Veuillez réessayer.","Unable to load log(s). Please try again.":"Impossible de charger le(s) journal(s). Veuillez réessayer.","Unable to load log. Please try again.":"Impossible de charger le journal. Veuillez réessayer.","Unable to load mail summary. Please try again.":"Impossible de charger le résumé du courrier. Veuillez réessayer.","Unable to load organization domain statuses. Please try again.":"Impossible de charger les statuts des domaines d'organisation. Veuillez réessayer.","Unable to load organization(s). Please try again.":"Impossible de charger l'organisation (s). Veuillez réessayer.","Unable to load owner information. Please try again.":"Impossible de charger les informations sur le propriétaire. Veuillez réessayer.","Unable to load summary. Please try again.":"Impossible de charger le résumé. Veuillez réessayer.","Unable to load tags(s). Please try again.":"Impossible de charger les balises. Veuillez réessayer.","Unable to load user(s). Please try again.":"Impossible de charger le(s) utilisateur(s). Veuillez réessayer.","Unable to load verified domain(s). Please try again.":"Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer.","Unable to load verified organization(s). Please try again.":"Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer.","Unable to load web connections summary. Please try again.":"Impossible de charger le résumé des connexions web. Veuillez réessayer.","Unable to load web scan(s). Please try again.":"Impossible de charger le(s) scan(s) web. Veuillez réessayer.","Unable to load web summary. Please try again.":"Impossible de charger le résumé web. Veuillez réessayer.","Unable to query affiliation(s). Please try again.":"Impossible de demander l'affiliation (s). Veuillez réessayer.","Unable to query domain(s). Please try again.":"Impossible d'interroger le(s) domaine(s). Veuillez réessayer.","Unable to query log(s). Please try again.":"Impossible d'interroger le(s) journal(s). Veuillez réessayer.","Unable to query user(s). Please try again.":"Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer.","Unable to refresh tokens, please sign in.":"Impossible de rafraîchir les jetons, veuillez vous connecter.","Unable to remove a user that already does not belong to this organization.":"Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation.","Unable to remove domain from unknown organization.":"Impossible de supprimer le domaine d'une organisation inconnue.","Unable to remove domain. Domain is not part of organization.":"Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation.","Unable to remove domain. Please try again.":"Impossible de supprimer le domaine. Veuillez réessayer.","Unable to remove domains from unknown organization.":"Impossible de supprimer les domaines d'une organisation inconnue.","Unable to remove organization. Please try again.":"Impossible de supprimer l'organisation. Veuillez réessayer.","Unable to remove phone number. Please try again.":"Impossible de supprimer le numéro de téléphone. Veuillez réessayer.","Unable to remove unknown domain.":"Impossible de supprimer un domaine inconnu.","Unable to remove unknown organization.":"Impossible de supprimer une organisation inconnue.","Unable to remove unknown user from organization.":"Impossible de supprimer un utilisateur inconnu de l'organisation.","Unable to remove user from organization.":"Impossible de supprimer un utilisateur de l'organisation.","Unable to remove user from this organization. Please try again.":"Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer.","Unable to remove user from unknown organization.":"Impossible de supprimer un utilisateur d'une organisation inconnue.","Unable to request a one time scan on a domain that already has a pending scan.":"Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours.","Unable to request a one time scan on an unknown domain.":"Impossible de demander un scan unique sur un domaine inconnu.","Unable to request a one time scan. Please try again.":"Impossible de demander une analyse unique. Veuillez réessayer.","Unable to request invite to organization with which you are already affiliated.":"Impossible de demander une invitation à une organisation à laquelle vous êtes déjà affilié.","Unable to request invite to organization with which you have already requested to join.":"Impossible de demander une invitation à une organisation à laquelle vous avez déjà demandé à adhérer.","Unable to request invite to unknown organization.":"Impossible de demander une invitation à une organisation inconnue.","Unable to request invite. Please try again.":"Impossible de demander une invitation. Veuillez réessayer.","Unable to reset password. Please request a new email.":"Impossible de réinitialiser le mot de passe. Veuillez demander un nouvel e-mail.","Unable to reset password. Please try again.":"Impossible de réinitialiser le mot de passe. Veuillez réessayer.","Unable to retrieve DMARC report information for: {domain}":["Impossible de récupérer les informations du rapport DMARC pour : ",["domain"]],"Unable to select DMARC report(s) for this period and year.":"Impossible de sélectionner le(s) rapport(s) DMARC pour cette période et cette année.","Unable to send authentication email. Please try again.":"Impossible d'envoyer l'email d'authentification. Veuillez réessayer.","Unable to send authentication text message. Please try again.":"Impossible d'envoyer un message texte d'authentification. Veuillez réessayer.","Unable to send org invite email. Please try again.":"Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer.","Unable to send org invite request email. Please try again.":"Impossible d'envoyer l'email de demande d'invitation à l'org. Veuillez réessayer.","Unable to send password reset email. Please try again.":"Impossible d'envoyer l'email de réinitialisation du mot de passe. Veuillez réessayer.","Unable to send two factor authentication message. Please try again.":"Impossible d'envoyer le message d'authentification à deux facteurs. Veuillez réessayer.","Unable to send verification email. Please try again.":"Impossible d'envoyer l'email de vérification. Veuillez réessayer.","Unable to set phone number, please try again.":"Impossible de définir le numéro de téléphone, veuillez réessayer.","Unable to sign in, please try again.":"Impossible de se connecter, veuillez réessayer.","Unable to sign up, please contact org admin for a new invite.":"Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation.","Unable to sign up. Please try again.":"Impossible de s'inscrire. Veuillez réessayer.","Unable to transfer organization ownership. Please try again.":"Impossible de transférer la propriété de l'organisation. Veuillez réessayer.","Unable to transfer ownership of a verified organization.":"Impossible de transférer la propriété d'une organisation vérifiée.","Unable to transfer ownership of an org to an undefined user.":"Impossible de transférer la propriété d'un org à un utilisateur non défini.","Unable to transfer ownership of undefined organization.":"Impossible de transférer la propriété d'une organisation non définie.","Unable to transfer ownership to a user outside the org. Please invite the user and try again.":"Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer.","Unable to two factor authenticate. Please try again.":"Impossible de s'authentifier par deux facteurs. Veuillez réessayer.","Unable to unfavourite domain, domain is not favourited.":"Impossible de désactiver le domaine, le domaine n'est pas favorisé.","Unable to unfavourite domain. Please try again.":"Impossible de défavoriser le domaine. Veuillez réessayer.","Unable to unfavourite unknown domain.":"Impossible de défavoriser un domaine inconnu.","Unable to update domain edge. Please try again.":"Impossible de mettre à jour le bord du domaine. Veuillez réessayer.","Unable to update domain in an unknown org.":"Impossible de mettre à jour le domaine dans un org inconnu.","Unable to update domain that does not belong to the given organization.":"Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée.","Unable to update domain. Please try again.":"Impossible de mettre à jour le domaine. Veuillez réessayer.","Unable to update organization. Please try again.":"Impossible de mettre à jour l'organisation. Veuillez réessayer.","Unable to update password, current password does not match. Please try again.":"Impossible de mettre à jour le mot de passe, le mot de passe actuel ne correspond pas. Veuillez réessayer.","Unable to update password, new passwords do not match. Please try again.":"Impossible de mettre à jour le mot de passe, les nouveaux mots de passe ne correspondent pas. Veuillez réessayer.","Unable to update password, passwords do not match requirements. Please try again.":"Impossible de mettre à jour le mot de passe, les mots de passe ne correspondent pas aux exigences. Veuillez réessayer.","Unable to update password. Please try again.":"Impossible de mettre à jour le mot de passe. Veuillez réessayer.","Unable to update profile. Please try again.":"Impossible de mettre à jour le profil. Veuillez réessayer.","Unable to update role: organization unknown.":"Impossible de mettre à jour le rôle : organisation inconnue.","Unable to update role: user does not belong to organization.":"Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation.","Unable to update role: user unknown.":"Impossible de mettre à jour le rôle : utilisateur inconnu.","Unable to update unknown domain.":"Impossible de mettre à jour un domaine inconnu.","Unable to update unknown organization.":"Impossible de mettre à jour une organisation inconnue.","Unable to update user's role. Please try again.":"Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer.","Unable to update your own role.":"Impossible de mettre à jour votre propre rôle.","Unable to verify account. Please request a new email.":"Impossible de vérifier le compte. Veuillez demander un nouvel e-mail.","Unable to verify account. Please try again.":"Impossible de vérifier le compte. Veuillez réessayer.","Unable to verify if user is a super admin, please try again.":"Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer.","Unable to verify if user is an admin, please try again.":"Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer.","Unable to verify organization. Please try again.":"Impossible de vérifier l'organisation. Veuillez réessayer.","Unable to verify unknown organization.":"Impossible de vérifier une organisation inconnue.","User could not be queried.":"L'utilisateur n'a pas pu être interrogé.","User role was updated successfully.":"Le rôle de l'utilisateur a été mis à jour avec succès.","Username not available, please try another.":"Le nom d'utilisateur n'est pas disponible, veuillez en essayer un autre.","Verification error. Please activate multi-factor authentication to access content.":"Erreur de vérification. Veuillez activer l'authentification multifactorielle pour accéder au contenu.","Verification error. Please verify your account via email to access content.":"Erreur de vérification. Veuillez vérifier votre compte par e-mail pour accéder au contenu.","You must provide a `first` or `last` value to properly paginate the `Affiliation` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Affiliation`.","You must provide a `first` or `last` value to properly paginate the `DKIMResults` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIMResults`.","You must provide a `first` or `last` value to properly paginate the `DKIM` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DKIM`.","You must provide a `first` or `last` value to properly paginate the `DMARC` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DMARC`.","You must provide a `first` or `last` value to properly paginate the `DkimFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DkimFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`.","You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`.","You must provide a `first` or `last` value to properly paginate the `Domain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`.","You must provide a `first` or `last` value to properly paginate the `FullPassTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `FullPassTable`.","You must provide a `first` or `last` value to properly paginate the `GuidanceTag` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `GuidanceTag`.","You must provide a `first` or `last` value to properly paginate the `HTTPS` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`.","You must provide a `first` or `last` value to properly paginate the `Log` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`.","You must provide a `first` or `last` value to properly paginate the `Organization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`.","You must provide a `first` or `last` value to properly paginate the `SPF` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SPF`.","You must provide a `first` or `last` value to properly paginate the `SSL` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SSL`.","You must provide a `first` or `last` value to properly paginate the `SpfFailureTable` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `SpfFailureTable`.","You must provide a `first` or `last` value to properly paginate the `User` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `User`.","You must provide a `first` or `last` value to properly paginate the `VerifiedDomain` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedDomain`.","You must provide a `first` or `last` value to properly paginate the `VerifiedOrganization` connection.":"Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `VerifiedOrganization`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `DNS`.","You must provide a `limit` value in the range of 1-100 to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` comprise entre 1 et 100 pour paginer correctement la connexion `web`.","You must provide a `limit` value to properly paginate the `DNS` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `DNS`.","You must provide a `limit` value to properly paginate the `web` connection.":"Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`.","You must provide a `period` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`.","You must provide a `year` value to access the `DmarcSummaries` connection.":"Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `DNS` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `DNS`.","You must provide at most one pagination method (`before`, `after`, `offset`) value to properly paginate the `web` connection.":"Vous devez fournir au plus une valeur de méthode de pagination (`before`, `after`, `offset`) pour paginer correctement la connexion `web`.","`{argSet}` must be of type `number` not `{typeSet}`.":["`",["argSet"],"` doit être de type `number` et non `",["typeSet"],"`."],"`{argSet}` on the `Affiliation` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Affiliation` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIMResults` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIMResults` ne peut être inférieur à zéro."],"`{argSet}` on the `DKIM` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DKIM` ne peut être inférieur à zéro."],"`{argSet}` on the `DMARC` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DMARC` ne peut être inférieur à zéro."],"`{argSet}` on the `DkimFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DkimFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `DmarcSummaries` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro."],"`{argSet}` on the `Domain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Domain` ne peut être inférieur à zéro."],"`{argSet}` on the `FullPassTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `FullPassTable` ne peut être inférieur à zéro."],"`{argSet}` on the `GuidanceTag` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `GuidanceTag` ne peut être inférieure à zéro."],"`{argSet}` on the `HTTPS` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `HTTPS` ne peut être inférieur à zéro."],"`{argSet}` on the `Log` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Log` ne peut être inférieur à zéro."],"`{argSet}` on the `Organization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `Organization` ne peut être inférieure à zéro."],"`{argSet}` on the `SPF` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SPF` ne peut être inférieure à zéro."],"`{argSet}` on the `SSL` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SSL` ne peut être inférieur à zéro."],"`{argSet}` on the `SpfFailureTable` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `SpfFailureTable` ne peut être inférieur à zéro."],"`{argSet}` on the `User` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `User` ne peut être inférieure à zéro."],"`{argSet}` on the `VerifiedDomain` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedDomain` ne peut être inférieur à zéro."],"`{argSet}` on the `VerifiedOrganization` connection cannot be less than zero.":["`",["argSet"],"` sur la connexion `VerifiedOrganization` ne peut être inférieur à zéro."]}}; \ No newline at end of file diff --git a/api/src/locale/fr/messages.po b/api/src/locale/fr/messages.po index cf5b442e4e..d8ea62b327 100644 --- a/api/src/locale/fr/messages.po +++ b/api/src/locale/fr/messages.po @@ -12,26 +12,43 @@ msgstr "" "Plural-Forms: \n" #: src/auth/check-permission.js:18 -#: src/auth/check-permission.js:42 +#: src/auth/check-permission.js:57 #: src/auth/user-required.js:10 #: src/auth/user-required.js:21 #: src/auth/user-required.js:28 msgid "Authentication error. Please sign in." msgstr "Erreur d'authentification. Veuillez vous connecter." -#: src/organization/objects/organization.js:161 +#: src/organization/objects/organization.js:241 msgid "Cannot query affiliations on organization without admin permission or higher." msgstr "Impossible d'interroger les affiliations sur l'organisation sans l'autorisation de l'administrateur ou plus." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:244 -#: src/audit-logs/queries/find-audit-logs.js:62 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:224 +#: src/audit-logs/queries/find-audit-logs.js:51 msgid "Cannot query audit logs on organization without admin permission or higher." msgstr "Impossible d'interroger les journaux d'audit sur l'organisation sans l'autorisation d'administrateur ou plus." +#: src/domain/objects/domain.js:149 +msgid "Cannot query dns scan results without permission." +msgstr "Impossible d'interroger les résultats de l'analyse DNS sans autorisation." + +#: src/domain/objects/domain.js:58 +msgid "Cannot query domain selectors without permission." +msgstr "Impossible d'interroger les sélecteurs de domaine sans autorisation." + +#: src/domain/objects/domain.js:195 +msgid "Cannot query web scan results without permission." +msgstr "Impossible d'interroger les résultats de l'analyse web sans autorisation." + #: src/user/mutations/sign-up.js:106 msgid "Email already in use." msgstr "Courriel déjà utilisé." +#: src/domain/mutations/request-scan.js:87 +#: src/domain/mutations/request-scan.js:97 +msgid "Error while requesting scan. Please try again." +msgstr "Erreur lors de la demande d'analyse. Veuillez réessayer." + #: src/user/mutations/send-password-reset.js:62 msgid "If an account with this username is found, a password reset link will be found in your inbox." msgstr "Si un compte avec ce nom d'utilisateur est trouvé, un lien de réinitialisation du mot de passe se trouvera dans votre boîte de réception." @@ -61,7 +78,7 @@ msgstr "Jeton invalide, veuillez vous connecter." msgid "New passwords do not match." msgstr "Les nouveaux mots de passe ne correspondent pas." -#: src/organization/queries/find-organization-by-slug.js:49 +#: src/organization/queries/find-organization-by-slug.js:41 #: src/user/queries/find-my-tracker.js:29 msgid "No organization with the provided slug could be found." msgstr "Aucune organisation avec le slug fourni n'a pu être trouvée." @@ -74,7 +91,7 @@ msgstr "Aucun domaine vérifié avec le domaine fourni n'a pu être trouvé." msgid "No verified organization with the provided slug could be found." msgstr "Aucune organisation vérifiée avec le slug fourni n'a pu être trouvée." -#: src/organization/mutations/verify-organization.js:82 +#: src/organization/mutations/verify-organization.js:81 msgid "Organization has already been verified." msgstr "L'organisation a déjà été vérifiée." @@ -82,14 +99,14 @@ msgstr "L'organisation a déjà été vérifiée." msgid "Organization name already in use, please choose another and try again." msgstr "Le nom de l'organisation est déjà utilisé, veuillez en choisir un autre et réessayer." -#: src/organization/mutations/create-organization.js:139 +#: src/organization/mutations/create-organization.js:124 msgid "Organization name already in use. Please try again with a different name." msgstr "Le nom de l'organisation est déjà utilisé. Veuillez réessayer avec un nom différent." -#: src/auth/check-domain-ownership.js:33 -#: src/auth/check-domain-ownership.js:45 -#: src/auth/check-domain-ownership.js:67 -#: src/auth/check-domain-ownership.js:78 +#: src/auth/check-domain-ownership.js:29 +#: src/auth/check-domain-ownership.js:39 +#: src/auth/check-domain-ownership.js:65 +#: src/auth/check-domain-ownership.js:74 msgid "Ownership check error. Unable to request domain information." msgstr "Erreur de vérification de la propriété. Impossible de demander des informations sur le domaine." @@ -119,12 +136,12 @@ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DkimFailu msgid "Passing both `first` and `last` to paginate the `DmarcFailureTable` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcFailureTable` n'est pas supporté." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:198 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:170 msgid "Passing both `first` and `last` to paginate the `DmarcSummaries` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `DmarcSummaries` n'est pas supporté." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:148 -#: src/domain/loaders/load-domain-connections-by-user-id.js:168 +#: src/domain/loaders/load-domain-connections-by-user-id.js:154 msgid "Passing both `first` and `last` to paginate the `Domain` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Domain` n'est pas supporté." @@ -145,12 +162,12 @@ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `GuidanceT #~ msgid "Passing both `first` and `last` to paginate the `HTTPS` connection is not supported." #~ msgstr "Passer à la fois `first` et `last` pour paginer la connexion `HTTPS` n'est pas supporté." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:110 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:105 msgid "Passing both `first` and `last` to paginate the `Log` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Log` n'est pas supporté." #: src/organization/loaders/load-organization-connections-by-domain-id.js:192 -#: src/organization/loaders/load-organization-connections-by-user-id.js:191 +#: src/organization/loaders/load-organization-connections-by-user-id.js:173 #: src/organization/loaders/load-web-check-connections-by-user-id.js:98 msgid "Passing both `first` and `last` to paginate the `Organization` connection is not supported." msgstr "Passer à la fois `first` et `last` pour paginer la connexion `Organization` n'est pas supporté." @@ -198,16 +215,16 @@ msgstr "Le mot de passe a été mis à jour avec succès." msgid "Passwords do not match." msgstr "Les mots de passe ne correspondent pas." -#: src/organization/queries/find-organization-by-slug.js:61 -#: src/organization/queries/find-organization-by-slug.js:74 +#: src/organization/queries/find-organization-by-slug.js:50 +#: src/organization/queries/find-organization-by-slug.js:55 msgid "Permission Denied: Could not retrieve specified organization." msgstr "Permission refusée : Impossible de récupérer l'organisation spécifiée." -#: src/user/mutations/update-user-profile.js:115 +#: src/user/mutations/update-user-profile.js:110 msgid "Permission Denied: Multi-factor authentication is required for admin accounts" msgstr "Permission refusée : L'authentification multifactorielle est requise pour les comptes admin." -#: src/affiliation/mutations/transfer-org-ownership.js:95 +#: src/affiliation/mutations/transfer-org-ownership.js:74 msgid "Permission Denied: Please contact org owner to transfer ownership." msgstr "Permission refusée : Veuillez contacter le propriétaire de l'org pour transférer la propriété." @@ -215,7 +232,7 @@ msgstr "Permission refusée : Veuillez contacter le propriétaire de l'org pour msgid "Permission Denied: Please contact organization admin for help with archiving domains." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur l'archivage des domaines." -#: src/domain/mutations/remove-domain.js:106 +#: src/domain/mutations/remove-domain.js:94 msgid "Permission Denied: Please contact organization admin for help with removing domain." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer le domaine." @@ -227,7 +244,8 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat msgid "Permission Denied: Please contact organization admin for help with removing organization." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide afin de supprimer l'organisation." -#: src/affiliation/mutations/remove-user-from-org.js:204 +#: src/affiliation/mutations/remove-user-from-org.js:126 +#: src/affiliation/mutations/remove-user-from-org.js:138 msgid "Permission Denied: Please contact organization admin for help with removing users." msgstr "Autorisation refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la suppression des utilisateurs." @@ -238,14 +256,16 @@ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisat #: src/affiliation/mutations/update-user-role.js:170 #: src/affiliation/mutations/update-user-role.js:193 #: src/affiliation/mutations/update-user-role.js:210 -msgid "Permission Denied: Please contact organization admin for help with updating user roles." -msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." +#~ msgid "Permission Denied: Please contact organization admin for help with updating user roles." +#~ msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur la mise à jour des rôles des utilisateurs." -#: src/affiliation/mutations/invite-user-to-org.js:105 +#: src/affiliation/mutations/invite-user-to-org.js:99 msgid "Permission Denied: Please contact organization admin for help with user invitations." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide concernant les invitations d'utilisateurs." -#: src/affiliation/mutations/update-user-role.js:108 +#: src/affiliation/mutations/update-user-role.js:109 +#: src/affiliation/mutations/update-user-role.js:160 +#: src/affiliation/mutations/update-user-role.js:172 msgid "Permission Denied: Please contact organization admin for help with user role changes." msgstr "Permission refusée : Veuillez contacter l'administrateur de l'organisation pour obtenir de l'aide sur les changements de rôle des utilisateurs." @@ -258,18 +278,23 @@ msgid "Permission Denied: Please contact organization user for help with creatin msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la création de domaines." #: src/domain/queries/find-domain-by-domain.js:57 +#: src/organization/objects/organization.js:98 msgid "Permission Denied: Please contact organization user for help with retrieving this domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide pour récupérer ce domaine." -#: src/domain/mutations/request-scan.js:60 +#: src/domain/mutations/request-scan.js:63 msgid "Permission Denied: Please contact organization user for help with scanning this domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur l'analyse de ce domaine." -#: src/domain/mutations/update-domain.js:150 +#: src/domain/mutations/update-domain.js:139 msgid "Permission Denied: Please contact organization user for help with updating this domain." msgstr "Autorisation refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." -#: src/domain/mutations/remove-domain.js:95 +#: src/organization/mutations/archive-organization.js:68 +msgid "Permission Denied: Please contact super admin for help with archiving organization." +msgstr "Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'organisation de l'archivage." + +#: src/domain/mutations/remove-domain.js:107 #: src/domain/mutations/remove-organizations-domains.js:105 msgid "Permission Denied: Please contact super admin for help with removing domain." msgstr "Permission refusée : Veuillez contacter l'utilisateur de l'organisation pour obtenir de l'aide sur la mise à jour de ce domaine." @@ -282,12 +307,12 @@ msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu #~ msgid "Permission Denied: Please contact super admin for help with scanning this domain." #~ msgstr "Permission refusée : Veuillez contacter le super administrateur pour obtenir de l'aide sur l'analyse de ce domaine." -#: src/organization/mutations/verify-organization.js:69 +#: src/organization/mutations/verify-organization.js:68 msgid "Permission Denied: Please contact super admin for help with verifying this organization." msgstr "Permission refusée : Veuillez contacter le super administrateur pour qu'il vous aide à vérifier cette organisation." -#: src/auth/check-domain-permission.js:24 -#: src/auth/check-domain-permission.js:48 +#: src/auth/check-domain-permission.js:22 +#: src/auth/check-domain-permission.js:51 #: src/auth/check-domain-permission.js:61 msgid "Permission check error. Unable to request domain information." msgstr "Erreur de vérification des permissions. Impossible de demander des informations sur le domaine." @@ -304,7 +329,7 @@ msgid "Permission error: Unable to close other user's account." msgstr "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur." #: src/auth/super-admin-required.js:15 -#: src/organization/queries/get-all-organization-domain-statuses.js:36 +#: src/organization/queries/get-all-organization-domain-statuses.js:27 msgid "Permissions error. You do not have sufficient permissions to access this data." msgstr "Erreur de permissions. Vous n'avez pas les autorisations suffisantes pour accéder à ces données." @@ -316,7 +341,7 @@ msgstr "Le numéro de téléphone a été supprimé avec succès." msgid "Phone number has been successfully set, you will receive a verification text message shortly." msgstr "Le numéro de téléphone a été configuré avec succès, vous recevrez bientôt un message de vérification." -#: src/user/mutations/update-user-profile.js:198 +#: src/user/mutations/update-user-profile.js:184 msgid "Profile successfully updated." msgstr "Le profil a été mis à jour avec succès." @@ -333,12 +358,12 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTab msgid "Requesting `{amount}` records on the `DmarcFailureTable` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `DkimFailureTable` dépasse la limite `{argSet}` de 100 enregistrements." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:221 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:188 msgid "Requesting `{amount}` records on the `DmarcSummaries` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `DmarcSummaries` dépasse la limite `{argSet}` de 100 enregistrements." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:164 -#: src/domain/loaders/load-domain-connections-by-user-id.js:186 +#: src/domain/loaders/load-domain-connections-by-user-id.js:172 msgid "Requesting `{amount}` records on the `Domain` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `Domain` dépasse la limite `{argSet}` de 100 enregistrements." @@ -355,12 +380,12 @@ msgstr "La demande d'enregistrements `{amount}` sur la connexion `FullPassTable` msgid "Requesting `{amount}` records on the `GuidanceTag` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `GuidanceTag` dépasse la limite `{argSet}` de 100 enregistrements." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:133 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:119 msgid "Requesting `{amount}` records on the `Log` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `Log` dépasse la limite `{argSet}` de 100 enregistrements." #: src/organization/loaders/load-organization-connections-by-domain-id.js:215 -#: src/organization/loaders/load-organization-connections-by-user-id.js:214 +#: src/organization/loaders/load-organization-connections-by-user-id.js:187 #: src/organization/loaders/load-web-check-connections-by-user-id.js:121 msgid "Requesting `{amount}` records on the `Organization` connection exceeds the `{argSet}` limit of 100 records." msgstr "La demande d'enregistrements `{amount}` sur la connexion `Organization` dépasse la limite `{argSet}` de 100 enregistrements." @@ -415,11 +440,15 @@ msgstr "Ajouté avec succès le(s) domaine(s) {domainCount} à {0}." #~ msgid "Successfully added {domainCount} domains to {0}." #~ msgstr "Ajouté avec succès les domaines {domainCount} à {0}." +#: src/organization/mutations/archive-organization.js:188 +msgid "Successfully archived organization: {0}." +msgstr "Organisation archivée avec succès : {0}." + #: src/user/mutations/close-account.js:405 msgid "Successfully closed account." msgstr "Le compte a été fermé avec succès." -#: src/domain/mutations/request-scan.js:108 +#: src/domain/mutations/request-scan.js:166 msgid "Successfully dispatched one time scan." msgstr "Un seul balayage a été effectué avec succès." @@ -427,11 +456,11 @@ msgstr "Un seul balayage a été effectué avec succès." msgid "Successfully email verified account, and set TFA send method to email." msgstr "Réussir à envoyer un email au compte vérifié, et définir la méthode d'envoi de la TFA sur email." -#: src/affiliation/mutations/invite-user-to-org.js:259 +#: src/affiliation/mutations/invite-user-to-org.js:280 msgid "Successfully invited user to organization, and sent notification email." msgstr "L'utilisateur a été invité avec succès à l'organisation et l'email de notification a été envoyé." -#: src/affiliation/mutations/leave-organization.js:308 +#: src/affiliation/mutations/leave-organization.js:84 msgid "Successfully left organization: {0}" msgstr "L'organisation a été quittée avec succès: {0}" @@ -439,7 +468,7 @@ msgstr "L'organisation a été quittée avec succès: {0}" msgid "Successfully removed domain: {0} from favourites." msgstr "A réussi à supprimer le domaine : {0} des favoris." -#: src/domain/mutations/remove-domain.js:325 +#: src/domain/mutations/remove-domain.js:326 msgid "Successfully removed domain: {0} from {1}." msgstr "A réussi à supprimer le domaine : {0} de {1}." @@ -447,7 +476,7 @@ msgstr "A réussi à supprimer le domaine : {0} de {1}." msgid "Successfully removed organization: {0}." msgstr "A réussi à supprimer l'organisation : {0}." -#: src/affiliation/mutations/remove-user-from-org.js:191 +#: src/affiliation/mutations/remove-user-from-org.js:195 msgid "Successfully removed user from organization." msgstr "L'utilisateur a été retiré de l'organisation avec succès." @@ -455,15 +484,15 @@ msgstr "L'utilisateur a été retiré de l'organisation avec succès." msgid "Successfully removed {domainCount} domain(s) from {0}." msgstr "Supprimé avec succès le(s) domaine(s) {domainCount} de {0}." -#: src/affiliation/mutations/request-org-affiliation.js:201 -msgid "Successfully requested invite to organization, and sent notification email." -msgstr "La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé." - #: src/domain/mutations/remove-organizations-domains.js:530 #~ msgid "Successfully removed {domainCount} domains from {0}." #~ msgstr "Suppression réussie des domaines {domainCount} de {0}." -#: src/affiliation/mutations/invite-user-to-org.js:150 +#: src/affiliation/mutations/request-org-affiliation.js:197 +msgid "Successfully requested invite to organization, and sent notification email." +msgstr "La demande d'invitation à l'organisation a été effectuée avec succès et un courriel de notification a été envoyé." + +#: src/affiliation/mutations/invite-user-to-org.js:172 msgid "Successfully sent invitation to service, and organization email." msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." @@ -471,11 +500,11 @@ msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisatio msgid "Successfully signed out." msgstr "J'ai réussi à me déconnecter." -#: src/affiliation/mutations/transfer-org-ownership.js:218 +#: src/affiliation/mutations/transfer-org-ownership.js:185 msgid "Successfully transferred org: {0} ownership to user: {1}" msgstr "A réussi à transférer la propriété de org: {0} à l'utilisateur: {1}" -#: src/organization/mutations/verify-organization.js:151 +#: src/organization/mutations/verify-organization.js:119 msgid "Successfully verified organization: {0}." msgstr "Envoi réussi de l'invitation au service, et de l'email de l'organisation." @@ -499,16 +528,8 @@ msgstr "Le code à deux facteurs est incorrect. Veuillez réessayer." msgid "Two factor code length is incorrect. Please try again." msgstr "La longueur du code à deux facteurs est incorrecte. Veuillez réessayer." -#: src/affiliation/mutations/leave-organization.js:71 -#: src/affiliation/mutations/leave-organization.js:81 -#: src/affiliation/mutations/leave-organization.js:111 -#: src/affiliation/mutations/leave-organization.js:126 -#: src/affiliation/mutations/leave-organization.js:156 -#: src/affiliation/mutations/leave-organization.js:166 -#: src/affiliation/mutations/leave-organization.js:239 -#: src/affiliation/mutations/leave-organization.js:275 -#: src/affiliation/mutations/leave-organization.js:293 -#: src/affiliation/mutations/leave-organization.js:301 +#: src/affiliation/mutations/leave-organization.js:70 +#: src/affiliation/mutations/leave-organization.js:77 msgid "Unable leave organization. Please try again." msgstr "Impossible de quitter l'organisation. Veuillez réessayer." @@ -516,6 +537,18 @@ msgstr "Impossible de quitter l'organisation. Veuillez réessayer." msgid "Unable to add domains in unknown organization." msgstr "Impossible d'ajouter des domaines dans une organisation inconnue." +#: src/organization/mutations/archive-organization.js:101 +#: src/organization/mutations/archive-organization.js:111 +#: src/organization/mutations/archive-organization.js:129 +#: src/organization/mutations/archive-organization.js:146 +#: src/organization/mutations/archive-organization.js:155 +msgid "Unable to archive organization. Please try again." +msgstr "Impossible d'archiver l'organisation. Veuillez réessayer." + +#: src/organization/mutations/archive-organization.js:54 +msgid "Unable to archive unknown organization." +msgstr "Impossible d'archiver une organisation inconnue." + #: src/user/mutations/authenticate.js:77 #: src/user/mutations/authenticate.js:117 #: src/user/mutations/authenticate.js:126 @@ -523,7 +556,7 @@ msgid "Unable to authenticate. Please try again." msgstr "Impossible de s'authentifier. Veuillez réessayer." #: src/auth/check-permission.js:26 -#: src/auth/check-permission.js:49 +#: src/auth/check-permission.js:64 #: src/auth/check-super-admin.js:20 #: src/auth/check-super-admin.js:30 msgid "Unable to check permission. Please try again." @@ -575,9 +608,9 @@ msgstr "Impossible de créer un domaine. Veuillez réessayer." msgid "Unable to create domains. Please try again." msgstr "Impossible de créer des domaines. Veuillez réessayer." -#: src/organization/mutations/create-organization.js:201 -#: src/organization/mutations/create-organization.js:224 -#: src/organization/mutations/create-organization.js:235 +#: src/organization/mutations/create-organization.js:182 +#: src/organization/mutations/create-organization.js:202 +#: src/organization/mutations/create-organization.js:211 msgid "Unable to create organization. Please try again." msgstr "Impossible de créer une organisation. Veuillez réessayer." @@ -587,6 +620,11 @@ msgstr "Impossible de créer une organisation. Veuillez réessayer." #~ msgid "Unable to dispatch one time scan. Please try again." #~ msgstr "Impossible d'envoyer un scan unique. Veuillez réessayer." +#: src/organization/objects/organization.js:139 +#: src/organization/objects/organization.js:149 +msgid "Unable to export organization. Please try again." +msgstr "Impossible d'exporter l'organisation. Veuillez réessayer." + #: src/domain/mutations/favourite-domain.js:94 msgid "Unable to favourite domain, user has already favourited it." msgstr "Impossible de favoriser le domaine, l'utilisateur l'a déjà favorisé." @@ -689,32 +727,33 @@ msgstr "Impossible de trouver l'affiliation de l'utilisateur (s). Veuillez rées msgid "Unable to find verified organization(s). Please try again." msgstr "Impossible de trouver une ou plusieurs organisations vérifiées. Veuillez réessayer." -#: src/affiliation/mutations/invite-user-to-org.js:170 +#: src/affiliation/mutations/invite-user-to-org.js:117 +#: src/affiliation/mutations/invite-user-to-org.js:127 +#: src/affiliation/mutations/invite-user-to-org.js:192 msgid "Unable to invite user to organization. Please try again." msgstr "Impossible d'inviter un utilisateur dans une organisation. Veuillez réessayer." -#: src/affiliation/mutations/invite-user-to-org.js:182 +#: src/affiliation/mutations/invite-user-to-org.js:204 msgid "Unable to invite user to organization. User is already affiliated with organization." msgstr "Impossible d'inviter un utilisateur dans une organisation. L'utilisateur est déjà affilié à l'organisation." -#: src/affiliation/mutations/invite-user-to-org.js:87 +#: src/affiliation/mutations/invite-user-to-org.js:82 msgid "Unable to invite user to unknown organization." msgstr "Impossible d'inviter un utilisateur à une organisation inconnue." -#: src/affiliation/mutations/invite-user-to-org.js:194 -#: src/affiliation/mutations/invite-user-to-org.js:209 -#: src/affiliation/mutations/request-org-affiliation.js:134 +#: src/affiliation/mutations/invite-user-to-org.js:233 +#: src/affiliation/mutations/invite-user-to-org.js:252 msgid "Unable to invite user. Please try again." msgstr "Impossible d'inviter un utilisateur. Veuillez réessayer." -#: src/affiliation/mutations/invite-user-to-org.js:73 +#: src/affiliation/mutations/invite-user-to-org.js:68 msgid "Unable to invite yourself to an org." msgstr "Impossible de s'inviter à un org." #: src/affiliation/mutations/leave-organization.js:190 #: src/affiliation/mutations/leave-organization.js:208 -msgid "Unable to leave organization. Please try again." -msgstr "Impossible de quitter l'organisation. Veuillez réessayer." +#~ msgid "Unable to leave organization. Please try again." +#~ msgstr "Impossible de quitter l'organisation. Veuillez réessayer." #: src/affiliation/mutations/leave-organization.js:48 msgid "Unable to leave undefined organization." @@ -746,6 +785,10 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation DKIM. Veuillez réessay #~ msgid "Unable to load DKIM scan(s). Please try again." #~ msgstr "Impossible de charger le(s) scan(s) DKIM. Veuillez réessayer." +#: src/summaries/queries/dkim-summary.js:12 +msgid "Unable to load DKIM summary. Please try again." +msgstr "Impossible de charger le résumé DKIM. Veuillez réessayer." + #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:13 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:141 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:153 @@ -766,8 +809,8 @@ msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." #~ msgid "Unable to load DMARC scan(s). Please try again." #~ msgstr "Impossible de charger le(s) scan(s) DMARC. Veuillez réessayer." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:477 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:489 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:446 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:456 #: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:20 #: src/dmarc-summaries/loaders/load-dmarc-sum-edge-by-domain-id-period.js:32 #: src/dmarc-summaries/loaders/load-yearly-dmarc-sum-edges.js:20 @@ -775,6 +818,10 @@ msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." msgid "Unable to load DMARC summary data. Please try again." msgstr "Impossible de charger les données de synthèse DMARC. Veuillez réessayer." +#: src/summaries/queries/dmarc-summary.js:12 +msgid "Unable to load DMARC summary. Please try again." +msgstr "Impossible de charger le résumé DMARC. Veuillez réessayer." + #: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:154 #: src/dns-scan/loaders/load-dns-connections-by-domain-id.js:164 msgid "Unable to load DNS scan(s). Please try again." @@ -811,6 +858,10 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SPF. Veuillez réessaye #~ msgid "Unable to load SPF scan(s). Please try again." #~ msgstr "Impossible de charger le(s) scan(s) SPF. Veuillez réessayer." +#: src/summaries/queries/spf-summary.js:12 +msgid "Unable to load SPF summary. Please try again." +msgstr "Impossible de charger le résumé SPF. Veuillez réessayer." + #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:260 #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:272 msgid "Unable to load SSL guidance tag(s). Please try again." @@ -821,6 +872,10 @@ msgstr "Impossible de charger le(s) tag(s) d'orientation SSL. Veuillez réessaye #~ msgid "Unable to load SSL scan(s). Please try again." #~ msgstr "Impossible de charger le(s) scan(s) SSL. Veuillez réessayer." +#: src/summaries/queries/ssl-summary.js:12 +msgid "Unable to load SSL summary. Please try again." +msgstr "Impossible de charger le résumé SSL. Veuillez réessayer." + #: src/auth/check-user-belongs-to-org.js:20 msgid "Unable to load affiliation information. Please try again." msgstr "Impossible de charger les informations d'affiliation. Veuillez réessayer." @@ -834,11 +889,11 @@ msgstr "Impossible de charger l'affiliation (s). Veuillez réessayer." msgid "Unable to load all organization domain statuses. Please try again." msgstr "Impossible de charger tous les statuts de domaine d'organisation. Veuillez réessayer." -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:534 -#: src/domain/loaders/load-domain-connections-by-organizations-id.js:544 -#: src/domain/loaders/load-domain-connections-by-user-id.js:462 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:549 +#: src/domain/loaders/load-domain-connections-by-organizations-id.js:559 +#: src/domain/loaders/load-domain-connections-by-user-id.js:448 #: src/domain/loaders/load-domain-tags-by-org-id.js:27 -#: src/user/loaders/load-my-tracker-by-user-id.js:43 +#: src/user/loaders/load-my-tracker-by-user-id.js:44 msgid "Unable to load domain(s). Please try again." msgstr "Impossible de charger le(s) domaine(s). Veuillez réessayer." @@ -855,7 +910,7 @@ msgstr "Impossible de charger le domaine. Veuillez réessayer." msgid "Unable to load full pass data. Please try again." msgstr "Impossible de charger les données complètes de la passe. Veuillez réessayer." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:340 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:318 msgid "Unable to load log(s). Please try again." msgstr "Impossible de charger le(s) journal(s). Veuillez réessayer." @@ -878,15 +933,15 @@ msgstr "Impossible de charger les statuts des domaines d'organisation. Veuillez #: src/organization/loaders/load-organization-by-slug.js:51 #: src/organization/loaders/load-organization-connections-by-domain-id.js:533 #: src/organization/loaders/load-organization-connections-by-domain-id.js:545 -#: src/organization/loaders/load-organization-connections-by-user-id.js:520 -#: src/organization/loaders/load-organization-connections-by-user-id.js:532 +#: src/organization/loaders/load-organization-connections-by-user-id.js:505 +#: src/organization/loaders/load-organization-connections-by-user-id.js:515 #: src/organization/loaders/load-web-check-connections-by-user-id.js:331 #: src/organization/loaders/load-web-check-connections-by-user-id.js:343 msgid "Unable to load organization(s). Please try again." msgstr "Impossible de charger l'organisation (s). Veuillez réessayer." -#: src/auth/check-org-owner.js:22 -#: src/auth/check-org-owner.js:34 +#: src/auth/check-org-owner.js:19 +#: src/auth/check-org-owner.js:27 msgid "Unable to load owner information. Please try again." msgstr "Impossible de charger les informations sur le propriétaire. Veuillez réessayer." @@ -925,6 +980,10 @@ msgstr "Impossible de charger le(s) domaine(s) vérifié(s). Veuillez réessayer msgid "Unable to load verified organization(s). Please try again." msgstr "Impossible de charger le(s) organisme(s) vérifié(s). Veuillez réessayer." +#: src/summaries/queries/web-connections-summary.js:12 +msgid "Unable to load web connections summary. Please try again." +msgstr "Impossible de charger le résumé des connexions web. Veuillez réessayer." + #: src/web-scan/loaders/load-web-connections-by-domain-id.js:169 #: src/web-scan/loaders/load-web-connections-by-domain-id.js:179 #: src/web-scan/loaders/load-web-scans-by-web-id.js:9 @@ -942,12 +1001,12 @@ msgstr "Impossible de charger le résumé web. Veuillez réessayer." msgid "Unable to query affiliation(s). Please try again." msgstr "Impossible de demander l'affiliation (s). Veuillez réessayer." -#: src/domain/loaders/load-domain-connections-by-user-id.js:452 -#: src/user/loaders/load-my-tracker-by-user-id.js:33 +#: src/domain/loaders/load-domain-connections-by-user-id.js:438 +#: src/user/loaders/load-my-tracker-by-user-id.js:34 msgid "Unable to query domain(s). Please try again." msgstr "Impossible d'interroger le(s) domaine(s). Veuillez réessayer." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:330 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:308 msgid "Unable to query log(s). Please try again." msgstr "Impossible d'interroger le(s) journal(s). Veuillez réessayer." @@ -965,7 +1024,7 @@ msgstr "Impossible d'interroger le(s) utilisateur(s). Veuillez réessayer." msgid "Unable to refresh tokens, please sign in." msgstr "Impossible de rafraîchir les jetons, veuillez vous connecter." -#: src/affiliation/mutations/remove-user-from-org.js:114 +#: src/affiliation/mutations/remove-user-from-org.js:104 msgid "Unable to remove a user that already does not belong to this organization." msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à cette organisation." @@ -973,19 +1032,19 @@ msgstr "Impossible de supprimer un utilisateur qui n'appartient déjà plus à c msgid "Unable to remove domain from unknown organization." msgstr "Impossible de supprimer le domaine d'une organisation inconnue." -#: src/domain/mutations/remove-domain.js:135 +#: src/domain/mutations/remove-domain.js:136 msgid "Unable to remove domain. Domain is not part of organization." msgstr "Impossible de supprimer le domaine. Le domaine ne fait pas partie de l'organisation." -#: src/domain/mutations/remove-domain.js:122 -#: src/domain/mutations/remove-domain.js:151 -#: src/domain/mutations/remove-domain.js:184 -#: src/domain/mutations/remove-domain.js:203 -#: src/domain/mutations/remove-domain.js:229 -#: src/domain/mutations/remove-domain.js:247 -#: src/domain/mutations/remove-domain.js:264 -#: src/domain/mutations/remove-domain.js:287 -#: src/domain/mutations/remove-domain.js:298 +#: src/domain/mutations/remove-domain.js:123 +#: src/domain/mutations/remove-domain.js:152 +#: src/domain/mutations/remove-domain.js:185 +#: src/domain/mutations/remove-domain.js:204 +#: src/domain/mutations/remove-domain.js:230 +#: src/domain/mutations/remove-domain.js:248 +#: src/domain/mutations/remove-domain.js:265 +#: src/domain/mutations/remove-domain.js:288 +#: src/domain/mutations/remove-domain.js:299 msgid "Unable to remove domain. Please try again." msgstr "Impossible de supprimer le domaine. Veuillez réessayer." @@ -1020,18 +1079,18 @@ msgstr "Impossible de supprimer un domaine inconnu." msgid "Unable to remove unknown organization." msgstr "Impossible de supprimer une organisation inconnue." -#: src/affiliation/mutations/remove-user-from-org.js:87 +#: src/affiliation/mutations/remove-user-from-org.js:77 msgid "Unable to remove unknown user from organization." msgstr "Impossible de supprimer un utilisateur inconnu de l'organisation." #: src/affiliation/mutations/remove-user-from-org.js:74 -msgid "Unable to remove user from organization." -msgstr "Impossible de supprimer un utilisateur de l'organisation." +#~ msgid "Unable to remove user from organization." +#~ msgstr "Impossible de supprimer un utilisateur de l'organisation." -#: src/affiliation/mutations/remove-user-from-org.js:104 -#: src/affiliation/mutations/remove-user-from-org.js:125 -#: src/affiliation/mutations/remove-user-from-org.js:156 -#: src/affiliation/mutations/remove-user-from-org.js:165 +#: src/affiliation/mutations/remove-user-from-org.js:94 +#: src/affiliation/mutations/remove-user-from-org.js:115 +#: src/affiliation/mutations/remove-user-from-org.js:160 +#: src/affiliation/mutations/remove-user-from-org.js:169 msgid "Unable to remove user from this organization. Please try again." msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez réessayer." @@ -1039,23 +1098,23 @@ msgstr "Impossible de supprimer l'utilisateur de cette organisation. Veuillez r msgid "Unable to remove user from unknown organization." msgstr "Impossible de supprimer un utilisateur d'une organisation inconnue." -#: src/domain/mutations/request-scan.js:82 +#: src/domain/mutations/request-scan.js:117 msgid "Unable to request a one time scan on a domain that already has a pending scan." msgstr "Impossible de demander une analyse unique sur un domaine qui a déjà une analyse en cours." -#: src/domain/mutations/request-scan.js:48 +#: src/domain/mutations/request-scan.js:52 msgid "Unable to request a one time scan on an unknown domain." msgstr "Impossible de demander un scan unique sur un domaine inconnu." -#: src/domain/mutations/request-scan.js:90 +#: src/domain/mutations/request-scan.js:125 msgid "Unable to request a one time scan. Please try again." msgstr "Impossible de demander une analyse unique. Veuillez réessayer." -#: src/affiliation/mutations/request-org-affiliation.js:109 +#: src/affiliation/mutations/request-org-affiliation.js:95 msgid "Unable to request invite to organization with which you are already affiliated." msgstr "Impossible de demander une invitation à une organisation à laquelle vous êtes déjà affilié." -#: src/affiliation/mutations/request-org-affiliation.js:83 +#: src/affiliation/mutations/request-org-affiliation.js:85 msgid "Unable to request invite to organization with which you have already requested to join." msgstr "Impossible de demander une invitation à une organisation à laquelle vous avez déjà demandé à adhérer." @@ -1063,10 +1122,11 @@ msgstr "Impossible de demander une invitation à une organisation à laquelle vo msgid "Unable to request invite to unknown organization." msgstr "Impossible de demander une invitation à une organisation inconnue." -#: src/affiliation/mutations/request-org-affiliation.js:73 -#: src/affiliation/mutations/request-org-affiliation.js:99 -#: src/affiliation/mutations/request-org-affiliation.js:150 -#: src/affiliation/mutations/request-org-affiliation.js:169 +#: src/affiliation/mutations/request-org-affiliation.js:72 +#: src/affiliation/mutations/request-org-affiliation.js:120 +#: src/affiliation/mutations/request-org-affiliation.js:136 +#: src/affiliation/mutations/request-org-affiliation.js:146 +#: src/affiliation/mutations/request-org-affiliation.js:165 msgid "Unable to request invite. Please try again." msgstr "Impossible de demander une invitation. Veuillez réessayer." @@ -1080,8 +1140,8 @@ msgstr "Impossible de réinitialiser le mot de passe. Veuillez demander un nouve msgid "Unable to reset password. Please try again." msgstr "Impossible de réinitialiser le mot de passe. Veuillez réessayer." -#: src/domain/objects/domain.js:206 -#: src/domain/objects/domain.js:248 +#: src/domain/objects/domain.js:236 +#: src/domain/objects/domain.js:271 msgid "Unable to retrieve DMARC report information for: {domain}" msgstr "Impossible de récupérer les informations du rapport DMARC pour : {domain}" @@ -1098,7 +1158,7 @@ msgstr "Impossible d'envoyer l'email d'authentification. Veuillez réessayer." msgid "Unable to send authentication text message. Please try again." msgstr "Impossible d'envoyer un message texte d'authentification. Veuillez réessayer." -#: src/notify/notify-send-org-invite-create-account.js:31 +#: src/notify/notify-send-org-invite-create-account.js:19 #: src/notify/notify-send-org-invite-email.js:26 msgid "Unable to send org invite email. Please try again." msgstr "Impossible d'envoyer l'e-mail d'invitation à l'org. Veuillez réessayer." @@ -1134,38 +1194,38 @@ msgstr "Impossible de définir le numéro de téléphone, veuillez réessayer." msgid "Unable to sign in, please try again." msgstr "Impossible de se connecter, veuillez réessayer." -#: src/user/mutations/sign-up.js:182 -#: src/user/mutations/sign-up.js:192 +#: src/user/mutations/sign-up.js:183 +#: src/user/mutations/sign-up.js:193 msgid "Unable to sign up, please contact org admin for a new invite." msgstr "Impossible de s'inscrire, veuillez contacter l'administrateur de l'organisation pour obtenir une nouvelle invitation." -#: src/user/mutations/sign-up.js:155 -#: src/user/mutations/sign-up.js:163 +#: src/user/mutations/sign-up.js:156 +#: src/user/mutations/sign-up.js:164 #: src/user/mutations/sign-up.js:213 #: src/user/mutations/sign-up.js:221 msgid "Unable to sign up. Please try again." msgstr "Impossible de s'inscrire. Veuillez réessayer." -#: src/affiliation/mutations/transfer-org-ownership.js:131 -#: src/affiliation/mutations/transfer-org-ownership.js:172 -#: src/affiliation/mutations/transfer-org-ownership.js:196 -#: src/affiliation/mutations/transfer-org-ownership.js:208 +#: src/affiliation/mutations/transfer-org-ownership.js:106 +#: src/affiliation/mutations/transfer-org-ownership.js:145 +#: src/affiliation/mutations/transfer-org-ownership.js:167 +#: src/affiliation/mutations/transfer-org-ownership.js:177 msgid "Unable to transfer organization ownership. Please try again." msgstr "Impossible de transférer la propriété de l'organisation. Veuillez réessayer." -#: src/affiliation/mutations/transfer-org-ownership.js:78 -msgid "Unable to transfer ownership of a verified organization." -msgstr "Impossible de transférer la propriété d'une organisation vérifiée." +#: src/affiliation/mutations/transfer-org-ownership.js:68 +#~ msgid "Unable to transfer ownership of a verified organization." +#~ msgstr "Impossible de transférer la propriété d'une organisation vérifiée." -#: src/affiliation/mutations/transfer-org-ownership.js:112 +#: src/affiliation/mutations/transfer-org-ownership.js:89 msgid "Unable to transfer ownership of an org to an undefined user." msgstr "Impossible de transférer la propriété d'un org à un utilisateur non défini." -#: src/affiliation/mutations/transfer-org-ownership.js:65 +#: src/affiliation/mutations/transfer-org-ownership.js:59 msgid "Unable to transfer ownership of undefined organization." msgstr "Impossible de transférer la propriété d'une organisation non définie." -#: src/affiliation/mutations/transfer-org-ownership.js:144 +#: src/affiliation/mutations/transfer-org-ownership.js:118 msgid "Unable to transfer ownership to a user outside the org. Please invite the user and try again." msgstr "Impossible de transférer la propriété à un utilisateur extérieur à l'org. Veuillez inviter l'utilisateur et réessayer." @@ -1187,21 +1247,21 @@ msgstr "Impossible de défavoriser le domaine. Veuillez réessayer." msgid "Unable to unfavourite unknown domain." msgstr "Impossible de défavoriser un domaine inconnu." -#: src/domain/mutations/update-domain.js:256 +#: src/domain/mutations/update-domain.js:237 msgid "Unable to update domain edge. Please try again." msgstr "Impossible de mettre à jour le bord du domaine. Veuillez réessayer." -#: src/domain/mutations/update-domain.js:131 +#: src/domain/mutations/update-domain.js:125 msgid "Unable to update domain in an unknown org." msgstr "Impossible de mettre à jour le domaine dans un org inconnu." -#: src/domain/mutations/update-domain.js:179 +#: src/domain/mutations/update-domain.js:166 msgid "Unable to update domain that does not belong to the given organization." msgstr "Impossible de mettre à jour un domaine qui n'appartient pas à l'organisation donnée." -#: src/domain/mutations/update-domain.js:168 -#: src/domain/mutations/update-domain.js:210 -#: src/domain/mutations/update-domain.js:267 +#: src/domain/mutations/update-domain.js:156 +#: src/domain/mutations/update-domain.js:196 +#: src/domain/mutations/update-domain.js:247 msgid "Unable to update domain. Please try again." msgstr "Impossible de mettre à jour le domaine. Veuillez réessayer." @@ -1230,8 +1290,8 @@ msgstr "Impossible de mettre à jour le mot de passe, les mots de passe ne corre msgid "Unable to update password. Please try again." msgstr "Impossible de mettre à jour le mot de passe. Veuillez réessayer." -#: src/user/mutations/update-user-profile.js:172 -#: src/user/mutations/update-user-profile.js:181 +#: src/user/mutations/update-user-profile.js:160 +#: src/user/mutations/update-user-profile.js:167 msgid "Unable to update profile. Please try again." msgstr "Impossible de mettre à jour le profil. Veuillez réessayer." @@ -1239,7 +1299,7 @@ msgstr "Impossible de mettre à jour le profil. Veuillez réessayer." msgid "Unable to update role: organization unknown." msgstr "Impossible de mettre à jour le rôle : organisation inconnue." -#: src/affiliation/mutations/update-user-role.js:135 +#: src/affiliation/mutations/update-user-role.js:137 msgid "Unable to update role: user does not belong to organization." msgstr "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas à l'organisation." @@ -1247,7 +1307,7 @@ msgstr "Impossible de mettre à jour le rôle : l'utilisateur n'appartient pas msgid "Unable to update role: user unknown." msgstr "Impossible de mettre à jour le rôle : utilisateur inconnu." -#: src/domain/mutations/update-domain.js:117 +#: src/domain/mutations/update-domain.js:111 msgid "Unable to update unknown domain." msgstr "Impossible de mettre à jour un domaine inconnu." @@ -1255,10 +1315,10 @@ msgstr "Impossible de mettre à jour un domaine inconnu." msgid "Unable to update unknown organization." msgstr "Impossible de mettre à jour une organisation inconnue." -#: src/affiliation/mutations/update-user-role.js:125 -#: src/affiliation/mutations/update-user-role.js:146 -#: src/affiliation/mutations/update-user-role.js:228 -#: src/affiliation/mutations/update-user-role.js:237 +#: src/affiliation/mutations/update-user-role.js:127 +#: src/affiliation/mutations/update-user-role.js:149 +#: src/affiliation/mutations/update-user-role.js:201 +#: src/affiliation/mutations/update-user-role.js:211 msgid "Unable to update user's role. Please try again." msgstr "Impossible de mettre à jour le rôle de l'utilisateur. Veuillez réessayer." @@ -1280,18 +1340,17 @@ msgstr "Impossible de vérifier le compte. Veuillez réessayer." msgid "Unable to verify if user is a super admin, please try again." msgstr "Impossible de vérifier si l'utilisateur est un super administrateur, veuillez réessayer." -#: src/user/mutations/update-user-profile.js:103 +#: src/user/mutations/update-user-profile.js:100 #: src/user/queries/is-user-admin.js:59 msgid "Unable to verify if user is an admin, please try again." msgstr "Impossible de vérifier si l'utilisateur est un administrateur, veuillez réessayer." -#: src/organization/mutations/verify-organization.js:109 -#: src/organization/mutations/verify-organization.js:131 -#: src/organization/mutations/verify-organization.js:142 +#: src/organization/mutations/verify-organization.js:105 +#: src/organization/mutations/verify-organization.js:112 msgid "Unable to verify organization. Please try again." msgstr "Impossible de vérifier l'organisation. Veuillez réessayer." -#: src/organization/mutations/verify-organization.js:54 +#: src/organization/mutations/verify-organization.js:53 msgid "Unable to verify unknown organization." msgstr "Impossible de vérifier une organisation inconnue." @@ -1299,7 +1358,7 @@ msgstr "Impossible de vérifier une organisation inconnue." msgid "User could not be queried." msgstr "L'utilisateur n'a pas pu être interrogé." -#: src/affiliation/mutations/update-user-role.js:270 +#: src/affiliation/mutations/update-user-role.js:244 msgid "User role was updated successfully." msgstr "Le rôle de l'utilisateur a été mis à jour avec succès." @@ -1340,12 +1399,12 @@ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctemen msgid "You must provide a `first` or `last` value to properly paginate the `DmarcFailureTable` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcFailureTable`." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:189 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:162 msgid "You must provide a `first` or `last` value to properly paginate the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `DmarcSummaries`." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:141 -#: src/domain/loaders/load-domain-connections-by-user-id.js:161 +#: src/domain/loaders/load-domain-connections-by-user-id.js:147 msgid "You must provide a `first` or `last` value to properly paginate the `Domain` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Domain`." @@ -1366,12 +1425,12 @@ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctemen #~ msgid "You must provide a `first` or `last` value to properly paginate the `HTTPS` connection." #~ msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `HTTPS`." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:101 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:100 msgid "You must provide a `first` or `last` value to properly paginate the `Log` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Log`." #: src/organization/loaders/load-organization-connections-by-domain-id.js:183 -#: src/organization/loaders/load-organization-connections-by-user-id.js:182 +#: src/organization/loaders/load-organization-connections-by-user-id.js:166 #: src/organization/loaders/load-web-check-connections-by-user-id.js:89 msgid "You must provide a `first` or `last` value to properly paginate the `Organization` connection." msgstr "Vous devez fournir une valeur `first` ou `last` pour paginer correctement la connexion `Organization`." @@ -1418,11 +1477,11 @@ msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la conne msgid "You must provide a `limit` value to properly paginate the `web` connection." msgstr "Vous devez fournir une valeur `limit` pour paginer correctement la connexion `web`." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:31 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:12 msgid "You must provide a `period` value to access the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `period` pour accéder à la connexion `DmarcSummaries`." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:43 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:18 msgid "You must provide a `year` value to access the `DmarcSummaries` connection." msgstr "Vous devez fournir une valeur `year` pour accéder à la connexion `DmarcSummaries`." @@ -1436,14 +1495,14 @@ msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before #: src/affiliation/loaders/load-affiliation-connections-by-org-id.js:109 #: src/affiliation/loaders/load-affiliation-connections-by-user-id.js:208 -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:148 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:131 #: src/dmarc-summaries/loaders/load-dkim-failure-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-dmarc-failure-connections-by-sum-id.js:83 -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:236 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:202 #: src/dmarc-summaries/loaders/load-full-pass-connections-by-sum-id.js:83 #: src/dmarc-summaries/loaders/load-spf-failure-connections-by-sum-id.js:83 #: src/domain/loaders/load-domain-connections-by-organizations-id.js:178 -#: src/domain/loaders/load-domain-connections-by-user-id.js:200 +#: src/domain/loaders/load-domain-connections-by-user-id.js:186 #: src/guidance-tag/loaders/load-aggregate-guidance-tags-connections.js:132 #: src/guidance-tag/loaders/load-dkim-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-dmarc-guidance-tags-connections.js:136 @@ -1451,7 +1510,7 @@ msgstr "Vous devez fournir au plus une valeur de méthode de pagination (`before #: src/guidance-tag/loaders/load-spf-guidance-tags-connections.js:136 #: src/guidance-tag/loaders/load-ssl-guidance-tags-connections.js:136 #: src/organization/loaders/load-organization-connections-by-domain-id.js:230 -#: src/organization/loaders/load-organization-connections-by-user-id.js:229 +#: src/organization/loaders/load-organization-connections-by-user-id.js:201 #: src/organization/loaders/load-web-check-connections-by-user-id.js:136 #: src/user/loaders/load-user-connections-by-user-id.js:154 #: src/verified-domains/loaders/load-verified-domain-connections-by-organization-id.js:164 @@ -1486,12 +1545,12 @@ msgstr "`{argSet}` sur la connexion `DkimFailureTable` ne peut être inférieur msgid "`{argSet}` on the `DmarcFailureTable` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `DmarcFailureTable` ne peut être inférieur à zéro." -#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:210 +#: src/dmarc-summaries/loaders/load-dmarc-sum-connections-by-user-id.js:179 msgid "`{argSet}` on the `DmarcSummaries` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `DmarcSummaries` ne peut être inférieur à zéro." #: src/domain/loaders/load-domain-connections-by-organizations-id.js:157 -#: src/domain/loaders/load-domain-connections-by-user-id.js:177 +#: src/domain/loaders/load-domain-connections-by-user-id.js:163 msgid "`{argSet}` on the `Domain` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Domain` ne peut être inférieur à zéro." @@ -1512,12 +1571,12 @@ msgstr "`{argSet}` sur la connexion `GuidanceTag` ne peut être inférieure à z #~ msgid "`{argSet}` on the `HTTPS` connection cannot be less than zero." #~ msgstr "`{argSet}` sur la connexion `HTTPS` ne peut être inférieur à zéro." -#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:122 +#: src/audit-logs/loaders/load-audit-logs-by-org-id.js:112 msgid "`{argSet}` on the `Log` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Log` ne peut être inférieur à zéro." #: src/organization/loaders/load-organization-connections-by-domain-id.js:204 -#: src/organization/loaders/load-organization-connections-by-user-id.js:203 +#: src/organization/loaders/load-organization-connections-by-user-id.js:180 #: src/organization/loaders/load-web-check-connections-by-user-id.js:110 msgid "`{argSet}` on the `Organization` connection cannot be less than zero." msgstr "`{argSet}` sur la connexion `Organization` ne peut être inférieure à zéro." diff --git a/api/src/organization/loaders/load-organization-connections-by-user-id.js b/api/src/organization/loaders/load-organization-connections-by-user-id.js index 00e09e103d..96a42cbded 100644 --- a/api/src/organization/loaders/load-organization-connections-by-user-id.js +++ b/api/src/organization/loaders/load-organization-connections-by-user-id.js @@ -2,385 +2,360 @@ import { aql } from 'arangojs' import { fromGlobalId, toGlobalId } from 'graphql-relay' import { t } from '@lingui/macro' -export const loadOrgConnectionsByUserId = ({ - query, - userKey, - cleanseInput, - language, - i18n, - auth: { loginRequiredBool }, -}) => async ({ - after, - before, - first, - last, - orderBy, - isSuperAdmin, - search, - isAdmin, - includeSuperAdminOrg, - isVerified, -}) => { - const userDBId = `users/${userKey}` +export const loadOrgConnectionsByUserId = + ({ query, userKey, cleanseInput, language, i18n, auth: { loginRequiredBool } }) => + async ({ after, before, first, last, orderBy, isSuperAdmin, search, isAdmin, includeSuperAdminOrg, isVerified }) => { + const userDBId = `users/${userKey}` - let afterTemplate = aql`` - let afterVar = aql`` - if (typeof after !== 'undefined') { - const { id: afterId } = fromGlobalId(cleanseInput(after)) - if (typeof orderBy === 'undefined') { - afterTemplate = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(${afterId})` + let afterTemplate = aql`` + let afterVar = aql`` + if (typeof after !== 'undefined') { + const { id: afterId } = fromGlobalId(cleanseInput(after)) + if (typeof orderBy === 'undefined') { + afterTemplate = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(${afterId})` + } else { + let afterTemplateDirection = aql`<` + if (orderBy.direction === 'ASC') { + afterTemplateDirection = aql`>` + } + + afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`afterVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`afterVar.summaries.mail.pass` + orgField = aql`org.summaries.mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`afterVar.summaries.mail.fail` + orgField = aql`org.summaries.mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`afterVar.summaries.mail.total` + orgField = aql`org.summaries.mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`afterVar.summaries.web.pass` + orgField = aql`org.summaries.web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`afterVar.summaries.web.fail` + orgField = aql`org.summaries.web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`afterVar.summaries.web.total` + orgField = aql`org.summaries.web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + afterTemplate = aql` + FILTER ${orgField} ${afterTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._key) > TO_NUMBER(${afterId})) + ` + } + } + + let beforeTemplate = aql`` + let beforeVar = aql`` + if (typeof before !== 'undefined') { + const { id: beforeId } = fromGlobalId(cleanseInput(before)) + if (typeof orderBy === 'undefined') { + beforeTemplate = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(${beforeId})` + } else { + let beforeTemplateDirection = aql`>` + if (orderBy.direction === 'ASC') { + beforeTemplateDirection = aql`<` + } + + beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` + + let documentField = aql`` + let orgField = aql`` + /* istanbul ignore else */ + if (orderBy.field === 'acronym') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` + orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + } else if (orderBy.field === 'name') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` + orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + } else if (orderBy.field === 'slug') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` + orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + } else if (orderBy.field === 'zone') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` + orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + } else if (orderBy.field === 'sector') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` + orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + } else if (orderBy.field === 'country') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` + orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + } else if (orderBy.field === 'province') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` + orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + } else if (orderBy.field === 'city') { + documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` + orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + } else if (orderBy.field === 'verified') { + documentField = aql`beforeVar.verified` + orgField = aql`org.verified` + } else if (orderBy.field === 'summary-mail-pass') { + documentField = aql`beforeVar.summaries.mail.pass` + orgField = aql`org.summaries.mail.pass` + } else if (orderBy.field === 'summary-mail-fail') { + documentField = aql`beforeVar.summaries.mail.fail` + orgField = aql`org.summaries.mail.fail` + } else if (orderBy.field === 'summary-mail-total') { + documentField = aql`beforeVar.summaries.mail.total` + orgField = aql`org.summaries.mail.total` + } else if (orderBy.field === 'summary-web-pass') { + documentField = aql`beforeVar.summaries.web.pass` + orgField = aql`org.summaries.web.pass` + } else if (orderBy.field === 'summary-web-fail') { + documentField = aql`beforeVar.summaries.web.fail` + orgField = aql`org.summaries.web.fail` + } else if (orderBy.field === 'summary-web-total') { + documentField = aql`beforeVar.summaries.web.total` + orgField = aql`org.summaries.web.total` + } else if (orderBy.field === 'domain-count') { + documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` + orgField = aql`COUNT(orgDomains)` + } + + beforeTemplate = aql` + FILTER ${orgField} ${beforeTemplateDirection} ${documentField} + OR (${orgField} == ${documentField} + AND TO_NUMBER(org._key) < TO_NUMBER(${beforeId})) + ` + } + } + + let limitTemplate = aql`` + if (typeof first === 'undefined' && typeof last === 'undefined') { + console.warn( + `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadOrgConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`), + ) + } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { + console.warn( + `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadOrgConnectionsByUserId.`, + ) + throw new Error( + i18n._(t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`), + ) + } else if (typeof first === 'number' || typeof last === 'number') { + /* istanbul ignore else */ + if (first < 0 || last < 0) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + console.warn(`User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadOrgConnectionsByUserId.`) + throw new Error(i18n._(t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`)) + } else if (first > 100 || last > 100) { + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const amount = typeof first !== 'undefined' ? first : last + console.warn(`User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByUserId.`) + throw new Error( + i18n._( + t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, + ), + ) + } else if (typeof first !== 'undefined' && typeof last === 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` + } else if (typeof first === 'undefined' && typeof last !== 'undefined') { + limitTemplate = aql`TO_NUMBER(org._key) DESC LIMIT TO_NUMBER(${last})` + } } else { - let afterTemplateDirection = aql`<` + const argSet = typeof first !== 'undefined' ? 'first' : 'last' + const typeSet = typeof first !== 'undefined' ? typeof first : typeof last + console.warn( + `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadOrgConnectionsByUserId.`, + ) + throw new Error(i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`)) + } + + let hasNextPageFilter = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)` + let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` + if (typeof orderBy !== 'undefined') { + let hasNextPageDirection = aql`<` + let hasPreviousPageDirection = aql`>` if (orderBy.direction === 'ASC') { - afterTemplateDirection = aql`>` + hasNextPageDirection = aql`>` + hasPreviousPageDirection = aql`<` } - afterVar = aql`LET afterVar = DOCUMENT(organizations, ${afterId})` - - let documentField = aql`` let orgField = aql`` + let hasNextPageDocumentField = aql`` + let hasPreviousPageDocumentField = aql`` /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).acronym` orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).name` orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).slug` orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).zone` orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).sector` orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).country` orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).province` orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, afterVar.orgDetails).city` orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` } else if (orderBy.field === 'verified') { - documentField = aql`afterVar.verified` orgField = aql`org.verified` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`afterVar.summaries.mail.pass` orgField = aql`org.summaries.mail.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`afterVar.summaries.mail.fail` orgField = aql`org.summaries.mail.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`afterVar.summaries.mail.total` orgField = aql`org.summaries.mail.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`afterVar.summaries.web.pass` orgField = aql`org.summaries.web.pass` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`afterVar.summaries.web.fail` orgField = aql`org.summaries.web.fail` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`afterVar.summaries.web.total` orgField = aql`org.summaries.web.total` + hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` + hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND afterVar._id claims RETURN e._to)` orgField = aql`COUNT(orgDomains)` + hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` + hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` } - afterTemplate = aql` - FILTER ${orgField} ${afterTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._key) > TO_NUMBER(${afterId})) - ` + hasNextPageFilter = aql` + FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} + OR (${orgField} == ${hasNextPageDocumentField} + AND TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)) + ` + hasPreviousPageFilter = aql` + FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} + OR (${orgField} == ${hasPreviousPageDocumentField} + AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) + ` } - } - let beforeTemplate = aql`` - let beforeVar = aql`` - if (typeof before !== 'undefined') { - const { id: beforeId } = fromGlobalId(cleanseInput(before)) - if (typeof orderBy === 'undefined') { - beforeTemplate = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(${beforeId})` - } else { - let beforeTemplateDirection = aql`>` - if (orderBy.direction === 'ASC') { - beforeTemplateDirection = aql`<` - } - - beforeVar = aql`LET beforeVar = DOCUMENT(organizations, ${beforeId})` - - let documentField = aql`` - let orgField = aql`` + let sortByField = aql`` + if (typeof orderBy !== 'undefined') { /* istanbul ignore else */ if (orderBy.field === 'acronym') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).acronym` - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` } else if (orderBy.field === 'name') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).name` - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` } else if (orderBy.field === 'slug') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).slug` - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` } else if (orderBy.field === 'zone') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).zone` - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` } else if (orderBy.field === 'sector') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).sector` - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` } else if (orderBy.field === 'country') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).country` - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` } else if (orderBy.field === 'province') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).province` - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` } else if (orderBy.field === 'city') { - documentField = aql`TRANSLATE(${language}, beforeVar.orgDetails).city` - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` + sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` } else if (orderBy.field === 'verified') { - documentField = aql`beforeVar.verified` - orgField = aql`org.verified` + sortByField = aql`org.verified ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-pass') { - documentField = aql`beforeVar.summaries.mail.pass` - orgField = aql`org.summaries.mail.pass` + sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-fail') { - documentField = aql`beforeVar.summaries.mail.fail` - orgField = aql`org.summaries.mail.fail` + sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-mail-total') { - documentField = aql`beforeVar.summaries.mail.total` - orgField = aql`org.summaries.mail.total` + sortByField = aql`org.summaries.mail.total ${orderBy.direction},` } else if (orderBy.field === 'summary-web-pass') { - documentField = aql`beforeVar.summaries.web.pass` - orgField = aql`org.summaries.web.pass` + sortByField = aql`org.summaries.web.pass ${orderBy.direction},` } else if (orderBy.field === 'summary-web-fail') { - documentField = aql`beforeVar.summaries.web.fail` - orgField = aql`org.summaries.web.fail` + sortByField = aql`org.summaries.web.fail ${orderBy.direction},` } else if (orderBy.field === 'summary-web-total') { - documentField = aql`beforeVar.summaries.web.total` - orgField = aql`org.summaries.web.total` + sortByField = aql`org.summaries.web.total ${orderBy.direction},` } else if (orderBy.field === 'domain-count') { - documentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND beforeVar._id claims RETURN e._to)` - orgField = aql`COUNT(orgDomains)` + sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` } - - beforeTemplate = aql` - FILTER ${orgField} ${beforeTemplateDirection} ${documentField} - OR (${orgField} == ${documentField} - AND TO_NUMBER(org._key) < TO_NUMBER(${beforeId})) - ` } - } - let limitTemplate = aql`` - if (typeof first === 'undefined' && typeof last === 'undefined') { - console.warn( - `User: ${userKey} did not have either \`first\` or \`last\` arguments set for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`You must provide a \`first\` or \`last\` value to properly paginate the \`Organization\` connection.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last !== 'undefined') { - console.warn( - `User: ${userKey} attempted to have \`first\` and \`last\` arguments set for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Passing both \`first\` and \`last\` to paginate the \`Organization\` connection is not supported.`, - ), - ) - } else if (typeof first === 'number' || typeof last === 'number') { - /* istanbul ignore else */ - if (first < 0 || last < 0) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set below zero for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`\`${argSet}\` on the \`Organization\` connection cannot be less than zero.`, - ), - ) - } else if (first > 100 || last > 100) { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const amount = typeof first !== 'undefined' ? first : last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` to ${amount} for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._( - t`Requesting \`${amount}\` records on the \`Organization\` connection exceeds the \`${argSet}\` limit of 100 records.`, - ), - ) - } else if (typeof first !== 'undefined' && typeof last === 'undefined') { - limitTemplate = aql`TO_NUMBER(org._key) ASC LIMIT TO_NUMBER(${first})` - } else if (typeof first === 'undefined' && typeof last !== 'undefined') { - limitTemplate = aql`TO_NUMBER(org._key) DESC LIMIT TO_NUMBER(${last})` + let sortString + if (typeof last !== 'undefined') { + sortString = aql`DESC` + } else { + sortString = aql`ASC` } - } else { - const argSet = typeof first !== 'undefined' ? 'first' : 'last' - const typeSet = typeof first !== 'undefined' ? typeof first : typeof last - console.warn( - `User: ${userKey} attempted to have \`${argSet}\` set as a ${typeSet} for: loadOrgConnectionsByUserId.`, - ) - throw new Error( - i18n._(t`\`${argSet}\` must be of type \`number\` not \`${typeSet}\`.`), - ) - } - let hasNextPageFilter = aql`FILTER TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)` - let hasPreviousPageFilter = aql`FILTER TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)` - if (typeof orderBy !== 'undefined') { - let hasNextPageDirection = aql`<` - let hasPreviousPageDirection = aql`>` - if (orderBy.direction === 'ASC') { - hasNextPageDirection = aql`>` - hasPreviousPageDirection = aql`<` + let includeSuperAdminOrgQuery = aql`` + if (!includeSuperAdminOrg) { + includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` } - let orgField = aql`` - let hasNextPageDocumentField = aql`` - let hasPreviousPageDocumentField = aql`` - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).acronym` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).acronym` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).acronym` - } else if (orderBy.field === 'name') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).name` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).name` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).name` - } else if (orderBy.field === 'slug') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).slug` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).slug` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).slug` - } else if (orderBy.field === 'zone') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).zone` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).zone` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).zone` - } else if (orderBy.field === 'sector') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).sector` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).sector` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).sector` - } else if (orderBy.field === 'country') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).country` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).country` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).country` - } else if (orderBy.field === 'province') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).province` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).province` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).province` - } else if (orderBy.field === 'city') { - orgField = aql`TRANSLATE(${language}, org.orgDetails).city` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).city` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).city` - } else if (orderBy.field === 'verified') { - orgField = aql`org.verified` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).verified` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).verified` - } else if (orderBy.field === 'summary-mail-pass') { - orgField = aql`org.summaries.mail.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.pass` - } else if (orderBy.field === 'summary-mail-fail') { - orgField = aql`org.summaries.mail.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.fail` - } else if (orderBy.field === 'summary-mail-total') { - orgField = aql`org.summaries.mail.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.mail.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.mail.total` - } else if (orderBy.field === 'summary-web-pass') { - orgField = aql`org.summaries.web.pass` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.pass` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.pass` - } else if (orderBy.field === 'summary-web-fail') { - orgField = aql`org.summaries.web.fail` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.fail` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.fail` - } else if (orderBy.field === 'summary-web-total') { - orgField = aql`org.summaries.web.total` - hasNextPageDocumentField = aql`LAST(retrievedOrgs).summaries.web.total` - hasPreviousPageDocumentField = aql`FIRST(retrievedOrgs).summaries.web.total` - } else if (orderBy.field === 'domain-count') { - orgField = aql`COUNT(orgDomains)` - hasNextPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND LAST(retrievedOrgs)._id claims RETURN e._to)` - hasPreviousPageDocumentField = aql`COUNT(FOR v, e IN 1..1 OUTBOUND FIRST(retrievedOrgs)._id claims RETURN e._to)` + let isVerifiedQuery = aql`` + if (isVerified) { + isVerifiedQuery = aql`FILTER org.verified == true` } - hasNextPageFilter = aql` - FILTER ${orgField} ${hasNextPageDirection} ${hasNextPageDocumentField} - OR (${orgField} == ${hasNextPageDocumentField} - AND TO_NUMBER(org._key) > TO_NUMBER(LAST(retrievedOrgs)._key)) - ` - hasPreviousPageFilter = aql` - FILTER ${orgField} ${hasPreviousPageDirection} ${hasPreviousPageDocumentField} - OR (${orgField} == ${hasPreviousPageDocumentField} - AND TO_NUMBER(org._key) < TO_NUMBER(FIRST(retrievedOrgs)._key)) - ` - } - - let sortByField = aql`` - if (typeof orderBy !== 'undefined') { - /* istanbul ignore else */ - if (orderBy.field === 'acronym') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).acronym ${orderBy.direction},` - } else if (orderBy.field === 'name') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).name ${orderBy.direction},` - } else if (orderBy.field === 'slug') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).slug ${orderBy.direction},` - } else if (orderBy.field === 'zone') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).zone ${orderBy.direction},` - } else if (orderBy.field === 'sector') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).sector ${orderBy.direction},` - } else if (orderBy.field === 'country') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).country ${orderBy.direction},` - } else if (orderBy.field === 'province') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).province ${orderBy.direction},` - } else if (orderBy.field === 'city') { - sortByField = aql`TRANSLATE(${language}, org.orgDetails).city ${orderBy.direction},` - } else if (orderBy.field === 'verified') { - sortByField = aql`org.verified ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-pass') { - sortByField = aql`org.summaries.mail.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-fail') { - sortByField = aql`org.summaries.mail.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-mail-total') { - sortByField = aql`org.summaries.mail.total ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-pass') { - sortByField = aql`org.summaries.web.pass ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-fail') { - sortByField = aql`org.summaries.web.fail ${orderBy.direction},` - } else if (orderBy.field === 'summary-web-total') { - sortByField = aql`org.summaries.web.total ${orderBy.direction},` - } else if (orderBy.field === 'domain-count') { - sortByField = aql`COUNT(orgDomains) ${orderBy.direction},` + let isAdminFilter = aql`` + if (isAdmin) { + isAdminFilter = aql`FILTER e.permission IN ["admin", "owner", "super_admin"]` } - } - - let sortString - if (typeof last !== 'undefined') { - sortString = aql`DESC` - } else { - sortString = aql`ASC` - } - - let includeSuperAdminOrgQuery = aql`` - if (!includeSuperAdminOrg) { - includeSuperAdminOrgQuery = aql`FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin"` - } - - let isVerifiedQuery = aql`` - if (isVerified) { - isVerifiedQuery = aql`FILTER org.verified == true` - } - let orgKeysQuery - if (isSuperAdmin) { - orgKeysQuery = aql` + let orgKeysQuery + if (isSuperAdmin) { + orgKeysQuery = aql` WITH claims, domains, organizations, organizationSearch LET orgKeys = ( FOR org IN organizations @@ -389,49 +364,51 @@ export const loadOrgConnectionsByUserId = ({ RETURN org._key ) ` - } else if (isAdmin) { - orgKeysQuery = aql` + } else if (!loginRequiredBool) { + orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users - LET orgKeys = ( - FOR org, e IN 1..1 - INBOUND ${userDBId} affiliations - FILTER e.permission == "admin" OR e.permission == "super_admin" - ${isVerifiedQuery} - ${includeSuperAdminOrgQuery} - RETURN org._key + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + ${isAdminFilter} + RETURN v ) - ` - } else { - if (!loginRequiredBool) { - orgKeysQuery = aql` - WITH claims, domains, organizations, organizationSearch LET orgKeys = ( FOR org IN organizations ${isVerifiedQuery} - FILTER org.orgDetails.en.slug != "super-admin" OR org.orgDetails.fr.slug != "super-admin" + ${includeSuperAdminOrgQuery} + FILTER org._key IN userAffiliations[*]._key ${isAdmin ? aql`` : aql`|| org.verified == true`} RETURN org._key ) - ` + ` } else { orgKeysQuery = aql` WITH affiliations, claims, domains, organizations, organizationSearch, users + LET userAffiliations = ( + FOR v, e IN 1..1 ANY ${userDBId} affiliations + FILTER e.permission != "pending" + ${isAdminFilter} + RETURN v + ) + LET hasVerifiedOrgAffiliation = POSITION(userAffiliations[*].verified, true) LET orgKeys = ( - FOR org, e IN 1..1 - INBOUND ${userDBId} affiliations - ${isVerifiedQuery} - ${includeSuperAdminOrgQuery} - RETURN org._key + FOR org IN organizations + ${isVerifiedQuery} + ${includeSuperAdminOrgQuery} + FILTER org._key IN userAffiliations[*]._key ${ + isAdmin ? aql`` : aql`|| (org.verified == true && hasVerifiedOrgAffiliation == true)` + } + RETURN org._key ) ` } - } - let orgQuery = aql`` - let filterString = aql`FILTER org._key IN orgKeys` - let totalCount = aql`LENGTH(orgKeys)` - if (typeof search !== 'undefined' && search !== '') { - search = cleanseInput(search) - orgQuery = aql` + let orgQuery = aql`` + let filterString = aql`FILTER org._key IN orgKeys` + let totalCount = aql`LENGTH(orgKeys)` + if (typeof search !== 'undefined' && search !== '') { + search = cleanseInput(search) + orgQuery = aql` LET tokenArrEN = TOKENS(${search}, "text_en") LET searchedOrgsEN = FLATTEN(UNIQUE( FOR token IN tokenArrEN @@ -456,13 +433,13 @@ export const loadOrgConnectionsByUserId = ({ )) LET searchedOrgs = UNION_DISTINCT(searchedOrgsEN, searchedOrgsFR) ` - filterString = aql`FILTER org._key IN searchedOrgs` - totalCount = aql`LENGTH(searchedOrgs)` - } + filterString = aql`FILTER org._key IN searchedOrgs` + totalCount = aql`LENGTH(searchedOrgs)` + } - let organizationInfoCursor - try { - organizationInfoCursor = await query` + let organizationInfoCursor + try { + organizationInfoCursor = await query` ${orgKeysQuery} ${orgQuery} @@ -521,55 +498,51 @@ export const loadOrgConnectionsByUserId = ({ "endKey": LAST(retrievedOrgs)._key } ` - } catch (err) { - console.error( - `Database error occurred while user: ${userKey} was trying to query organizations in loadOrgConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } + } catch (err) { + console.error( + `Database error occurred while user: ${userKey} was trying to query organizations in loadOrgConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } - let organizationInfo - try { - organizationInfo = await organizationInfoCursor.next() - } catch (err) { - console.error( - `Cursor error occurred while user: ${userKey} was trying to gather organizations in loadOrgConnectionsByUserId, error: ${err}`, - ) - throw new Error( - i18n._(t`Unable to load organization(s). Please try again.`), - ) - } + let organizationInfo + try { + organizationInfo = await organizationInfoCursor.next() + } catch (err) { + console.error( + `Cursor error occurred while user: ${userKey} was trying to gather organizations in loadOrgConnectionsByUserId, error: ${err}`, + ) + throw new Error(i18n._(t`Unable to load organization(s). Please try again.`)) + } - if (organizationInfo.organizations.length === 0) { - return { - edges: [], - totalCount: 0, - pageInfo: { - hasNextPage: false, - hasPreviousPage: false, - startCursor: '', - endCursor: '', - }, + if (organizationInfo.organizations.length === 0) { + return { + edges: [], + totalCount: 0, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + } } - } - const edges = organizationInfo.organizations.map((org) => { + const edges = organizationInfo.organizations.map((org) => { + return { + cursor: toGlobalId('organization', org._key), + node: org, + } + }) + return { - cursor: toGlobalId('organization', org._key), - node: org, + edges, + totalCount: organizationInfo.totalCount, + pageInfo: { + hasNextPage: organizationInfo.hasNextPage, + hasPreviousPage: organizationInfo.hasPreviousPage, + startCursor: toGlobalId('organization', organizationInfo.startKey), + endCursor: toGlobalId('organization', organizationInfo.endKey), + }, } - }) - - return { - edges, - totalCount: organizationInfo.totalCount, - pageInfo: { - hasNextPage: organizationInfo.hasNextPage, - hasPreviousPage: organizationInfo.hasPreviousPage, - startCursor: toGlobalId('organization', organizationInfo.startKey), - endCursor: toGlobalId('organization', organizationInfo.endKey), - }, } -} diff --git a/api/src/organization/mutations/__tests__/verify-organization.test.js b/api/src/organization/mutations/__tests__/verify-organization.test.js index f1f4059648..7f7eaac007 100644 --- a/api/src/organization/mutations/__tests__/verify-organization.test.js +++ b/api/src/organization/mutations/__tests__/verify-organization.test.js @@ -182,8 +182,7 @@ describe('removing an organization', () => { data: { verifyOrganization: { result: { - status: - 'Successfully verified organization: treasury-board-secretariat.', + status: 'Successfully verified organization: treasury-board-secretariat.', organization: { name: 'Treasury Board of Canada Secretariat', }, @@ -193,9 +192,7 @@ describe('removing an organization', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key}, successfully verified org: ${org._key}.`]) const orgLoader = loadOrgByKey({ query, @@ -294,8 +291,7 @@ describe('removing an organization', () => { data: { verifyOrganization: { result: { - status: - "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", + status: "Envoi réussi de l'invitation au service, et de l'email de l'organisation.", organization: { name: 'Secrétariat du Conseil Trésor du Canada', }, @@ -305,9 +301,7 @@ describe('removing an organization', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key}, successfully verified org: ${org._key}.`, - ]) + expect(consoleOutput).toEqual([`User: ${user._key}, successfully verified org: ${org._key}.`]) const orgLoader = loadOrgByKey({ query, @@ -637,9 +631,7 @@ describe('removing an organization', () => { query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -658,11 +650,7 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to verify organization. Please try again.')] expect(response.errors).toEqual(error) @@ -703,10 +691,7 @@ describe('removing an organization', () => { query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -725,16 +710,12 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to verify organization. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when clearing owners for org: 123: Error: trx step error`, + `Transaction error occurred while upserting verified org: 123, err: Error: trx step error`, ]) }) }) @@ -772,9 +753,7 @@ describe('removing an organization', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, auth: { @@ -793,11 +772,7 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - 'Unable to verify organization. Please try again.', - ), - ] + const error = [new GraphQLError('Unable to verify organization. Please try again.')] expect(response.errors).toEqual(error) @@ -875,8 +850,7 @@ describe('removing an organization', () => { verifyOrganization: { result: { code: 400, - description: - 'Impossible de vérifier une organisation inconnue.', + description: 'Impossible de vérifier une organisation inconnue.', }, }, }, @@ -1124,9 +1098,7 @@ describe('removing an organization', () => { query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -1145,11 +1117,7 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de vérifier l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) @@ -1190,10 +1158,7 @@ describe('removing an organization', () => { query, collections: collectionNames, transaction: jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), }), userKey: 123, auth: { @@ -1212,16 +1177,12 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de vérifier l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Trx step error occurred when clearing owners for org: 123: Error: trx step error`, + `Transaction error occurred while upserting verified org: 123, err: Error: trx step error`, ]) }) }) @@ -1259,9 +1220,7 @@ describe('removing an organization', () => { collections: collectionNames, transaction: jest.fn().mockReturnValue({ step: jest.fn().mockReturnValue(), - commit: jest - .fn() - .mockRejectedValue(new Error('trx commit error')), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }), userKey: 123, auth: { @@ -1280,11 +1239,7 @@ describe('removing an organization', () => { }, ) - const error = [ - new GraphQLError( - "Impossible de vérifier l'organisation. Veuillez réessayer.", - ), - ] + const error = [new GraphQLError("Impossible de vérifier l'organisation. Veuillez réessayer.")] expect(response.errors).toEqual(error) diff --git a/api/src/organization/mutations/create-organization.js b/api/src/organization/mutations/create-organization.js index 2fef6741d5..3fadac03a5 100644 --- a/api/src/organization/mutations/create-organization.js +++ b/api/src/organization/mutations/create-organization.js @@ -8,8 +8,7 @@ import { logActivity } from '../../audit-logs/mutations/log-activity' export const createOrganization = new mutationWithClientMutationId({ name: 'CreateOrganization', - description: - 'This mutation allows the creation of an organization inside the database.', + description: 'This mutation allows the creation of an organization inside the database.', inputFields: () => ({ acronymEN: { type: GraphQLNonNull(Acronym), @@ -29,60 +28,49 @@ export const createOrganization = new mutationWithClientMutationId({ }, zoneEN: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the zone the organization belongs to.', + description: 'The English translation of the zone the organization belongs to.', }, zoneFR: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the zone the organization belongs to.', + description: 'The English translation of the zone the organization belongs to.', }, sectorEN: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the sector the organization belongs to.', + description: 'The English translation of the sector the organization belongs to.', }, sectorFR: { type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the sector the organization belongs to.', + description: 'The French translation of the sector the organization belongs to.', }, countryEN: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the country the organization resides in.', + description: 'The English translation of the country the organization resides in.', }, countryFR: { type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the country the organization resides in.', + description: 'The French translation of the country the organization resides in.', }, provinceEN: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the province the organization resides in.', + description: 'The English translation of the province the organization resides in.', }, provinceFR: { type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the province the organization resides in.', + description: 'The French translation of the province the organization resides in.', }, cityEN: { type: GraphQLNonNull(GraphQLString), - description: - 'The English translation of the city the organization resides in.', + description: 'The English translation of the city the organization resides in.', }, cityFR: { type: GraphQLNonNull(GraphQLString), - description: - 'The French translation of the city the organization resides in.', + description: 'The French translation of the city the organization resides in.', }, }), outputFields: () => ({ result: { type: createOrganizationUnion, - description: - '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', + description: '`CreateOrganizationUnion` returning either an `Organization`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -129,15 +117,11 @@ export const createOrganization = new mutationWithClientMutationId({ const [orgEN, orgFR] = await loadOrgBySlug.loadMany([slugEN, slugFR]) if (typeof orgEN !== 'undefined' || typeof orgFR !== 'undefined') { - console.warn( - `User: ${userKey} attempted to create an organization that already exists: ${slugEN}`, - ) + console.warn(`User: ${userKey} attempted to create an organization that already exists: ${slugEN}`) return { _type: 'error', code: 400, - description: i18n._( - t`Organization name already in use. Please try again with a different name.`, - ), + description: i18n._(t`Organization name already in use. Please try again with a different name.`), } } @@ -194,12 +178,8 @@ export const createOrganization = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Transaction error occurred when user: ${userKey} was creating new organization ${slugEN}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) + console.error(`Transaction error occurred when user: ${userKey} was creating new organization ${slugEN}: ${err}`) + throw new Error(i18n._(t`Unable to create organization. Please try again.`)) } const organization = await cursor.next() @@ -211,8 +191,7 @@ export const createOrganization = new mutationWithClientMutationId({ INSERT { _from: ${organization._id}, _to: ${user._id}, - permission: "admin", - owner: true + permission: "owner", } INTO affiliations `, ) @@ -220,9 +199,7 @@ export const createOrganization = new mutationWithClientMutationId({ console.error( `Transaction error occurred when inserting edge definition for user: ${userKey} to ${slugEN}: ${err}`, ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to create organization. Please try again.`)) } try { @@ -231,14 +208,10 @@ export const createOrganization = new mutationWithClientMutationId({ console.error( `Transaction error occurred when committing new organization: ${slugEN} for user: ${userKey} to db: ${err}`, ) - throw new Error( - i18n._(t`Unable to create organization. Please try again.`), - ) + throw new Error(i18n._(t`Unable to create organization. Please try again.`)) } - console.info( - `User: ${userKey} successfully created a new organization: ${slugEN}`, - ) + console.info(`User: ${userKey} successfully created a new organization: ${slugEN}`) await logActivity({ transaction, collections, diff --git a/api/src/organization/mutations/verify-organization.js b/api/src/organization/mutations/verify-organization.js index 24d65f0a28..ebac063cca 100644 --- a/api/src/organization/mutations/verify-organization.js +++ b/api/src/organization/mutations/verify-organization.js @@ -16,8 +16,7 @@ export const verifyOrganization = new mutationWithClientMutationId({ outputFields: () => ({ result: { type: verifyOrganizationUnion, - description: - '`VerifyOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', + description: '`VerifyOrganizationUnion` returning either an `OrganizationResult`, or `OrganizationError` object.', resolve: (payload) => payload, }, }), @@ -102,54 +101,22 @@ export const verifyOrganization = new mutationWithClientMutationId({ `, ) } catch (err) { - console.error( - `Transaction error occurred while upserting verified org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) - } - - // Set all affiliation owner fields to false - try { - await trx.step( - () => query` - WITH affiliations, organizations, users - FOR v, e IN 1..1 OUTBOUND ${currentOrg._id} affiliations - UPSERT { _key: e._key } - INSERT { owner: false } - UPDATE { owner: false } - IN affiliations - RETURN e - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when clearing owners for org: ${orgKey}: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) + console.error(`Transaction error occurred while upserting verified org: ${orgKey}, err: ${err}`) + throw new Error(i18n._(t`Unable to verify organization. Please try again.`)) } try { await trx.commit() } catch (err) { - console.error( - `Transaction error occurred while committing newly verified org: ${orgKey}, err: ${err}`, - ) - throw new Error( - i18n._(t`Unable to verify organization. Please try again.`), - ) + console.error(`Transaction error occurred while committing newly verified org: ${orgKey}, err: ${err}`) + throw new Error(i18n._(t`Unable to verify organization. Please try again.`)) } console.info(`User: ${userKey}, successfully verified org: ${orgKey}.`) return { _type: 'result', - status: i18n._( - t`Successfully verified organization: ${currentOrg.slug}.`, - ), + status: i18n._(t`Successfully verified organization: ${currentOrg.slug}.`), organization: currentOrg, } }, diff --git a/api/src/organization/objects/organization.js b/api/src/organization/objects/organization.js index 5d70560883..f94031d5d0 100644 --- a/api/src/organization/objects/organization.js +++ b/api/src/organization/objects/organization.js @@ -9,6 +9,7 @@ import { affiliationUserOrder } from '../../affiliation/inputs' import { affiliationConnection } from '../../affiliation/objects' import { domainOrder, domainFilter } from '../../domain/inputs' import { domainConnection } from '../../domain/objects' +import { logActivity } from '../../audit-logs' export const organizationType = new GraphQLObjectType({ name: 'Organization', @@ -73,7 +74,30 @@ export const organizationType = new GraphQLObjectType({ type: GraphQLString, description: 'CSV formatted output of all domains in the organization including their email and web scan statuses.', - resolve: async ({ _id }, _args, { loaders: { loadOrganizationDomainStatuses } }) => { + resolve: async ( + { _id }, + _args, + { + i18n, + userKey, + query, + transaction, + collections, + auth: { checkPermission, userRequired, verifiedRequired }, + loaders: { loadOrganizationDomainStatuses }, + }, + ) => { + const user = await userRequired() + verifiedRequired({ user }) + + const permission = await checkPermission({ orgId: _id }) + if (!['user', 'admin', 'owner', 'super_admin'].includes(permission)) { + console.error( + `User "${userKey}" attempted to retrieve CSV output for organization "${_id}". Permission: ${permission}`, + ) + throw new Error(t`Permission Denied: Please contact organization user for help with retrieving this domain.`) + } + const domains = await loadOrganizationDomainStatuses({ orgId: _id, }) @@ -97,6 +121,57 @@ export const organizationType = new GraphQLObjectType({ }, '') csvOutput += `\n${csvLine}` }) + + // Get org names to use in activity log + let orgNamesCursor + try { + orgNamesCursor = await query` + LET org = DOCUMENT(organizations, ${_id}) + RETURN { + "orgNameEN": org.orgDetails.en.name, + "orgNameFR": org.orgDetails.fr.name, + } + ` + } catch (err) { + console.error( + `Database error occurred when user: ${userKey} attempted to export org: ${_id}. Error while creating cursor for retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to export organization. Please try again.`)) + } + + let orgNames + try { + orgNames = await orgNamesCursor.next() + } catch (err) { + console.error( + `Cursor error occurred when user: ${userKey} attempted to export org: ${_id}. Error while retrieving organization names. error: ${err}`, + ) + throw new Error(i18n._(t`Unable to export organization. Please try again.`)) + } + + await logActivity({ + transaction, + collections, + query, + initiatedBy: { + id: user._key, + userName: user.userName, + role: permission, + }, + action: 'export', + target: { + resource: { + en: orgNames.orgNameEN, + fr: orgNames.orgNameFR, + }, + organization: { + id: _id, + name: orgNames.orgNameEN, + }, // name of resource being acted upon + resourceType: 'organization', + }, + }) + return csvOutput }, }, @@ -162,14 +237,24 @@ export const organizationType = new GraphQLObjectType({ { i18n, auth: { checkPermission }, loaders: { loadAffiliationConnectionsByOrgId } }, ) => { const permission = await checkPermission({ orgId: _id }) - if (permission === 'admin' || permission === 'super_admin') { - const affiliations = await loadAffiliationConnectionsByOrgId({ - orgId: _id, - ...args, - }) - return affiliations + if (['admin', 'owner', 'super_admin'].includes(permission) === false) { + throw new Error(i18n._(t`Cannot query affiliations on organization without admin permission or higher.`)) } - throw new Error(i18n._(t`Cannot query affiliations on organization without admin permission or higher.`)) + + const affiliations = await loadAffiliationConnectionsByOrgId({ + orgId: _id, + ...args, + }) + return affiliations + }, + }, + userHasPermission: { + type: GraphQLBoolean, + description: + 'Value that determines if a user is affiliated with an organization, whether through organization affiliation, verified affiliation, or through super admin status.', + resolve: async ({ _id }, _args, { auth: { checkPermission } }) => { + const permission = await checkPermission({ orgId: _id }) + return ['user', 'admin', 'super_admin', 'owner'].includes(permission) }, }, }), diff --git a/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js index 07fc951564..269a63bca8 100644 --- a/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js +++ b/api/src/organization/queries/__tests__/get-all-organization-domain-statuses.test.js @@ -14,18 +14,7 @@ import frenchMessages from '../../../locale/fr/messages' const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given getAllOrganizationDomainStatuses', () => { - let query, - drop, - truncate, - schema, - collections, - orgOne, - orgTwo, - superAdminOrg, - domainOne, - domainTwo, - i18n, - user + let query, drop, truncate, schema, collections, orgOne, orgTwo, superAdminOrg, domainOne, domainTwo, i18n, user const consoleOutput = [] const mockedInfo = (output) => consoleOutput.push(output) @@ -198,7 +187,7 @@ describe('given getAllOrganizationDomainStatuses', () => { loginRequiredBool = false }) describe('the user is not a super admin', () => { - it('returns all domain status results', async () => { + it('returns a permission error', async () => { const response = await graphql( schema, ` @@ -229,25 +218,20 @@ describe('given getAllOrganizationDomainStatuses', () => { loginRequiredBool: loginRequiredBool, }, loaders: { - loadAllOrganizationDomainStatuses: - loadAllOrganizationDomainStatuses({ - query, - userKey: user._key, - i18n, - }), + loadAllOrganizationDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }), }, }, ) - const expectedResponse = { - data: { - getAllOrganizationDomainStatuses: `Organization name (English),Nom de l'organisation (Français),Domain,HTTPS,HSTS,Ciphers,Curves,Protocols,SPF,DKIM,DMARC -"Definitely Treasury Board of Canada Secretariat","Définitivement Secrétariat du Conseil du Trésor du Canada","domain.one","fail","pass","pass","pass","pass","pass","pass","pass" -"Not Treasury Board of Canada Secretariat","Ne Pas Secrétariat du Conseil Trésor du Canada","domain.two","pass","fail","fail","pass","fail","pass","pass","fail"`, - }, - } - expect(response).toEqual(expectedResponse) + const error = [ + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), + ] + expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved all domain statuses.`, + `User: ${user._key} attempted to load all organization statuses but login is required and they are not a super admin.`, ]) }) }) @@ -291,12 +275,11 @@ describe('given getAllOrganizationDomainStatuses', () => { loginRequiredBool: loginRequiredBool, }, loaders: { - loadAllOrganizationDomainStatuses: - loadAllOrganizationDomainStatuses({ - query, - userKey: user._key, - i18n, - }), + loadAllOrganizationDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }), }, }, ) @@ -310,9 +293,7 @@ describe('given getAllOrganizationDomainStatuses', () => { } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved all domain statuses.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all domain statuses.`]) }) }) }) @@ -352,19 +333,16 @@ describe('given getAllOrganizationDomainStatuses', () => { loginRequiredBool: loginRequiredBool, }, loaders: { - loadAllOrganizationDomainStatuses: - loadAllOrganizationDomainStatuses({ - query, - userKey: user._key, - i18n, - }), + loadAllOrganizationDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }), }, }, ) const error = [ - new GraphQLError( - 'Permissions error. You do not have sufficient permissions to access this data.', - ), + new GraphQLError('Permissions error. You do not have sufficient permissions to access this data.'), ] expect(response.errors).toEqual(error) @@ -413,12 +391,11 @@ describe('given getAllOrganizationDomainStatuses', () => { loginRequiredBool: loginRequiredBool, }, loaders: { - loadAllOrganizationDomainStatuses: - loadAllOrganizationDomainStatuses({ - query, - userKey: user._key, - i18n, - }), + loadAllOrganizationDomainStatuses: loadAllOrganizationDomainStatuses({ + query, + userKey: user._key, + i18n, + }), }, }, ) @@ -430,9 +407,7 @@ describe('given getAllOrganizationDomainStatuses', () => { }, } expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User ${user._key} successfully retrieved all domain statuses.`, - ]) + expect(consoleOutput).toEqual([`User ${user._key} successfully retrieved all domain statuses.`]) }) }) }) diff --git a/api/src/organization/queries/find-organization-by-slug.js b/api/src/organization/queries/find-organization-by-slug.js index 16ef398c82..1fde3c47c4 100644 --- a/api/src/organization/queries/find-organization-by-slug.js +++ b/api/src/organization/queries/find-organization-by-slug.js @@ -6,13 +6,11 @@ const { organizationType } = require('../objects') export const findOrganizationBySlug = { type: organizationType, - description: - 'Select all information on a selected organization that a user has access to.', + description: 'Select all information on a selected organization that a user has access to.', args: { orgSlug: { type: GraphQLNonNull(Slug), - description: - 'The slugified organization name you want to retrieve data for.', + description: 'The slugified organization name you want to retrieve data for.', }, }, resolve: async ( @@ -21,12 +19,7 @@ export const findOrganizationBySlug = { { i18n, userKey, - auth: { - checkPermission, - userRequired, - verifiedRequired, - loginRequiredBool, - }, + auth: { checkPermission, userRequired, verifiedRequired, loginRequiredBool }, loaders: { loadOrgBySlug }, validators: { cleanseInput }, }, @@ -45,42 +38,25 @@ export const findOrganizationBySlug = { if (typeof org === 'undefined') { console.warn(`User ${userKey} could not retrieve organization.`) - throw new Error( - i18n._(t`No organization with the provided slug could be found.`), - ) + throw new Error(i18n._(t`No organization with the provided slug could be found.`)) } - if (loginRequiredBool) { - // Check user permission for organization access - const permission = await checkPermission({ orgId: org._id }) + // Check user permission for organization access + const permission = await checkPermission({ orgId: org._id }) - if (!['super_admin', 'admin', 'user'].includes(permission)) { + if (loginRequiredBool) { + if (!['user', 'admin', 'owner', 'super_admin'].includes(permission)) { console.warn(`User ${userKey} could not retrieve organization.`) - throw new Error( - i18n._( - t`Permission Denied: Could not retrieve specified organization.`, - ), - ) + throw new Error(i18n._(t`Permission Denied: Could not retrieve specified organization.`)) } } else { - if (org.slugEN === 'super-admin' || org.slugFR === 'super-admin') { - // Check user permission for super-admin organization access - const permission = await checkPermission({ orgId: org._id }) - - if (!['super_admin', 'admin', 'user'].includes(permission)) { - console.warn(`User ${userKey} could not retrieve organization.`) - throw new Error( - i18n._( - t`Permission Denied: Could not retrieve specified organization.`, - ), - ) - } + if (org.verified !== true && !['user', 'admin', 'owner', 'super_admin'].includes(permission)) { + console.warn(`User ${userKey} could not retrieve organization.`) + throw new Error(i18n._(t`Permission Denied: Could not retrieve specified organization.`)) } } - console.info( - `User ${userKey} successfully retrieved organization ${org._key}.`, - ) + console.info(`User ${userKey} successfully retrieved organization ${org._key}.`) org.id = org._key return org }, diff --git a/api/src/organization/queries/get-all-organization-domain-statuses.js b/api/src/organization/queries/get-all-organization-domain-statuses.js index b4cd0691bc..61b7c45a84 100644 --- a/api/src/organization/queries/get-all-organization-domain-statuses.js +++ b/api/src/organization/queries/get-all-organization-domain-statuses.js @@ -4,39 +4,27 @@ import { t } from '@lingui/macro' export const getAllOrganizationDomainStatuses = { type: GraphQLString, - description: - 'CSV formatted output of all domains in all organizations including their email and web scan statuses.', + description: 'CSV formatted output of all domains in all organizations including their email and web scan statuses.', resolve: async ( _, _args, { userKey, i18n, - auth: { - checkSuperAdmin, - userRequired, - verifiedRequired, - loginRequiredBool, - }, + auth: { checkSuperAdmin, userRequired, verifiedRequired }, loaders: { loadAllOrganizationDomainStatuses }, }, ) => { - if (loginRequiredBool) { - const user = await userRequired() - verifiedRequired({ user }) + const user = await userRequired() + verifiedRequired({ user }) - const isSuperAdmin = await checkSuperAdmin() + const isSuperAdmin = await checkSuperAdmin() - if (!isSuperAdmin) { - console.warn( - `User: ${userKey} attempted to load all organization statuses but login is required and they are not a super admin.`, - ) - throw new Error( - i18n._( - t`Permissions error. You do not have sufficient permissions to access this data.`, - ), - ) - } + if (!isSuperAdmin) { + console.warn( + `User: ${userKey} attempted to load all organization statuses but login is required and they are not a super admin.`, + ) + throw new Error(i18n._(t`Permissions error. You do not have sufficient permissions to access this data.`)) } const domainStatuses = await loadAllOrganizationDomainStatuses() @@ -60,9 +48,7 @@ export const getAllOrganizationDomainStatuses = { ] let csvOutput = headers.join(',') domainStatuses.forEach((domainStatus) => { - const csvLine = headers - .map((header) => `"${domainStatus[header]}"`) - .join(',') + const csvLine = headers.map((header) => `"${domainStatus[header]}"`).join(',') csvOutput += `\n${csvLine}` }) diff --git a/api/src/user/mutations/__tests__/close-account.test.js b/api/src/user/mutations/__tests__/close-account.test.js index b23a448cfb..be1356e126 100644 --- a/api/src/user/mutations/__tests__/close-account.test.js +++ b/api/src/user/mutations/__tests__/close-account.test.js @@ -1,32 +1,23 @@ -import {setupI18n} from '@lingui/core' -import {ensure, dbNameFromFile} from 'arango-tools' -import {graphql, GraphQLSchema, GraphQLError} from 'graphql' -import {toGlobalId} from 'graphql-relay' +import { setupI18n } from '@lingui/core' +import { ensure, dbNameFromFile } from 'arango-tools' +import { graphql, GraphQLSchema, GraphQLError } from 'graphql' +import { toGlobalId } from 'graphql-relay' import englishMessages from '../../../locale/en/messages' import frenchMessages from '../../../locale/fr/messages' -import {checkSuperAdmin, userRequired} from '../../../auth' -import {loadOrgByKey} from '../../../organization/loaders' -import {loadUserByKey} from '../../../user/loaders' -import {cleanseInput} from '../../../validators' -import {createMutationSchema} from '../../../mutation' -import {createQuerySchema} from '../../../query' +import { checkSuperAdmin, userRequired } from '../../../auth' +import { loadOrgByKey } from '../../../organization/loaders' +import { loadUserByKey } from '../../../user/loaders' +import { cleanseInput } from '../../../validators' +import { createMutationSchema } from '../../../mutation' +import { createQuerySchema } from '../../../query' import dbschema from '../../../../database.json' import { collectionNames } from '../../../collection-names' -const {DB_PASS: rootPass, DB_URL: url} = process.env +const { DB_PASS: rootPass, DB_URL: url } = process.env describe('given the closeAccount mutation', () => { - let i18n, - query, - drop, - truncate, - schema, - collections, - transaction, - user, - org, - domain + let i18n, query, drop, truncate, schema, collections, transaction, user, org, domain const consoleOutput = [] const mockedConsole = (output) => consoleOutput.push(output) @@ -41,8 +32,8 @@ describe('given the closeAccount mutation', () => { i18n = setupI18n({ locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -57,7 +48,7 @@ describe('given the closeAccount mutation', () => { describe('given a successful closing of an account', () => { beforeEach(async () => { - ;({query, drop, truncate, collections, transaction} = await ensure({ + ;({ query, drop, truncate, collections, transaction } = await ensure({ variables: { dbname: dbNameFromFile(__filename), username: 'root', @@ -140,3833 +131,580 @@ describe('given the closeAccount mutation', () => { _to: dmarcSummary._id, }) }) - describe('user is an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: true, - }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', }) - describe('org is owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } + }) + it('removes the users affiliations', async () => { + await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } - `, - null, - { + } + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ i18n, + userKey: user._key, query, - collections: collectionNames, - transaction, + }), + userRequired: userRequired({ + i18n, userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + ) - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) + const testAffiliationCursor = await query` + FOR aff IN affiliations + OPTIONS { waitForSync: true } + RETURN aff + ` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) - it('removes ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } + }) + it('returns a status message', async () => { + const response = await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ query, - }), - userRequired: userRequired({ - i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', i18n, - userKey: user._key, }), - }, - validators: {cleanseInput}, + }), }, - ) + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + ) - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const expectedResponse = { + data: { + closeAccount: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) - describe('org is not owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) + }) + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) - it('does not remove dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } + }) + it('returns a status message', async () => { + const response = await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ query, - }), - userRequired: userRequired({ - i18n, userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', i18n, - userKey: user._key, }), - }, - validators: {cleanseInput}, + }), }, - ) + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + }, + validators: { cleanseInput }, + }, + ) - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` + const expectedResponse = { + data: { + closeAccount: { + result: { + status: 'Le compte a été fermé avec succès.', + }, + }, + }, + } - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) - describe('org is the only one claiming a domain', () => { - it('removes web scan result data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } + }) + it('closes the users account', async () => { + await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } - `, - null, - { + } + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ i18n, - query, - collections: collectionNames, - transaction, userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testWebScanCursor = - await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` - const testWebScan = await testWebScanCursor.next() - expect(testWebScan).toEqual(undefined) - - }) - it('removes scan data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { + query, + }), + userRequired: userRequired({ i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ query, - collections: collectionNames, - transaction, + language: 'en', + i18n, userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - - const testDNSCursor = - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` - const testDNS = await testDNSCursor.next() - expect(testDNS).toEqual(undefined) + }), + }, + validators: { cleanseInput }, + }, + ) - const testWebCursor = - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - const testWeb = await testWebCursor.next() - expect(testWeb).toEqual(undefined) + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - }) - it('removes claims', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - const testOrg = await testClaimCursor.next() - expect(testOrg).toEqual(undefined) - }) - it('removes domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.claims.save({ - _from: org2._id, - _to: domain._id, - }) - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes the claim', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = await query` - FOR claim IN claims - OPTIONS { waitForSync: true } - FILTER claim._from == ${org._id} - RETURN claim - ` - const testClaim = await testClaimCursor.next() - expect(testClaim).toEqual(undefined) - }) - }) - it('removes affiliated users and org', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - describe('user belongs to multiple orgs', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes requesting users remaining affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes the users affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Successfully closed account.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Le compte a été fermé avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - it('closes the users account', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - - const testUserCursor = - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - const testUser = await testUserCursor.next() - expect(testUser).toEqual(undefined) - }) - }) - describe('super admin is closing another users account', () => { - let superAdmin, superAdminOrg - beforeEach(async () => { - superAdmin = await collections.users.save({ - userName: 'super.admin@istio.actually.exists', - emailValidated: true, - }) - superAdminOrg = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'super-admin', - acronym: 'SA', - name: 'Super Admin', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'super-admin', - acronym: 'SA', - name: 'Super Admin', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: superAdminOrg._id, - _to: superAdmin._id, - permission: 'super_admin', - owner: false, - }) - user = await collections.users.save({ - userName: 'test.account@istio.actually.exists', - emailValidated: true, - }) - org = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - domain = await collections.domains.save({ - domain: 'test.gc.ca', - slug: 'test-gc-ca', - }) - await collections.claims.save({ - _from: org._id, - _to: domain._id, - }) - - const dns = await collections.dns.save({ dns: true }) - await collections.domainsDNS.save({ - _from: domain._id, - _to: dns._id, - }) - - const web = await collections.web.save({ web: true }) - await collections.domainsWeb.save({ - _from: domain._id, - _to: web._id, - }) - - const webScan = await collections.webScan.save({ - webScan: true, - }) - await collections.webToWebScans.save({ - _from: web._id, - _to: webScan._id, - }) - - const dmarcSummary = await collections.dmarcSummaries.save({ - dmarcSummary: true, - }) - await collections.domainsToDmarcSummaries.save({ - _from: domain._id, - _to: dmarcSummary._id, - }) - }) - describe('user is an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'admin', - owner: true, - }) - }) - describe('org is owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: org._id, - _to: domain._id, - }) - }) - it('removes dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toEqual(undefined) - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toEqual(undefined) - }) - it('removes ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toEqual(undefined) - }) - }) - describe('org is not owner of a domain', () => { - beforeEach(async () => { - await collections.ownership.save({ - _from: 'organizations/1', - _to: domain._id, - }) - }) - it('does not remove dmarc summary data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - - const testDmarcSummaryCursor = - await query`FOR dmarcSum IN dmarcSummaries OPTIONS { waitForSync: true } RETURN dmarcSum` - const testDmarcSummary = await testDmarcSummaryCursor.next() - expect(testDmarcSummary).toBeDefined() - - const testDomainsToDmarcSumCursor = - await query`FOR item IN domainsToDmarcSummaries OPTIONS { waitForSync: true } RETURN item` - const testDomainsToDmarcSum = - await testDomainsToDmarcSumCursor.next() - expect(testDomainsToDmarcSum).toBeDefined() - }) - it('does not remove ownership', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - - const testOwnershipCursor = - await query`FOR owner IN ownership OPTIONS { waitForSync: true } RETURN owner` - const testOwnership = await testOwnershipCursor.next() - expect(testOwnership).toBeDefined() - }) - }) - describe('org is the only one claiming a domain', () => { - it('removes web scan result data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - const testWebScanCursor = - await query`FOR wScan IN webScan OPTIONS { waitForSync: true } RETURN wScan` - const testWebScan = await testWebScanCursor.next() - expect(testWebScan).toEqual(undefined) - - }) - it('removes scan data', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - - const testDNSCursor = - await query`FOR dnsResult IN dns OPTIONS { waitForSync: true } RETURN dnsResult` - const testDNS = await testDNSCursor.next() - expect(testDNS).toEqual(undefined) - - const testWebCursor = - await query`FOR webResult IN web OPTIONS { waitForSync: true } RETURN webResult` - const testWeb = await testWebCursor.next() - expect(testWeb).toEqual(undefined) - - }) - it('removes claims', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - const testOrg = await testClaimCursor.next() - expect(testOrg).toEqual(undefined) - }) - it('removes domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: superAdmin._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toEqual(undefined) - }) - }) - describe('multiple orgs claim the domain', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.claims.save({ - _from: org2._id, - _to: domain._id, - }) - }) - it('does not remove the domain', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - - const testDomainCursor = - await query`FOR domain IN domains OPTIONS { waitForSync: true } RETURN domain` - const testDomain = await testDomainCursor.next() - expect(testDomain).toBeDefined() - }) - it('removes the claim', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR claim IN claims OPTIONS { waitForSync: true } RETURN claim` - - const testClaimCursor = await query` - FOR claim IN claims - OPTIONS { waitForSync: true } - FILTER claim._from == ${org._id} - RETURN claim - ` - const testClaim = await testClaimCursor.next() - expect(testClaim).toEqual(undefined) - }) - }) - describe('user belongs to multiple orgs', () => { - let org2 - beforeEach(async () => { - org2 = await collections.organizations.save({ - orgDetails: { - en: { - slug: 'treasury-board-secretariat-2', - acronym: 'TBS', - name: 'Treasury Board of Canada Secretariat', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - fr: { - slug: 'secretariat-conseil-tresor-2', - acronym: 'SCT', - name: 'Secrétariat du Conseil Trésor du Canada', - zone: 'FED', - sector: 'TBS', - country: 'Canada', - province: 'Ontario', - city: 'Ottawa', - }, - }, - }) - await collections.affiliations.save({ - _from: org2._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes requesting users remaining affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - it('removes affiliated users and org', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - await query`FOR org IN organizations OPTIONS { waitForSync: true } RETURN org` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - - const testOrgCursor = await query` - FOR org IN organizations - OPTIONS { waitForSync: true } - FILTER org._key != ${superAdminOrg._key} - RETURN org - ` - const testOrg = await testOrgCursor.next() - expect(testOrg).toEqual(undefined) - }) - }) - describe('user is not an org owner', () => { - beforeEach(async () => { - await collections.affiliations.save({ - _from: org._id, - _to: user._id, - permission: 'user', - owner: false, - }) - }) - it('removes the users affiliations', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - - const testAffiliationCursor = await query` - FOR aff IN affiliations - OPTIONS { waitForSync: true } - FILTER aff._from != ${superAdminOrg._id} - RETURN aff - ` - const testAffiliation = await testAffiliationCursor.next() - expect(testAffiliation).toEqual(undefined) - }) - }) - describe('users language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Successfully closed account.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - describe('users language is set to french', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'fr', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - it('returns a status message', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: user._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: user._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: user._key, - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: user._key, - }), - loadUserByKey: loadUserByKey({ - query, - userKey: user._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - status: 'Le compte a été fermé avec succès.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: ${user._key} successfully closed user: ${user._id} account.`, - ]) - }) - }) - it('closes the users account', async () => { - await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', user._key)}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: superAdmin._key, - auth: { - checkSuperAdmin: checkSuperAdmin({ - i18n, - userKey: superAdmin._key, - query, - }), - userRequired: userRequired({ - i18n, - userKey: superAdmin._key, - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }), - }, - loaders: { - loadUserByKey: loadUserByKey({ - query, - userKey: superAdmin._key, - i18n, - }), - }, - validators: {cleanseInput}, - }, - ) - - await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - - const testUserCursor = await query` - FOR user IN users - OPTIONS { waitForSync: true } - FILTER user.userName != "super.admin@istio.actually.exists" - RETURN user - ` - const testUser = await testUserCursor.next() - expect(testUser).toEqual(undefined) - }) - }) - }) - - describe('given an unsuccessful closing of an account', () => { - describe('language is set to english', () => { - beforeAll(() => { - i18n = setupI18n({ - locale: 'en', - localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, - }, - locales: ['en', 'fr'], - messages: { - en: englishMessages.messages, - fr: frenchMessages.messages, - }, - }) - }) - describe('user attempts to close another users account', () => { - describe('requesting user is not a super admin', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({_key: '123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - "Permission error: Unable to close other user's account.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, - ]) - }) - }) - describe('requested user is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({_key: '123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - 'Unable to close account of an undefined user.', - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requested user is undefined.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when gathering domain claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - }) - describe('cursor error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) + const testUserCursor = await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` + const testUser = await testUserCursor.next() + expect(testUser).toEqual(undefined) + }) + }) + describe('super admin is closing another users account', () => { + let superAdmin, superAdminOrg + beforeEach(async () => { + superAdmin = await collections.users.save({ + userName: 'super.admin@istio.actually.exists', + emailValidated: true, }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) + superAdminOrg = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'super-admin', + acronym: 'SA', + name: 'Super Admin', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, }) - describe('when getting claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Cursor error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: cursor error`, - ]) - }) + await collections.affiliations.save({ + _from: superAdminOrg._id, + _to: superAdmin._id, + permission: 'super_admin', }) - }) - describe('trx step error occurs', () => { - describe('domain is only claimed by one org', () => { - describe('when removing dmarc summary info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summaries when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownerships when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing web scan result data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: users/123 attempted to remove web data for test.gc.ca in org: organizations/123 while closing account, Error: trx step error`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: users/123 attempted to remove web data for test.gc.ca in org: organizations/123 while closing account, Error: trx step error`, - ]) - }) - }) - describe('when removing domain info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domains and claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) + user = await collections.users.save({ + userName: 'test.account@istio.actually.exists', + emailValidated: true, + }) + org = await collections.organizations.save({ + orgDetails: { + en: { + slug: 'treasury-board-secretariat', + acronym: 'TBS', + name: 'Treasury Board of Canada Secretariat', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + fr: { + slug: 'secretariat-conseil-tresor', + acronym: 'SCT', + name: 'Secrétariat du Conseil Trésor du Canada', + zone: 'FED', + sector: 'TBS', + country: 'Canada', + province: 'Ontario', + city: 'Ottawa', + }, + }, }) - describe('domain is claimed by multiple orgs', () => { - describe('when removing domain claim', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) + domain = await collections.domains.save({ + domain: 'test.gc.ca', + slug: 'test-gc-ca', }) - describe('when removing ownership orgs, and affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) + await collections.claims.save({ + _from: org._id, + _to: domain._id, }) - describe('when removing the orgs remaining affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership org and users affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) + const dns = await collections.dns.save({ dns: true }) + await collections.domainsDNS.save({ + _from: domain._id, + _to: dns._id, }) - describe('when removing the users affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) + const web = await collections.web.save({ web: true }) + await collections.domainsWeb.save({ + _from: domain._id, + _to: web._id, + }) - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) + const webScan = await collections.webScan.save({ + webScan: true, + }) + await collections.webToWebScans.save({ + _from: web._id, + _to: webScan._id, + }) - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } + const dmarcSummary = await collections.dmarcSummaries.save({ + dmarcSummary: true, + }) + await collections.domainsToDmarcSummaries.save({ + _from: domain._id, + _to: dmarcSummary._id, + }) + }) + beforeEach(async () => { + await collections.affiliations.save({ + _from: org._id, + _to: user._id, + permission: 'user', + }) + }) + it('removes the users affiliations', async () => { + await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } - `, - null, - { + } + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + ) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + await query`FOR aff IN affiliations OPTIONS { waitForSync: true } RETURN aff` - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) + const testAffiliationCursor = await query` + FOR aff IN affiliations + OPTIONS { waitForSync: true } + FILTER aff._from != ${superAdminOrg._id} + RETURN aff + ` + const testAffiliation = await testAffiliationCursor.next() + expect(testAffiliation).toEqual(undefined) + }) + describe('users language is set to english', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'en', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) }) - describe('when removing the user', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } + it('returns a status message', async () => { + const response = await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description } } } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: user._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ query, - language: 'en', + userKey: user._key, i18n, - userKey: '123', }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, + }), }, - ) + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', + i18n, + userKey: user._key, + }), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + ) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + const expectedResponse = { + data: { + closeAccount: { + result: { + status: 'Successfully closed account.', + }, + }, + }, + } - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) }) }) - describe('trx commit error occurs', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn().mockRejectedValue(new Error('trx commit error')), + describe('users language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) - + }) + it('returns a status message', async () => { const response = await graphql( schema, ` @@ -3987,49 +725,133 @@ describe('given the closeAccount mutation', () => { null, { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', + transaction, + userKey: user._key, auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: user._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: user._key, + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }), }, loaders: { loadOrgByKey: loadOrgByKey({ query, language: 'en', i18n, - userKey: '123', + userKey: user._key, }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + loadUserByKey: loadUserByKey({ + query, + userKey: user._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + ) + + const expectedResponse = { + data: { + closeAccount: { + result: { + status: 'Le compte a été fermé avec succès.', }, }, - validators: {cleanseInput}, }, - ) + } + + expect(response).toEqual(expectedResponse) + expect(consoleOutput).toEqual([`User: ${user._key} successfully closed user: ${user._id} account.`]) + }) + }) + it('closes the users account', async () => { + await graphql( + schema, + ` + mutation { + closeAccount(input: { + userId: "${toGlobalId('user', user._key)}" + }) { + result { + ... on CloseAccountResult { + status + } + ... on CloseAccountError { + code + description + } + } + } + } + `, + null, + { + i18n, + query, + collections: collectionNames, + transaction, + userKey: superAdmin._key, + auth: { + checkSuperAdmin: checkSuperAdmin({ + i18n, + userKey: superAdmin._key, + query, + }), + userRequired: userRequired({ + i18n, + userKey: superAdmin._key, + loadUserByKey: loadUserByKey({ + query, + userKey: superAdmin._key, + i18n, + }), + }), + }, + loaders: { + loadUserByKey: loadUserByKey({ + query, + userKey: superAdmin._key, + i18n, + }), + }, + validators: { cleanseInput }, + }, + ) - const error = [ - new GraphQLError('Unable to close account. Please try again.'), - ] + await query`FOR user IN users OPTIONS { waitForSync: true } RETURN user` - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, - ]) - }) + const testUserCursor = await query` + FOR user IN users + OPTIONS { waitForSync: true } + FILTER user.userName != "super.admin@istio.actually.exists" + RETURN user + ` + const testUser = await testUserCursor.next() + expect(testUser).toEqual(undefined) }) }) - describe('language is set to french', () => { + }) + + describe('given an unsuccessful closing of an account', () => { + describe('language is set to english', () => { beforeAll(() => { i18n = setupI18n({ - locale: 'fr', + locale: 'en', localeData: { - en: {plurals: {}}, - fr: {plurals: {}}, + en: { plurals: {} }, + fr: { plurals: {} }, }, locales: ['en', 'fr'], messages: { @@ -4069,69 +891,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(false), - userRequired: jest.fn().mockReturnValue({_key: '123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - }, - validators: {cleanseInput}, - }, - ) - - const expectedResponse = { - data: { - closeAccount: { - result: { - code: 400, - description: - "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, - ]) - }) - }) - describe('requested user is undefined', () => { - it('returns an error', async () => { - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: { - userId: "${toGlobalId('user', '456')}" - }) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query, - collections: collectionNames, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest.fn().mockReturnValue({_key: '123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4140,11 +900,8 @@ describe('given the closeAccount mutation', () => { i18n, userKey: '123', }), - loadUserByKey: { - load: jest.fn().mockReturnValue(undefined), - }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) @@ -4153,251 +910,27 @@ describe('given the closeAccount mutation', () => { closeAccount: { result: { code: 400, - description: - "Impossible de fermer le compte d'un utilisateur non défini.", - }, - }, - }, - } - - expect(response).toEqual(expectedResponse) - expect(consoleOutput).toEqual([ - `User: 123 attempted to close user: 456 account, but requested user is undefined.`, - ]) - }) - }) - }) - describe('database error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedQuery = jest - .fn() - .mockRejectedValue(new Error('database error')) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when getting ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + description: "Permission error: Unable to close other user's account.", }, }, - validators: {cleanseInput}, }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Database error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: database error`, - ]) - }) - }) - describe('when gathering domain claim info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), } - const mockedQuery = jest - .fn() - .mockReturnValueOnce(mockedCursor) - .mockReturnValueOnce(mockedCursor) - .mockRejectedValue(new Error('database error')) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Database error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: database error`, + `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, ]) }) }) - }) - describe('cursor error occurs', () => { - describe('when getting affiliation info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockRejectedValue(new Error('cursor error')), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), - commit: jest.fn(), - }) - + describe('requested user is undefined', () => { + it('returns an error', async () => { const response = await graphql( schema, ` mutation { - closeAccount(input: {}) { + closeAccount(input: { + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -4413,15 +946,13 @@ describe('given the closeAccount mutation', () => { null, { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4431,38 +962,42 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccount: { + result: { + code: 400, + description: 'Unable to close account of an undefined user.', + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Cursor error occurred when getting affiliations when user: 123 attempted to close account: users/123: Error: cursor error`, + `User: 123 attempted to close user: 456 account, but requested user is undefined.`, ]) }) }) - describe('when getting ownership info', () => { + }) + describe('trx step error occurs', () => { + describe('when removing the users affiliations', () => { it('throws an error', async () => { const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), + step: jest.fn().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), }) @@ -4492,9 +1027,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4504,39 +1037,31 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue({ _key: '123' }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Unable to close account. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Cursor error occurred when getting ownership info when user: 123 attempted to close account: users/123: Error: cursor error`, + `Trx step error occurred when removing users remaining affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, ]) }) }) - describe('when getting claim info', () => { + describe('when removing the user', () => { it('throws an error', async () => { const mockedCursor = { - all: jest - .fn() - .mockReturnValueOnce([{}]) - .mockReturnValueOnce([{}]) - .mockRejectedValue(new Error('cursor error')), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockReturnValue(), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), }) @@ -4566,9 +1091,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -4578,497 +1101,112 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue({ _key: '123' }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Unable to close account. Please try again.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ - `Cursor error occurred when getting claim info when user: 123 attempted to close account: users/123: Error: cursor error`, + `Trx step error occurred when removing user: 123 attempted to close account: users/123: Error: trx step error`, ]) }) }) }) - describe('trx step error occurs', () => { - describe('domain is only claimed by one org', () => { - describe('when removing dmarc summary info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest.fn().mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing dmarc summaries when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) - describe('when removing ownership info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) + describe('trx commit error occurs', () => { + it('throws an error', async () => { + const mockedCursor = { + all: jest.fn().mockReturnValue([{ count: 2 }]), + } - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownerships when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) + const mockedTransaction = jest.fn().mockReturnValue({ + step: jest.fn().mockReturnValue(), + commit: jest.fn().mockRejectedValue(new Error('trx commit error')), }) - describe('when removing web scan result data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } + const response = await graphql( + schema, + ` + mutation { + closeAccount(input: {}) { + result { + ... on CloseAccountResult { + status } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: users/123 attempted to remove web data for test.gc.ca in org: organizations/123 while closing account, Error: trx step error`, - ]) - }) - }) - describe('when removing scan data', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } + ... on CloseAccountError { + code + description } } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred while user: users/123 attempted to remove web data for test.gc.ca in org: organizations/123 while closing account, Error: trx step error`, - ]) - }) - }) - describe('when removing domain info', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn(). - mockReturnValue([{count: 1, domain: "test.gc.ca", _from: "organizations/123"}]), + } } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { + `, + null, + { + i18n, + query: mockedQuery, + collections: collectionNames, + transaction: mockedTransaction, + userKey: '123', + auth: { + checkSuperAdmin: jest.fn().mockReturnValue(true), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), + }, + loaders: { + loadOrgByKey: loadOrgByKey({ + query, + language: 'en', i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, + }), + loadUserByKey: { + load: jest.fn().mockReturnValue({ _key: '123' }), }, - ) + }, + validators: { cleanseInput }, + }, + ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Unable to close account. Please try again.')] - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domains and claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) + expect(response.errors).toEqual(error) + expect(consoleOutput).toEqual([ + `Trx commit error occurred when user: 123 attempted to close account: users/123: Error: trx commit error`, + ]) }) - describe('domain is claimed by multiple orgs', () => { - describe('when removing domain claim', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - - const response = await graphql( - schema, - ` - mutation { - closeAccount(input: {}) { - result { - ... on CloseAccountResult { - status - } - ... on CloseAccountError { - code - description - } - } - } - } - `, - null, - { - i18n, - query: mockedQuery, - collections: collectionNames, - transaction: mockedTransaction, - userKey: '123', - auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), - }, - loaders: { - loadOrgByKey: loadOrgByKey({ - query, - language: 'en', - i18n, - userKey: '123', - }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, - }, - validators: {cleanseInput}, - }, - ) - - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] - - expect(response.errors).toEqual(error) - expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, - ]) - }) - }) + }) + }) + describe('language is set to french', () => { + beforeAll(() => { + i18n = setupI18n({ + locale: 'fr', + localeData: { + en: { plurals: {} }, + fr: { plurals: {} }, + }, + locales: ['en', 'fr'], + messages: { + en: englishMessages.messages, + fr: frenchMessages.messages, + }, }) - describe('when removing ownership orgs, and affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - + }) + describe('user attempts to close another users account', () => { + describe('requesting user is not a super admin', () => { + it('returns an error', async () => { const response = await graphql( schema, ` mutation { - closeAccount(input: {}) { + closeAccount(input: { + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -5084,15 +1222,13 @@ describe('given the closeAccount mutation', () => { null, { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', auth: { - checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + checkSuperAdmin: jest.fn().mockReturnValue(false), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5101,49 +1237,37 @@ describe('given the closeAccount mutation', () => { i18n, userKey: '123', }), - loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), - }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccount: { + result: { + code: 400, + description: "Erreur de permission: Impossible de fermer le compte d'un autre utilisateur.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing domain claims when user: 123 attempted to close account: users/123: Error: trx step error`, + `User: 123 attempted to close user: 456 account, but requesting user is not a super admin.`, ]) }) }) - describe('when removing the orgs remaining affiliations', () => { - it('throws an error', async () => { - const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), - } - - const mockedQuery = jest.fn().mockReturnValue(mockedCursor) - - const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), - commit: jest.fn(), - }) - + describe('requested user is undefined', () => { + it('returns an error', async () => { const response = await graphql( schema, ` mutation { - closeAccount(input: {}) { + closeAccount(input: { + userId: "${toGlobalId('user', '456')}" + }) { result { ... on CloseAccountResult { status @@ -5159,15 +1283,13 @@ describe('given the closeAccount mutation', () => { null, { i18n, - query: mockedQuery, + query, collections: collectionNames, - transaction: mockedTransaction, + transaction, userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5177,42 +1299,42 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue(undefined), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const expectedResponse = { + data: { + closeAccount: { + result: { + code: 400, + description: "Impossible de fermer le compte d'un utilisateur non défini.", + }, + }, + }, + } - expect(response.errors).toEqual(error) + expect(response).toEqual(expectedResponse) expect(consoleOutput).toEqual([ - `Trx step error occurred when removing ownership org and users affiliations when user: 123 attempted to close account: users/123: Error: trx step error`, + `User: 123 attempted to close user: 456 account, but requested user is undefined.`, ]) }) }) + }) + describe('trx step error occurs', () => { describe('when removing the users affiliations', () => { it('throws an error', async () => { const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), }) @@ -5242,9 +1364,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5254,18 +1374,14 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue({ _key: '123' }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -5276,21 +1392,13 @@ describe('given the closeAccount mutation', () => { describe('when removing the user', () => { it('throws an error', async () => { const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) const mockedTransaction = jest.fn().mockReturnValue({ - step: jest - .fn() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockReturnValueOnce() - .mockRejectedValue(new Error('trx step error')), + step: jest.fn().mockReturnValueOnce().mockRejectedValue(new Error('trx step error')), commit: jest.fn(), }) @@ -5320,9 +1428,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5332,18 +1438,14 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue({ _key: '123' }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ @@ -5355,7 +1457,7 @@ describe('given the closeAccount mutation', () => { describe('trx commit error occurs', () => { it('throws an error', async () => { const mockedCursor = { - all: jest.fn().mockReturnValue([{count: 2}]), + all: jest.fn().mockReturnValue([{ count: 2 }]), } const mockedQuery = jest.fn().mockReturnValue(mockedCursor) @@ -5391,9 +1493,7 @@ describe('given the closeAccount mutation', () => { userKey: '123', auth: { checkSuperAdmin: jest.fn().mockReturnValue(true), - userRequired: jest - .fn() - .mockReturnValue({_key: '123', _id: 'users/123'}), + userRequired: jest.fn().mockReturnValue({ _key: '123', _id: 'users/123' }), }, loaders: { loadOrgByKey: loadOrgByKey({ @@ -5403,18 +1503,14 @@ describe('given the closeAccount mutation', () => { userKey: '123', }), loadUserByKey: { - load: jest.fn().mockReturnValue({_key: '123'}), + load: jest.fn().mockReturnValue({ _key: '123' }), }, }, - validators: {cleanseInput}, + validators: { cleanseInput }, }, ) - const error = [ - new GraphQLError( - 'Impossible de fermer le compte. Veuillez réessayer.', - ), - ] + const error = [new GraphQLError('Impossible de fermer le compte. Veuillez réessayer.')] expect(response.errors).toEqual(error) expect(consoleOutput).toEqual([ diff --git a/api/src/user/mutations/close-account.js b/api/src/user/mutations/close-account.js index cd3a849770..012dbc0b76 100644 --- a/api/src/user/mutations/close-account.js +++ b/api/src/user/mutations/close-account.js @@ -73,276 +73,9 @@ export const closeAccount = new mutationWithClientMutationId({ targetUserName = user.userName } - // check to see if user owns any orgs - let orgOwnerAffiliationCursor - try { - orgOwnerAffiliationCursor = await query` - WITH users, affiliations, organizations - FOR v, e IN 1..1 INBOUND ${userId} affiliations - FILTER e.owner == true - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let orgOwnerAffiliationCheck - try { - orgOwnerAffiliationCheck = await orgOwnerAffiliationCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - // Setup Trans action const trx = await transaction(collections) - // loop through each found org - for (const affiliation of orgOwnerAffiliationCheck) { - let dmarcSummaryCheckCursor - try { - dmarcSummaryCheckCursor = await query` - WITH domains, ownership, organizations, users - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} ownership - RETURN e - ` - } catch (err) { - console.error( - `Database error occurred when getting ownership info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let dmarcSummaryCheckList - try { - dmarcSummaryCheckList = await dmarcSummaryCheckCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting ownership info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - // remove dmarc summary related things - for (const ownership of dmarcSummaryCheckList) { - try { - await trx.step( - () => query` - WITH ownership, organizations, domains, dmarcSummaries, domainsToDmarcSummaries - LET dmarcSummaryEdges = ( - FOR v, e IN 1..1 OUTBOUND ${ownership._to} domainsToDmarcSummaries - RETURN { edgeKey: e._key, dmarcSummaryId: e._to } - ) - LET removeDmarcSummaryEdges = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - REMOVE dmarcSummaryEdge.edgeKey IN domainsToDmarcSummaries - OPTIONS { waitForSync: true } - ) - LET removeDmarcSummary = ( - FOR dmarcSummaryEdge IN dmarcSummaryEdges - LET key = PARSE_IDENTIFIER(dmarcSummaryEdge.dmarcSummaryId).key - REMOVE key IN dmarcSummaries - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing dmarc summaries when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH ownership, organizations, domains - REMOVE ${ownership._key} IN ownership - OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing ownerships when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } - - let domainCountCursor - try { - domainCountCursor = await query` - WITH claims, domains, organizations, users - LET domainIds = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - RETURN e._to - ) - FOR domain IN domains - FILTER domain._id IN domainIds - LET count = LENGTH( - FOR v, e IN 1..1 INBOUND domain._id claims - RETURN 1 - ) - RETURN { - _id: domain._id, - _key: domain._key, - domain: domain.domain, - count: count - } - ` - } catch (err) { - console.error( - `Database error occurred when getting claim info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - let domainCountList - try { - domainCountList = await domainCountCursor.all() - } catch (err) { - console.error( - `Cursor error occurred when getting claim info when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - for (const domainObj of domainCountList) { - if (domainObj.count === 1) { - // Remove scan data - - try { - // Remove web data - await trx.step(async () => { - await query` - WITH web, webScan, domains - FOR webV, domainsWebEdge IN 1..1 OUTBOUND ${domainObj._id} domainsWeb - FOR webScanV, webToWebScansV In 1..1 OUTBOUND webV._id webToWebScans - REMOVE webScanV IN webScan - REMOVE webToWebScansV IN webToWebScans - OPTIONS { waitForSync: true } - REMOVE webV IN web - REMOVE domainsWebEdge IN domainsWeb - OPTIONS { waitForSync: true } - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${userId} attempted to remove web data for ${domainObj.domain} in org: ${affiliation._from} while closing account, ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - // Remove DNS data - await trx.step(async () => { - await query` - WITH dns, domains - FOR dnsV, domainsDNSEdge IN 1..1 OUTBOUND ${domainObj._id} domainsDNS - REMOVE dnsV IN dns - REMOVE domainsDNSEdge IN domainsDNS - OPTIONS { waitForSync: true } - ` - }) - } catch (err) { - console.error( - `Trx step error occurred while user: ${userId} attempted to remove DNS data for ${domainObj.domain} in org: ${affiliation._from}, ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - - try { - await trx.step( - () => query` - WITH claims, domains, organizations, users - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - FILTER e._to == ${domainObj._id} - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge IN domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - LET removeDomain = ( - FOR domainEdge IN domainEdges - REMOVE PARSE_IDENTIFIER(domainEdge.domainId).key - IN domains OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing domains and claims when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } else { - try { - await trx.step( - () => query` - WITH claims, domains, organizations, users - LET domainEdges = ( - FOR v, e IN 1..1 OUTBOUND ${affiliation._from} claims - RETURN { edgeKey: e._key, domainId: e._to } - ) - LET removeDomainEdges = ( - FOR domainEdge IN domainEdges - REMOVE domainEdge.edgeKey IN claims - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing domain claims when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } - } - - // remove users affiliation - try { - await trx.step( - () => query` - WITH affiliations, organizations, users - LET userEdges = ( - FOR v, e IN 1..1 INBOUND ${affiliation._from} affiliations - RETURN { edgeKey: e._key, userKey: e._to } - ) - LET removeUserEdges = ( - FOR userEdge IN userEdges - REMOVE userEdge.userKey IN affiliations - OPTIONS { waitForSync: true } - ) - RETURN true - `, - ) - await trx.step( - () => query` - WITH organizations - REMOVE PARSE_IDENTIFIER(${affiliation._from}).key - IN organizations OPTIONS { waitForSync: true } - `, - ) - } catch (err) { - console.error( - `Trx step error occurred when removing ownership org and users affiliations when user: ${user._key} attempted to close account: ${userId}: ${err}`, - ) - throw new Error(i18n._(t`Unable to close account. Please try again.`)) - } - } - try { await trx.step( () => query` diff --git a/api/src/user/mutations/sign-up.js b/api/src/user/mutations/sign-up.js index 77dd529dc6..90f5f0e81d 100644 --- a/api/src/user/mutations/sign-up.js +++ b/api/src/user/mutations/sign-up.js @@ -203,7 +203,6 @@ export const signUp = new mutationWithClientMutationId({ _from: ${checkOrg._id}, _to: ${insertedUser._id}, permission: ${tokenRequestedRole}, - owner: false } INTO affiliations `, ) diff --git a/frontend/src/admin/AuditLogTable.js b/frontend/src/admin/AuditLogTable.js index b057a674a6..a842f17b43 100644 --- a/frontend/src/admin/AuditLogTable.js +++ b/frontend/src/admin/AuditLogTable.js @@ -37,30 +37,21 @@ export function AuditLogTable({ orgId = null }) { setDebouncedSearchTerm(searchTerm) }, [searchTerm]) useDebouncedFunction(memoizedSetDebouncedSearchTermCallback, 500) - const { - loading, - isLoadingMore, - error, - nodes, - next, - previous, - resetToFirstPage, - hasNextPage, - hasPreviousPage, - } = usePaginatedCollection({ - fetchForward: AUDIT_LOGS, - recordsPerPage: logsPerPage, - relayRoot: 'findAuditLogs', - variables: { - orgId, - orderBy: { field: orderField, direction: orderDirection }, - search: debouncedSearchTerm, - filters: { resource: activeResourceFilters, action: activeActionFilters }, - }, - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first', - errorPolicy: 'ignore', // allow partial success - }) + const { loading, isLoadingMore, error, nodes, next, previous, resetToFirstPage, hasNextPage, hasPreviousPage } = + usePaginatedCollection({ + fetchForward: AUDIT_LOGS, + recordsPerPage: logsPerPage, + relayRoot: 'findAuditLogs', + variables: { + orgId, + orderBy: { field: orderField, direction: orderDirection }, + search: debouncedSearchTerm, + filters: { resource: activeResourceFilters, action: activeActionFilters }, + }, + fetchPolicy: 'cache-and-network', + nextFetchPolicy: 'cache-first', + errorPolicy: 'ignore', // allow partial success + }) if (error) { return @@ -83,6 +74,8 @@ export function AuditLogTable({ orgId = null }) { { value: 'UPDATE', text: t`Update` }, { value: 'REMOVE', text: t`Remove` }, { value: 'DELETE', text: t`Delete` }, + { value: 'EXPORT', text: t`Export` }, + { value: 'SCAN', text: t`Scan` }, ] let logTable @@ -131,64 +124,50 @@ export function AuditLogTable({ orgId = null }) {
{formatTimestamp(timestamp)}{initiatedBy?.userName}{action?.text.toUpperCase()}{resourceType?.text.toUpperCase()}{target?.resource}{target?.organization?.name} - {target?.updatedProperties?.map( - ({ name, oldValue, newValue }) => { - return ( - - - Name: {name} - - - Old Value: {oldValue} - - - New Value: {newValue} - - {target?.updatedProperties?.length > 1 && ( - - )} - - ) - }, - )} - {reason}
{formatTimestamp(timestamp)}{initiatedBy?.userName}{action?.text.toUpperCase()}{resourceType?.text.toUpperCase()}{target?.resource}{target?.organization?.name} + {target?.updatedProperties?.map(({ name, oldValue, newValue }) => { + return ( + + + Name: {name} + + + Old Value: {oldValue} + + + New Value: {newValue} + + {target?.updatedProperties?.length > 1 && } + + ) + })} + {reason}
@@ -227,33 +206,16 @@ export function AuditLogTable({ orgId = null }) { borderRadius="full" borderWidth="1px" borderColor="gray.900" - bg={ - activeResourceFilters.indexOf(value) < 0 - ? 'gray.50' - : 'gray.900' - } - color={ - activeResourceFilters.indexOf(value) < 0 - ? 'gray.900' - : 'gray.50' - } + bg={activeResourceFilters.indexOf(value) < 0 ? 'gray.50' : 'gray.900'} + color={activeResourceFilters.indexOf(value) < 0 ? 'gray.900' : 'gray.50'} as="button" - _hover={ - activeResourceFilters.indexOf(value) < 0 - ? { bg: 'gray.200' } - : { bg: 'gray.500' } - } + _hover={activeResourceFilters.indexOf(value) < 0 ? { bg: 'gray.200' } : { bg: 'gray.500' }} onClick={() => { let optionIdx = activeResourceFilters.indexOf(value) if (optionIdx < 0) { - setActiveResourceFilters([ - ...activeResourceFilters, - value, - ]) + setActiveResourceFilters([...activeResourceFilters, value]) } else { - setActiveResourceFilters( - activeResourceFilters.filter((tag) => tag !== value), - ) + setActiveResourceFilters(activeResourceFilters.filter((tag) => tag !== value)) } }} > @@ -275,30 +237,16 @@ export function AuditLogTable({ orgId = null }) { borderRadius="full" borderWidth="1px" borderColor="gray.900" - bg={ - activeActionFilters.indexOf(value) < 0 - ? 'gray.50' - : 'gray.900' - } - color={ - activeActionFilters.indexOf(value) < 0 - ? 'gray.900' - : 'gray.50' - } + bg={activeActionFilters.indexOf(value) < 0 ? 'gray.50' : 'gray.900'} + color={activeActionFilters.indexOf(value) < 0 ? 'gray.900' : 'gray.50'} as="button" - _hover={ - activeActionFilters.indexOf(value) < 0 - ? { bg: 'gray.200' } - : { bg: 'gray.500' } - } + _hover={activeActionFilters.indexOf(value) < 0 ? { bg: 'gray.200' } : { bg: 'gray.500' }} onClick={() => { let optionIdx = activeActionFilters.indexOf(value) if (optionIdx < 0) { setActiveActionFilters([...activeActionFilters, value]) } else { - setActiveActionFilters( - activeActionFilters.filter((tag) => tag !== value), - ) + setActiveActionFilters(activeActionFilters.filter((tag) => tag !== value)) } }} > diff --git a/frontend/src/admin/UserListModal.js b/frontend/src/admin/UserListModal.js index 7f113fcc70..c4904503e5 100644 --- a/frontend/src/admin/UserListModal.js +++ b/frontend/src/admin/UserListModal.js @@ -254,7 +254,7 @@ export function UserListModal({ > {editingUserRole === 'PENDING' && } {(['PENDING', 'USER'].includes(editingUserRole) || - (permission === 'SUPER_ADMIN' && editingUserRole === 'ADMIN')) && ( + (['ADMIN', 'OWNER', 'SUPER_ADMIN'].includes(permission) && editingUserRole === 'ADMIN')) && ( )} {['PENDING', 'USER', 'ADMIN'].includes(editingUserRole) && ( diff --git a/frontend/src/admin/__tests__/UserListModal.test.js b/frontend/src/admin/__tests__/UserListModal.test.js index 3301a429bb..882b4edccf 100644 --- a/frontend/src/admin/__tests__/UserListModal.test.js +++ b/frontend/src/admin/__tests__/UserListModal.test.js @@ -410,7 +410,7 @@ describe('', () => { }) }) describe('admin is updating user with "ADMIN" privileges', () => { - it('admin cannot downgrade user to "USER"', async () => { + it('admin can downgrade user to "USER"', async () => { const { getByRole, queryByText } = render( ', () => { const roleChangeSelect = getByRole('combobox', { name: /Role:/, }) - expect(roleChangeSelect.options.length).toEqual(1) - expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/ADMIN/) - expect(roleChangeSelect).not.toHaveTextContent(/USER/) + expect(roleChangeSelect.options.length).toEqual(2) + expect(roleChangeSelect.value).toEqual('ADMIN') + expect(Object.values(roleChangeSelect.options)[0]).toHaveTextContent(/USER/) + expect(Object.values(roleChangeSelect.options)[1]).toHaveTextContent(/ADMIN/) + expect(roleChangeSelect).toHaveTextContent(/USER/) }) }) }) diff --git a/frontend/src/app/App.js b/frontend/src/app/App.js index 4242ec5632..dc7434f92f 100644 --- a/frontend/src/app/App.js +++ b/frontend/src/app/App.js @@ -85,12 +85,15 @@ export function App() { Domains - - DMARC Summaries - )} + {isLoggedIn() && isEmailValidated() && currentTFAMethod() !== 'NONE' && ( + + DMARC Summaries + + )} + {isLoggedIn() && ( <> @@ -225,12 +228,7 @@ export function App() { )} - + {() => ( @@ -239,7 +237,7 @@ export function App() { - + {() => ( diff --git a/frontend/src/dmarc/DmarcReportPage.js b/frontend/src/dmarc/DmarcReportPage.js index 33e5edab41..e1908fca7c 100644 --- a/frontend/src/dmarc/DmarcReportPage.js +++ b/frontend/src/dmarc/DmarcReportPage.js @@ -1,15 +1,6 @@ import React, { useState } from 'react' import { useQuery } from '@apollo/client' -import { - Accordion, - Box, - Divider, - Flex, - Heading, - Link, - Text, - useDisclosure, -} from '@chakra-ui/react' +import { Accordion, Box, Divider, Flex, Heading, Link, Text, useDisclosure } from '@chakra-ui/react' import { LinkIcon } from '@chakra-ui/icons' import { t, Trans } from '@lingui/macro' import { useLingui } from '@lingui/react' @@ -38,9 +29,7 @@ export default function DmarcReportPage() { const [selectedPeriod, setSelectedPeriod] = useState(period) const [selectedYear, setSelectedYear] = useState(year) - const [selectedDate, setSelectedDate] = useState( - `${selectedPeriod}, ${selectedYear}`, - ) + const [selectedDate, setSelectedDate] = useState(`${selectedPeriod}, ${selectedYear}`) const { isOpen: fullPassOpen, onToggle: fullPassToggle } = useDisclosure() const { isOpen: failDkimOpen, onToggle: failDkimToggle } = useDisclosure() @@ -50,8 +39,7 @@ export default function DmarcReportPage() { // Allows the use of forward/backward navigation if (selectedPeriod !== period) setSelectedPeriod(period) if (selectedYear !== year) setSelectedPeriod(year) - if (selectedDate !== `${period}, ${year}`) - setSelectedDate(`${period}, ${year}`) + if (selectedDate !== `${period}, ${year}`) setSelectedDate(`${period}, ${year}`) const { loading: graphLoading, @@ -83,9 +71,7 @@ export default function DmarcReportPage() { const [newPeriod, newYear] = e.target.value.split(', ') setSelectedPeriod(newPeriod) setSelectedYear(newYear) - history.replace( - `/domains/${domainSlug}/dmarc-report/${newPeriod}/${newYear}`, - ) + history.replace(`/domains/${domainSlug}/dmarc-report/${newPeriod}/${newYear}`) } // Set DMARC bar graph Loading @@ -97,7 +83,20 @@ export default function DmarcReportPage() { ) } - if (!graphData?.findDomainByDomain?.hasDMARCReport) { + if (tableError || graphError) { + return ( + + + + Error while retrieving DMARC data for {domainSlug}.
+ This could be due to insufficient user privileges or the domain does not exist in the system. +
+
+
+ ) + } + + if (graphData?.findDomainByDomain?.hasDMARCReport === false) { return ( @@ -131,16 +130,14 @@ export default function DmarcReportPage() { } const formattedGraphData = { - periods: graphData.findDomainByDomain.yearlyDmarcSummaries.map( - (entry) => { - return { - month: entry.month, - year: entry.year, - ...entry.categoryTotals, - ...entry.categoryPercentages, - } - }, - ), + periods: graphData.findDomainByDomain.yearlyDmarcSummaries.map((entry) => { + return { + month: entry.month, + year: entry.year, + ...entry.categoryTotals, + ...entry.categoryPercentages, + } + }), } formattedGraphData.strengths = strengths graphDisplay = ( @@ -278,19 +275,10 @@ export default function DmarcReportPage() { const generalGlossary = ( <> - + - - + + ) @@ -327,10 +315,7 @@ export default function DmarcReportPage() { ) } // DKIM Failure query no longer loading, check if data exists - else if ( - tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables - ?.dkimFailure?.edges.length > 0 - ) { + else if (tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables?.dkimFailure?.edges.length > 0) { const dkimFailureColumns = [ { Header: t`DKIM Failures by IP Address`, @@ -351,17 +336,16 @@ export default function DmarcReportPage() { ] // Convert boolean values to string and properly format - const dkimFailureNodes = - tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.dkimFailure.edges.map( - (edge) => { - const node = { ...edge.node } - node.dkimAligned = node.dkimAligned.toString() - node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') - node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') - node.dkimResults = node.dkimResults.replace(/,/g, ', ') - return node - }, - ) + const dkimFailureNodes = tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.dkimFailure.edges.map( + (edge) => { + const node = { ...edge.node } + node.dkimAligned = node.dkimAligned.toString() + node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') + node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') + node.dkimResults = node.dkimResults.replace(/,/g, ', ') + return node + }, + ) dkimFailureTable = ( @@ -373,9 +357,7 @@ export default function DmarcReportPage() { frontendPagination={true} searchPlaceholder={t`Search DKIM Failing Items`} fileName={fileName} - exportDataFunction={() => - dataToCsv(dkimFailureColumns[0].columns, dkimFailureNodes) - } + exportDataFunction={() => dataToCsv(dkimFailureColumns[0].columns, dkimFailureNodes)} onToggle={failDkimToggle} /> @@ -406,10 +388,7 @@ export default function DmarcReportPage() { ) } // Full pass query no longer loading, check if data exists - else if ( - tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables?.fullPass - ?.edges.length > 0 - ) { + else if (tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables?.fullPass?.edges.length > 0) { const fullPassColumns = [ { Header: t`Fully Aligned by IP Address`, @@ -428,16 +407,13 @@ export default function DmarcReportPage() { ] // Convert boolean values to string and properly format - const fullPassNodes = - tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.fullPass.edges.map( - (edge) => { - const node = { ...edge.node } - node.spfDomains = node.spfDomains.replace(/,/g, ', ') - node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') - node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') - return node - }, - ) + const fullPassNodes = tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.fullPass.edges.map((edge) => { + const node = { ...edge.node } + node.spfDomains = node.spfDomains.replace(/,/g, ', ') + node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') + node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') + return node + }) fullPassTable = ( @@ -449,9 +425,7 @@ export default function DmarcReportPage() { frontendPagination={true} searchPlaceholder={t`Search Fully Aligned Items`} fileName={fileName} - exportDataFunction={() => - dataToCsv(fullPassColumns[0].columns, fullPassNodes) - } + exportDataFunction={() => dataToCsv(fullPassColumns[0].columns, fullPassNodes)} onToggle={fullPassToggle} /> @@ -482,10 +456,7 @@ export default function DmarcReportPage() { ) } // SPF Failure query no longer loading, check if data exists - else if ( - tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables - ?.spfFailure?.edges.length > 0 - ) { + else if (tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables?.spfFailure?.edges.length > 0) { const spfFailureColumns = [ { Header: t`SPF Failures by IP Address`, @@ -504,15 +475,14 @@ export default function DmarcReportPage() { }, ] // Convert boolean values to string and properly format - const spfFailureNodes = - tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.spfFailure.edges.map( - (edge) => { - const node = { ...edge.node } - node.spfAligned = node.spfAligned.toString() - node.spfDomains = node.spfDomains.replace(/,/g, ', ') - return node - }, - ) + const spfFailureNodes = tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.spfFailure.edges.map( + (edge) => { + const node = { ...edge.node } + node.spfAligned = node.spfAligned.toString() + node.spfDomains = node.spfDomains.replace(/,/g, ', ') + return node + }, + ) spfFailureTable = ( @@ -524,9 +494,7 @@ export default function DmarcReportPage() { frontendPagination={true} searchPlaceholder={t`Search SPF Failing Items`} fileName={fileName} - exportDataFunction={() => - dataToCsv(spfFailureColumns[0].columns, spfFailureNodes) - } + exportDataFunction={() => dataToCsv(spfFailureColumns[0].columns, spfFailureNodes)} onToggle={failSpfToggle} /> @@ -562,10 +530,7 @@ export default function DmarcReportPage() { ) } // DMARC Failure query no longer loading, check if data exists - else if ( - tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables - ?.dmarcFailure?.edges.length > 0 - ) { + else if (tableData?.findDomainByDomain?.dmarcSummaryByPeriod?.detailTables?.dmarcFailure?.edges.length > 0) { const dmarcFailureColumns = [ { Header: t`DMARC Failures by IP Address`, @@ -585,20 +550,19 @@ export default function DmarcReportPage() { ] // Convert boolean values to string and properly format - const dmarcFailureNodes = - tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.dmarcFailure.edges.map( - (edge) => { - const node = { ...edge.node } + const dmarcFailureNodes = tableData.findDomainByDomain.dmarcSummaryByPeriod.detailTables.dmarcFailure.edges.map( + (edge) => { + const node = { ...edge.node } - // calculate dmarcFailStats totals - dmarcFailStats[node.disposition] += node.totalMessages + // calculate dmarcFailStats totals + dmarcFailStats[node.disposition] += node.totalMessages - node.spfDomains = node.spfDomains.replace(/,/g, ', ') - node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') - node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') - return node - }, - ) + node.spfDomains = node.spfDomains.replace(/,/g, ', ') + node.dkimDomains = node.dkimDomains.replace(/,/g, ', ') + node.dkimSelectors = node.dkimSelectors.replace(/,/g, ', ') + return node + }, + ) dmarcFailureTable = ( @@ -610,9 +574,7 @@ export default function DmarcReportPage() { frontendPagination={true} searchPlaceholder={t`Search DMARC Failing Items`} fileName={fileName} - exportDataFunction={() => - dataToCsv(dmarcFailureColumns[0].columns, dmarcFailureNodes) - } + exportDataFunction={() => dataToCsv(dmarcFailureColumns[0].columns, dmarcFailureNodes)} onToggle={failDmarcToggle} /> @@ -631,22 +593,15 @@ export default function DmarcReportPage() { ) } - const fakeEmailDomainBlocks = - dmarcFailStats.reject + dmarcFailStats.quarantine + const fakeEmailDomainBlocks = dmarcFailStats.reject + dmarcFailStats.quarantine const domainSpoofingVolume = fakeEmailDomainBlocks + dmarcFailStats.none const tableDisplay = ( - - {fullPassTable} - - - {dkimFailureTable} - - - {spfFailureTable} - + {fullPassTable} + {dkimFailureTable} + {spfFailureTable} {dmarcFailureTable} @@ -658,10 +613,7 @@ export default function DmarcReportPage() { - - Volume of messages spoofing domain (reject + quarantine + - none): - + Volume of messages spoofing domain (reject + quarantine + none): {domainSpoofingVolume} @@ -679,14 +631,7 @@ export default function DmarcReportPage() { {domainSlug.toUpperCase()} - + Guidance )} diff --git a/frontend/src/summaries/TieredSummaries.js b/frontend/src/summaries/TieredSummaries.js index afb2745451..b20781bd74 100644 --- a/frontend/src/summaries/TieredSummaries.js +++ b/frontend/src/summaries/TieredSummaries.js @@ -34,10 +34,24 @@ export function TieredSummaries({ summaries }) { - From 7e77592e41546f30e0a01a6814d4f95a16cd36a8 Mon Sep 17 00:00:00 2001 From: fluxbot Date: Fri, 16 Jun 2023 17:54:21 +0000 Subject: [PATCH 112/113] [ci skip] gcr.io/track-compliance/api-js:master-af5d359-1686937994 --- app/bases/tracker-api-deployment.yaml | 2 +- k8s/apps/bases/api/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-api-deployment.yaml b/app/bases/tracker-api-deployment.yaml index 477d550dab..1d9fe49c0b 100644 --- a/app/bases/tracker-api-deployment.yaml +++ b/app/bases/tracker-api-deployment.yaml @@ -50,7 +50,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-15ba3b4-1686838565 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-af5d359-1686937994 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 diff --git a/k8s/apps/bases/api/deployment.yaml b/k8s/apps/bases/api/deployment.yaml index fde3d9a2b4..ec72ee22e7 100644 --- a/k8s/apps/bases/api/deployment.yaml +++ b/k8s/apps/bases/api/deployment.yaml @@ -54,7 +54,7 @@ spec: - name: database-config mountPath: /app containers: - - image: gcr.io/track-compliance/api-js:master-15ba3b4-1686838565 # {"$imagepolicy": "flux-system:api"} + - image: gcr.io/track-compliance/api-js:master-af5d359-1686937994 # {"$imagepolicy": "flux-system:api"} name: api ports: - containerPort: 4000 From e9426f34a7312ebef032a4c3d4553d74180b678a Mon Sep 17 00:00:00 2001 From: fluxbot Date: Mon, 19 Jun 2023 12:23:25 +0000 Subject: [PATCH 113/113] [ci skip] gcr.io/track-compliance/frontend:master-af5d359-1687177261 --- app/bases/tracker-frontend-deployment.yaml | 2 +- k8s/apps/bases/frontend/deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/bases/tracker-frontend-deployment.yaml b/app/bases/tracker-frontend-deployment.yaml index e0d088fdc5..4a4a0b695e 100644 --- a/app/bases/tracker-frontend-deployment.yaml +++ b/app/bases/tracker-frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-60ba99e-1686928145 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-af5d359-1687177261 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: diff --git a/k8s/apps/bases/frontend/deployment.yaml b/k8s/apps/bases/frontend/deployment.yaml index 5d68f29b39..f9ae0d6e8c 100644 --- a/k8s/apps/bases/frontend/deployment.yaml +++ b/k8s/apps/bases/frontend/deployment.yaml @@ -20,7 +20,7 @@ spec: app: tracker-frontend spec: containers: - - image: gcr.io/track-compliance/frontend:master-60ba99e-1686928145 # {"$imagepolicy": "flux-system:frontend"} + - image: gcr.io/track-compliance/frontend:master-af5d359-1687177261 # {"$imagepolicy": "flux-system:frontend"} name: frontend resources: limits: