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 ab8a9ca18..0dce9972c 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"; @@ -36,6 +35,7 @@ import { mockBankHolidays } from "./mocks"; import assert from "assert"; import fs from "fs"; import { SchedulerMock } from "../integrationtest/api/testUtil/util"; +import { NoVaylaAuthenticationError } from "../src/error/NoVaylaAuthenticationError"; const chai = require("chai"); const { expect } = chai; @@ -365,7 +365,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 @@ -439,7 +439,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/migration-cli/package-lock.json b/migration-cli/package-lock.json index 986c552d6..236dbee7d 100644 --- a/migration-cli/package-lock.json +++ b/migration-cli/package-lock.json @@ -36,6 +36,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", @@ -58,6 +59,7 @@ "graphql": "14.7.0", "graphql-tag": "2.12.5", "html-react-parser": "3.0.8", + "js-cookie": "^3.0.1", "jsonschema": "1.4.1", "jsonwebtoken": "9.0.0", "jwk-to-pem": "2.0.5", @@ -1097,6 +1099,7 @@ "@types/aws-lambda": "8.10.92", "@types/chai": "4.3.0", "@types/chai-as-promised": "7.1.4", + "@types/js-cookie": "^3.0.2", "@types/jsonwebtoken": "8.5.8", "@types/jwk-to-pem": "2.0.1", "@types/mime-types": "2.1.1", @@ -1157,6 +1160,7 @@ "husky": "7.0.2", "identity-obj-proxy": "3.0.0", "jest": "27.2.4", + "js-cookie": "^3.0.1", "jsonschema": "1.4.1", "jsonwebtoken": "9.0.0", "jwk-to-pem": "2.0.5", diff --git a/package-lock.json b/package-lock.json index be1c57fbf..610f6bcd0 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", @@ -48,6 +49,7 @@ "graphql": "14.7.0", "graphql-tag": "2.12.5", "html-react-parser": "3.0.8", + "js-cookie": "^3.0.1", "jsonschema": "1.4.1", "jsonwebtoken": "9.0.0", "jwk-to-pem": "2.0.5", @@ -13025,6 +13027,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", @@ -13963,6 +13970,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", @@ -24320,10 +24333,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", @@ -45812,6 +45827,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", @@ -46580,6 +46600,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": { @@ -54631,10 +54659,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", diff --git a/package.json b/package.json index 51a4eec29..c0deaba44 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", @@ -42,6 +43,7 @@ "graphql": "14.7.0", "graphql-tag": "2.12.5", "html-react-parser": "3.0.8", + "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 };