diff --git a/backend/src/error/NoVaylaAuthenticationError.ts b/backend/src/error/NoVaylaAuthenticationError.ts new file mode 100644 index 000000000..e075a6716 --- /dev/null +++ b/backend/src/error/NoVaylaAuthenticationError.ts @@ -0,0 +1,10 @@ +import { ClientError } from "./ClientError"; + +export class NoVaylaAuthenticationError extends ClientError { + constructor(m?: string) { + super("NoVaylaAuthenticationError", m); + + // Set the prototype explicitly. + Object.setPrototypeOf(this, NoVaylaAuthenticationError.prototype); + } +} diff --git a/backend/src/user/userService.ts b/backend/src/user/userService.ts index 4db3f1a2b..0bfeeed3b 100644 --- a/backend/src/user/userService.ts +++ b/backend/src/user/userService.ts @@ -9,6 +9,7 @@ import { createSignedCookies } from "./signedCookie"; import { apiConfig } from "../../../common/abstractApi"; import { isAorL } from "../util/userUtil"; import { NoHassuAccessError } from "../error/NoHassuAccessError"; +import { NoVaylaAuthenticationError } from "../error/NoVaylaAuthenticationError"; function parseRoles(roles: string): string[] | undefined { return roles @@ -99,7 +100,7 @@ export function getVaylaUser(): NykyinenKayttaja | undefined { export function requireVaylaUser(): NykyinenKayttaja { if (!(globalThis as any).currentUser) { - throw new IllegalAccessError("Väylä-kirjautuminen puuttuu"); + throw new NoVaylaAuthenticationError("Väylä-kirjautuminen puuttuu"); } return (globalThis as any).currentUser; } diff --git a/backend/test/apiHandler.test.ts b/backend/test/apiHandler.test.ts index 9e3f047c4..386e0d9de 100644 --- a/backend/test/apiHandler.test.ts +++ b/backend/test/apiHandler.test.ts @@ -3,7 +3,6 @@ import * as sinon from "sinon"; import { projektiDatabase } from "../src/database/projektiDatabase"; import { ProjektiFixture } from "./fixture/projektiFixture"; import { UserFixture } from "./fixture/userFixture"; -import { IllegalAccessError } from "../src/error/IllegalAccessError"; import { velho } from "../src/velho/velhoClient"; import { api } from "../integrationtest/api/apiClient"; import { personSearch } from "../src/personSearch/personSearchClient"; @@ -35,6 +34,7 @@ import SMTPTransport from "nodemailer/lib/smtp-transport"; import { mockBankHolidays } from "./mocks"; import assert from "assert"; import fs from "fs"; +import { NoVaylaAuthenticationError } from "../src/error/NoVaylaAuthenticationError"; const chai = require("chai"); const { expect } = chai; @@ -363,7 +363,7 @@ describe("apiHandler", () => { // Verify that projekti is not visible for anonymous users userFixture.logout(); - await expect(api.lataaProjekti(fixture.PROJEKTI1_OID)).to.eventually.be.rejectedWith(IllegalAccessError); + await expect(api.lataaProjekti(fixture.PROJEKTI1_OID)).to.eventually.be.rejectedWith(NoVaylaAuthenticationError); userFixture.loginAs(UserFixture.pekkaProjari); // Send aloituskuulutus to be approved @@ -437,7 +437,7 @@ describe("apiHandler", () => { createProjektiStub.resolves(); - await chai.assert.isRejected(api.tallennaProjekti(fixture.tallennaProjektiInput), IllegalAccessError); + await chai.assert.isRejected(api.tallennaProjekti(fixture.tallennaProjektiInput), NoVaylaAuthenticationError); }); }); diff --git a/package-lock.json b/package-lock.json index 8fc1a8ae1..f29f28fd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@mui/material": "5.10.6", "@mui/system": "5.10.6", "@mui/x-date-pickers": "5.0.2", + "@types/js-cookie": "^3.0.2", "@types/mime-types": "2.1.1", "@types/nodemailer": "6.4.4", "@types/pdfkit": "0.12.3", @@ -47,6 +48,7 @@ "esbuild": "0.12.19", "graphql": "14.7.0", "graphql-tag": "2.12.5", + "js-cookie": "^3.0.1", "jsonschema": "1.4.1", "jsonwebtoken": "9.0.0", "jwk-to-pem": "2.0.5", @@ -13024,6 +13026,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.2.tgz", + "integrity": "sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -13962,6 +13969,12 @@ "js-cookie": "^2.2.1" } }, + "node_modules/amazon-cognito-identity-js/node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "dev": true + }, "node_modules/anser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz", @@ -24200,10 +24213,12 @@ } }, "node_modules/js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==", + "engines": { + "node": ">=12" + } }, "node_modules/js-tokens": { "version": "4.0.0", @@ -35323,7 +35338,7 @@ "@aws-sdk/client-pinpoint": "3.6.1", "@aws-sdk/util-utf8-browser": "3.6.1", "lodash": "^4.17.20", - "uuid": "8.3.2" + "uuid": "^3.2.1" } }, "@aws-amplify/api": { @@ -35348,7 +35363,7 @@ "@aws-amplify/core": "4.3.6", "@aws-amplify/pubsub": "4.2.0", "graphql": "14.5.0", - "uuid": "8.3.2", + "uuid": "^3.2.1", "zen-observable-ts": "0.8.19" }, "dependencies": { @@ -35464,7 +35479,7 @@ "idb": "5.0.6", "immer": "9.0.6", "ulid": "2.3.0", - "uuid": "8.3.2", + "uuid": "3.3.2", "zen-observable-ts": "0.8.19", "zen-push": "0.2.1" }, @@ -35523,7 +35538,7 @@ "@aws-sdk/client-translate": "3.6.1", "@aws-sdk/eventstream-marshaller": "3.6.1", "@aws-sdk/util-utf8-node": "3.6.1", - "uuid": "8.3.2" + "uuid": "^3.2.1" } }, "@aws-amplify/pubsub": { @@ -35537,7 +35552,7 @@ "@aws-amplify/core": "4.3.6", "graphql": "14.0.0", "paho-mqtt": "^1.1.0", - "uuid": "8.3.2", + "uuid": "^3.2.1", "zen-observable-ts": "0.8.19" }, "dependencies": { @@ -36059,7 +36074,7 @@ "@aws-sdk/util-utf8-browser": "3.6.1", "@aws-sdk/util-utf8-node": "3.6.1", "tslib": "^2.0.0", - "uuid": "8.3.2" + "uuid": "^3.0.0" }, "dependencies": { "@aws-crypto/sha256-js": { @@ -36597,7 +36612,7 @@ "@aws-sdk/service-error-classification": "3.22.0", "@aws-sdk/types": "3.22.0", "tslib": "^2.0.0", - "uuid": "8.3.2" + "uuid": "^8.3.2" } }, "@aws-sdk/middleware-serde": { @@ -37470,7 +37485,7 @@ "@aws-sdk/service-error-classification": "3.22.0", "@aws-sdk/types": "3.22.0", "tslib": "^2.0.0", - "uuid": "8.3.2" + "uuid": "^8.3.2" } }, "@aws-sdk/middleware-serde": { @@ -37969,7 +37984,7 @@ "@aws-sdk/service-error-classification": "3.22.0", "@aws-sdk/types": "3.22.0", "tslib": "^2.0.0", - "uuid": "8.3.2" + "uuid": "^8.3.2" } }, "@aws-sdk/middleware-serde": { @@ -38372,7 +38387,7 @@ "@aws-sdk/util-utf8-browser": "3.6.1", "@aws-sdk/util-utf8-node": "3.6.1", "tslib": "^2.0.0", - "uuid": "8.3.2" + "uuid": "^3.0.0" }, "dependencies": { "@aws-crypto/sha256-js": { @@ -39249,7 +39264,7 @@ "@aws-sdk/types": "3.6.1", "react-native-get-random-values": "^1.4.0", "tslib": "^1.8.0", - "uuid": "8.3.2" + "uuid": "^3.0.0" }, "dependencies": { "@aws-sdk/protocol-http": { @@ -41955,7 +41970,7 @@ "safe-buffer": "^5.1.2", "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", - "uuid": "8.3.2" + "uuid": "^8.3.2" } }, "@cypress/xvfb": { @@ -43312,7 +43327,7 @@ "css.escape": "1.5.1", "data-uri-to-buffer": "3.0.1", "platform": "1.3.6", - "shell-quote": "1.7.3", + "shell-quote": "1.7.2", "source-map": "0.8.0-beta.0", "stacktrace-parser": "0.1.10", "strip-ansi": "6.0.0" @@ -44921,7 +44936,7 @@ "open": "^6.2.0", "ora": "^5.4.1", "semver": "^6.3.0", - "shell-quote": "1.7.3" + "shell-quote": "^1.7.3" }, "dependencies": { "ansi-styles": { @@ -45671,6 +45686,11 @@ } } }, + "@types/js-cookie": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.2.tgz", + "integrity": "sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -46439,6 +46459,14 @@ "fast-base64-decode": "^1.0.0", "isomorphic-unfetch": "^3.0.0", "js-cookie": "^2.2.1" + }, + "dependencies": { + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "dev": true + } } }, "anser": { @@ -51434,7 +51462,7 @@ "integrity": "sha512-b6OHfQuKasIKM9b6YPkX+KUj/TLBTx3B/1aT1T5F12FEuEqyFMdr59OMS53aoaSw8eVtapdqieX6lbg5opaOhA==", "dev": true, "requires": { - "loader-utils": "1.4.2" + "loader-utils": "^0.2.16" } }, "gensync": { @@ -52606,7 +52634,7 @@ "istanbul-lib-coverage": "^3.2.0", "p-map": "^3.0.0", "rimraf": "^3.0.0", - "uuid": "8.3.2" + "uuid": "^8.3.2" }, "dependencies": { "p-map": { @@ -54407,10 +54435,9 @@ "dev": true }, "js-cookie": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", - "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz", + "integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==" }, "js-tokens": { "version": "4.0.0", @@ -59566,7 +59593,7 @@ "dev": true, "peer": true, "requires": { - "shell-quote": "1.7.3", + "shell-quote": "^1.6.1", "ws": "^7" } }, diff --git a/package.json b/package.json index 2f99a4360..0e998ca6f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mui/material": "5.10.6", "@mui/system": "5.10.6", "@mui/x-date-pickers": "5.0.2", + "@types/js-cookie": "^3.0.2", "@types/mime-types": "2.1.1", "@types/nodemailer": "6.4.4", "@types/pdfkit": "0.12.3", @@ -41,6 +42,7 @@ "esbuild": "0.12.19", "graphql": "14.7.0", "graphql-tag": "2.12.5", + "js-cookie": "^3.0.1", "jsonschema": "1.4.1", "jsonwebtoken": "9.0.0", "jwk-to-pem": "2.0.5", diff --git a/src/components/ApiProvider.tsx b/src/components/ApiProvider.tsx index da5587106..b33b2cef9 100644 --- a/src/components/ApiProvider.tsx +++ b/src/components/ApiProvider.tsx @@ -9,6 +9,8 @@ import useTranslation from "next-translate/useTranslation"; import { Translate } from "next-translate"; import { GraphQLError } from "graphql"; import { NoHassuAccessError } from "backend/src/error/NoHassuAccessError"; +import { NoVaylaAuthenticationError } from "backend/src/error/NoVaylaAuthenticationError"; +import Cookies from "js-cookie"; type ApiContextType = { api: API; unauthorized: boolean }; @@ -65,15 +67,31 @@ function ApiProvider({ children }: Props) { const errorArray: readonly GraphQLError[] = Array.isArray(errors) ? errors : [errors]; const unauthorized = errorArray.some((error) => (error as any)?.errorInfo?.errorSubType === new NoHassuAccessError().className); setIsUnauthorized(unauthorized); + // Do not show snackbar errors on unauthorized 'page' if (!unauthorized) { commonErrorHandler(errorResponse); } + const noVaylaAuthentication = errorArray.some( + (error) => (error as any)?.errorInfo?.errorSubType === new NoVaylaAuthenticationError().className + ); + if (noVaylaAuthentication) { + removeAWSALBAndCookieSessionCookies(); + router.push("/yllapito/kirjaudu"); + } }; const api = createApiWithAdditionalErrorHandling(commonErrorHandler, authenticatedErrorHandler); return { api, unauthorized: isUnauthorized }; - }, [isUnauthorized, isYllapito, showErrorMessage, t]); + }, [isUnauthorized, isYllapito, router, showErrorMessage, t]); return {children}; } +function removeAWSALBAndCookieSessionCookies() { + Object.keys(Cookies.get() || {}) + .filter((cookie) => cookie.startsWith("AWSALB") || cookie.startsWith("cookiesession")) + .forEach((cookie) => { + Cookies.remove(cookie); + }); +} + export { ApiProvider };