Skip to content
Snippets Groups Projects
Verified Commit 8a9a26c3 authored by Andreas Ellewsen's avatar Andreas Ellewsen
Browse files

Use hash in invite url

To prevent the invite id from showing up in various logs along the way,
we change to use an anchor in the invite url, and a post request to the
backend.

Invite links are now on the form /invitelink/#<uuid> and the frontend
redirects to /invite as usual after the session has been created.

Resolves: GREG-90
parent e7b064a1
No related branches found
No related tags found
1 merge request!133Greg 90 safer invites
...@@ -64,7 +64,7 @@ export default function App() { ...@@ -64,7 +64,7 @@ export default function App() {
<ProtectedRoute path="/register"> <ProtectedRoute path="/register">
<Register /> <Register />
</ProtectedRoute> </ProtectedRoute>
<Route path="/invite/:id" component={InviteLink} /> <Route path="/invitelink/" component={InviteLink} />
<Route path="/invite/" component={Invite} /> <Route path="/invite/" component={Invite} />
<Route path="/guestregister/" component={GuestRegister} /> <Route path="/guestregister/" component={GuestRegister} />
<Route> <Route>
......
import { useEffect } from 'react' import { useEffect } from 'react'
import { Redirect, useParams } from 'react-router-dom' import { Redirect } from 'react-router-dom'
import { submitJsonOpts } from 'utils'
type TParams = { id: string }
function InviteLink() { function InviteLink() {
// Fetch backend endpoint to preserve invite_id in backend session then redirect // Fetch backend endpoint to preserve invite_id in backend session then redirect
// to generic invite page with info about feide login or manual with passport. // to generic invite page with info about feide login or manual with passport.
const { id } = useParams<TParams>() const id = window.location.hash.slice(1)
useEffect(() => { useEffect(() => {
fetch(`/api/ui/v1/invited/${id}`) fetch('/api/ui/v1/invitecheck/', submitJsonOpts('POST', { uuid: id }))
}, []) }, [])
return <Redirect to="/invite" /> return <Redirect to="/invite" />
} }
......
...@@ -20,7 +20,7 @@ urlpatterns += [ ...@@ -20,7 +20,7 @@ urlpatterns += [
re_path(r"roletypes/$", RoleTypeViewSet.as_view(), name="role-types"), re_path(r"roletypes/$", RoleTypeViewSet.as_view(), name="role-types"),
re_path(r"units/$", UnitsViewSet.as_view(), name="units"), re_path(r"units/$", UnitsViewSet.as_view(), name="units"),
path("invited/", InvitedGuestView.as_view(), name="invited-info"), path("invited/", InvitedGuestView.as_view(), name="invited-info"),
path("invited/<uuid>", CheckInvitationView.as_view(), name="invite-verify"), path("invitecheck/", CheckInvitationView.as_view(), name="invite-verify"),
path("invite/", InvitationView.as_view(), name="invitation"), path("invite/", InvitationView.as_view(), name="invitation"),
path("person/<int:id>", PersonView.as_view(), name="person-get"), path("person/<int:id>", PersonView.as_view(), name="person-get"),
path( path(
......
...@@ -97,7 +97,7 @@ class CheckInvitationView(APIView): ...@@ -97,7 +97,7 @@ class CheckInvitationView(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = [AllowAny] permission_classes = [AllowAny]
def get(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
""" """
Endpoint for verifying and setting invite_id in session. Endpoint for verifying and setting invite_id in session.
...@@ -105,8 +105,12 @@ class CheckInvitationView(APIView): ...@@ -105,8 +105,12 @@ class CheckInvitationView(APIView):
page you get to by following the invitation url to the frontend. This way a page you get to by following the invitation url to the frontend. This way a
session is created keeping the invite id safe, until the user returns from session is created keeping the invite id safe, until the user returns from
feide login if they choose to use it. feide login if they choose to use it.
Uses post to prevent invite id from showing up in various logs.
""" """
invite_id = kwargs["uuid"] invite_id = request.data.get("uuid")
if not invite_id:
return Response(status=status.HTTP_403_FORBIDDEN)
try: try:
invite_link = InvitationLink.objects.get(uuid=invite_id) invite_link = InvitationLink.objects.get(uuid=invite_id)
except (InvitationLink.DoesNotExist, exceptions.ValidationError): except (InvitationLink.DoesNotExist, exceptions.ValidationError):
......
...@@ -10,17 +10,15 @@ from greg.models import InvitationLink, Person, Identity ...@@ -10,17 +10,15 @@ from greg.models import InvitationLink, Person, Identity
@pytest.mark.django_db @pytest.mark.django_db
def test_get_invite(client): def test_get_invite(client):
"""Forbid access with bad invitation link uuid""" """Forbid access with bad invitation link uuid"""
response = client.get( response = client.post(reverse("gregui-v1:invite-verify"), data={"uuid": "baduuid"})
reverse("gregui-v1:invite-verify", kwargs={"uuid": "baduuid"})
)
assert response.status_code == status.HTTP_403_FORBIDDEN assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.django_db @pytest.mark.django_db
def test_get_invite_ok(client, invitation_link): def test_get_invite_ok(client, invitation_link):
"""Access okay with valid invitation link""" """Access okay with valid invitation link"""
response = client.get( response = client.post(
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid}) reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid}
) )
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
...@@ -28,10 +26,8 @@ def test_get_invite_ok(client, invitation_link): ...@@ -28,10 +26,8 @@ def test_get_invite_ok(client, invitation_link):
@pytest.mark.django_db @pytest.mark.django_db
def test_get_invite_expired(client, invitation_link_expired): def test_get_invite_expired(client, invitation_link_expired):
"""Forbid access with expired invite link""" """Forbid access with expired invite link"""
response = client.get( response = client.post(
reverse( reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link_expired.uuid}
"gregui-v1:invite-verify", kwargs={"uuid": invitation_link_expired.uuid}
)
) )
assert response.status_code == status.HTTP_403_FORBIDDEN assert response.status_code == status.HTTP_403_FORBIDDEN
...@@ -47,9 +43,7 @@ def test_get_invited_info_session_okay( ...@@ -47,9 +43,7 @@ def test_get_invited_info_session_okay(
client, invitation_link, person_foo_data, sponsor_guy_data, role_type_foo, unit_foo client, invitation_link, person_foo_data, sponsor_guy_data, role_type_foo, unit_foo
): ):
# get a session # get a session
client.get( client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid})
)
# Get info # Get info
response = client.get(reverse("gregui-v1:invited-info")) response = client.get(reverse("gregui-v1:invited-info"))
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
...@@ -82,9 +76,7 @@ def test_get_invited_info_expired_link( ...@@ -82,9 +76,7 @@ def test_get_invited_info_expired_link(
client, invitation_link, invitation_expired_date client, invitation_link, invitation_expired_date
): ):
# Get a session while link is valid # Get a session while link is valid
client.get( client.get(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid})
)
# Set expire link to expire long ago # Set expire link to expire long ago
invlink = InvitationLink.objects.get(uuid=invitation_link.uuid) invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
invlink.expire = invitation_expired_date invlink.expire = invitation_expired_date
...@@ -100,9 +92,7 @@ def test_invited_guest_can_post_information( ...@@ -100,9 +92,7 @@ def test_invited_guest_can_post_information(
client: APIClient, invitation_link, person_foo_data client: APIClient, invitation_link, person_foo_data
): ):
# get a session # get a session
client.get( client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid})
)
person = invitation_link.invitation.role.person person = invitation_link.invitation.role.person
assert person.private_mobile is None assert person.private_mobile is None
...@@ -129,9 +119,7 @@ def test_post_invited_info_expired_session( ...@@ -129,9 +119,7 @@ def test_post_invited_info_expired_session(
client, invitation_link, invitation_expired_date client, invitation_link, invitation_expired_date
): ):
# get a session # get a session
client.get( client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid})
)
# Set expire link to expire long ago # Set expire link to expire long ago
invlink = InvitationLink.objects.get(uuid=invitation_link.uuid) invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
invlink.expire = invitation_expired_date invlink.expire = invitation_expired_date
...@@ -145,9 +133,7 @@ def test_post_invited_info_expired_session( ...@@ -145,9 +133,7 @@ def test_post_invited_info_expired_session(
@pytest.mark.django_db @pytest.mark.django_db
def test_post_invited_info_deleted_inv_link(client, invitation_link): def test_post_invited_info_deleted_inv_link(client, invitation_link):
# get a session # get a session
client.get( client.post(reverse("gregui-v1:invite-verify"), data={"uuid": invitation_link.uuid})
reverse("gregui-v1:invite-verify", kwargs={"uuid": invitation_link.uuid})
)
# Delete link # Delete link
invlink = InvitationLink.objects.get(uuid=invitation_link.uuid) invlink = InvitationLink.objects.get(uuid=invitation_link.uuid)
invlink.delete() invlink.delete()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment