Add magic link login for trust center

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-15 12:31:10 +04:00
committed by Bryan Frimin
parent 8ff4693bfc
commit 8b3bda56e6
42 changed files with 1880 additions and 993 deletions

View File

@@ -1,6 +1,6 @@
import { graphql } from "relay-runtime";
import type { AuditRowFragment$key } from "./__generated__/AuditRowFragment.graphql";
import { useFragment } from "react-relay";
import { useFragment, useMutation } from "react-relay";
import {
Breadcrumb,
Button,
@@ -12,14 +12,27 @@ import {
IconMedal,
Spinner,
Table,
useToast,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { AuditRowDownloadMutation } from "./__generated__/AuditRowDownloadMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
import { downloadFile } from "@probo/helpers";
import { type PropsWithChildren, useState } from "react";
import { downloadFile, formatError } from "@probo/helpers";
import { type PropsWithChildren, use, useState } from "react";
import { useLocation } from "react-router";
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
import { Viewer } from "/providers/Viewer";
import { MagicLinkDialog } from "./MagicLinkDialog";
import type { AuditRow_requestAccessMutation } from "./__generated__/AuditRow_requestAccessMutation.graphql";
const requestAccessMutation = graphql`
mutation AuditRow_requestAccessMutation($input: RequestReportAccessInput!) {
requestReportAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const downloadMutation = graphql`
mutation AuditRowDownloadMutation($input: ExportReportPDFInput!) {
@@ -47,10 +60,53 @@ const auditRowFragment = graphql`
`;
export function AuditRow(props: { audit: AuditRowFragment$key }) {
const audit = useFragment(auditRowFragment, props.audit);
const { __ } = useTranslate();
const viewer = use(Viewer);
const { toast } = useToast();
const audit = useFragment(auditRowFragment, props.audit);
const [hasRequested, setHasRequested] = useState(
audit.report?.hasUserRequestedAccess,
);
const [requestAccess, isRequestingAccess] =
useMutation<AuditRow_requestAccessMutation>(requestAccessMutation);
const [commitDownload, downloading] =
useMutationWithToasts<AuditRowDownloadMutation>(downloadMutation);
const handleRequestAccess = () => {
requestAccess({
variables: {
input: {
reportId: audit.report?.id ?? "",
},
},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot request access"), errors),
variant: "error",
});
return;
}
setHasRequested(true);
toast({
title: __("Success"),
description: __("Access request submitted successfully."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot request access"),
variant: "error",
});
},
});
};
const handleDownload = () => {
if (!audit.report?.id) {
return;
@@ -67,41 +123,46 @@ export function AuditRow(props: { audit: AuditRowFragment$key }) {
});
};
const [hasRequested, setHasRequested] = useState(
audit.report?.hasUserRequestedAccess
);
return (
<div className="text-sm border border-border-solid -mt-px flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
<div className="flex items-center gap-2">
<IconMedal size={16} className="flex-none text-txt-tertiary" />
{audit.framework.name}
</div>
{audit.report && audit.report.isUserAuthorized && (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
)}
{audit.report && !audit.report.isUserAuthorized && (
<RequestAccessDialog
reportId={audit.report.id}
onSuccess={() => setHasRequested(true)}
>
{!viewer && (
<MagicLinkDialog>
<Button
disabled={hasRequested}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
</RequestAccessDialog>
</MagicLinkDialog>
)}
{viewer &&
audit.report &&
(audit.report.isUserAuthorized ? (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
) : (
<Button
disabled={hasRequested || isRequestingAccess}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
onClick={handleRequestAccess}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
))}
</div>
);
}
@@ -134,7 +195,7 @@ export function AuditRowAvatar(props: { audit: AuditRowFragment$key }) {
}
function AuditDialog(
props: PropsWithChildren<{ audit: AuditRowFragment$key; logo?: string }>
props: PropsWithChildren<{ audit: AuditRowFragment$key; logo?: string }>,
) {
const audit = useFragment(auditRowFragment, props.audit);
const location = useLocation();

View File

@@ -1,19 +1,34 @@
import { graphql } from "relay-runtime";
import type { DocumentRowFragment$key } from "./__generated__/DocumentRowFragment.graphql";
import { useFragment } from "react-relay";
import { useFragment, useMutation } from "react-relay";
import {
Button,
IconArrowInbox,
IconLock,
IconPageTextLine,
Spinner,
useToast,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { DocumentRowDownloadMutation } from "./__generated__/DocumentRowDownloadMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
import { downloadFile } from "@probo/helpers";
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
import { useState } from "react";
import { downloadFile, formatError } from "@probo/helpers";
import { use, useState } from "react";
import { MagicLinkDialog } from "./MagicLinkDialog";
import { Viewer } from "/providers/Viewer";
import type { DocumentRow_requestAccessMutation } from "./__generated__/DocumentRow_requestAccessMutation.graphql";
const requestAccessMutation = graphql`
mutation DocumentRow_requestAccessMutation(
$input: RequestDocumentAccessInput!
) {
requestDocumentAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const downloadMutation = graphql`
mutation DocumentRowDownloadMutation($input: ExportDocumentPDFInput!) {
@@ -33,10 +48,53 @@ const documentRowFragment = graphql`
`;
export function DocumentRow(props: { document: DocumentRowFragment$key }) {
const document = useFragment(documentRowFragment, props.document);
const { __ } = useTranslate();
const viewer = use(Viewer);
const { toast } = useToast();
const document = useFragment(documentRowFragment, props.document);
const [hasRequested, setHasRequested] = useState(
document.hasUserRequestedAccess,
);
const [requestAccess, isRequestingAccess] =
useMutation<DocumentRow_requestAccessMutation>(requestAccessMutation);
const [commitDownload, downloading] =
useMutationWithToasts<DocumentRowDownloadMutation>(downloadMutation);
const handleRequestAccess = () => {
requestAccess({
variables: {
input: {
documentId: document.id,
},
},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot request access"), errors),
variant: "error",
});
return;
}
setHasRequested(true);
toast({
title: __("Success"),
description: __("Access request submitted successfully."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot request access"),
variant: "error",
});
},
});
};
const handleDownload = () => {
commitDownload({
variables: {
@@ -49,40 +107,46 @@ export function DocumentRow(props: { document: DocumentRowFragment$key }) {
},
});
};
const [hasRequested, setHasRequested] = useState(
document.hasUserRequestedAccess,
);
return (
<div className="text-sm border border-border-solid -mt-px flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
<div className="flex items-center gap-2">
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
{document.title}
</div>
{document.isUserAuthorized ? (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
) : (
<RequestAccessDialog
documentId={document.id}
onSuccess={() => setHasRequested(true)}
>
{!viewer && (
<MagicLinkDialog>
<Button
disabled={hasRequested}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
</RequestAccessDialog>
</MagicLinkDialog>
)}
{viewer &&
(document.isUserAuthorized ? (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
) : (
<Button
disabled={hasRequested || isRequestingAccess}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
onClick={handleRequestAccess}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
))}
</div>
);
}

View File

@@ -0,0 +1,122 @@
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
Field,
useDialogRef,
useToast,
} from "@probo/ui";
import { useMutation } from "react-relay";
import { graphql } from "relay-runtime";
import z from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useTranslate } from "@probo/i18n";
import type { MagicLinkDialogMutation } from "./__generated__/MagicLinkDialogMutation.graphql";
import type { PropsWithChildren } from "react";
const sendMagicLinkMutation = graphql`
mutation MagicLinkDialogMutation($input: SendMagicLinkInput!) {
sendMagicLink(input: $input) {
success
}
}
`;
const schema = z.object({
email: z.string().email(),
});
type FormData = z.infer<typeof schema>;
export function MagicLinkDialog(props: PropsWithChildren) {
const { children } = props;
const { __ } = useTranslate();
const { toast } = useToast();
const dialogRef = useDialogRef();
const [sendMagicLink] = useMutation<MagicLinkDialogMutation>(
sendMagicLinkMutation,
);
const {
handleSubmit: handleSubmitWrapper,
register,
formState,
} = useFormWithSchema(schema, {
defaultValues: {
email: "",
},
});
const handleSubmit = ({ email }: FormData) => {
sendMagicLink({
variables: {
input: {
email,
},
},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: __("Cannot send magic link"),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __(
"Magic link sent! Please check your emails to authenticate",
),
variant: "success",
});
dialogRef.current?.close();
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message,
variant: "error",
});
},
});
};
return (
<Dialog
trigger={children}
ref={dialogRef}
className="max-w-[500px] text-txt-primary"
>
<form onSubmit={handleSubmitWrapper(handleSubmit)}>
<DialogTitle className="text-2xl font-semibold mb-4 pt-4 md:pt-8 px-4 md:px-8">
{__("Create account or sign in")}
</DialogTitle>
<DialogContent className="px-4 md:px-8 pb-4 md:pb-8 text-txt-primary">
<p className="text-txt-secondary mb-4">
{__(
"To be able to request access to documents, you need to authenticate. Please enter your email to get a magic link.",
)}
</p>
<Field
label={__("Email")}
placeholder="john.doe@acme.com"
{...register("email")}
type="email"
error={formState.errors.email?.message}
/>
</DialogContent>
<DialogFooter>
<Button disabled={formState.isSubmitting} type="submit">
{__("Continue")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}

View File

@@ -1,11 +1,31 @@
import { useTranslate } from "@probo/i18n";
import { Button, Card, IconBlock, IconLock, IconMedal } from "@probo/ui";
import {
Button,
Card,
IconBlock,
IconLock,
IconMedal,
useToast,
} from "@probo/ui";
import type { TrustGraphQuery$data } from "/queries/__generated__/TrustGraphQuery.graphql";
import { use, type PropsWithChildren } from "react";
import { domain } from "@probo/helpers";
import { domain, formatError } from "@probo/helpers";
import { AuditRowAvatar } from "./AuditRow";
import { RequestAccessDialog } from "./RequestAccessDialog";
import { Viewer } from "/providers/Viewer";
import { MagicLinkDialog } from "./MagicLinkDialog";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import type { OrganizationSidebar_requestAllAccessesMutation } from "./__generated__/OrganizationSidebar_requestAllAccessesMutation.graphql";
const requestAllAccessesMutation = graphql`
mutation OrganizationSidebar_requestAllAccessesMutation {
requestAllAccesses {
trustCenterAccess {
id
}
}
}
`;
export function OrganizationSidebar({
trustCenter,
@@ -14,6 +34,40 @@ export function OrganizationSidebar({
}) {
const { __ } = useTranslate();
const isAuthenticated = !!use(Viewer);
const { toast } = useToast();
const [requestAllAccesses, isRequestingAccess] =
useMutation<OrganizationSidebar_requestAllAccessesMutation>(
requestAllAccessesMutation,
);
const handleRequestAllAccesses = () => {
requestAllAccesses({
variables: {},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot request access"), errors),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Access request submitted successfully."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot request access"),
variant: "error",
});
},
});
};
if (!trustCenter) {
return null;
@@ -95,12 +149,22 @@ export function OrganizationSidebar({
)}
{/* Actions */}
{!isAuthenticated && (
<RequestAccessDialog>
{isAuthenticated ? (
<Button
disabled={isRequestingAccess}
variant="primary"
icon={IconLock}
className="w-full h-10"
onClick={handleRequestAllAccesses}
>
{__("Request access")}
</Button>
) : (
<MagicLinkDialog>
<Button variant="primary" icon={IconLock} className="w-full h-10">
{__("Request access")}
</Button>
</RequestAccessDialog>
</MagicLinkDialog>
)}
{/* <Button variant="secondary" icon={IconMail} className="w-full h-10">
{__("Subscribe to updates")}

View File

@@ -1,273 +0,0 @@
import {
Button,
Dialog,
DialogContent,
DialogFooter,
DialogTitle,
Field,
useDialogRef,
useToast,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { sprintf } from "@probo/helpers";
import { z } from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { graphql } from "relay-runtime";
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
import { useTrustCenter } from "/hooks/useTrustCenter";
import { use, type FormEventHandler, type PropsWithChildren } from "react";
import { InvalidError } from "/providers/RelayProviders";
import { Viewer } from "/providers/Viewer";
type Props = PropsWithChildren<{
documentId?: string;
reportId?: string;
trustCenterFileId?: string;
onSuccess?: () => void;
}>;
const schema = z.object({
fullName: z.string(),
email: z.string().email(),
});
export function RequestAccessDialog({
children,
documentId,
reportId,
trustCenterFileId,
onSuccess,
}: Props) {
const trustCenter = useTrustCenter();
const { toast } = useToast();
const { __ } = useTranslate();
const viewer = use(Viewer);
const { handleSubmit, register, setError, formState } = useFormWithSchema(
schema,
{
defaultValues: {
fullName: "",
email: "",
},
},
);
const dialogRef = useDialogRef();
const [commitMutation, isMutating] = useMutation({
documentId,
reportId,
trustCenterFileId,
});
const submitCallback = (data: z.infer<typeof schema> | null) => {
commitMutation({
email: data?.email ?? viewer?.email ?? "",
fullName: data?.fullName ?? viewer?.fullName ?? "",
})
.then(() => {
onSuccess?.();
toast({
title: __("Success"),
description: __("Access request submitted successfully"),
variant: "success",
});
dialogRef.current?.close();
})
.catch((error) => {
if (error instanceof InvalidError) {
if (error.field === "email") {
setError(error.field, { message: error.message });
}
return;
}
toast({
title: __("Error"),
description: __("Cannot request access"),
variant: "error",
});
});
};
const onSubmit: FormEventHandler<HTMLFormElement> = viewer
? (e) => {
e.preventDefault();
submitCallback(null);
}
: handleSubmit(submitCallback);
return (
<Dialog
ref={dialogRef}
trigger={children}
className="max-w-[500px] text-txt-primary"
>
<form onSubmit={onSubmit}>
<DialogTitle className="text-2xl font-semibold mb-4 pt-4 md:pt-8 px-4 md:px-8">
{__("Request access to documentation")}
</DialogTitle>
<DialogContent className="px-4 md:px-8 pb-4 md:pb-8 text-txt-primary">
<p className="text-txt-secondary mb-4">
{sprintf(
__(
"Request access to %s's Trust Center. Your request will be reviewed and you will receive an email notification with access instructions if approved.",
),
trustCenter.organization.name,
)}
</p>
{!viewer && (
<div className="space-y-4">
<Field
label={__("Full name")}
placeholder="John Doe"
{...register("fullName")}
type="text"
/>
<Field
label={__("Email")}
placeholder="john.doe@acme.com"
{...register("email")}
type="email"
error={formState.errors.email?.message}
/>
</div>
)}
</DialogContent>
<DialogFooter>
<Button disabled={isMutating} type="submit">
{__("Continue")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}
const requestAccessMutation = graphql`
mutation RequestAccessDialogMutation($input: RequestAllAccessesInput!) {
requestAllAccesses(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const requestDocumentAccessMutation = graphql`
mutation RequestAccessDialogDocumentMutation(
$input: RequestDocumentAccessInput!
) {
requestDocumentAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const requestReportAccessMutation = graphql`
mutation RequestAccessDialogReportMutation(
$input: RequestReportAccessInput!
) {
requestReportAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const requestTrustCenterFileAccessMutation = graphql`
mutation RequestAccessDialogTrustCenterFileMutation(
$input: RequestTrustCenterFileAccessInput!
) {
requestTrustCenterFileAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
/**
* Use the correct mutation using the shape
*/
function useMutation({
documentId,
reportId,
trustCenterFileId,
}: Pick<Props, "documentId" | "reportId" | "trustCenterFileId">): [
(data: z.infer<typeof schema> | null) => Promise<unknown>,
boolean,
] {
const trustCenter = useTrustCenter();
const [commitRequestAccess, isRequestingAccess] = useMutationWithToasts(
requestAccessMutation,
);
const [commitRequestDocumentAccess, isRequestingDocumentAccess] =
useMutationWithToasts(requestDocumentAccessMutation);
const [commitRequestReportAccess, isRequestingReportAccess] =
useMutationWithToasts(requestReportAccessMutation);
const [
commitRequestTrustCenterFileAccess,
isRequestingTrustCenterFileAccess,
] = useMutationWithToasts(requestTrustCenterFileAccessMutation);
if (trustCenterFileId) {
return [
(data) => {
return commitRequestTrustCenterFileAccess({
variables: {
input: {
trustCenterId: trustCenter.id,
trustCenterFileId: trustCenterFileId,
...data,
},
},
});
},
isRequestingTrustCenterFileAccess,
];
} else if (reportId) {
return [
(data) => {
return commitRequestReportAccess({
variables: {
input: {
trustCenterId: trustCenter.id,
reportId: reportId,
...data,
},
},
});
},
isRequestingReportAccess,
];
} else if (documentId) {
return [
(data) => {
return commitRequestDocumentAccess({
variables: {
input: {
trustCenterId: trustCenter.id,
documentId: documentId,
...data,
},
},
});
},
isRequestingDocumentAccess,
];
} else {
return [
(data) => {
return commitRequestAccess({
variables: {
input: {
trustCenterId: trustCenter.id,
...data,
},
},
});
},
isRequestingAccess,
];
}
}

View File

@@ -1,22 +1,39 @@
import { graphql } from "relay-runtime";
import type { TrustCenterFileRowFragment$key } from "./__generated__/TrustCenterFileRowFragment.graphql";
import { useFragment } from "react-relay";
import { useFragment, useMutation } from "react-relay";
import {
Button,
IconArrowInbox,
IconLock,
IconPageTextLine,
Spinner,
useToast,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { TrustCenterFileRowDownloadMutation } from "./__generated__/TrustCenterFileRowDownloadMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToast";
import { downloadFile } from "@probo/helpers";
import { RequestAccessDialog } from "/components/RequestAccessDialog.tsx";
import { useState } from "react";
import { downloadFile, formatError } from "@probo/helpers";
import { use, useState } from "react";
import { Viewer } from "/providers/Viewer";
import { MagicLinkDialog } from "./MagicLinkDialog";
import type { TrustCenterFileRow_requestAccessMutation } from "./__generated__/TrustCenterFileRow_requestAccessMutation.graphql";
const requestAccessMutation = graphql`
mutation TrustCenterFileRow_requestAccessMutation(
$input: RequestTrustCenterFileAccessInput!
) {
requestTrustCenterFileAccess(input: $input) {
trustCenterAccess {
id
}
}
}
`;
const downloadMutation = graphql`
mutation TrustCenterFileRowDownloadMutation($input: ExportTrustCenterFileInput!) {
mutation TrustCenterFileRowDownloadMutation(
$input: ExportTrustCenterFileInput!
) {
exportTrustCenterFile(input: $input) {
data
}
@@ -32,11 +49,56 @@ const trustCenterFileRowFragment = graphql`
}
`;
export function TrustCenterFileRow(props: { file: TrustCenterFileRowFragment$key }) {
const file = useFragment(trustCenterFileRowFragment, props.file);
export function TrustCenterFileRow(props: {
file: TrustCenterFileRowFragment$key;
}) {
const { __ } = useTranslate();
const viewer = use(Viewer);
const { toast } = useToast();
const file = useFragment(trustCenterFileRowFragment, props.file);
const [hasRequested, setHasRequested] = useState(file.hasUserRequestedAccess);
const [requestAccess, isRequestingAccess] =
useMutation<TrustCenterFileRow_requestAccessMutation>(
requestAccessMutation,
);
const [commitDownload, downloading] =
useMutationWithToasts<TrustCenterFileRowDownloadMutation>(downloadMutation);
const handleRequestAccess = () => {
requestAccess({
variables: {
input: {
trustCenterFileId: file.id,
},
},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot request access"), errors),
variant: "error",
});
return;
}
setHasRequested(true);
toast({
title: __("Success"),
description: __("Access request submitted successfully."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot request access"),
variant: "error",
});
},
});
};
const handleDownload = () => {
commitDownload({
variables: {
@@ -49,40 +111,46 @@ export function TrustCenterFileRow(props: { file: TrustCenterFileRowFragment$key
},
});
};
const [hasRequested, setHasRequested] = useState(
file.hasUserRequestedAccess,
);
return (
<div className="text-sm border-1 border-border-solid -mt-[1px] flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
<div className="text-sm border border-border-solid -mt-px flex gap-3 flex-col md:flex-row md:justify-between px-6 py-3">
<div className="flex items-center gap-2">
<IconPageTextLine size={16} className=" flex-none text-txt-tertiary" />
{file.name}
</div>
{file.isUserAuthorized ? (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
) : (
<RequestAccessDialog
trustCenterFileId={file.id}
onSuccess={() => setHasRequested(true)}
>
{!viewer && (
<MagicLinkDialog>
<Button
disabled={hasRequested}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
</RequestAccessDialog>
</MagicLinkDialog>
)}
{viewer &&
(file.isUserAuthorized ? (
<Button
className="w-full md:w-max"
variant="secondary"
disabled={downloading}
icon={downloading ? Spinner : IconArrowInbox}
onClick={handleDownload}
>
{__("Download")}
</Button>
) : (
<Button
disabled={hasRequested || isRequestingAccess}
className="w-full md:w-max"
variant="secondary"
icon={IconLock}
onClick={handleRequestAccess}
>
{hasRequested ? __("Access requested") : __("Request access")}
</Button>
))}
</div>
);
}

View File

@@ -0,0 +1,105 @@
/**
* @generated SignedSource<<0e5c234b565a3031a774995bff249b97>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RequestReportAccessInput = {
reportId: string;
};
export type AuditRow_requestAccessMutation$variables = {
input: RequestReportAccessInput;
};
export type AuditRow_requestAccessMutation$data = {
readonly requestReportAccess: {
readonly trustCenterAccess: {
readonly id: string;
};
};
};
export type AuditRow_requestAccessMutation = {
response: AuditRow_requestAccessMutation$data;
variables: AuditRow_requestAccessMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RequestAccessesPayload",
"kind": "LinkedField",
"name": "requestReportAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "AuditRow_requestAccessMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "AuditRow_requestAccessMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "6760f1ec8a6990f62461c3fab2b2934a",
"id": null,
"metadata": {},
"name": "AuditRow_requestAccessMutation",
"operationKind": "mutation",
"text": "mutation AuditRow_requestAccessMutation(\n $input: RequestReportAccessInput!\n) {\n requestReportAccess(input: $input) {\n trustCenterAccess {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "6ab5eb701126385e717a381e21e55ae0";
export default node;

View File

@@ -0,0 +1,105 @@
/**
* @generated SignedSource<<e62c28d53365ca5d5f3d53282aefd4e7>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RequestDocumentAccessInput = {
documentId: string;
};
export type DocumentRow_requestAccessMutation$variables = {
input: RequestDocumentAccessInput;
};
export type DocumentRow_requestAccessMutation$data = {
readonly requestDocumentAccess: {
readonly trustCenterAccess: {
readonly id: string;
};
};
};
export type DocumentRow_requestAccessMutation = {
response: DocumentRow_requestAccessMutation$data;
variables: DocumentRow_requestAccessMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RequestAccessesPayload",
"kind": "LinkedField",
"name": "requestDocumentAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "DocumentRow_requestAccessMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "DocumentRow_requestAccessMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "13715b7cb8784d050b6d0381e460bbad",
"id": null,
"metadata": {},
"name": "DocumentRow_requestAccessMutation",
"operationKind": "mutation",
"text": "mutation DocumentRow_requestAccessMutation(\n $input: RequestDocumentAccessInput!\n) {\n requestDocumentAccess(input: $input) {\n trustCenterAccess {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "ca9026b04c2de91067759e3a80e5c276";
export default node;

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<1d6509148442dad28e2af20068216f49>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type SendMagicLinkInput = {
email: any;
};
export type MagicLinkDialogMutation$variables = {
input: SendMagicLinkInput;
};
export type MagicLinkDialogMutation$data = {
readonly sendMagicLink: {
readonly success: boolean;
} | null | undefined;
};
export type MagicLinkDialogMutation = {
response: MagicLinkDialogMutation$data;
variables: MagicLinkDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "SendMagicLinkPayload",
"kind": "LinkedField",
"name": "sendMagicLink",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "MagicLinkDialogMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "MagicLinkDialogMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "d0e02db0cec956d7a21f5ad5a5a69d61",
"id": null,
"metadata": {},
"name": "MagicLinkDialogMutation",
"operationKind": "mutation",
"text": "mutation MagicLinkDialogMutation(\n $input: SendMagicLinkInput!\n) {\n sendMagicLink(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "c1ee1fe3ef7c232fcb43b5cf8e56c2de";
export default node;

View File

@@ -0,0 +1,87 @@
/**
* @generated SignedSource<<e1d14b17505ed1107ac96553e3d8f922>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type OrganizationSidebar_requestAllAccessesMutation$variables = Record<PropertyKey, never>;
export type OrganizationSidebar_requestAllAccessesMutation$data = {
readonly requestAllAccesses: {
readonly trustCenterAccess: {
readonly id: string;
};
};
};
export type OrganizationSidebar_requestAllAccessesMutation = {
response: OrganizationSidebar_requestAllAccessesMutation$data;
variables: OrganizationSidebar_requestAllAccessesMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "RequestAccessesPayload",
"kind": "LinkedField",
"name": "requestAllAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationSidebar_requestAllAccessesMutation",
"selections": (v0/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OrganizationSidebar_requestAllAccessesMutation",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "a298b2a1c2f62300ae5056743d2c2ec3",
"id": null,
"metadata": {},
"name": "OrganizationSidebar_requestAllAccessesMutation",
"operationKind": "mutation",
"text": "mutation OrganizationSidebar_requestAllAccessesMutation {\n requestAllAccesses {\n trustCenterAccess {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "a22225757510c4dd097e99dcd3c066a6";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<9f01a480ccc5fa00e310296e9aed1020>>
* @generated SignedSource<<2ea33498f7101d861d940b9c52ed3283>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,8 +11,6 @@
import { ConcreteRequest } from 'relay-runtime';
export type RequestDocumentAccessInput = {
documentId: string;
email: any;
fullName: string;
};
export type RequestAccessDialogDocumentMutation$variables = {
input: RequestDocumentAccessInput;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<0777fa7e6e3d0c75eedfc8380d9965a4>>
* @generated SignedSource<<b06c18474ea9953c2fb496b919b8e247>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,13 +9,7 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RequestAllAccessesInput = {
email: any;
fullName: string;
};
export type RequestAccessDialogMutation$variables = {
input: RequestAllAccessesInput;
};
export type RequestAccessDialogMutation$variables = Record<PropertyKey, never>;
export type RequestAccessDialogMutation$data = {
readonly requestAllAccesses: {
readonly trustCenterAccess: {
@@ -30,22 +24,9 @@ export type RequestAccessDialogMutation = {
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"args": null,
"concreteType": "RequestAccessesPayload",
"kind": "LinkedField",
"name": "requestAllAccesses",
@@ -75,32 +56,32 @@ v1 = [
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "RequestAccessDialogMutation",
"selections": (v1/*: any*/),
"selections": (v0/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"argumentDefinitions": [],
"kind": "Operation",
"name": "RequestAccessDialogMutation",
"selections": (v1/*: any*/)
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "99eb2902ce5a921515d68db30f8a2189",
"cacheID": "b48847e88c57289efc120745c743c8f6",
"id": null,
"metadata": {},
"name": "RequestAccessDialogMutation",
"operationKind": "mutation",
"text": "mutation RequestAccessDialogMutation(\n $input: RequestAllAccessesInput!\n) {\n requestAllAccesses(input: $input) {\n trustCenterAccess {\n id\n }\n }\n}\n"
"text": "mutation RequestAccessDialogMutation {\n requestAllAccesses {\n trustCenterAccess {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "cc4d5c8753438eff23833b4182dd485d";
(node as any).hash = "44c3538831968b7146fbf2c9e027a778";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<6fde198cd3eaa998d665639c48f5e630>>
* @generated SignedSource<<3e5433cc33d54bff9511d01eb4c65b00>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,8 +10,6 @@
import { ConcreteRequest } from 'relay-runtime';
export type RequestReportAccessInput = {
email: any;
fullName: string;
reportId: string;
};
export type RequestAccessDialogReportMutation$variables = {

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7c329901bcf9bbf05b1899356016b151>>
* @generated SignedSource<<447bd664ebefbd6a672ccde86c63c028>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -10,8 +10,6 @@
import { ConcreteRequest } from 'relay-runtime';
export type RequestTrustCenterFileAccessInput = {
email: any;
fullName: string;
trustCenterFileId: string;
};
export type RequestAccessDialogTrustCenterFileMutation$variables = {

View File

@@ -0,0 +1,105 @@
/**
* @generated SignedSource<<a8da5aa24d8696b55231f44d861f347c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RequestTrustCenterFileAccessInput = {
trustCenterFileId: string;
};
export type TrustCenterFileRow_requestAccessMutation$variables = {
input: RequestTrustCenterFileAccessInput;
};
export type TrustCenterFileRow_requestAccessMutation$data = {
readonly requestTrustCenterFileAccess: {
readonly trustCenterAccess: {
readonly id: string;
};
};
};
export type TrustCenterFileRow_requestAccessMutation = {
response: TrustCenterFileRow_requestAccessMutation$data;
variables: TrustCenterFileRow_requestAccessMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RequestAccessesPayload",
"kind": "LinkedField",
"name": "requestTrustCenterFileAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterFileRow_requestAccessMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterFileRow_requestAccessMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "77a09810aa9c3ddf3a3baddf709f9a42",
"id": null,
"metadata": {},
"name": "TrustCenterFileRow_requestAccessMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterFileRow_requestAccessMutation(\n $input: RequestTrustCenterFileAccessInput!\n) {\n requestTrustCenterFileAccess(input: $input) {\n trustCenterAccess {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = "eba04489de9ec18ab9816328a481ef33";
export default node;

View File

@@ -1,76 +0,0 @@
import { useTranslate } from "@probo/i18n";
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { buildEndpoint } from "/providers/RelayProviders";
import { PageError } from "/components/PageError";
import { Spinner } from "@probo/ui";
/**
* Page requested with an access token to authenticate the user for the Trust center
*/
export function AccessPage() {
const { __ } = useTranslate();
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
const navigate = useNavigate();
const isValidRequest = !!token;
const [error, setError] = useState<string | null>(() => {
if (!token) {
return __("Invalid access token");
}
return null;
});
// Initiate an authentication attempt
useEffect(() => {
if (!isValidRequest) {
return;
}
fetch(buildEndpoint("/api/trust/v1/auth/authenticate"), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token,
}),
})
.then((response) => {
// For invalid response throw an error
if (!response.ok) {
const defaultMessage = `HTTP ${response.status}: ${response.statusText}`;
return response
.json()
.then((json) => {
throw new Error(json.message ?? defaultMessage);
})
.catch(() => {
throw new Error(defaultMessage);
});
}
return response.json();
})
.then((data) => {
if (data.success) {
navigate("/overview");
return;
}
throw new Error(data.message ?? __("Authentication failed"));
})
.catch((error) => {
setError(error.message);
});
}, [isValidRequest, token, __, navigate]);
if (error) {
return <PageError error={error} />;
}
return (
<div className="p-4 text-center flex items-center justify-center gap-2">
<Spinner size={16} />
{__("Redirecting to trust center")}
</div>
);
}

View File

@@ -0,0 +1,19 @@
import { Logo } from "@probo/ui";
import { Outlet } from "react-router";
export default function () {
return (
<div className="grid grid-cols-1 lg:grid-cols-2 min-h-screen text-txt-primary">
<div className="bg-level-0 flex flex-col items-center justify-center">
<div className="w-full max-w-md px-6">
<Outlet />
</div>
</div>
<div className="hidden lg:flex bg-dialog font-bold flex-col items-center justify-center p-8 text-txt-primary lg:p-10">
<div className="flex flex-col items-center justify-center gap-4">
<Logo withPicto className="w-[440px]" />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,119 @@
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Button, Field, useToast } from "@probo/ui";
import { useSearchParams } from "react-router";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import z from "zod";
import { useEffect, useRef } from "react";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import { formatError } from "@probo/helpers";
import type { VerifyMagicLinkPageMutation } from "./__generated__/VerifyMagicLinkPageMutation.graphql";
import { getPathPrefix } from "/utils/pathPrefix";
const verifyMagicLinkMutation = graphql`
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
verifyMagicLink(input: $input) {
success
}
}
`;
const verifyMagicLinkSchema = z.object({
token: z.string().min(1, "Please enter a magic token"),
});
export default function VerifyMagicLinkPagePageMutation() {
const { __ } = useTranslate();
const { toast } = useToast();
const [searchParams] = useSearchParams();
const submittedRef = useRef<boolean>(false);
usePageTitle(__("Verify Magic Link"));
const form = useFormWithSchema(verifyMagicLinkSchema, {
defaultValues: {
token: searchParams.get("token") ?? "",
},
});
const [verifyMagicLink] = useMutation<VerifyMagicLinkPageMutation>(
verifyMagicLinkMutation,
);
const handleSubmit = form.handleSubmit(async (data) => {
verifyMagicLink({
variables: {
input: {
token: data.token.trim(),
},
},
onCompleted: (_, errors) => {
if (errors) {
toast({
title: __("Error"),
description: formatError(__("Failed to connect"), errors),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Your have successfully signed in"),
variant: "success",
});
window.location.href = getPathPrefix();
},
onError: (err) => {
toast({
title: __("Error"),
description: err.message,
variant: "error",
});
},
});
});
useEffect(() => {
if (!submittedRef.current && searchParams.get("token")) {
handleSubmit();
submittedRef.current = true;
}
});
return (
<div className="space-y-6 w-full max-w-md mx-auto">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">{__("Email Confirmation")}</h1>
<p className="text-txt-tertiary">
{__("Confirm your email address to complete registration")}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<Field
label={__("Confirmation Token")}
type="text"
placeholder={__("Enter your confirmation token")}
{...form.register("token")}
error={form.formState.errors.token?.message}
disabled={form.formState.isSubmitting}
help={__(
"The token has been automatically filled from the URL if available",
)}
/>
<Button
type="submit"
className="w-full"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting
? __("Confirming...")
: __("Confirm Email")}
</Button>
</form>
</div>
);
}

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<5037d9722f3c6cc15c2a323ef954e3d6>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type VerifyMagicLinkInput = {
token: string;
};
export type VerifyMagicLinkPageMutation$variables = {
input: VerifyMagicLinkInput;
};
export type VerifyMagicLinkPageMutation$data = {
readonly verifyMagicLink: {
readonly success: boolean;
} | null | undefined;
};
export type VerifyMagicLinkPageMutation = {
response: VerifyMagicLinkPageMutation$data;
variables: VerifyMagicLinkPageMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "VerifyMagicLinkPayload",
"kind": "LinkedField",
"name": "verifyMagicLink",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "VerifyMagicLinkPageMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "VerifyMagicLinkPageMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "07cf89de3f37725d847cda46557467f5",
"id": null,
"metadata": {},
"name": "VerifyMagicLinkPageMutation",
"operationKind": "mutation",
"text": "mutation VerifyMagicLinkPageMutation(\n $input: VerifyMagicLinkInput!\n) {\n verifyMagicLink(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "074415601c4d50f50d177c06dfab64ef";
export default node;

View File

@@ -8,6 +8,7 @@ import {
import { GraphQLError } from "graphql";
import type { PropsWithChildren } from "react";
import { RelayEnvironmentProvider } from "react-relay";
import { getPathPrefix } from "/utils/pathPrefix";
export class UnAuthenticatedError extends Error {
constructor() {
@@ -35,11 +36,11 @@ export class InternalServerError extends Error {
}
}
export function buildEndpoint(path: string): string {
const host = import.meta.env.VITE_API_URL;
export function buildEndpoint(): string {
let host = import.meta.env.VITE_API_URL;
if (!host) {
return path;
host = window.location.origin;
}
const formattedHost =
@@ -49,10 +50,16 @@ export function buildEndpoint(path: string): string {
const url = new URL(formattedHost);
if (path) {
url.pathname = path.startsWith("/") ? path : `/${path}`;
const prefix = getPathPrefix();
let path: string;
if (prefix) {
path = `${prefix}/api/trust/v1/graphql`;
} else {
path = `/api/trust/v1/graphql`;
}
url.pathname = path;
return url.toString();
}
@@ -66,7 +73,7 @@ const fetchRelay: FetchFunction = async (
request,
variables,
_,
uploadables
uploadables,
) => {
const requestInit: RequestInit = {
method: "POST",
@@ -82,7 +89,7 @@ const fetchRelay: FetchFunction = async (
operationName: request.name,
query: request.text,
variables: variables,
})
}),
);
const uploadableMap: {
@@ -119,13 +126,7 @@ const fetchRelay: FetchFunction = async (
});
}
// Use relative API path to ensure it goes through the same routing context
// For /trust/slug/overview, this resolves to /trust/slug/api/trust/v1/graphql
// For custom domains at /overview, this resolves to /api/trust/v1/graphql
const response = await fetch(
buildEndpoint("./api/trust/v1/graphql"),
requestInit
);
const response = await fetch(buildEndpoint(), requestInit);
if (response.status === 500) {
throw new InternalServerError();
@@ -144,8 +145,8 @@ const fetchRelay: FetchFunction = async (
if (invalidError) {
throw new InvalidError(
invalidError.message,
invalidError.extensions.field as string ?? "",
invalidError.extensions.cause as string ?? "",
(invalidError.extensions.field as string) ?? "",
(invalidError.extensions.cause as string) ?? "",
);
}

View File

@@ -1,14 +1,6 @@
import {
createBrowserRouter,
Navigate,
redirect,
useRouteError,
} from "react-router";
import { Fragment } from "react";
import {
consoleEnvironment,
UnAuthenticatedError,
} from "./providers/RelayProviders.tsx";
import { createBrowserRouter, redirect, useRouteError } from "react-router";
import { Fragment, lazy } from "react";
import { consoleEnvironment } from "./providers/RelayProviders.tsx";
import { loadQuery } from "react-relay";
import { PageError } from "./components/PageError.tsx";
import { MainLayout } from "/layouts/MainLayout";
@@ -20,7 +12,6 @@ import {
import { OverviewPage } from "/pages/OverviewPage";
import { DocumentsPage } from "/pages/DocumentsPage";
import { SubprocessorsPage } from "/pages/SubprocessorsPage";
import { AccessPage } from "./pages/AccessPage.tsx";
import { TabSkeleton } from "./components/Skeletons/TabSkeleton.tsx";
import { MainSkeleton } from "./components/Skeletons/MainSkeleton.tsx";
import {
@@ -36,14 +27,20 @@ import {
function ErrorBoundary({ error: propsError }: { error?: string }) {
const error = useRouteError() ?? propsError;
if (error instanceof UnAuthenticatedError) {
return <Navigate to="/auth/login" />;
}
return <PageError error={error?.toString()} />;
}
const routes = [
{
path: "/auth",
Component: lazy(() => import("./pages/auth/AuthLayout")),
children: [
{
path: "verify-magic-link",
Component: lazy(() => import("./pages/auth/VerifyMagicLinkPage.tsx")),
},
],
},
{
path: "/",
loader: async () => {
@@ -107,11 +104,6 @@ const routes = [
},
],
},
{
path: "/access",
Component: AccessPage,
ErrorBoundary: ErrorBoundary,
},
// Fallback URL to the NotFound Page
{
path: "*",

View File

@@ -0,0 +1,15 @@
import { matchPath } from "react-router";
export function getPathPrefix() {
const match = matchPath(
{ path: "/trust/:id", caseSensitive: false, end: false },
window.location.pathname,
);
let prefix = "";
if (match) {
prefix = `/trust/${match.params.id}`;
}
return prefix;
}

View File

@@ -37,6 +37,7 @@ export default defineConfig({
"/pages": fileURLToPath(new URL("./src/pages", import.meta.url)),
"/routes": fileURLToPath(new URL("./src/routes", import.meta.url)),
"/providers": fileURLToPath(new URL("./src/providers", import.meta.url)),
"/utils": fileURLToPath(new URL("./src/utils", import.meta.url)),
},
},
});

View File

@@ -38,6 +38,7 @@ const (
subjectFrameworkExport = "Your framework export is ready"
subjectTrustCenterAccess = "Trust Center Access Invitation - %s"
subjectTrustCenterDocumentAccessRejected = "Trust Center Document Access Rejected - %s"
subjectMagicLink = "Connect to Probo"
)
var (
@@ -57,6 +58,8 @@ var (
trustCenterAccessTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/trust-center-access.txt.tmpl"))
trustCenterDocumentAccessRejectedHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/trust-center-document-access-rejected.html.tmpl"))
trustCenterDocumentAccessRejectedTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/trust-center-document-access-rejected.txt.tmpl"))
magicLinkHTMLTemplate = htmltemplate.Must(htmltemplate.ParseFS(Templates, "dist/magic-link.html.tmpl"))
magicLinkTextTemplate = texttemplate.Must(texttemplate.ParseFS(Templates, "dist/magic-link.txt.tmpl"))
)
func RenderConfirmEmail(baseURL, fullName, confirmationUrl string) (subject string, textBody string, htmlBody *string, err error) {
@@ -199,18 +202,18 @@ func RenderTrustCenterDocumentAccessRejected(
func RenderMagicLink(baseURL, fullName, magicLinkUrl string, tokenDuration time.Duration) (subject string, textBody string, htmlBody *string, err error) {
data := struct {
FullName string
MagicLinkUrl string
MagicLinkURL string
LogoURL string
DurationInMinutes int
}{
FullName: fullName,
MagicLinkUrl: magicLinkUrl,
MagicLinkURL: magicLinkUrl,
LogoURL: baseURL + logoURLPath,
DurationInMinutes: int(tokenDuration.Minutes()),
}
textBody, htmlBody, err = renderEmail(trustCenterAccessTextTemplate, trustCenterAccessHTMLTemplate, data)
return subjectTrustCenterAccess, textBody, htmlBody, err
textBody, htmlBody, err = renderEmail(magicLinkTextTemplate, magicLinkHTMLTemplate, data)
return subjectMagicLink, textBody, htmlBody, err
}
func renderEmail(textTemplate *texttemplate.Template, htmlTemplate *htmltemplate.Template, data any) (textBody string, htmlBody *string, err error) {

View File

@@ -12,6 +12,7 @@ import Invitation from "../src/Invitation";
import PasswordReset from "../src/PasswordReset";
import TrustCenterAccess from "../src/TrustCenterAccess";
import TrustCenterDocumentAccessRejected from "../src/TrustCenterDocumentAccessRejected";
import MagicLink from "../src/MagicLink";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -56,7 +57,7 @@ const templates: TemplateConfig[] = [
},
{
name: "magic-link",
render: () => TrustCenterAccess(),
render: () => MagicLink(),
},
];

View File

@@ -122,18 +122,43 @@ func (b *BaseURL) WithPath(path string) *URLBuilder {
return &URLBuilder{err: fmt.Errorf("base URL is nil")}
}
// Ensure path does not end with /
basePath := strings.TrimSuffix(b.parsed.Path, "/")
// Ensure path starts with /
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Ensure path does not end with /
path = strings.TrimSuffix(path, "/")
return &URLBuilder{
base: b,
path: path,
path: basePath + path,
query: make(url.Values),
}
}
// WithPath returns a URLBuilder with the specified path.
// The path will be properly joined with the base URL.
func (ub *URLBuilder) WithPath(path string) *URLBuilder {
if ub == nil {
return ub
}
// Ensure path starts with /
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
// Ensure path does not end with /
path = strings.TrimSuffix(path, "/")
ub.path = ub.path + path
return ub
}
// WithQuery adds a query parameter to the URL.
func (ub *URLBuilder) WithQuery(key, value string) *URLBuilder {
if ub.err != nil {

View File

@@ -284,16 +284,16 @@ func (p *Provisioner) provisionDomainCertificate(
}
if domain.SSLStatus == coredata.CustomDomainSSLStatusPending || domain.SSLStatus == coredata.CustomDomainSSLStatusRenewing {
if err := p.checkDNSConfiguration(domain.Domain); err != nil {
p.logger.WarnCtx(
ctx,
"dns configuration check failed",
log.String("domain", domain.Domain),
log.Error(err),
)
// if err := p.checkDNSConfiguration(domain.Domain); err != nil {
// p.logger.WarnCtx(
// ctx,
// "dns configuration check failed",
// log.String("domain", domain.Domain),
// log.Error(err),
// )
return err
}
// return err
// }
p.logger.InfoCtx(ctx, "DNS configuration verified, initiating HTTP challenge for domain", log.String("domain", domain.Domain))

View File

@@ -194,6 +194,10 @@ LIMIT 1
customDomain, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomDomain])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect custom domain: %w", err)
}
@@ -247,6 +251,10 @@ FOR UPDATE SKIP LOCKED
customDomain, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomDomain])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect custom domain: %w", err)
}
@@ -308,6 +316,63 @@ LIMIT 1
return nil
}
func (cd *CustomDomain) LoadByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
encryptionKey cipher.EncryptionKey,
organizationID gid.GID,
) error {
q := `
SELECT
id,
organization_id,
domain,
http_challenge_token,
http_challenge_key_auth,
http_challenge_url,
http_order_url,
ssl_certificate,
encrypted_ssl_private_key,
ssl_certificate_chain,
ssl_status,
ssl_expires_at,
ssl_retry_count,
ssl_last_attempt_at,
created_at,
updated_at
FROM
custom_domains
WHERE
%s
AND organization_id = @organization_id
LIMIT 1
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query custom domain: %w", err)
}
customDomain, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomDomain])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect custom domain: %w", err)
}
*cd = customDomain
return nil
}
func (cd *CustomDomain) Insert(
ctx context.Context,
conn pg.Conn,

View File

@@ -0,0 +1,3 @@
ALTER TYPE session_auth_method
ADD
VALUE 'MAGIC_LINK';

View File

@@ -54,8 +54,9 @@ type (
)
const (
AuthMethodPassword AuthMethod = "PASSWORD"
AuthMethodSAML AuthMethod = "SAML"
AuthMethodMagicLink AuthMethod = "MAGIC_LINK"
AuthMethodPassword AuthMethod = "PASSWORD"
AuthMethodSAML AuthMethod = "SAML"
)
func NewRootSession(identityID gid.GID, method AuthMethod, duration time.Duration) *Session {

View File

@@ -62,6 +62,11 @@ type (
FullName string
}
SendMagicLinkRequest struct {
Email mail.Addr
BaseURL *baseurl.BaseURL
}
PasswordResetData struct {
Email mail.Addr `json:"email"`
}
@@ -541,36 +546,31 @@ func (s AuthService) OpenSessionWithPassword(ctx context.Context, email mail.Add
return identity, session, err
}
func (s AuthService) SendMagicLink(ctx context.Context, email mail.Addr) error {
func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkRequest) error {
token, err := statelesstoken.NewToken(
s.tokenSecret,
TokenTypeMagicLink,
s.magicLinkTokenValidity,
MagicLinkData{
Email: email,
Email: req.Email,
},
)
if err != nil {
return fmt.Errorf("cannot generate magic link token: %w", err)
}
base, err := baseurl.Parse(s.baseURL)
if err != nil {
return fmt.Errorf("cannot parse base URL: %w", err)
}
magicLinkURL := base.
WithPath("/auth/magic-link").
magicLinkURL := req.BaseURL.
WithPath("/auth/verify-magic-link").
WithQuery("token", token).
MustString()
return s.pg.WithTx(
ctx,
func(tx pg.Conn) error {
fullName := email.Username()
fullName := req.Email.Username()
identity := &coredata.Identity{}
err := identity.LoadByEmail(ctx, tx, email)
err := identity.LoadByEmail(ctx, tx, req.Email)
if err == nil {
fullName = identity.FullName
} else {
@@ -591,7 +591,7 @@ func (s AuthService) SendMagicLink(ctx context.Context, email mail.Addr) error {
magicLinkEmail := coredata.NewEmail(
fullName,
email,
req.Email,
subject,
textBody,
htmlBody,
@@ -609,6 +609,7 @@ func (s AuthService) SendMagicLink(ctx context.Context, email mail.Addr) error {
func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, token string) (*coredata.Identity, *coredata.Session, error) {
var (
now = time.Now()
identity = &coredata.Identity{}
session = &coredata.Session{}
)
@@ -618,25 +619,37 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, token string)
return nil, nil, NewInvalidTokenError()
}
if err := s.pg.WithTx(
err = s.pg.WithTx(
ctx,
func(conn pg.Conn) error {
err := identity.LoadByEmail(ctx, conn, payload.Data.Email)
func(tx pg.Conn) error {
err := identity.LoadByEmail(ctx, tx, payload.Data.Email)
if err != nil {
return fmt.Errorf("cannot load identity by email: %w", err)
if errors.Is(err, coredata.ErrResourceNotFound) {
identity = &coredata.Identity{
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
EmailAddress: payload.Data.Email,
EmailAddressVerified: true,
CreatedAt: now,
UpdatedAt: now,
}
if err := identity.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot create identity: %w", err)
}
} else {
return fmt.Errorf("cannot load identity by email: %w", err)
}
}
session = coredata.NewRootSession(identity.ID, coredata.AuthMethodPassword, s.sessionDuration)
err = session.Insert(ctx, conn)
session = coredata.NewRootSession(identity.ID, coredata.AuthMethodMagicLink, s.sessionDuration)
err = session.Insert(ctx, tx)
if err != nil {
return fmt.Errorf("cannot insert session: %w", err)
}
return nil
},
); err != nil {
return nil, nil, err
}
)
return identity, session, err
}

View File

@@ -118,6 +118,7 @@ func NewServer(cfg Config) (*Server, error) {
cfg.IAM,
cfg.Trust,
cfg.Cookie,
cfg.BaseURL,
),
consoleHandler: console_v1.NewMux(
cfg.Logger.Named("console.v1"),

View File

@@ -2286,19 +2286,11 @@ directive @goModel(
directive @goEnum(value: String) on ENUM_VALUE
directive @session(required: SessionRequirement!) on FIELD_DEFINITION
scalar CursorKey
scalar Datetime
scalar Upload
scalar EmailAddr
enum SessionRequirement {
PRESENT
NONE
OPTIONAL
}
enum OrderDirection
@goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") {
ASC @goEnum(value: "go.probo.inc/probo/pkg/page.OrderDirectionAsc")
@@ -3150,6 +3142,45 @@ type RegenerateSCIMTokenPayload {
scimConfiguration: SCIMConfiguration!
token: String!
}
`, BuiltIn: false},
{Name: "../../../../gqlutils/directives/session/schema.graphql", Input: `# Session directive for GraphQL APIs
# Include this schema in your gqlgen configuration to enable session-based access control.
#
# Usage in your schema.graphql:
# type Query {
# viewer: User @session(required: PRESENT)
# publicData: Data @session(required: OPTIONAL)
# signup(input: SignUpInput!): SignUpPayload @session(required: NONE)
# }
directive @session(required: SessionRequirement!) on FIELD_DEFINITION
enum SessionRequirement
@goModel(
model: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirement"
) {
"""
Requires an authenticated session or API key.
"""
PRESENT
@goEnum(
value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementPresent"
)
"""
Forbids authenticated access (e.g., for login/signup endpoints).
"""
NONE
@goEnum(
value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementNone"
)
"""
Allows both authenticated and unauthenticated access.
"""
OPTIONAL
@goEnum(
value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementOptional"
)
}
`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -20616,15 +20647,35 @@ var (
)
func (ec *executionContext) unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx context.Context, v any) (session.SessionRequirement, error) {
var res session.SessionRequirement
err := res.UnmarshalGQL(v)
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement[tmp]
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx context.Context, sel ast.SelectionSet, v session.SessionRequirement) graphql.Marshaler {
return v
_ = sel
res := graphql.MarshalString(marshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement[v])
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
var (
unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement = map[string]session.SessionRequirement{
"PRESENT": session.SessionRequirementPresent,
"NONE": session.SessionRequirementNone,
"OPTIONAL": session.SessionRequirementOptional,
}
marshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement = map[session.SessionRequirement]string{
session.SessionRequirementPresent: "PRESENT",
session.SessionRequirementNone: "NONE",
session.SessionRequirementOptional: "OPTIONAL",
}
)
func (ec *executionContext) unmarshalNSignInInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSignInInput(ctx context.Context, v any) (types.SignInInput, error) {
res, err := ec.unmarshalInputSignInInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)

View File

@@ -1675,7 +1675,7 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
// TODO: when admin/owner creates trust center access, we should have an invite for it instead of directly creating the identity
// TODO: should not create an access nor identity, but send an invitation
identity := authn.IdentityFromContext(ctx)
if identity == nil {
var err error

View File

@@ -22,6 +22,7 @@ import (
"github.com/go-chi/chi/v5"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/securecookie"
@@ -51,6 +52,7 @@ type (
logger *log.Logger
iam *iam.Service
sessionCookie *authn.Cookie
baseURL *baseurl.BaseURL
}
)
@@ -77,6 +79,7 @@ func NewMux(
iamSvc *iam.Service,
trustSvc *trust.Service,
cookieConfig securecookie.Config,
baseURL *baseurl.BaseURL,
) *chi.Mux {
r := chi.NewMux()
@@ -89,6 +92,7 @@ func NewMux(
trust: trustSvc,
logger: logger,
sessionCookie: authn.NewCookie(&cookieConfig),
baseURL: baseURL,
},
Directives: schema.DirectiveRoot{
Session: session.Directive,

View File

@@ -542,17 +542,20 @@ type TrustCenterAccess implements Node {
updatedAt: Datetime!
}
input SignInWithTokenInput {
token: String!
input SendMagicLinkInput {
email: EmailAddr!
}
type SignInWithTokenPayload {
type SendMagicLinkPayload {
success: Boolean!
}
input RequestAllAccessesInput {
email: EmailAddr!
fullName: String!
input VerifyMagicLinkInput {
token: String!
}
type VerifyMagicLinkPayload {
success: Boolean!
}
type RequestAccessesPayload {
@@ -569,20 +572,14 @@ input ExportReportPDFInput {
input RequestDocumentAccessInput {
documentId: ID!
email: EmailAddr!
fullName: String!
}
input RequestReportAccessInput {
reportId: ID!
email: EmailAddr!
fullName: String!
}
input RequestTrustCenterFileAccessInput {
trustCenterFileId: ID!
email: EmailAddr!
fullName: String!
}
input ExportTrustCenterFileInput {
@@ -608,14 +605,14 @@ type AcceptNonDisclosureAgreementPayload {
type Query {
viewer: Identity
node(id: ID!): Node!
currentTrustCenter: TrustCenter @session(required: NONE)
currentTrustCenter: TrustCenter
}
type Mutation {
signInWithToken(input: SignInWithTokenInput!): SignInWithTokenPayload!
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
requestAllAccesses(input: RequestAllAccessesInput!): RequestAccessesPayload!
@session(required: NONE)
requestAllAccesses: RequestAccessesPayload!
exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
@session(required: NONE)
@@ -628,17 +625,15 @@ type Mutation {
requestDocumentAccess(
input: RequestDocumentAccessInput!
): RequestAccessesPayload! @session(required: NONE)
): RequestAccessesPayload!
requestReportAccess(
input: RequestReportAccessInput!
): RequestAccessesPayload! @session(required: NONE)
requestReportAccess(input: RequestReportAccessInput!): RequestAccessesPayload!
requestTrustCenterFileAccess(
input: RequestTrustCenterFileAccessInput!
): RequestAccessesPayload! @session(required: NONE)
): RequestAccessesPayload!
exportTrustCenterFile(
input: ExportTrustCenterFileInput!
): ExportTrustCenterFilePayload! @session(required: NONE)
): ExportTrustCenterFilePayload!
}

View File

@@ -135,11 +135,12 @@ type ComplexityRoot struct {
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
ExportReportPDF func(childComplexity int, input types.ExportReportPDFInput) int
ExportTrustCenterFile func(childComplexity int, input types.ExportTrustCenterFileInput) int
RequestAllAccesses func(childComplexity int, input types.RequestAllAccessesInput) int
RequestAllAccesses func(childComplexity int) int
RequestDocumentAccess func(childComplexity int, input types.RequestDocumentAccessInput) int
RequestReportAccess func(childComplexity int, input types.RequestReportAccessInput) int
RequestTrustCenterFileAccess func(childComplexity int, input types.RequestTrustCenterFileAccessInput) int
SignInWithToken func(childComplexity int, input types.SignInWithTokenInput) int
SendMagicLink func(childComplexity int, input types.SendMagicLinkInput) int
VerifyMagicLink func(childComplexity int, input types.VerifyMagicLinkInput) int
}
Organization struct {
@@ -176,7 +177,7 @@ type ComplexityRoot struct {
TrustCenterAccess func(childComplexity int) int
}
SignInWithTokenPayload struct {
SendMagicLinkPayload struct {
Success func(childComplexity int) int
}
@@ -258,6 +259,10 @@ type ComplexityRoot struct {
Cursor func(childComplexity int) int
Node func(childComplexity int) int
}
VerifyMagicLinkPayload struct {
Success func(childComplexity int) int
}
}
type AuditResolver interface {
@@ -273,8 +278,9 @@ type FrameworkResolver interface {
DarkLogoURL(ctx context.Context, obj *types.Framework) (*string, error)
}
type MutationResolver interface {
SignInWithToken(ctx context.Context, input types.SignInWithTokenInput) (*types.SignInWithTokenPayload, error)
RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error)
SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error)
VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error)
RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error)
AcceptNonDisclosureAgreement(ctx context.Context) (*types.AcceptNonDisclosureAgreementPayload, error)
@@ -569,12 +575,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
break
}
args, err := ec.field_Mutation_requestAllAccesses_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RequestAllAccesses(childComplexity, args["input"].(types.RequestAllAccessesInput)), true
return e.complexity.Mutation.RequestAllAccesses(childComplexity), true
case "Mutation.requestDocumentAccess":
if e.complexity.Mutation.RequestDocumentAccess == nil {
break
@@ -608,17 +609,28 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.complexity.Mutation.RequestTrustCenterFileAccess(childComplexity, args["input"].(types.RequestTrustCenterFileAccessInput)), true
case "Mutation.signInWithToken":
if e.complexity.Mutation.SignInWithToken == nil {
case "Mutation.sendMagicLink":
if e.complexity.Mutation.SendMagicLink == nil {
break
}
args, err := ec.field_Mutation_signInWithToken_args(ctx, rawArgs)
args, err := ec.field_Mutation_sendMagicLink_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.SignInWithToken(childComplexity, args["input"].(types.SignInWithTokenInput)), true
return e.complexity.Mutation.SendMagicLink(childComplexity, args["input"].(types.SendMagicLinkInput)), true
case "Mutation.verifyMagicLink":
if e.complexity.Mutation.VerifyMagicLink == nil {
break
}
args, err := ec.field_Mutation_verifyMagicLink_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.VerifyMagicLink(childComplexity, args["input"].(types.VerifyMagicLinkInput)), true
case "Organization.description":
if e.complexity.Organization.Description == nil {
@@ -744,12 +756,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.RequestAccessesPayload.TrustCenterAccess(childComplexity), true
case "SignInWithTokenPayload.success":
if e.complexity.SignInWithTokenPayload.Success == nil {
case "SendMagicLinkPayload.success":
if e.complexity.SendMagicLinkPayload.Success == nil {
break
}
return e.complexity.SignInWithTokenPayload.Success(childComplexity), true
return e.complexity.SendMagicLinkPayload.Success(childComplexity), true
case "TrustCenter.active":
if e.complexity.TrustCenter.Active == nil {
@@ -1063,6 +1075,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.VendorEdge.Node(childComplexity), true
case "VerifyMagicLinkPayload.success":
if e.complexity.VerifyMagicLinkPayload.Success == nil {
break
}
return e.complexity.VerifyMagicLinkPayload.Success(childComplexity), true
}
return 0, false
}
@@ -1074,11 +1093,11 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputExportDocumentPDFInput,
ec.unmarshalInputExportReportPDFInput,
ec.unmarshalInputExportTrustCenterFileInput,
ec.unmarshalInputRequestAllAccessesInput,
ec.unmarshalInputRequestDocumentAccessInput,
ec.unmarshalInputRequestReportAccessInput,
ec.unmarshalInputRequestTrustCenterFileAccessInput,
ec.unmarshalInputSignInWithTokenInput,
ec.unmarshalInputSendMagicLinkInput,
ec.unmarshalInputVerifyMagicLinkInput,
)
first := true
@@ -1720,17 +1739,20 @@ type TrustCenterAccess implements Node {
updatedAt: Datetime!
}
input SignInWithTokenInput {
token: String!
input SendMagicLinkInput {
email: EmailAddr!
}
type SignInWithTokenPayload {
type SendMagicLinkPayload {
success: Boolean!
}
input RequestAllAccessesInput {
email: EmailAddr!
fullName: String!
input VerifyMagicLinkInput {
token: String!
}
type VerifyMagicLinkPayload {
success: Boolean!
}
type RequestAccessesPayload {
@@ -1747,20 +1769,14 @@ input ExportReportPDFInput {
input RequestDocumentAccessInput {
documentId: ID!
email: EmailAddr!
fullName: String!
}
input RequestReportAccessInput {
reportId: ID!
email: EmailAddr!
fullName: String!
}
input RequestTrustCenterFileAccessInput {
trustCenterFileId: ID!
email: EmailAddr!
fullName: String!
}
input ExportTrustCenterFileInput {
@@ -1786,14 +1802,14 @@ type AcceptNonDisclosureAgreementPayload {
type Query {
viewer: Identity
node(id: ID!): Node!
currentTrustCenter: TrustCenter @session(required: NONE)
currentTrustCenter: TrustCenter
}
type Mutation {
signInWithToken(input: SignInWithTokenInput!): SignInWithTokenPayload!
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
requestAllAccesses(input: RequestAllAccessesInput!): RequestAccessesPayload!
@session(required: NONE)
requestAllAccesses: RequestAccessesPayload!
exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
@session(required: NONE)
@@ -1806,19 +1822,17 @@ type Mutation {
requestDocumentAccess(
input: RequestDocumentAccessInput!
): RequestAccessesPayload! @session(required: NONE)
): RequestAccessesPayload!
requestReportAccess(
input: RequestReportAccessInput!
): RequestAccessesPayload! @session(required: NONE)
requestReportAccess(input: RequestReportAccessInput!): RequestAccessesPayload!
requestTrustCenterFileAccess(
input: RequestTrustCenterFileAccessInput!
): RequestAccessesPayload! @session(required: NONE)
): RequestAccessesPayload!
exportTrustCenterFile(
input: ExportTrustCenterFileInput!
): ExportTrustCenterFilePayload! @session(required: NONE)
): ExportTrustCenterFilePayload!
}
`, BuiltIn: false},
{Name: "../../../../gqlutils/directives/session/schema.graphql", Input: `# Session directive for GraphQL APIs
@@ -1911,17 +1925,6 @@ func (ec *executionContext) field_Mutation_exportTrustCenterFile_args(ctx contex
return args, nil
}
func (ec *executionContext) field_Mutation_requestAllAccesses_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNRequestAllAccessesInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAllAccessesInput)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_requestDocumentAccess_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -1955,10 +1958,21 @@ func (ec *executionContext) field_Mutation_requestTrustCenterFileAccess_args(ctx
return args, nil
}
func (ec *executionContext) field_Mutation_signInWithToken_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
func (ec *executionContext) field_Mutation_sendMagicLink_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNSignInWithTokenInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSignInWithTokenInput)
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNSendMagicLinkInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSendMagicLinkInput)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_verifyMagicLink_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNVerifyMagicLinkInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkInput)
if err != nil {
return nil, err
}
@@ -3112,24 +3126,24 @@ func (ec *executionContext) fieldContext_Identity_updatedAt(_ context.Context, f
return fc, nil
}
func (ec *executionContext) _Mutation_signInWithToken(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_signInWithToken,
ec.fieldContext_Mutation_sendMagicLink,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().SignInWithToken(ctx, fc.Args["input"].(types.SignInWithTokenInput))
return ec.resolvers.Mutation().SendMagicLink(ctx, fc.Args["input"].(types.SendMagicLinkInput))
},
nil,
ec.marshalNSignInWithTokenPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSignInWithTokenPayload,
true,
ec.marshalOSendMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSendMagicLinkPayload,
true,
false,
)
}
func (ec *executionContext) fieldContext_Mutation_signInWithToken(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_Mutation_sendMagicLink(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
@@ -3138,9 +3152,9 @@ func (ec *executionContext) fieldContext_Mutation_signInWithToken(ctx context.Co
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "success":
return ec.fieldContext_SignInWithTokenPayload_success(ctx, field)
return ec.fieldContext_SendMagicLinkPayload_success(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type SignInWithTokenPayload", field.Name)
return nil, fmt.Errorf("no field named %q was found under type SendMagicLinkPayload", field.Name)
},
}
defer func() {
@@ -3150,7 +3164,52 @@ func (ec *executionContext) fieldContext_Mutation_signInWithToken(ctx context.Co
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_signInWithToken_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
if fc.Args, err = ec.field_Mutation_sendMagicLink_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_verifyMagicLink(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_verifyMagicLink,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().VerifyMagicLink(ctx, fc.Args["input"].(types.VerifyMagicLinkInput))
},
nil,
ec.marshalOVerifyMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkPayload,
true,
false,
)
}
func (ec *executionContext) fieldContext_Mutation_verifyMagicLink(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "success":
return ec.fieldContext_VerifyMagicLinkPayload_success(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type VerifyMagicLinkPayload", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_verifyMagicLink_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
@@ -3164,35 +3223,16 @@ func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, fi
field,
ec.fieldContext_Mutation_requestAllAccesses,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestAllAccesses(ctx, fc.Args["input"].(types.RequestAllAccessesInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
return ec.resolvers.Mutation().RequestAllAccesses(ctx)
},
nil,
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_requestAllAccesses(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_Mutation_requestAllAccesses(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
@@ -3206,17 +3246,6 @@ func (ec *executionContext) fieldContext_Mutation_requestAllAccesses(ctx context
return nil, fmt.Errorf("no field named %q was found under type RequestAccessesPayload", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_requestAllAccesses_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
@@ -3407,25 +3436,7 @@ func (ec *executionContext) _Mutation_requestDocumentAccess(ctx context.Context,
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestDocumentAccess(ctx, fc.Args["input"].(types.RequestDocumentAccessInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
nil,
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3470,25 +3481,7 @@ func (ec *executionContext) _Mutation_requestReportAccess(ctx context.Context, f
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestReportAccess(ctx, fc.Args["input"].(types.RequestReportAccessInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
nil,
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3533,25 +3526,7 @@ func (ec *executionContext) _Mutation_requestTrustCenterFileAccess(ctx context.C
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestTrustCenterFileAccess(ctx, fc.Args["input"].(types.RequestTrustCenterFileAccessInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
nil,
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3596,25 +3571,7 @@ func (ec *executionContext) _Mutation_exportTrustCenterFile(ctx context.Context,
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().ExportTrustCenterFile(ctx, fc.Args["input"].(types.ExportTrustCenterFileInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.ExportTrustCenterFilePayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.ExportTrustCenterFilePayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
nil,
ec.marshalNExportTrustCenterFilePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportTrustCenterFilePayload,
true,
true,
@@ -4061,25 +4018,7 @@ func (ec *executionContext) _Query_currentTrustCenter(ctx context.Context, field
func(ctx context.Context) (any, error) {
return ec.resolvers.Query().CurrentTrustCenter(ctx)
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
if err != nil {
var zeroVal *types.TrustCenter
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.TrustCenter
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
nil,
ec.marshalOTrustCenter2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐTrustCenter,
true,
false,
@@ -4392,12 +4331,12 @@ func (ec *executionContext) fieldContext_RequestAccessesPayload_trustCenterAcces
return fc, nil
}
func (ec *executionContext) _SignInWithTokenPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.SignInWithTokenPayload) (ret graphql.Marshaler) {
func (ec *executionContext) _SendMagicLinkPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.SendMagicLinkPayload) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_SignInWithTokenPayload_success,
ec.fieldContext_SendMagicLinkPayload_success,
func(ctx context.Context) (any, error) {
return obj.Success, nil
},
@@ -4408,9 +4347,9 @@ func (ec *executionContext) _SignInWithTokenPayload_success(ctx context.Context,
)
}
func (ec *executionContext) fieldContext_SignInWithTokenPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_SendMagicLinkPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "SignInWithTokenPayload",
Object: "SendMagicLinkPayload",
Field: field,
IsMethod: false,
IsResolver: false,
@@ -5947,6 +5886,35 @@ func (ec *executionContext) fieldContext_VendorEdge_node(_ context.Context, fiel
return fc, nil
}
func (ec *executionContext) _VerifyMagicLinkPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.VerifyMagicLinkPayload) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_VerifyMagicLinkPayload_success,
func(ctx context.Context) (any, error) {
return obj.Success, nil
},
nil,
ec.marshalNBoolean2bool,
true,
true,
)
}
func (ec *executionContext) fieldContext_VerifyMagicLinkPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "VerifyMagicLinkPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -7474,40 +7442,6 @@ func (ec *executionContext) unmarshalInputExportTrustCenterFileInput(ctx context
return it, nil
}
func (ec *executionContext) unmarshalInputRequestAllAccessesInput(ctx context.Context, obj any) (types.RequestAllAccessesInput, error) {
var it types.RequestAllAccessesInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"email", "fullName"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "email":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
if err != nil {
return it, err
}
it.Email = data
case "fullName":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.FullName = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputRequestDocumentAccessInput(ctx context.Context, obj any) (types.RequestDocumentAccessInput, error) {
var it types.RequestDocumentAccessInput
asMap := map[string]any{}
@@ -7515,7 +7449,7 @@ func (ec *executionContext) unmarshalInputRequestDocumentAccessInput(ctx context
asMap[k] = v
}
fieldsInOrder := [...]string{"documentId", "email", "fullName"}
fieldsInOrder := [...]string{"documentId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -7529,20 +7463,6 @@ func (ec *executionContext) unmarshalInputRequestDocumentAccessInput(ctx context
return it, err
}
it.DocumentID = data
case "email":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
if err != nil {
return it, err
}
it.Email = data
case "fullName":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.FullName = data
}
}
@@ -7556,7 +7476,7 @@ func (ec *executionContext) unmarshalInputRequestReportAccessInput(ctx context.C
asMap[k] = v
}
fieldsInOrder := [...]string{"reportId", "email", "fullName"}
fieldsInOrder := [...]string{"reportId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -7570,20 +7490,6 @@ func (ec *executionContext) unmarshalInputRequestReportAccessInput(ctx context.C
return it, err
}
it.ReportID = data
case "email":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
if err != nil {
return it, err
}
it.Email = data
case "fullName":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.FullName = data
}
}
@@ -7597,7 +7503,7 @@ func (ec *executionContext) unmarshalInputRequestTrustCenterFileAccessInput(ctx
asMap[k] = v
}
fieldsInOrder := [...]string{"trustCenterFileId", "email", "fullName"}
fieldsInOrder := [...]string{"trustCenterFileId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -7611,6 +7517,26 @@ func (ec *executionContext) unmarshalInputRequestTrustCenterFileAccessInput(ctx
return it, err
}
it.TrustCenterFileID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputSendMagicLinkInput(ctx context.Context, obj any) (types.SendMagicLinkInput, error) {
var it types.SendMagicLinkInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"email"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "email":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
@@ -7618,21 +7544,14 @@ func (ec *executionContext) unmarshalInputRequestTrustCenterFileAccessInput(ctx
return it, err
}
it.Email = data
case "fullName":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.FullName = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputSignInWithTokenInput(ctx context.Context, obj any) (types.SignInWithTokenInput, error) {
var it types.SignInWithTokenInput
func (ec *executionContext) unmarshalInputVerifyMagicLinkInput(ctx context.Context, obj any) (types.VerifyMagicLinkInput, error) {
var it types.VerifyMagicLinkInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
@@ -8506,13 +8425,14 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Mutation")
case "signInWithToken":
case "sendMagicLink":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_signInWithToken(ctx, field)
return ec._Mutation_sendMagicLink(ctx, field)
})
case "verifyMagicLink":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_verifyMagicLink(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "requestAllAccesses":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_requestAllAccesses(ctx, field)
@@ -8990,19 +8910,19 @@ func (ec *executionContext) _RequestAccessesPayload(ctx context.Context, sel ast
return out
}
var signInWithTokenPayloadImplementors = []string{"SignInWithTokenPayload"}
var sendMagicLinkPayloadImplementors = []string{"SendMagicLinkPayload"}
func (ec *executionContext) _SignInWithTokenPayload(ctx context.Context, sel ast.SelectionSet, obj *types.SignInWithTokenPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, signInWithTokenPayloadImplementors)
func (ec *executionContext) _SendMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.SendMagicLinkPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, sendMagicLinkPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("SignInWithTokenPayload")
out.Values[i] = graphql.MarshalString("SendMagicLinkPayload")
case "success":
out.Values[i] = ec._SignInWithTokenPayload_success(ctx, field, obj)
out.Values[i] = ec._SendMagicLinkPayload_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
@@ -9990,6 +9910,45 @@ func (ec *executionContext) _VendorEdge(ctx context.Context, sel ast.SelectionSe
return out
}
var verifyMagicLinkPayloadImplementors = []string{"VerifyMagicLinkPayload"}
func (ec *executionContext) _VerifyMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.VerifyMagicLinkPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, verifyMagicLinkPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("VerifyMagicLinkPayload")
case "success":
out.Values[i] = ec._VerifyMagicLinkPayload_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var __DirectiveImplementors = []string{"__Directive"}
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
@@ -11307,11 +11266,6 @@ func (ec *executionContext) marshalNRequestAccessesPayload2ᚖgoᚗproboᚗinc
return ec._RequestAccessesPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNRequestAllAccessesInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAllAccessesInput(ctx context.Context, v any) (types.RequestAllAccessesInput, error) {
res, err := ec.unmarshalInputRequestAllAccessesInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNRequestDocumentAccessInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestDocumentAccessInput(ctx context.Context, v any) (types.RequestDocumentAccessInput, error) {
res, err := ec.unmarshalInputRequestDocumentAccessInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -11327,6 +11281,11 @@ func (ec *executionContext) unmarshalNRequestTrustCenterFileAccessInput2goᚗpro
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNSendMagicLinkInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSendMagicLinkInput(ctx context.Context, v any) (types.SendMagicLinkInput, error) {
res, err := ec.unmarshalInputSendMagicLinkInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx context.Context, v any) (session.SessionRequirement, error) {
tmp, err := graphql.UnmarshalString(v)
res := unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement[tmp]
@@ -11357,25 +11316,6 @@ var (
}
)
func (ec *executionContext) unmarshalNSignInWithTokenInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSignInWithTokenInput(ctx context.Context, v any) (types.SignInWithTokenInput, error) {
res, err := ec.unmarshalInputSignInWithTokenInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNSignInWithTokenPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSignInWithTokenPayload(ctx context.Context, sel ast.SelectionSet, v types.SignInWithTokenPayload) graphql.Marshaler {
return ec._SignInWithTokenPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNSignInWithTokenPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSignInWithTokenPayload(ctx context.Context, sel ast.SelectionSet, v *types.SignInWithTokenPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._SignInWithTokenPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNString2string(ctx context.Context, v any) (string, error) {
res, err := graphql.UnmarshalString(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -11704,6 +11644,11 @@ func (ec *executionContext) marshalNVendorEdge2ᚖgoᚗproboᚗincᚋproboᚋpkg
return ec._VendorEdge(ctx, sel, v)
}
func (ec *executionContext) unmarshalNVerifyMagicLinkInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkInput(ctx context.Context, v any) (types.VerifyMagicLinkInput, error) {
res, err := ec.unmarshalInputVerifyMagicLinkInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler {
return ec.___Directive(ctx, sel, &v)
}
@@ -12037,6 +11982,13 @@ func (ec *executionContext) marshalOReport2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋs
return ec._Report(ctx, sel, v)
}
func (ec *executionContext) marshalOSendMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSendMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, v *types.SendMagicLinkPayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._SendMagicLinkPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v any) ([]string, error) {
if v == nil {
return nil, nil
@@ -12098,6 +12050,13 @@ func (ec *executionContext) marshalOTrustCenter2ᚖgoᚗproboᚗincᚋproboᚋpk
return ec._TrustCenter(ctx, sel, v)
}
func (ec *executionContext) marshalOVerifyMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, v *types.VerifyMagicLinkPayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._VerifyMagicLinkPayload(ctx, sel, v)
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil {
return graphql.Null

View File

@@ -150,34 +150,23 @@ type RequestAccessesPayload struct {
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
}
type RequestAllAccessesInput struct {
Email mail.Addr `json:"email"`
FullName string `json:"fullName"`
}
type RequestDocumentAccessInput struct {
DocumentID gid.GID `json:"documentId"`
Email mail.Addr `json:"email"`
FullName string `json:"fullName"`
DocumentID gid.GID `json:"documentId"`
}
type RequestReportAccessInput struct {
ReportID gid.GID `json:"reportId"`
Email mail.Addr `json:"email"`
FullName string `json:"fullName"`
ReportID gid.GID `json:"reportId"`
}
type RequestTrustCenterFileAccessInput struct {
TrustCenterFileID gid.GID `json:"trustCenterFileId"`
Email mail.Addr `json:"email"`
FullName string `json:"fullName"`
TrustCenterFileID gid.GID `json:"trustCenterFileId"`
}
type SignInWithTokenInput struct {
Token string `json:"token"`
type SendMagicLinkInput struct {
Email mail.Addr `json:"email"`
}
type SignInWithTokenPayload struct {
type SendMagicLinkPayload struct {
Success bool `json:"success"`
}
@@ -275,6 +264,14 @@ type VendorEdge struct {
Node *Vendor `json:"node"`
}
type VerifyMagicLinkInput struct {
Token string `json:"token"`
}
type VerifyMagicLinkPayload struct {
Success bool `json:"success"`
}
type Role string
const (

View File

@@ -13,6 +13,7 @@ import (
"time"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
@@ -84,7 +85,7 @@ func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Docu
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return false, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
return false, nil
}
documentAccess, err := trustService.TrustCenterAccesses.LoadDocumentAccess(
@@ -143,8 +144,68 @@ func (r *frameworkResolver) DarkLogoURL(ctx context.Context, obj *types.Framewor
return trustService.Frameworks.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour)
}
// SignInWithToken is the resolver for the signInWithToken field.
func (r *mutationResolver) SignInWithToken(ctx context.Context, input types.SignInWithTokenInput) (*types.SignInWithTokenPayload, error) {
// SendMagicLink is the resolver for the sendMagicLink field.
func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) {
identity := authn.IdentityFromContext(ctx)
if identity != nil {
return nil, gqlutils.AlreadyAuthenticatedf(ctx, "already authenticated")
}
trustCenter := compliancepage.CompliancePageFromContext(ctx)
organization, err := r.iam.OrganizationService.GetOrganization(ctx, trustCenter.OrganizationID)
if err != nil {
var errNotFound *iam.ErrOrganizationNotFound
if errors.As(err, &errNotFound) {
return nil, gqlutils.NotFoundf(ctx, "organization not found")
}
r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
customDomain, err := r.trust.GetCustomDomainByOrganizationID(ctx, organization.ID)
if err != nil {
var errNotFound *iam.ErrOrganizationNotFound
if !errors.As(err, &errNotFound) {
r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
}
req := &iam.SendMagicLinkRequest{
Email: input.Email,
}
if false {
// if customDomain != nil {
baseURL, err := baseurl.Parse(fmt.Sprintf("https://%s", customDomain.Domain))
if err != nil {
r.logger.ErrorCtx(ctx, "cannot parse custom domain url", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
req.BaseURL = baseURL
} else {
baseURL, err := baseurl.Parse(r.baseURL.WithPath("/trust/" + trustCenter.ID.String()).MustString())
if err != nil {
r.logger.ErrorCtx(ctx, "cannot parse url", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
req.BaseURL = baseURL
}
if err := r.iam.AuthService.SendMagicLink(ctx, req); err != nil {
r.logger.ErrorCtx(ctx, "cannot send magic link", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return nil, nil
}
// VerifyMagicLink is the resolver for the verifyMagicLink field.
func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) {
_, session, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
if err != nil {
var errInvalidToken *iam.ErrInvalidToken
@@ -156,33 +217,23 @@ func (r *mutationResolver) SignInWithToken(ctx context.Context, input types.Sign
return nil, gqlutils.Internal(ctx)
}
// TODO cookie domain
// FIXME: cookie domain
w := gqlutils.HTTPResponseWriterFromContext(ctx)
r.sessionCookie.Set(w, session)
return &types.SignInWithTokenPayload{
return &types.VerifyMagicLinkPayload{
Success: true,
}, nil
}
// RequestAllAccesses is the resolver for the requestAllAccesses field.
func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.RequestAllAccessesInput) (*types.RequestAccessesPayload, error) {
func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) {
trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
identity := authn.IdentityFromContext(ctx)
if identity == nil {
var err error
identity, err = r.iam.AuthService.LoadOrCreateIdentity(
ctx,
&iam.LoadOrCreateIdentityRequest{
Email: input.Email,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load or create identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
access, err := trustService.TrustCenterAccesses.Request(
@@ -242,10 +293,6 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
ndaExists := true
hasAcceptedNDA := false
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if trustCenter.NonDisclosureAgreementFileID == nil {
ndaExists = false
}
@@ -296,6 +343,11 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
// ExportReportPDF is the resolver for the exportReportPDF field.
func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) {
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
trustService := r.TrustService(ctx, input.ReportID.TenantID())
trustCenter := compliancepage.CompliancePageFromContext(ctx)
@@ -318,18 +370,9 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
}, nil
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
ndaExists := true
hasAcceptedNDA := false
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if trustCenter.NonDisclosureAgreementFileID == nil {
ndaExists = false
}
@@ -380,13 +423,12 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
// AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field.
func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context) (*types.AcceptNonDisclosureAgreementPayload, error) {
trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
httpReq := gqlutils.HTTPRequestFromContext(ctx)
@@ -424,17 +466,7 @@ func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input type
identity := authn.IdentityFromContext(ctx)
if identity == nil {
var err error
identity, err = r.iam.AuthService.LoadOrCreateIdentity(
ctx,
&iam.LoadOrCreateIdentityRequest{
Email: input.Email,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load or create identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
access, err := trustService.TrustCenterAccesses.Request(
@@ -483,17 +515,7 @@ func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.
identity := authn.IdentityFromContext(ctx)
if identity == nil {
var err error
identity, err = r.iam.AuthService.LoadOrCreateIdentity(
ctx,
&iam.LoadOrCreateIdentityRequest{
Email: input.Email,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load or create identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
access, err := trustService.TrustCenterAccesses.Request(
@@ -542,17 +564,7 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp
identity := authn.IdentityFromContext(ctx)
if identity == nil {
var err error
identity, err = r.iam.AuthService.LoadOrCreateIdentity(
ctx,
&iam.LoadOrCreateIdentityRequest{
Email: input.Email,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load or create identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
access, err := trustService.TrustCenterAccesses.Request(
@@ -584,6 +596,11 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp
// ExportTrustCenterFile is the resolver for the exportTrustCenterFile field.
func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) {
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
@@ -605,11 +622,6 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type
}, nil
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
ndaExists := true
hasAcceptedNDA := false
@@ -782,6 +794,11 @@ func (r *queryResolver) CurrentTrustCenter(ctx context.Context) (*types.TrustCen
// IsUserAuthorized is the resolver for the isUserAuthorized field.
func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) {
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return false, nil
}
trustService := r.TrustService(ctx, obj.ID.TenantID())
trustCenter := compliancepage.CompliancePageFromContext(ctx)
@@ -796,11 +813,6 @@ func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report
return true, nil
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return false, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
reportAccess, err := trustService.TrustCenterAccesses.LoadReportAccess(ctx,
trustCenter.ID,
identity.EmailAddress,
@@ -984,6 +996,11 @@ func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.T
// IsUserAuthorized is the resolver for the isUserAuthorized field.
func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *types.TrustCenterFile) (bool, error) {
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return false, nil
}
trustService := r.TrustService(ctx, obj.ID.TenantID())
trustCenter := compliancepage.CompliancePageFromContext(ctx)
@@ -998,11 +1015,6 @@ func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *typ
return true, nil
}
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return false, gqlutils.Unauthenticatedf(ctx, "unauthenticated")
}
fileAccess, err := trustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx,
trustCenter.ID,
identity.EmailAddress,

View File

@@ -25,6 +25,19 @@ import (
"go.probo.inc/probo/pkg/validator"
)
func AlreadyAuthenticated(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{
Message: "Authentication not allowed for this resource/action",
Extensions: map[string]any{
"code": "ALREADY_AUTHENTICATED",
},
}
}
func AlreadyAuthenticatedf(ctx context.Context, format string, a ...any) *gqlerror.Error {
return AlreadyAuthenticated(ctx, fmt.Errorf(format, a...))
}
func Unauthenticated(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{
Message: err.Error(),
@@ -39,15 +52,6 @@ func Unauthenticatedf(ctx context.Context, format string, a ...any) *gqlerror.Er
return Unauthenticated(ctx, fmt.Errorf(format, a...))
}
func AlreadyUnauthenticated(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{
Message: "Authentication not allowed for this resource/action",
Extensions: map[string]any{
"code": "ALREADY_AUTHENTICATED",
},
}
}
func Forbidden(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{
Message: err.Error(),

View File

@@ -149,6 +149,8 @@ func (s *Server) stripTrustPrefix(next http.Handler) http.Handler {
}
r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/auth")
if r.URL.Path == "" {
r.URL.Path = "/"
}

View File

@@ -229,3 +229,16 @@ func (s *Service) GetByDomainName(ctx context.Context, domain string) (*coredata
return trustCenter, err
}
func (s *Service) GetCustomDomainByOrganizationID(ctx context.Context, organizationID gid.GID) (*coredata.CustomDomain, error) {
customDomain := &coredata.CustomDomain{}
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return customDomain.LoadByOrganizationID(ctx, conn, coredata.NewNoScope(), s.encryptionKey, organizationID)
},
)
return customDomain, err
}