From a8e458da8f5dd431544bed28a75ad31f04322b12 Mon Sep 17 00:00:00 2001 From: Okke Harsta Date: Fri, 20 Oct 2023 10:30:20 +0200 Subject: [PATCH] Do not return already accepted invitations --- client/src/pages/Invitation.js | 16 +-- .../java/access/api/InvitationController.java | 3 + .../access/api/InvitationControllerTest.java | 16 +++ welcome/src/pages/Invitation.js | 101 +++++++++--------- 4 files changed, 79 insertions(+), 57 deletions(-) diff --git a/client/src/pages/Invitation.js b/client/src/pages/Invitation.js index 89c7e7aa..4065d778 100644 --- a/client/src/pages/Invitation.js +++ b/client/src/pages/Invitation.js @@ -32,11 +32,6 @@ export const Invitation = ({authenticated}) => { invitationByHash(hashParam) .then(res => { setInvitation(res); - if (res.status !== "OPEN") { - localStorage.removeItem("location"); - navigate("/"); - return; - } const reloaded = performance.getEntriesByType("navigation").map(entry => entry.type).includes("reload"); const mayAccept = localStorage.getItem(MAY_ACCEPT); if (mayAccept && config.name && !reloaded) { @@ -78,7 +73,16 @@ export const Invitation = ({authenticated}) => { }) .catch(e => { localStorage.removeItem(MAY_ACCEPT); - navigate(e.response?.status === 404 ? "/404" : "/profile"); + const status = e.response?.status; + if (status === 409) { + if (config.authenticated) { + navigate(`/`); + } else { + login(config); + } + } else { + navigate("/404"); + } }) //Prevent in dev mode an accidental acceptance of an invitation return () => localStorage.removeItem(MAY_ACCEPT); diff --git a/server/src/main/java/access/api/InvitationController.java b/server/src/main/java/access/api/InvitationController.java index 4a8e5b78..14d6b459 100644 --- a/server/src/main/java/access/api/InvitationController.java +++ b/server/src/main/java/access/api/InvitationController.java @@ -168,6 +168,9 @@ public ResponseEntity> resendInvitation(@PathVariable("id") @GetMapping("public") public ResponseEntity getInvitation(@RequestParam("hash") String hash) { Invitation invitation = invitationRepository.findByHash(hash).orElseThrow(NotFoundException::new); + if (!invitation.getStatus().equals(Status.OPEN)) { + throw new InvitationStatusException(); + } manage.deriveRemoteApplications(invitation.getRoles().stream().map(InvitationRole::getRole).toList()); return ResponseEntity.ok(invitation); } diff --git a/server/src/test/java/access/api/InvitationControllerTest.java b/server/src/test/java/access/api/InvitationControllerTest.java index a6d9229d..d60d7500 100644 --- a/server/src/test/java/access/api/InvitationControllerTest.java +++ b/server/src/test/java/access/api/InvitationControllerTest.java @@ -2,6 +2,7 @@ import access.AbstractTest; import access.AccessCookieFilter; +import access.exception.NotFoundException; import access.manage.EntityType; import access.model.*; import com.fasterxml.jackson.core.JsonProcessingException; @@ -43,6 +44,21 @@ void getInvitation() throws JsonProcessingException { .get("metaDataFields")).get("name:en")); } + @Test + void getInvitationAlreadyAccepted() { + Invitation invitation = invitationRepository.findByHash(Authority.GUEST.name()).orElseThrow(NotFoundException::new); + invitation.setStatus(Status.ACCEPTED); + invitationRepository.save(invitation); + given() + .when() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .queryParam("hash", Authority.GUEST.name()) + .get("/api/v1/invitations/public") + .then() + .statusCode(409); + } + @Test void newInvitation() throws Exception { AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/login", MANAGE_SUB); diff --git a/welcome/src/pages/Invitation.js b/welcome/src/pages/Invitation.js index e38380ca..4d753880 100644 --- a/welcome/src/pages/Invitation.js +++ b/welcome/src/pages/Invitation.js @@ -43,66 +43,65 @@ export const Invitation = ({authenticated}) => { useAppStore.setState(() => ({ invitation: res })); - const status = res.status; - if (status !== "OPEN") { - navigate(`/profile`); - } else { - const mayAccept = localStorage.getItem(MAY_ACCEPT); - if (mayAccept && config.name) { - acceptInvitation(hashParam, res.id) - .then(res=> { - localStorage.removeItem(MAY_ACCEPT); - me() - .then(userWithRoles => { - useAppStore.setState(() => ({ - user: userWithRoles, - authenticated: true - })); - const inviteRedeemUrlQueryParam = res.inviteRedeemUrl ? `&inviteRedeemUrl=${encodeURIComponent(res.inviteRedeemUrl)}` : ""; - localStorage.removeItem("location"); - navigate(`/proceed?hash=${hashParam}${inviteRedeemUrlQueryParam}`); - }) - }) - .catch(e => { - setLoading(false); - if (e.response && e.response.status === 412) { - setConfirmation({ - cancel: null, - action: () => logout().then(() => login(config, true, hashParam)), - warning: false, - error: true, - question: I18n.t("invitationAccept.emailMismatch", { - email: res.email, - userEmail: user.email - }), - confirmationHeader: I18n.t("confirmationDialog.error"), - confirmationTxt: I18n.t("invitationAccept.login") - }); - localStorage.setItem(MAY_ACCEPT, "true"); - setConfirmationOpen(true); - } else { - localStorage.removeItem(MAY_ACCEPT); - handleError(e); - } - + const mayAccept = localStorage.getItem(MAY_ACCEPT); + if (mayAccept && config.name) { + acceptInvitation(hashParam, res.id) + .then(res => { + localStorage.removeItem(MAY_ACCEPT); + me() + .then(userWithRoles => { + useAppStore.setState(() => ({ + user: userWithRoles, + authenticated: true + })); + const inviteRedeemUrlQueryParam = res.inviteRedeemUrl ? `&inviteRedeemUrl=${encodeURIComponent(res.inviteRedeemUrl)}` : ""; + localStorage.removeItem("location"); + navigate(`/proceed?hash=${hashParam}${inviteRedeemUrlQueryParam}`); + }) + }) + .catch(e => { + setLoading(false); + if (e.response && e.response.status === 412) { + setConfirmation({ + cancel: null, + action: () => logout().then(() => login(config, true, hashParam)), + warning: false, + error: true, + question: I18n.t("invitationAccept.emailMismatch", { + email: res.email, + userEmail: user.email + }), + confirmationHeader: I18n.t("confirmationDialog.error"), + confirmationTxt: I18n.t("invitationAccept.login") + }); + localStorage.setItem(MAY_ACCEPT, "true"); + setConfirmationOpen(true); + } else { + localStorage.removeItem(MAY_ACCEPT); + handleError(e); } - ) - } else { - localStorage.setItem(MAY_ACCEPT, "true"); - setExpired(new Date() > new Date(res.expiryDate * 1000)); - setLoading(false); - } + + } + ) + } else { + localStorage.setItem(MAY_ACCEPT, "true"); + setExpired(new Date() > new Date(res.expiryDate * 1000)); + setLoading(false); } } ) .catch(e => { localStorage.removeItem(MAY_ACCEPT); - let path = "/404"; const status = e.response?.status; if (status === 409) { - path = "/profile"; + if (config.authenticated) { + navigate(`/profile`); + } else { + login(config); + } + } else { + navigate("/404"); } - navigate(path); }) //Prevent in dev mode an accidental acceptance of an invitation return () => localStorage.removeItem(MAY_ACCEPT);