diff --git a/backend/iam/urls.py b/backend/iam/urls.py index 33e795c9f9..e653ccf263 100644 --- a/backend/iam/urls.py +++ b/backend/iam/urls.py @@ -9,6 +9,7 @@ LoginView, PasswordResetView, ResetPasswordConfirmView, + SendInvtationView, SessionTokenView, SetPasswordView, ) @@ -25,6 +26,7 @@ ResetPasswordConfirmView.as_view(), name="password-reset-confirm", ), + path("send-invitation/", SendInvtationView.as_view(), name="send-invitation"), path("set-password/", SetPasswordView.as_view(), name="set-password"), path("sso/", include("iam.sso.urls")), path( diff --git a/backend/iam/views.py b/backend/iam/views.py index 9b83b678c8..a6e19ca944 100644 --- a/backend/iam/views.py +++ b/backend/iam/views.py @@ -267,6 +267,37 @@ def post(self, request): return Response({"token": session_token}) +class SendInvtationView(views.APIView): + permission_classes = [permissions.AllowAny] + + @method_decorator(ensure_csrf_cookie) + def post(self, request): + email = request.data["email"] # type: ignore + associated_user = User.objects.filter(email=email).first() + if associated_user is None: + return Response( + status=HTTP_500_INTERNAL_SERVER_ERROR, + data={"error": "No user associated with this email"}, + ) + if EMAIL_HOST or EMAIL_HOST_RESCUE: + if associated_user is not None and associated_user.is_local: + try: + associated_user.mailing( + email_template_name="registration/first_connection_email.html", + subject=_("CISO Assistant: Invitation"), + ) + print("Sending invitation mail to", email) + except Exception as e: + print(e) + return Response(status=HTTP_202_ACCEPTED) + return Response( + data={ + "error": "Email server not configured, please contact your administrator" + }, + status=HTTP_500_INTERNAL_SERVER_ERROR, + ) + + class PasswordResetView(views.APIView): permission_classes = [permissions.AllowAny] diff --git a/frontend/src/lib/components/DetailView/DetailView.svelte b/frontend/src/lib/components/DetailView/DetailView.svelte index defecb3a04..3e4fdd6996 100644 --- a/frontend/src/lib/components/DetailView/DetailView.svelte +++ b/frontend/src/lib/components/DetailView/DetailView.svelte @@ -227,6 +227,26 @@ modalStore.trigger(modal); } + function modalSendInvitation(id: string, email: string, action: string): void { + const modalComponent: ModalComponent = { + ref: ConfirmModal, + props: { + _form: { id: id, urlmodel: getModelInfo('representatives').urlModel, email: email }, + id: id, + debug: false, + URLModel: getModelInfo('representatives').urlModel, + formAction: action + } + }; + const modal: ModalSettings = { + type: 'component', + component: modalComponent, + title: m.confirmModalTitle(), + body: `Do you want to send the invitation to ${email}?` + }; + modalStore.trigger(modal); + } + function getReverseForeignKeyEndpoint({ parentModel, targetUrlModel, @@ -632,6 +652,21 @@ {/if} {#if displayEditButton()} + {#if data.urlModel === 'representatives' && data.data.user} + + {/if} {#if data.data.state === 'Created'} { + const requestInitOptions: RequestInit = { + method: 'POST' + }; + const form = await event.request.formData(); + const raw = form.get('__superform_json') as string; + + let parsed = JSON.parse(raw); + + let email: string | undefined; + + if (Array.isArray(parsed)) { + const mapping = parsed[0]; + if (mapping && typeof mapping === 'object' && typeof mapping.email === 'number') { + const emailIndex = mapping.email; + email = parsed[emailIndex]; + } else { + email = parsed.find((v: any) => typeof v === 'string' && v.includes('@')); + } + } else if (parsed && typeof parsed === 'object') { + email = parsed.email ?? parsed['__email'] ?? undefined; + } + + const res = await fetch(`${BASE_API_URL}/iam/send-invitation/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email: email }) + }); + return new Response(null, { + headers: { + 'Content-Type': 'application/json' + } + }); +};