Add nda to trust center

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-09 16:17:24 +02:00
parent 28c41fa4ae
commit b06bd113f3
37 changed files with 2921 additions and 259 deletions

View File

@@ -26,6 +26,7 @@ export const trustCenterAccessesQuery = graphql`
email
name
active
hasAcceptedNonDisclosureAgreement
createdAt
}
}
@@ -48,6 +49,7 @@ export const createTrustCenterAccessMutation = graphql`
email
name
active
hasAcceptedNonDisclosureAgreement
createdAt
}
}
@@ -65,6 +67,7 @@ export const updateTrustCenterAccessMutation = graphql`
email
name
active
hasAcceptedNonDisclosureAgreement
createdAt
updatedAt
}

View File

@@ -12,6 +12,8 @@ export const trustCenterQuery = graphql`
id
active
slug
ndaFileName
ndaFileUrl
createdAt
updatedAt
}
@@ -66,3 +68,48 @@ export function useUpdateTrustCenterMutation() {
}
);
}
export const uploadTrustCenterNDAMutation = graphql`
mutation TrustCenterGraphUploadNDAMutation($input: UploadTrustCenterNDAInput!) {
uploadTrustCenterNDA(input: $input) {
trustCenter {
id
ndaFileName
updatedAt
}
}
}
`;
export function useUploadTrustCenterNDAMutation() {
return useMutationWithToasts(
uploadTrustCenterNDAMutation,
{
successMessage: "NDA uploaded successfully",
errorMessage: "Failed to upload NDA",
}
);
}
export const deleteTrustCenterNDAMutation = graphql`
mutation TrustCenterGraphDeleteNDAMutation($input: DeleteTrustCenterNDAInput!) {
deleteTrustCenterNDA(input: $input) {
trustCenter {
id
ndaFileName
updatedAt
}
}
}
`;
export function useDeleteTrustCenterNDAMutation() {
return useMutationWithToasts(
deleteTrustCenterNDAMutation,
{
successMessage: "NDA deleted successfully",
errorMessage: "Failed to delete NDA",
}
);
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<ae95aed7bc22c95f8cc2ab591039c810>>
* @generated SignedSource<<5ccaab70e4cb8c4b5bee7fbe6ba2c0b6>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -27,6 +27,7 @@ export type TrustCenterAccessGraphCreateMutation$data = {
readonly active: boolean;
readonly createdAt: any;
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly name: string;
};
@@ -107,6 +108,13 @@ v3 = {
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -186,16 +194,16 @@ return {
]
},
"params": {
"cacheID": "eafddcd0263963235d3249c22eb50593",
"cacheID": "fa88b100be7598cf46159cf79199c398",
"id": null,
"metadata": {},
"name": "TrustCenterAccessGraphCreateMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterAccessGraphCreateMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccessEdge {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n }\n }\n }\n}\n"
"text": "mutation TrustCenterAccessGraphCreateMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccessEdge {\n cursor\n node {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "99676fee0b2de06a92cdad66c577eee7";
(node as any).hash = "6c6c1344730e7d908a5c6392f578ca81";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<2333a6a7d5415f1a08a5612dcaceee8f>>
* @generated SignedSource<<e9cdda9f586cee5ffe66f238216060f6>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -22,6 +22,7 @@ export type TrustCenterAccessGraphQuery$data = {
readonly active: boolean;
readonly createdAt: any;
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly name: string;
};
@@ -163,6 +164,13 @@ v5 = [
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -290,7 +298,7 @@ return {
]
},
"params": {
"cacheID": "5b83cd4ae2434ce2e00de7230d264432",
"cacheID": "0286ec03fb4ae012ada7bae8a2234152",
"id": null,
"metadata": {
"connection": [
@@ -307,11 +315,11 @@ return {
},
"name": "TrustCenterAccessGraphQuery",
"operationKind": "query",
"text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n accesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n"
"text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n accesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "af598fd2af198e63ed84fd618857a985";
(node as any).hash = "f837b96937f1397ac1a3065f58386e4d";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<6de2ecb63a58c88c3008368061943b49>>
* @generated SignedSource<<d2f40d8fc2bd9c7627d308660dedb1e5>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -23,6 +23,7 @@ export type TrustCenterAccessGraphUpdateMutation$data = {
readonly active: boolean;
readonly createdAt: any;
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly name: string;
readonly updatedAt: any;
@@ -93,6 +94,13 @@ v1 = [
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -132,16 +140,16 @@ return {
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "683ee01fb5173b49b0a7f3c2d99cf002",
"cacheID": "223169ee8a4f65008047097ecff68fc5",
"id": null,
"metadata": {},
"name": "TrustCenterAccessGraphUpdateMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterAccessGraphUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n createdAt\n updatedAt\n }\n }\n}\n"
"text": "mutation TrustCenterAccessGraphUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "cccd083b0047f6bc7b504b7494205e9d";
(node as any).hash = "0da1f737e6ea1db7a1b9930c4b6cc545";
export default node;

View File

@@ -0,0 +1,121 @@
/**
* @generated SignedSource<<43a8665719f251c1bae028ff7821b14a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteTrustCenterNDAInput = {
trustCenterId: string;
};
export type TrustCenterGraphDeleteNDAMutation$variables = {
input: DeleteTrustCenterNDAInput;
};
export type TrustCenterGraphDeleteNDAMutation$data = {
readonly deleteTrustCenterNDA: {
readonly trustCenter: {
readonly id: string;
readonly ndaFileName: string | null | undefined;
readonly updatedAt: any;
};
};
};
export type TrustCenterGraphDeleteNDAMutation = {
response: TrustCenterGraphDeleteNDAMutation$data;
variables: TrustCenterGraphDeleteNDAMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "DeleteTrustCenterNDAPayload",
"kind": "LinkedField",
"name": "deleteTrustCenterNDA",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterGraphDeleteNDAMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterGraphDeleteNDAMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "fa3ebf3593c396bf05819d357d9f909f",
"id": null,
"metadata": {},
"name": "TrustCenterGraphDeleteNDAMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterGraphDeleteNDAMutation(\n $input: DeleteTrustCenterNDAInput!\n) {\n deleteTrustCenterNDA(input: $input) {\n trustCenter {\n id\n ndaFileName\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "9c055069b9e2e7432fed7509f6578a07";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<25550a93c5459c3b405c782be19ac888>>
* @generated SignedSource<<9dbea87f398ce8189cb2431d7c03912c>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -37,6 +37,8 @@ export type TrustCenterGraphQuery$data = {
readonly active: boolean;
readonly createdAt: any;
readonly id: string;
readonly ndaFileName: string | null | undefined;
readonly ndaFileUrl: string | null | undefined;
readonly slug: string;
readonly updatedAt: any;
} | null | undefined;
@@ -114,6 +116,20 @@ v5 = {
"name": "slug",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileUrl",
"storageKey": null
},
(v4/*: any*/),
{
"alias": null,
@@ -544,16 +560,16 @@ return {
]
},
"params": {
"cacheID": "28c1a32327e2fee9320fd091100ec800",
"cacheID": "8dff30a639470418ba7151cb5743d3cc",
"id": null,
"metadata": {},
"name": "TrustCenterGraphQuery",
"operationKind": "query",
"text": "query TrustCenterGraphQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n trustCenter {\n id\n active\n slug\n createdAt\n updatedAt\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...TrustCenterDocumentsCardFragment\n }\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n ...TrustCenterAuditsCardFragment\n }\n }\n }\n vendors(first: 100) {\n edges {\n node {\n id\n ...TrustCenterVendorsCardFragment\n }\n }\n }\n }\n id\n }\n}\n\nfragment TrustCenterAuditsCardFragment on Audit {\n id\n name\n framework {\n name\n id\n }\n validFrom\n validUntil\n state\n showOnTrustCenter\n createdAt\n}\n\nfragment TrustCenterDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n showOnTrustCenter\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment TrustCenterVendorsCardFragment on Vendor {\n id\n name\n category\n description\n showOnTrustCenter\n createdAt\n}\n"
"text": "query TrustCenterGraphQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n trustCenter {\n id\n active\n slug\n ndaFileName\n ndaFileUrl\n createdAt\n updatedAt\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...TrustCenterDocumentsCardFragment\n }\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n ...TrustCenterAuditsCardFragment\n }\n }\n }\n vendors(first: 100) {\n edges {\n node {\n id\n ...TrustCenterVendorsCardFragment\n }\n }\n }\n }\n id\n }\n}\n\nfragment TrustCenterAuditsCardFragment on Audit {\n id\n name\n framework {\n name\n id\n }\n validFrom\n validUntil\n state\n showOnTrustCenter\n createdAt\n}\n\nfragment TrustCenterDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n showOnTrustCenter\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment TrustCenterVendorsCardFragment on Vendor {\n id\n name\n category\n description\n showOnTrustCenter\n createdAt\n}\n"
}
};
})();
(node as any).hash = "260529b3ca03240b07989f278bb7a539";
(node as any).hash = "21b2915ae5002dd54c990013e94adf5d";
export default node;

View File

@@ -0,0 +1,123 @@
/**
* @generated SignedSource<<966d4aa638042469c717ddc96e5c5d78>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UploadTrustCenterNDAInput = {
file: any;
fileName: string;
trustCenterId: string;
};
export type TrustCenterGraphUploadNDAMutation$variables = {
input: UploadTrustCenterNDAInput;
};
export type TrustCenterGraphUploadNDAMutation$data = {
readonly uploadTrustCenterNDA: {
readonly trustCenter: {
readonly id: string;
readonly ndaFileName: string | null | undefined;
readonly updatedAt: any;
};
};
};
export type TrustCenterGraphUploadNDAMutation = {
response: TrustCenterGraphUploadNDAMutation$data;
variables: TrustCenterGraphUploadNDAMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UploadTrustCenterNDAPayload",
"kind": "LinkedField",
"name": "uploadTrustCenterNDA",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterGraphUploadNDAMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterGraphUploadNDAMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "09a5dbf73f3e279173691b235f9da2e2",
"id": null,
"metadata": {},
"name": "TrustCenterGraphUploadNDAMutation",
"operationKind": "mutation",
"text": "mutation TrustCenterGraphUploadNDAMutation(\n $input: UploadTrustCenterNDAInput!\n) {\n uploadTrustCenterNDA(input: $input) {\n trustCenter {\n id\n ndaFileName\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "b9877f8a5b9c2c12addeb939360719f1";
export default node;

View File

@@ -34,6 +34,9 @@ interface TrustCenterQueryData {
active: boolean;
slug: string;
isUserAuthenticated: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
ndaFileName: string | null;
ndaFileUrl: string | null;
organization: {
id: string;
name: string;
@@ -96,7 +99,19 @@ interface CreateTrustCenterAccessVariables {
};
}
type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | CreateTrustCenterAccessVariables | Record<string, never>;
interface AcceptNonDisclosureAgreementData {
acceptNonDisclosureAgreement: {
success: boolean;
};
}
interface AcceptNonDisclosureAgreementVariables {
input: {
trustCenterId: string;
};
}
type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | CreateTrustCenterAccessVariables | AcceptNonDisclosureAgreementVariables | Record<string, never>;
async function trustCenterGraphQLRequest<T = unknown>(
operationName: string,
@@ -127,12 +142,14 @@ async function trustCenterGraphQLRequest<T = unknown>(
function isCriticalError(error: GraphQLError): boolean {
const message = error.message?.toLowerCase() || '';
const path = error.path || [];
if (message.includes('access denied') || message.includes('authentication required')) {
if (path.length > 2) {
return false;
}
if (
message.includes('access denied') ||
message.includes('authentication required') ||
message.includes('user has not accepted nda') ||
message.includes('no nda file found')
) {
return false;
}
return true;
@@ -145,6 +162,9 @@ const TRUST_CENTER_QUERY = `
active
slug
isUserAuthenticated
hasAcceptedNonDisclosureAgreement
ndaFileName
ndaFileUrl
organization {
id
name
@@ -213,6 +233,16 @@ const CREATE_TRUST_CENTER_ACCESS_MUTATION = `
}
`;
const ACCEPT_NDA_MUTATION = `
mutation AcceptNonDisclosureAgreementMutation(
$input: AcceptNonDisclosureAgreementInput!
) {
acceptNonDisclosureAgreement(input: $input) {
success
}
}
`;
export function useTrustCenterQuery(slug: string) {
return useQuery<TrustCenterQueryData>({
queryKey: ["trust-center", slug],
@@ -297,3 +327,27 @@ export function useCreateTrustCenterAccess() {
},
});
}
export function useAcceptNonDisclosureAgreement() {
return useMutation<AcceptNonDisclosureAgreementData, Error, { trustCenterId: string }>({
mutationFn: async (input: { trustCenterId: string }) => {
const result = await trustCenterGraphQLRequest<AcceptNonDisclosureAgreementData>(
"AcceptNonDisclosureAgreementMutation",
ACCEPT_NDA_MUTATION,
{ input }
);
if (result.errors && result.errors.length > 0) {
throw new Error(
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
);
}
if (!result.data) {
throw new Error("No data returned from mutation");
}
return result.data;
},
});
}

View File

@@ -127,7 +127,9 @@ export default function TrustCenterAccessPage() {
if (error) {
const isTokenError = error.toLowerCase().includes('token') ||
error.toLowerCase().includes('expired') ||
error.toLowerCase().includes('invalid');
error.toLowerCase().includes('invalid') ||
error.toLowerCase().includes('401') ||
error.toLowerCase().includes('unauthorized');
if (isTokenError) {
return <TokenErrorPage error={error} />;

View File

@@ -16,6 +16,7 @@ import {
useDialogRef,
IconTrashCan,
IconPencil,
IconCheckmark1,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
@@ -85,6 +86,7 @@ export default function TrustCenterAccessTab() {
email: string;
name: string;
active: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
createdAt: Date;
};
@@ -95,6 +97,7 @@ export default function TrustCenterAccessTab() {
email: edge.node.email,
name: edge.node.name,
active: edge.node.active,
hasAcceptedNonDisclosureAgreement: edge.node.hasAcceptedNonDisclosureAgreement,
createdAt: new Date(edge.node.createdAt)
})) ?? [];
@@ -203,6 +206,7 @@ export default function TrustCenterAccessTab() {
<Th>{__("Email")}</Th>
<Th>{__("Date")}</Th>
<Th>{__("Active")}</Th>
<Th>{__("NDA")}</Th>
<Th></Th>
</Tr>
</Thead>
@@ -220,6 +224,11 @@ export default function TrustCenterAccessTab() {
onChange={(active) => handleToggleActive(access.id, active)}
/>
</Td>
<Td>
{access.hasAcceptedNonDisclosureAgreement && (
<IconCheckmark1 size={16} className="text-txt-success" />
)}
</Td>
<Td noLink width={160} className="text-end">
<div className="flex gap-2 justify-end">
<Button

View File

@@ -4,6 +4,7 @@ import {
Button,
Card,
Checkbox,
Dropzone,
Field,
Input,
PageHeader,
@@ -12,9 +13,10 @@ import {
Tabs,
TabLink,
TabItem,
IconTrashCan,
} from "@probo/ui";
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
import { trustCenterQuery, useUpdateTrustCenterMutation } from "/hooks/graph/TrustCenterGraph";
import { trustCenterQuery, useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
import type { TrustCenterGraphQuery } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
import { useState } from "react";
import { useOrganizationId } from "/hooks/useOrganizationId";
@@ -32,6 +34,8 @@ export default function TrustCenterPage({ queryRef }: Props) {
const { organization } = usePreloadedQuery(trustCenterQuery, queryRef);
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
const [isActive, setIsActive] = useState(organization.trustCenter?.active || false);
const [slug, setSlug] = useState(organization.trustCenter?.slug || "");
const [isUpdatingSlug, setIsUpdatingSlug] = useState(false);
@@ -101,6 +105,57 @@ export default function TrustCenterPage({ queryRef }: Props) {
});
};
const handleNDAUpload = async (files: File[]) => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
if (files.length === 0) return;
const file = files[0];
await uploadNDA({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
fileName: file.name,
file: null,
},
},
uploadables: {
"input.file": file,
},
});
};
const handleNDADelete = async () => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
if (!confirm(__("Are you sure you want to delete the NDA file?"))) {
return;
}
await deleteNDA({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
},
},
});
};
const trustCenterUrl = organization.trustCenter?.slug
? `${window.location.origin}/trust/${organization.trustCenter.slug}`
: null;
@@ -211,6 +266,68 @@ export default function TrustCenterPage({ queryRef }: Props) {
</div>
</Card>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Non-Disclosure Agreement")}</h2>
{(isUploadingNDA || isDeletingNDA) && <Spinner />}
</div>
<Card padded className="space-y-4">
<div className="space-y-2">
{!organization.trustCenter?.ndaFileName ? (
<p className="text-sm text-txt-tertiary">
{__("Upload a Non-Disclosure Agreement that visitors must accept before accessing your trust center")}
</p>
) : (<></>)}
{organization.trustCenter?.ndaFileName ? (
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<p className="text-sm font-medium">
{organization.trustCenter.ndaFileName || __("Non-Disclosure Agreement")}
</p>
</div>
<p className="text-xs text-txt-tertiary">
{__("Visitors will need to accept this NDA before accessing your trust center")}
</p>
</div>
<div className="flex items-center gap-2">
<Button
type="button"
variant="secondary"
onClick={() => {
if (organization.trustCenter?.ndaFileUrl) {
window.open(organization.trustCenter.ndaFileUrl, '_blank');
}
}}
>
{__("Download PDF")}
</Button>
<Button
variant="quaternary"
icon={IconTrashCan}
onClick={handleNDADelete}
disabled={isDeletingNDA}
/>
</div>
</div>
</div>
) : (
<Dropzone
description={__("Upload PDF files up to 10MB")}
isUploading={isUploadingNDA}
onDrop={handleNDAUpload}
accept={{
"application/pdf": [".pdf"],
}}
maxSize={10}
/>
)}
</div>
</Card>
</div>
<div className="space-y-4">
<Tabs>
<TabItem

View File

@@ -0,0 +1,187 @@
import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
DialogFooter,
Button,
Checkbox,
IconLock,
IconArrowDown,
useToast,
useDialogRef
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useAcceptNonDisclosureAgreement } from "/hooks/useTrustCenterQueries";
import { buildEndpoint } from "/providers/RelayProviders";
import { sprintf } from "@probo/helpers";
type Props = {
trustCenterId: string;
organizationName: string;
ndaFileName?: string | null;
ndaFileUrl?: string | null;
};
export function NDAAcceptanceDialog({ trustCenterId, organizationName, ndaFileName, ndaFileUrl }: Props) {
const { __ } = useTranslate();
const { toast } = useToast();
const [isChecked, setIsChecked] = useState(false);
const dialogRef = useDialogRef();
const acceptNdaMutation = useAcceptNonDisclosureAgreement();
useEffect(() => {
dialogRef.current?.open();
}, []);
const handleLogout = async () => {
try {
const response = await fetch(buildEndpoint('/api/trust/v1/auth/logout'), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});
if (!response.ok) {
throw new Error("Logout failed");
}
window.location.reload();
} catch (error) {
toast({
title: __("Error"),
description: __("Logout failed"),
variant: "error",
});
}
};
const handleAccept = () => {
if (!isChecked) {
toast({
title: __("Agreement Required"),
description: __("Please check the box to confirm your agreement"),
variant: "error",
});
return;
}
acceptNdaMutation.mutate(
{ trustCenterId },
{
onSuccess: () => {
window.location.reload();
},
onError: () => {
toast({
title: __("Error"),
description: __("Failed to accept the Non-Disclosure Agreement"),
variant: "error",
});
},
}
);
};
const handleCancel = () => {
handleLogout();
};
return (
<Dialog
ref={dialogRef}
closable={false}
onClose={handleCancel}
>
<DialogContent>
<div className="space-y-3 p-4">
<div className="text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-amber-50 border border-amber-200 mb-3">
<IconLock className="h-6 w-6 text-amber-600" />
</div>
<h2 className="text-lg font-semibold text-txt-primary mb-2">
{__("Non-Disclosure Agreement")}
</h2>
<p className="text-sm text-txt-secondary">
{sprintf(__("To access %s's trust center, you must accept the Non-Disclosure Agreement."), organizationName)}
</p>
</div>
<div className="bg-level-1 p-3 rounded-lg border border-border-subtle">
{ndaFileName && ndaFileUrl ? (
<div className="text-center">
<p className="text-sm font-medium text-txt-primary mb-3">
{__("Please review and download the Non-Disclosure Agreement:")}
</p>
<div className="flex justify-center">
<Button
variant="secondary"
icon={IconArrowDown}
onClick={() => {
const link = document.createElement('a');
link.href = ndaFileUrl;
link.download = ndaFileName || 'NDA.pdf';
link.target = '_blank';
link.rel = 'noopener noreferrer';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
>
{sprintf(__("Download %s"), ndaFileName)}
</Button>
</div>
</div>
) : (
<>
<p className="text-sm font-medium text-txt-primary mb-2">
{__("By accepting this agreement, you commit to:")}
</p>
<ul className="text-sm text-txt-secondary space-y-0.5">
<li className="flex items-start">
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
{__("Keep confidential information secure")}
</li>
<li className="flex items-start">
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
{__("Not share or disclose sensitive data")}
</li>
<li className="flex items-start">
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
{__("Use information only for authorized purposes")}
</li>
</ul>
</>
)}
</div>
<div className="flex items-start space-x-2 p-3 bg-level-0 rounded-lg border border-border-subtle">
<div className="mt-0.5">
<Checkbox
checked={isChecked}
onChange={setIsChecked}
/>
</div>
<label
className="text-sm text-txt-primary cursor-pointer flex-1"
onClick={() => setIsChecked(!isChecked)}
>
{__("I agree to the terms of the Non-Disclosure Agreement and will handle all information accordingly.")}
</label>
</div>
</div>
</DialogContent>
<DialogFooter exitLabel={__("Disconnect")}>
<Button
variant="primary"
onClick={handleAccept}
disabled={!isChecked || acceptNdaMutation.isPending}
>
{acceptNdaMutation.isPending ? __("Accepting...") : __("Accept & Continue")}
</Button>
</DialogFooter>
</Dialog>
);
}

View File

@@ -5,6 +5,7 @@ import { PublicTrustCenterLayout } from "/layouts/PublicTrustCenterLayout";
import { PublicTrustCenterAudits } from "../components/PublicTrustCenterAudits";
import { PublicTrustCenterVendors } from "../components/PublicTrustCenterVendors";
import { PublicTrustCenterDocuments } from "../components/PublicTrustCenterDocuments";
import { NDAAcceptanceDialog } from "../components/NDAAcceptanceDialog";
import { Spinner } from "@probo/ui";
import { useTrustCenterQuery, type TrustCenterDocument, type TrustCenterAudit, type TrustCenterVendor } from "/hooks/useTrustCenterQueries";
@@ -66,36 +67,49 @@ export default function PublicTrustCenterPage() {
}
const { trustCenterBySlug } = data;
const { documents, audits, vendors, isUserAuthenticated } = trustCenterBySlug;
const { documents, audits, vendors, isUserAuthenticated, hasAcceptedNonDisclosureAgreement } = trustCenterBySlug;
const trustCenterDocuments = documents.edges.map((edge) => edge.node) as TrustCenterDocument[];
const trustCenterAudits = audits.edges.map((edge) => edge.node) as TrustCenterAudit[];
const trustCenterVendors = vendors.edges.map((edge) => edge.node) as TrustCenterVendor[];
const showNdaDialog = isUserAuthenticated && !hasAcceptedNonDisclosureAgreement;
return (
<PublicTrustCenterLayout
organizationName={organizationName}
organizationLogo={organization?.logoUrl}
isAuthenticated={isUserAuthenticated}
>
<div className="space-y-12">
<PublicTrustCenterAudits
audits={trustCenterAudits}
organizationName={organizationName}
isAuthenticated={isUserAuthenticated}
<>
{showNdaDialog && (
<NDAAcceptanceDialog
trustCenterId={trustCenterBySlug.id}
/>
<PublicTrustCenterDocuments
documents={trustCenterDocuments}
organizationName={organizationName}
isAuthenticated={isUserAuthenticated}
trustCenterId={trustCenterBySlug.id}
ndaFileName={trustCenterBySlug.ndaFileName}
ndaFileUrl={trustCenterBySlug.ndaFileUrl}
/>
<PublicTrustCenterVendors
vendors={trustCenterVendors}
organizationName={organizationName}
/>
</div>
</PublicTrustCenterLayout>
)}
<PublicTrustCenterLayout
organizationName={organizationName}
organizationLogo={organization?.logoUrl}
isAuthenticated={isUserAuthenticated}
>
<div className="space-y-12">
<PublicTrustCenterAudits
audits={trustCenterAudits}
organizationName={organizationName}
isAuthenticated={isUserAuthenticated}
trustCenterId={trustCenterBySlug.id}
/>
<PublicTrustCenterDocuments
documents={trustCenterDocuments}
organizationName={organizationName}
isAuthenticated={isUserAuthenticated}
trustCenterId={trustCenterBySlug.id}
/>
<PublicTrustCenterVendors
vendors={trustCenterVendors}
organizationName={organizationName}
/>
</div>
</PublicTrustCenterLayout>
</>
);
}

View File

@@ -50,6 +50,7 @@ type Props = {
className?: string;
ref?: DialogRef;
onClose?: () => void;
closable?: boolean;
};
export const useDialogRef = (): DialogRef => {
@@ -64,6 +65,7 @@ export function Dialog({
ref,
defaultOpen,
onClose,
closable = true,
}: Props) {
const { overlay, content, header, title: titleClassname } = dialog();
const [open, setOpen] = useState(!!defaultOpen);
@@ -80,41 +82,55 @@ export function Dialog({
}
const onOpenChange = (open: boolean) => {
if (!open && !closable) {
return;
}
setOpen(open);
if (!open) {
onClose?.();
}
};
const contentProps = closable ? {} : {
onEscapeKeyDown: (e: Event) => e.preventDefault(),
onPointerDownOutside: (e: Event) => e.preventDefault(),
onInteractOutside: (e: Event) => e.preventDefault(),
};
return (
<Root open={open} onOpenChange={onOpenChange}>
<Root open={open} onOpenChange={closable ? onOpenChange : undefined}>
{trigger && <Trigger asChild>{trigger}</Trigger>}
<Portal>
<Overlay className={overlay()} />
<Content
aria-describedby={undefined}
className={content({ className })}
{...contentProps}
>
{title ? (
<div className={header()}>
<Title className={titleClassname()}> {title}</Title>
{closable && (
<Close asChild>
<Button
tabIndex={-1}
variant="tertiary"
icon={IconCrossLargeX}
/>
</Close>
)}
</div>
) : (
closable && (
<Close asChild>
<Button
tabIndex={-1}
variant="tertiary"
className="absolute top-4 right-4"
icon={IconCrossLargeX}
/>
</Close>
</div>
) : (
<Close asChild>
<Button
tabIndex={-1}
variant="tertiary"
className="absolute top-4 right-4"
icon={IconCrossLargeX}
/>
</Close>
)
)}
{children}
</Content>

View File

@@ -14,6 +14,12 @@
package coredata
type ctxKey struct{ name string }
var (
ContextKeyIPAddress = &ctxKey{name: "ip_address"}
)
const (
OrganizationEntityType uint16 = iota
FrameworkEntityType

View File

@@ -0,0 +1,10 @@
ALTER TABLE trust_center_accesses ADD COLUMN has_accepted_non_disclosure_agreement BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE trust_center_accesses ALTER COLUMN has_accepted_non_disclosure_agreement DROP DEFAULT;
ALTER TABLE trust_center_accesses ADD COLUMN has_accepted_non_disclosure_agreement_metadata JSONB;
ALTER TABLE trust_centers ADD COLUMN non_disclosure_agreement_file_id TEXT;
ALTER TABLE trust_centers ADD CONSTRAINT trust_centers_non_disclosure_agreement_file_id_fkey
FOREIGN KEY (non_disclosure_agreement_file_id)
REFERENCES files(id)
ON UPDATE CASCADE
ON DELETE RESTRICT;

View File

@@ -28,13 +28,14 @@ import (
type (
TrustCenter struct {
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
TenantID gid.TenantID `db:"tenant_id"`
Active bool `db:"active"`
Slug string `db:"slug"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
TenantID gid.TenantID `db:"tenant_id"`
Active bool `db:"active"`
Slug string `db:"slug"`
NonDisclosureAgreementFileID *gid.GID `db:"non_disclosure_agreement_file_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
TrustCenters []*TrustCenter
@@ -62,6 +63,7 @@ SELECT
tenant_id,
active,
slug,
non_disclosure_agreement_file_id,
created_at,
updated_at
FROM
@@ -105,6 +107,7 @@ SELECT
tenant_id,
active,
slug,
non_disclosure_agreement_file_id,
created_at,
updated_at
FROM
@@ -147,6 +150,7 @@ SELECT
tenant_id,
active,
slug,
non_disclosure_agreement_file_id,
created_at,
updated_at
FROM
@@ -185,6 +189,7 @@ INSERT INTO trust_centers (
tenant_id,
active,
slug,
non_disclosure_agreement_file_id,
created_at,
updated_at
) VALUES (
@@ -193,19 +198,21 @@ INSERT INTO trust_centers (
@tenant_id,
@active,
@slug,
@non_disclosure_agreement_file_id,
@created_at,
@updated_at
)
`
args := pgx.StrictNamedArgs{
"id": tc.ID,
"organization_id": tc.OrganizationID,
"tenant_id": tc.TenantID,
"active": tc.Active,
"slug": tc.Slug,
"created_at": tc.CreatedAt,
"updated_at": tc.UpdatedAt,
"id": tc.ID,
"organization_id": tc.OrganizationID,
"tenant_id": tc.TenantID,
"active": tc.Active,
"slug": tc.Slug,
"non_disclosure_agreement_file_id": tc.NonDisclosureAgreementFileID,
"created_at": tc.CreatedAt,
"updated_at": tc.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
@@ -226,6 +233,7 @@ UPDATE trust_centers
SET
active = @active,
slug = @slug,
non_disclosure_agreement_file_id = @non_disclosure_agreement_file_id,
updated_at = @updated_at
WHERE
%s
@@ -235,10 +243,11 @@ WHERE
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": tc.ID,
"active": tc.Active,
"slug": tc.Slug,
"updated_at": tc.UpdatedAt,
"id": tc.ID,
"active": tc.Active,
"slug": tc.Slug,
"non_disclosure_agreement_file_id": tc.NonDisclosureAgreementFileID,
"updated_at": tc.UpdatedAt,
}
maps.Copy(args, scope.SQLArguments())

View File

@@ -16,6 +16,7 @@ package coredata
import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
@@ -29,14 +30,16 @@ import (
type (
TrustCenterAccess struct {
ID gid.GID `db:"id"`
TenantID gid.TenantID `db:"tenant_id"`
TrustCenterID gid.GID `db:"trust_center_id"`
Email string `db:"email"`
Name string `db:"name"`
Active bool `db:"active"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
TenantID gid.TenantID `db:"tenant_id"`
TrustCenterID gid.GID `db:"trust_center_id"`
Email string `db:"email"`
Name string `db:"name"`
Active bool `db:"active"`
HasAcceptedNonDisclosureAgreement bool `db:"has_accepted_non_disclosure_agreement"`
HasAcceptedNonDisclosureAgreementMetadata json.RawMessage `db:"has_accepted_non_disclosure_agreement_metadata"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
TrustCenterAccesses []*TrustCenterAccess
@@ -73,6 +76,8 @@ SELECT
email,
name,
active,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
created_at,
updated_at
FROM
@@ -122,6 +127,8 @@ SELECT
email,
name,
active,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
created_at,
updated_at
FROM
@@ -173,6 +180,7 @@ INSERT INTO trust_center_accesses (
email,
name,
active,
has_accepted_non_disclosure_agreement,
created_at,
updated_at
) VALUES (
@@ -182,20 +190,22 @@ INSERT INTO trust_center_accesses (
@email,
@name,
@active,
@has_accepted_non_disclosure_agreement,
@created_at,
@updated_at
)
`
args := pgx.StrictNamedArgs{
"id": tca.ID,
"tenant_id": tca.TenantID,
"trust_center_id": tca.TrustCenterID,
"email": tca.Email,
"name": tca.Name,
"active": tca.Active,
"created_at": tca.CreatedAt,
"updated_at": tca.UpdatedAt,
"id": tca.ID,
"tenant_id": tca.TenantID,
"trust_center_id": tca.TrustCenterID,
"email": tca.Email,
"name": tca.Name,
"active": tca.Active,
"has_accepted_non_disclosure_agreement": tca.HasAcceptedNonDisclosureAgreement,
"created_at": tca.CreatedAt,
"updated_at": tca.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
@@ -215,7 +225,9 @@ func (tca *TrustCenterAccess) Update(
UPDATE trust_center_accesses SET
name = @name,
active = @active,
updated_at = @updated_at
updated_at = @updated_at,
has_accepted_non_disclosure_agreement = @has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata = @has_accepted_non_disclosure_agreement_metadata
WHERE
%s
AND id = @id
@@ -224,10 +236,12 @@ WHERE
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.StrictNamedArgs{
"id": tca.ID,
"name": tca.Name,
"active": tca.Active,
"updated_at": tca.UpdatedAt,
"id": tca.ID,
"name": tca.Name,
"active": tca.Active,
"updated_at": tca.UpdatedAt,
"has_accepted_non_disclosure_agreement": tca.HasAcceptedNonDisclosureAgreement,
"has_accepted_non_disclosure_agreement_metadata": tca.HasAcceptedNonDisclosureAgreementMetadata,
}
maps.Copy(args, scope.SQLArguments())
@@ -281,6 +295,8 @@ SELECT
email,
name,
active,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
created_at,
updated_at
FROM

View File

@@ -149,14 +149,15 @@ func (s TrustCenterAccessService) Create(
}
access = &coredata.TrustCenterAccess{
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID,
Email: req.Email,
Name: req.Name,
Active: req.Active,
CreatedAt: now,
UpdatedAt: now,
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID,
Email: req.Email,
Name: req.Name,
Active: req.Active,
HasAcceptedNonDisclosureAgreement: false,
CreatedAt: now,
UpdatedAt: now,
}
if err := access.Insert(ctx, tx, s.svc.scope); err != nil {

View File

@@ -17,10 +17,17 @@ package probo
import (
"context"
"fmt"
"io"
"mime"
"net/url"
"path/filepath"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"go.gearno.de/crypto/uuid"
"go.gearno.de/kit/pg"
)
@@ -30,71 +37,100 @@ type (
}
UpdateTrustCenterRequest struct {
ID gid.GID
Active *bool
Slug *string
ID gid.GID
Active *bool
Slug *string
NonDisclosureAgreementFileID *gid.GID
}
UploadTrustCenterNDARequest struct {
TrustCenterID gid.GID
File io.Reader
FileName string
}
DeleteTrustCenterNDARequest struct {
TrustCenterID gid.GID
}
)
func (s TrustCenterService) Get(
ctx context.Context,
trustCenterID gid.GID,
) (*coredata.TrustCenter, error) {
trustCenter := &coredata.TrustCenter{}
) (*coredata.TrustCenter, *coredata.File, error) {
var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID)
if err != nil {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID != nil {
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
}
return nil
},
)
if err != nil {
return nil, err
return nil, nil, fmt.Errorf("cannot load trust center: %w", err)
}
return trustCenter, nil
return trustCenter, file, nil
}
func (s TrustCenterService) GetByOrganizationID(
ctx context.Context,
organizationID gid.GID,
) (*coredata.TrustCenter, error) {
trustCenter := &coredata.TrustCenter{}
) (*coredata.TrustCenter, *coredata.File, error) {
var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := trustCenter.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID)
if err != nil {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID != nil {
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
}
return nil
},
)
if err != nil {
return nil, err
return nil, nil, err
}
return trustCenter, nil
return trustCenter, file, nil
}
func (s TrustCenterService) Update(
ctx context.Context,
req *UpdateTrustCenterRequest,
) (*coredata.TrustCenter, error) {
trustCenter := &coredata.TrustCenter{}
) (*coredata.TrustCenter, *coredata.File, error) {
var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
@@ -112,13 +148,189 @@ func (s TrustCenterService) Update(
return fmt.Errorf("cannot update trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID != nil {
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
}
return nil
},
)
if err != nil {
return nil, nil, err
}
return trustCenter, file, nil
}
func (s TrustCenterService) UploadNDA(
ctx context.Context,
req *UploadTrustCenterNDARequest,
) (*coredata.TrustCenter, *coredata.File, error) {
objectKey, err := uuid.NewV7()
if err != nil {
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
}
mimeType := mime.TypeByExtension(filepath.Ext(req.FileName))
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
Bucket: &s.svc.bucket,
Key: aws.String(objectKey.String()),
Body: req.File,
ContentType: &mimeType,
Metadata: map[string]string{
"type": "trust-center-nda",
"trust-center-id": req.TrustCenterID.String(),
},
})
if err != nil {
return nil, nil, fmt.Errorf("cannot upload file to S3: %w", err)
}
headOutput, err := s.svc.s3.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(objectKey.String()),
})
if err != nil {
return nil, nil, fmt.Errorf("cannot get object metadata: %w", err)
}
now := time.Now()
fileID := gid.New(s.svc.scope.GetTenantID(), coredata.FileEntityType)
var trustCenter *coredata.TrustCenter
var file *coredata.File
err = s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.TrustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
file = &coredata.File{
ID: fileID,
BucketName: s.svc.bucket,
MimeType: mimeType,
FileName: req.FileName,
FileKey: objectKey.String(),
FileSize: int(*headOutput.ContentLength),
CreatedAt: now,
UpdatedAt: now,
}
if err := file.Insert(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert file: %w", err)
}
trustCenter.NonDisclosureAgreementFileID = &fileID
trustCenter.UpdatedAt = now
if err := trustCenter.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center: %w", err)
}
return nil
},
)
if err != nil {
return nil, nil, err
}
return trustCenter, file, nil
}
func (s TrustCenterService) DeleteNDA(
ctx context.Context,
req *DeleteTrustCenterNDARequest,
) (*coredata.TrustCenter, *coredata.File, error) {
var trustCenter *coredata.TrustCenter
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.TrustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
trustCenter.NonDisclosureAgreementFileID = nil
trustCenter.UpdatedAt = time.Now()
if err := trustCenter.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center: %w", err)
}
return nil
},
)
if err != nil {
return nil, nil, err
}
return trustCenter, nil, nil // File is nil after deletion
}
func (s TrustCenterService) GenerateNDAFileURL(
ctx context.Context,
trustCenterID gid.GID,
expiresIn time.Duration,
) (*string, error) {
var file *coredata.File
trustCenter := &coredata.TrustCenter{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID == nil {
return nil
}
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return trustCenter, nil
if trustCenter.NonDisclosureAgreementFileID == nil {
return nil, nil
}
presignClient := s3.NewPresignClient(s.svc.s3)
encodedFilename := url.QueryEscape(file.FileName)
contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(file.FileKey),
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = expiresIn
})
if err != nil {
return nil, fmt.Errorf("cannot presign GetObject request: %w", err)
}
return &presignedReq.URL, nil
}

View File

@@ -1144,6 +1144,8 @@ type TrustCenter implements Node {
id: ID!
active: Boolean!
slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
organization: Organization! @goField(forceResolver: true)
@@ -1889,6 +1891,7 @@ type TrustCenterAccess implements Node {
email: String!
name: String!
active: Boolean!
hasAcceptedNonDisclosureAgreement: Boolean!
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -2218,6 +2221,14 @@ type Mutation {
input: UpdateTrustCenterInput!
): UpdateTrustCenterPayload!
uploadTrustCenterNDA(
input: UploadTrustCenterNDAInput!
): UploadTrustCenterNDAPayload!
deleteTrustCenterNDA(
input: DeleteTrustCenterNDAInput!
): DeleteTrustCenterNDAPayload!
# Trust Center Access CRUD mutations
createTrustCenterAccess(
input: CreateTrustCenterAccessInput!
@@ -2503,6 +2514,16 @@ input UpdateTrustCenterInput {
slug: String
}
input UploadTrustCenterNDAInput {
trustCenterId: ID!
fileName: String!
file: Upload!
}
input DeleteTrustCenterNDAInput {
trustCenterId: ID!
}
input CreateTrustCenterAccessInput {
trustCenterId: ID!
email: String!
@@ -3135,6 +3156,14 @@ type UpdateTrustCenterPayload {
trustCenter: TrustCenter!
}
type UploadTrustCenterNDAPayload {
trustCenter: TrustCenter!
}
type DeleteTrustCenterNDAPayload {
trustCenter: TrustCenter!
}
type CreateTrustCenterAccessPayload {
trustCenterAccessEdge: TrustCenterAccessEdge!
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,12 +18,18 @@ import (
"github.com/getprobo/probo/pkg/coredata"
)
func NewTrustCenter(tc *coredata.TrustCenter) *TrustCenter {
func NewTrustCenter(tc *coredata.TrustCenter, file *coredata.File) *TrustCenter {
var ndaFileName *string
if file != nil {
ndaFileName = &file.FileName
}
return &TrustCenter{
ID: tc.ID,
Active: tc.Active,
Slug: tc.Slug,
CreatedAt: tc.CreatedAt,
UpdatedAt: tc.UpdatedAt,
ID: tc.ID,
Active: tc.Active,
Slug: tc.Slug,
NdaFileName: ndaFileName,
CreatedAt: tc.CreatedAt,
UpdatedAt: tc.UpdatedAt,
}
}

View File

@@ -23,12 +23,13 @@ type TrustCenterAccessOrderBy = OrderBy[coredata.TrustCenterAccessOrderField]
func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
return &TrustCenterAccess{
ID: tca.ID,
Email: tca.Email,
Name: tca.Name,
Active: tca.Active,
CreatedAt: tca.CreatedAt,
UpdatedAt: tca.UpdatedAt,
ID: tca.ID,
Email: tca.Email,
Name: tca.Name,
Active: tca.Active,
HasAcceptedNonDisclosureAgreement: tca.HasAcceptedNonDisclosureAgreement,
CreatedAt: tca.CreatedAt,
UpdatedAt: tca.UpdatedAt,
}
}

View File

@@ -828,6 +828,14 @@ type DeleteTrustCenterAccessPayload struct {
DeletedTrustCenterAccessID gid.GID `json:"deletedTrustCenterAccessId"`
}
type DeleteTrustCenterNDAInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
}
type DeleteTrustCenterNDAPayload struct {
TrustCenter *TrustCenter `json:"trustCenter"`
}
type DeleteVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"`
}
@@ -1436,6 +1444,8 @@ type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"`
@@ -1446,12 +1456,13 @@ func (TrustCenter) IsNode() {}
func (this TrustCenter) GetID() gid.GID { return this.ID }
type TrustCenterAccess struct {
ID gid.GID `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Active bool `json:"active"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID gid.GID `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
Active bool `json:"active"`
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (TrustCenterAccess) IsNode() {}
@@ -1831,6 +1842,16 @@ type UploadTaskEvidencePayload struct {
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
}
type UploadTrustCenterNDAInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
FileName string `json:"fileName"`
File graphql.Upload `json:"file"`
}
type UploadTrustCenterNDAPayload struct {
TrustCenter *TrustCenter `json:"trustCenter"`
}
type UploadVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"`
ValidFrom *time.Time `json:"validFrom,omitempty"`

View File

@@ -1145,7 +1145,7 @@ func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.D
func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error) {
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
trustCenter, err := prb.TrustCenters.Update(ctx, &probo.UpdateTrustCenterRequest{
trustCenter, file, err := prb.TrustCenters.Update(ctx, &probo.UpdateTrustCenterRequest{
ID: input.TrustCenterID,
Active: input.Active,
Slug: input.Slug,
@@ -1155,7 +1155,41 @@ func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.Up
}
return &types.UpdateTrustCenterPayload{
TrustCenter: types.NewTrustCenter(trustCenter),
TrustCenter: types.NewTrustCenter(trustCenter, file),
}, nil
}
// UploadTrustCenterNda is the resolver for the uploadTrustCenterNDA field.
func (r *mutationResolver) UploadTrustCenterNda(ctx context.Context, input types.UploadTrustCenterNDAInput) (*types.UploadTrustCenterNDAPayload, error) {
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
trustCenter, file, err := prb.TrustCenters.UploadNDA(ctx, &probo.UploadTrustCenterNDARequest{
TrustCenterID: input.TrustCenterID,
File: input.File.File,
FileName: input.FileName,
})
if err != nil {
return nil, fmt.Errorf("cannot upload trust center NDA: %w", err)
}
return &types.UploadTrustCenterNDAPayload{
TrustCenter: types.NewTrustCenter(trustCenter, file),
}, nil
}
// DeleteTrustCenterNda is the resolver for the deleteTrustCenterNDA field.
func (r *mutationResolver) DeleteTrustCenterNda(ctx context.Context, input types.DeleteTrustCenterNDAInput) (*types.DeleteTrustCenterNDAPayload, error) {
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, &probo.DeleteTrustCenterNDARequest{
TrustCenterID: input.TrustCenterID,
})
if err != nil {
return nil, fmt.Errorf("cannot delete trust center NDA: %w", err)
}
return &types.DeleteTrustCenterNDAPayload{
TrustCenter: types.NewTrustCenter(trustCenter, file),
}, nil
}
@@ -3895,12 +3929,12 @@ func (r *organizationResolver) Snapshots(ctx context.Context, obj *types.Organiz
func (r *organizationResolver) TrustCenter(ctx context.Context, obj *types.Organization) (*types.TrustCenter, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())
trustCenter, err := prb.TrustCenters.GetByOrganizationID(ctx, obj.ID)
trustCenter, file, err := prb.TrustCenters.GetByOrganizationID(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot get trust center: %w", err)
}
return types.NewTrustCenter(trustCenter), nil
return types.NewTrustCenter(trustCenter, file), nil
}
// TotalCount is the resolver for the totalCount field.
@@ -4115,11 +4149,12 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
}
return types.NewSnapshot(snapshot), nil
case coredata.TrustCenterEntityType:
trustCenter, err := prb.TrustCenters.Get(ctx, id)
trustCenter, file, err := prb.TrustCenters.Get(ctx, id)
if err != nil {
panic(fmt.Errorf("cannot get trust center: %w", err))
panic(fmt.Errorf("cannot get trust center with file: %w", err))
}
return types.NewTrustCenter(trustCenter), nil
return types.NewTrustCenter(trustCenter, file), nil
default:
}
@@ -4462,6 +4497,18 @@ func (r *taskConnectionResolver) TotalCount(ctx context.Context, obj *types.Task
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
}
// NdaFileURL is the resolver for the ndaFileUrl field.
func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())
fileURL, err := prb.TrustCenters.GenerateNDAFileURL(ctx, obj.ID, 15*time.Minute)
if err != nil {
panic(fmt.Errorf("failed to generate NDA file URL: %w", err))
}
return fileURL, nil
}
// Organization is the resolver for the organization field.
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
prb := r.ProboService(ctx, obj.ID.TenantID())

View File

@@ -31,6 +31,10 @@ type TokenAccessData struct {
Scope string
}
func (t *TokenAccessData) GetEmail() string {
return t.Email
}
type ContextAccessor interface {
UserFromContext(ctx context.Context) *coredata.User
TokenAccessFromContext(ctx context.Context) *TokenAccessData

View File

@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/99designs/gqlgen/graphql/handler"
@@ -138,20 +139,29 @@ func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *tru
return WithSession(usrmgrSvc, trustSvc, authCfg, trustAuthCfg, srv.ServeHTTP)
}
// TrustService returns a trust service scoped to the given tenant
func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
func (r *Resolver) RootTrustService(ctx context.Context) *trust.TenantService {
return r.trustCenterSvc.WithTenant(gid.NewTenantID())
}
func (r *Resolver) PublicTrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
return r.trustCenterSvc.WithTenant(tenantID)
}
// GetTenantService returns a tenant service for the given tenant ID
func (r *Resolver) GetTenantService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
return r.trustCenterSvc.WithTenant(tenantID)
func (r *Resolver) PrivateTrustService(ctx context.Context, tenantID gid.TenantID) (*trust.TenantService, error) {
if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, tenantID); err != nil {
return nil, fmt.Errorf("cannot access trust center: %w", err)
}
return r.trustCenterSvc.WithTenant(tenantID), nil
}
func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg console_v1.AuthConfig, trustAuthCfg TrustAuthConfig, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ip := extractIPAddress(r)
ctx = context.WithValue(ctx, coredata.ContextKeyIPAddress, ip)
if authCtx := tryTokenAuth(ctx, w, r, trustSvc, trustAuthCfg); authCtx != nil {
next(w, r.WithContext(authCtx))
return
@@ -219,15 +229,14 @@ func tryTokenAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, t
tenantID := basicPayload.Data.TrustCenterID.TenantID()
tenantSvc := trustSvc.WithTenant(tenantID)
payload, err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, cookie.Value)
if err != nil {
if err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, basicPayload.Data.TrustCenterID, basicPayload.Data.Email); err != nil {
clearTokenCookie(w, trustAuthCfg)
return nil
}
tokenAccess := &auth.TokenAccessData{
TrustCenterID: payload.TrustCenterID,
Email: payload.Email,
TrustCenterID: basicPayload.Data.TrustCenterID,
Email: basicPayload.Data.Email,
TenantID: tenantID,
Scope: trustAuthCfg.Scope,
}
@@ -256,3 +265,21 @@ func updateSessionIfNeeded(ctx context.Context, usrmgrSvc *usrmgr.Service) {
}
}
}
func extractIPAddress(r *http.Request) string {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
if ip := strings.Split(xff, ",")[0]; ip != "" {
return strings.TrimSpace(ip)
}
}
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return strings.TrimSpace(xri)
}
if ip := strings.Split(r.RemoteAddr, ":")[0]; ip != "" {
return ip
}
return "unknown"
}

View File

@@ -199,8 +199,11 @@ type TrustCenter implements Node {
id: ID!
active: Boolean!
slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents(
first: Int
@@ -246,10 +249,18 @@ input ExportDocumentPDFInput {
documentId: ID!
}
input AcceptNonDisclosureAgreementInput {
trustCenterId: ID!
}
type ExportDocumentPDFPayload {
data: String!
}
type AcceptNonDisclosureAgreementPayload{
success: Boolean!
}
type Query {
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
}
@@ -262,4 +273,8 @@ type Mutation {
exportDocumentPDF(
input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)
}

View File

@@ -57,6 +57,10 @@ type DirectiveRoot struct {
}
type ComplexityRoot struct {
AcceptNonDisclosureAgreementPayload struct {
Success func(childComplexity int) int
}
Audit struct {
Framework func(childComplexity int) int
ID func(childComplexity int) int
@@ -103,8 +107,9 @@ type ComplexityRoot struct {
}
Mutation struct {
CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
AcceptNonDisclosureAgreement func(childComplexity int, input types.AcceptNonDisclosureAgreementInput) int
CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
}
Organization struct {
@@ -131,14 +136,17 @@ type ComplexityRoot struct {
}
TrustCenter struct {
Active func(childComplexity int) int
Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
ID func(childComplexity int) int
IsUserAuthenticated func(childComplexity int) int
Organization func(childComplexity int) int
Slug func(childComplexity int) int
Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
Active func(childComplexity int) int
Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
HasAcceptedNonDisclosureAgreement func(childComplexity int) int
ID func(childComplexity int) int
IsUserAuthenticated func(childComplexity int) int
NdaFileName func(childComplexity int) int
NdaFileURL func(childComplexity int) int
Organization func(childComplexity int) int
Slug func(childComplexity int) int
Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
}
TrustCenterAccess struct {
@@ -175,6 +183,7 @@ type AuditResolver interface {
type MutationResolver interface {
CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error)
}
type OrganizationResolver interface {
LogoURL(ctx context.Context, obj *types.Organization) (*string, error)
@@ -186,8 +195,10 @@ type ReportResolver interface {
DownloadURL(ctx context.Context, obj *types.Report) (*string, error)
}
type TrustCenterResolver interface {
NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error)
Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error)
IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, error)
HasAcceptedNonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (bool, error)
Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error)
Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error)
Vendors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error)
@@ -212,6 +223,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
_ = ec
switch typeName + "." + field {
case "AcceptNonDisclosureAgreementPayload.success":
if e.complexity.AcceptNonDisclosureAgreementPayload.Success == nil {
break
}
return e.complexity.AcceptNonDisclosureAgreementPayload.Success(childComplexity), true
case "Audit.framework":
if e.complexity.Audit.Framework == nil {
break
@@ -338,6 +356,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Framework.Name(childComplexity), true
case "Mutation.acceptNonDisclosureAgreement":
if e.complexity.Mutation.AcceptNonDisclosureAgreement == nil {
break
}
args, err := ec.field_Mutation_acceptNonDisclosureAgreement_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.AcceptNonDisclosureAgreement(childComplexity, args["input"].(types.AcceptNonDisclosureAgreementInput)), true
case "Mutation.createTrustCenterAccess":
if e.complexity.Mutation.CreateTrustCenterAccess == nil {
break
@@ -475,6 +505,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.TrustCenter.Documents(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
case "TrustCenter.hasAcceptedNonDisclosureAgreement":
if e.complexity.TrustCenter.HasAcceptedNonDisclosureAgreement == nil {
break
}
return e.complexity.TrustCenter.HasAcceptedNonDisclosureAgreement(childComplexity), true
case "TrustCenter.id":
if e.complexity.TrustCenter.ID == nil {
break
@@ -489,6 +526,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.TrustCenter.IsUserAuthenticated(childComplexity), true
case "TrustCenter.ndaFileName":
if e.complexity.TrustCenter.NdaFileName == nil {
break
}
return e.complexity.TrustCenter.NdaFileName(childComplexity), true
case "TrustCenter.ndaFileUrl":
if e.complexity.TrustCenter.NdaFileURL == nil {
break
}
return e.complexity.TrustCenter.NdaFileURL(childComplexity), true
case "TrustCenter.organization":
if e.complexity.TrustCenter.Organization == nil {
break
@@ -621,6 +672,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
opCtx := graphql.GetOperationContext(ctx)
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
ec.unmarshalInputAcceptNonDisclosureAgreementInput,
ec.unmarshalInputCreateTrustCenterAccessInput,
ec.unmarshalInputExportDocumentPDFInput,
)
@@ -921,8 +973,11 @@ type TrustCenter implements Node {
id: ID!
active: Boolean!
slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents(
first: Int
@@ -968,10 +1023,18 @@ input ExportDocumentPDFInput {
documentId: ID!
}
input AcceptNonDisclosureAgreementInput {
trustCenterId: ID!
}
type ExportDocumentPDFPayload {
data: String!
}
type AcceptNonDisclosureAgreementPayload{
success: Boolean!
}
type Query {
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
}
@@ -984,6 +1047,10 @@ type Mutation {
exportDocumentPDF(
input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)
}
`, BuiltIn: false},
}
@@ -1021,6 +1088,29 @@ func (ec *executionContext) dir_mustBeAuthenticated_argsRole(
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_acceptNonDisclosureAgreement_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_acceptNonDisclosureAgreement_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_acceptNonDisclosureAgreement_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.AcceptNonDisclosureAgreementInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNAcceptNonDisclosureAgreementInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementInput(ctx, tmp)
}
var zeroVal types.AcceptNonDisclosureAgreementInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_createTrustCenterAccess_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -1444,6 +1534,50 @@ func (ec *executionContext) field___Type_fields_argsIncludeDeprecated(
// region **************************** field.gotpl *****************************
func (ec *executionContext) _AcceptNonDisclosureAgreementPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.AcceptNonDisclosureAgreementPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_AcceptNonDisclosureAgreementPayload_success(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Success, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_AcceptNonDisclosureAgreementPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "AcceptNonDisclosureAgreementPayload",
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) _Audit_id(ctx context.Context, field graphql.CollectedField, obj *types.Audit) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Audit_id(ctx, field)
if err != nil {
@@ -2479,6 +2613,92 @@ func (ec *executionContext) fieldContext_Mutation_exportDocumentPDF(ctx context.
return fc, nil
}
func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_acceptNonDisclosureAgreement(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
directive0 := func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().AcceptNonDisclosureAgreement(rctx, fc.Args["input"].(types.AcceptNonDisclosureAgreementInput))
}
directive1 := func(ctx context.Context) (any, error) {
role, err := ec.unmarshalORole2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRole(ctx, "USER")
if err != nil {
var zeroVal *types.AcceptNonDisclosureAgreementPayload
return zeroVal, err
}
if ec.directives.MustBeAuthenticated == nil {
var zeroVal *types.AcceptNonDisclosureAgreementPayload
return zeroVal, errors.New("directive mustBeAuthenticated is not implemented")
}
return ec.directives.MustBeAuthenticated(ctx, nil, directive0, role)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*types.AcceptNonDisclosureAgreementPayload); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/getprobo/probo/pkg/server/api/trust/v1/types.AcceptNonDisclosureAgreementPayload`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.AcceptNonDisclosureAgreementPayload)
fc.Result = res
return ec.marshalNAcceptNonDisclosureAgreementPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_acceptNonDisclosureAgreement(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_AcceptNonDisclosureAgreementPayload_success(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type AcceptNonDisclosureAgreementPayload", 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_acceptNonDisclosureAgreement_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil {
@@ -2847,10 +3067,16 @@ func (ec *executionContext) fieldContext_Query_trustCenterBySlug(ctx context.Con
return ec.fieldContext_TrustCenter_active(ctx, field)
case "slug":
return ec.fieldContext_TrustCenter_slug(ctx, field)
case "ndaFileName":
return ec.fieldContext_TrustCenter_ndaFileName(ctx, field)
case "ndaFileUrl":
return ec.fieldContext_TrustCenter_ndaFileUrl(ctx, field)
case "organization":
return ec.fieldContext_TrustCenter_organization(ctx, field)
case "isUserAuthenticated":
return ec.fieldContext_TrustCenter_isUserAuthenticated(ctx, field)
case "hasAcceptedNonDisclosureAgreement":
return ec.fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field)
case "documents":
return ec.fieldContext_TrustCenter_documents(ctx, field)
case "audits":
@@ -3294,6 +3520,88 @@ func (ec *executionContext) fieldContext_TrustCenter_slug(_ context.Context, fie
return fc, nil
}
func (ec *executionContext) _TrustCenter_ndaFileName(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_ndaFileName(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.NdaFileName, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_TrustCenter_ndaFileName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenter",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _TrustCenter_ndaFileUrl(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_ndaFileUrl(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TrustCenter().NdaFileURL(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_TrustCenter_ndaFileUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenter",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _TrustCenter_organization(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_organization(ctx, field)
if err != nil {
@@ -3390,6 +3698,50 @@ func (ec *executionContext) fieldContext_TrustCenter_isUserAuthenticated(_ conte
return fc, nil
}
func (ec *executionContext) _TrustCenter_hasAcceptedNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.TrustCenter().HasAcceptedNonDisclosureAgreement(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenter",
Field: field,
IsMethod: true,
IsResolver: true,
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) _TrustCenter_documents(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_documents(ctx, field)
if err != nil {
@@ -6162,6 +6514,33 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field
// region **************************** input.gotpl *****************************
func (ec *executionContext) unmarshalInputAcceptNonDisclosureAgreementInput(ctx context.Context, obj any) (types.AcceptNonDisclosureAgreementInput, error) {
var it types.AcceptNonDisclosureAgreementInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"trustCenterId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "trustCenterId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("trustCenterId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.TrustCenterID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputCreateTrustCenterAccessInput(ctx context.Context, obj any) (types.CreateTrustCenterAccessInput, error) {
var it types.CreateTrustCenterAccessInput
asMap := map[string]any{}
@@ -6303,6 +6682,45 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj
// region **************************** object.gotpl ****************************
var acceptNonDisclosureAgreementPayloadImplementors = []string{"AcceptNonDisclosureAgreementPayload"}
func (ec *executionContext) _AcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, obj *types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, acceptNonDisclosureAgreementPayloadImplementors)
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("AcceptNonDisclosureAgreementPayload")
case "success":
out.Values[i] = ec._AcceptNonDisclosureAgreementPayload_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 auditImplementors = []string{"Audit", "Node"}
func (ec *executionContext) _Audit(ctx context.Context, sel ast.SelectionSet, obj *types.Audit) graphql.Marshaler {
@@ -6791,6 +7209,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "acceptNonDisclosureAgreement":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_acceptNonDisclosureAgreement(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -7111,6 +7536,41 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1)
}
case "ndaFileName":
out.Values[i] = ec._TrustCenter_ndaFileName(ctx, field, obj)
case "ndaFileUrl":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TrustCenter_ndaFileUrl(ctx, field, obj)
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "organization":
field := field
@@ -7182,6 +7642,42 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "hasAcceptedNonDisclosureAgreement":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&fs.Invalids, 1)
}
return res
}
if field.Deferrable != nil {
dfs, ok := deferred[field.Deferrable.Label]
di := 0
if ok {
dfs.AddField(field)
di = len(dfs.Values) - 1
} else {
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
deferred[field.Deferrable.Label] = dfs
}
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
return innerFunc(ctx, dfs)
})
// don't run the out.Concurrently() call below
out.Values[i] = graphql.Null
continue
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "documents":
field := field
@@ -7849,6 +8345,25 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl *****************************
func (ec *executionContext) unmarshalNAcceptNonDisclosureAgreementInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementInput(ctx context.Context, v any) (types.AcceptNonDisclosureAgreementInput, error) {
res, err := ec.unmarshalInputAcceptNonDisclosureAgreementInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNAcceptNonDisclosureAgreementPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, v types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler {
return ec._AcceptNonDisclosureAgreementPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNAcceptNonDisclosureAgreementPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, v *types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._AcceptNonDisclosureAgreementPayload(ctx, sel, v)
}
func (ec *executionContext) marshalNAudit2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAudit(ctx context.Context, sel ast.SelectionSet, v *types.Audit) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {

View File

@@ -109,15 +109,12 @@ func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
}
tenantID := token.Data.TrustCenterID.TenantID()
tenantSvc := trustSvc.WithTenant(tenantID)
accessData, err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, tokenString)
if err != nil {
tenantSvc := trustSvc.WithTenant(token.Data.TrustCenterID.TenantID())
if err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, token.Data.TrustCenterID, token.Data.Email); err != nil {
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
}
return accessData, nil
return &token.Data, nil
}
func trustCenterLogoutHandler(authCfg console_v1.AuthConfig, trustAuthCfg TrustAuthConfig) http.HandlerFunc {

View File

@@ -18,10 +18,16 @@ import (
"github.com/getprobo/probo/pkg/coredata"
)
func NewTrustCenter(tc *coredata.TrustCenter) *TrustCenter {
func NewTrustCenter(tc *coredata.TrustCenter, file *coredata.File) *TrustCenter {
var ndaFileName *string
if file != nil {
ndaFileName = &file.FileName
}
return &TrustCenter{
ID: tc.ID,
Active: tc.Active,
Slug: tc.Slug,
ID: tc.ID,
Active: tc.Active,
Slug: tc.Slug,
NdaFileName: ndaFileName,
}
}

View File

@@ -19,6 +19,14 @@ type Node interface {
GetID() gid.GID
}
type AcceptNonDisclosureAgreementInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
}
type AcceptNonDisclosureAgreementPayload struct {
Success bool `json:"success"`
}
type Audit struct {
ID gid.GID `json:"id"`
Framework *Framework `json:"framework"`
@@ -115,14 +123,17 @@ func (Report) IsNode() {}
func (this Report) GetID() gid.GID { return this.ID }
type TrustCenter struct {
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
Organization *Organization `json:"organization"`
IsUserAuthenticated bool `json:"isUserAuthenticated"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
ID gid.GID `json:"id"`
Active bool `json:"active"`
Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
Organization *Organization `json:"organization"`
IsUserAuthenticated bool `json:"isUserAuthenticated"`
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Vendors *VendorConnection `json:"vendors"`
}
func (TrustCenter) IsNode() {}

View File

@@ -11,9 +11,7 @@ import (
"time"
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/server/api/trust/v1/auth"
"github.com/getprobo/probo/pkg/server/api/trust/v1/schema"
"github.com/getprobo/probo/pkg/server/api/trust/v1/types"
"github.com/getprobo/probo/pkg/trust"
@@ -21,14 +19,14 @@ import (
// Framework is the resolver for the framework field.
func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) {
trust := r.TrustService(ctx, obj.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
audit, err := trust.Audits.Get(ctx, obj.ID)
audit, err := publicTrustService.Audits.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot load audit: %w", err))
}
framework, err := trust.Frameworks.Get(ctx, audit.FrameworkID)
framework, err := publicTrustService.Frameworks.Get(ctx, audit.FrameworkID)
if err != nil {
panic(fmt.Errorf("cannot load framework: %w", err))
}
@@ -38,9 +36,9 @@ func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types
// Report is the resolver for the report field.
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
trust := r.TrustService(ctx, obj.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
audit, err := trust.Audits.Get(ctx, obj.ID)
audit, err := publicTrustService.Audits.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot load audit: %w", err))
}
@@ -49,7 +47,7 @@ func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Re
return nil, nil
}
report, err := trust.Reports.Get(ctx, *audit.ReportID)
report, err := publicTrustService.Reports.Get(ctx, *audit.ReportID)
if err != nil {
panic(fmt.Errorf("cannot load report: %w", err))
}
@@ -59,9 +57,9 @@ func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Re
// CreateTrustCenterAccess is the resolver for the createTrustCenterAccess field.
func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) {
trustSvc := r.trustCenterSvc.WithTenant(input.TrustCenterID.TenantID())
publicTrustService := r.PublicTrustService(ctx, input.TrustCenterID.TenantID())
access, err := trustSvc.TrustCenterAccesses.Create(ctx, &trust.CreateTrustCenterAccessRequest{
access, err := publicTrustService.TrustCenterAccesses.Create(ctx, &trust.CreateTrustCenterAccessRequest{
TrustCenterID: input.TrustCenterID,
Email: input.Email,
Name: input.Name,
@@ -83,34 +81,72 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
// ExportDocumentPDF is the resolver for the exportDocumentPDF field.
func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) {
if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, input.DocumentID.TenantID()); err != nil {
return nil, err
}
trust := r.trustCenterSvc.WithTenant(input.DocumentID.TenantID())
pdf, err := trust.Documents.ExportPDF(ctx, input.DocumentID)
privateTrustService, err := r.PrivateTrustService(ctx, input.DocumentID.TenantID())
if err != nil {
return nil, fmt.Errorf("cannot export document PDF: %w", err)
}
hasAcceptedNDA := false
userData := UserFromContext(ctx)
if userData != nil {
hasAcceptedNDA = true
}
tokenData := TokenAccessFromContext(ctx)
if tokenData != nil {
tokenData := TokenAccessFromContext(ctx)
hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail())
if err != nil {
panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err))
}
}
if !hasAcceptedNDA {
return nil, fmt.Errorf("user has not accepted NDA")
}
pdf, err := privateTrustService.Documents.ExportPDF(ctx, input.DocumentID)
if err != nil {
panic(fmt.Errorf("cannot export document PDF: %w", err))
}
return &types.ExportDocumentPDFPayload{
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
}, nil
}
// AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field.
func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) {
privateTrustService, err := r.PrivateTrustService(ctx, input.TrustCenterID.TenantID())
if err != nil {
return nil, fmt.Errorf("cannot accept NDA: %w", err)
}
tokenData := TokenAccessFromContext(ctx)
if tokenData == nil {
return nil, fmt.Errorf("token not found")
}
err = privateTrustService.TrustCenterAccesses.AcceptNonDisclosureAgreement(ctx, input.TrustCenterID, tokenData.GetEmail())
if err != nil {
return nil, fmt.Errorf("cannot accept NDA: %w", err)
}
return &types.AcceptNonDisclosureAgreementPayload{Success: true}, nil
}
// LogoURL is the resolver for the logoUrl field.
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
trust := r.TrustService(ctx, obj.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
return trust.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
return publicTrustService.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
}
// TrustCenterBySlug is the resolver for the trustCenterBySlug field.
func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) {
publicTrust := r.trustCenterSvc.WithTenant(gid.NewTenantID())
rootTrustService := r.RootTrustService(ctx)
trustCenter, err := publicTrust.TrustCenters.GetBySlug(ctx, slug)
trustCenter, err := rootTrustService.TrustCenters.GetBySlug(ctx, slug)
if err != nil {
return nil, nil
}
@@ -119,28 +155,48 @@ func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*ty
return nil, nil
}
result := types.NewTrustCenter(trustCenter)
publicTrustService := r.PublicTrustService(ctx, trustCenter.TenantID)
trustCenter, file, err := publicTrustService.TrustCenters.Get(ctx, trustCenter.ID)
if err != nil {
panic(fmt.Errorf("cannot get trust center: %w", err))
}
orgTrust := r.trustCenterSvc.WithTenant(trustCenter.TenantID)
org, err := orgTrust.Organizations.Get(ctx, trustCenter.OrganizationID)
org, err := publicTrustService.Organizations.Get(ctx, trustCenter.OrganizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
response := types.NewTrustCenter(trustCenter, file)
response.Organization = types.NewOrganization(org)
result.Organization = types.NewOrganization(org)
return result, nil
return response, nil
}
// DownloadURL is the resolver for the downloadUrl field.
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, obj.ID.TenantID()); err != nil {
return nil, err
privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
if err != nil {
return nil, fmt.Errorf("cannot generate download URL: %w", err)
}
trust := r.TrustService(ctx, obj.ID.TenantID())
hasAcceptedNDA := false
userData := UserFromContext(ctx)
if userData != nil {
hasAcceptedNDA = true
}
url, err := trust.Reports.GenerateDownloadURL(ctx, obj.ID, r.trustAuthCfg.ReportURLDuration)
tokenData := TokenAccessFromContext(ctx)
if tokenData != nil {
hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail())
if err != nil {
panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err))
}
}
if !hasAcceptedNDA {
return nil, fmt.Errorf("user has not accepted NDA")
}
url, err := privateTrustService.Reports.GenerateDownloadURL(ctx, obj.ID, r.trustAuthCfg.ReportURLDuration)
if err != nil {
panic(fmt.Errorf("cannot generate download URL: %w", err))
}
@@ -148,6 +204,21 @@ func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*s
return url, nil
}
// NdaFileURL is the resolver for the ndaFileUrl field.
func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) {
privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
if err != nil {
return nil, fmt.Errorf("failed to get private trust service: %w", err)
}
fileURL, err := privateTrustService.TrustCenters.GenerateNDAFileURL(ctx, obj.ID, 15*time.Minute)
if err != nil {
return nil, fmt.Errorf("failed to generate NDA file URL: %w", err)
}
return &fileURL, nil
}
// Organization is the resolver for the organization field.
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
return obj.Organization, nil
@@ -155,15 +226,41 @@ func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.Trust
// IsUserAuthenticated is the resolver for the isUserAuthenticated field.
func (r *trustCenterResolver) IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, error) {
if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, obj.Organization.ID.TenantID()); err != nil {
_, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
if err != nil {
return false, nil
}
return true, nil
}
// HasAcceptedNonDisclosureAgreement is the resolver for the hasAcceptedNonDisclosureAgreement field.
func (r *trustCenterResolver) HasAcceptedNonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (bool, error) {
privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
if err != nil {
return false, nil
}
userData := UserFromContext(ctx)
if userData != nil {
return true, nil
}
tokenData := TokenAccessFromContext(ctx)
if tokenData != nil {
hasAcceptedNDA, err := privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, obj.ID, tokenData.GetEmail())
if err != nil {
panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err))
}
return hasAcceptedNDA, nil
}
panic(fmt.Errorf("no user or token data found"))
}
// Documents is the resolver for the documents field.
func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) {
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
Field: coredata.DocumentOrderFieldTitle,
@@ -171,7 +268,7 @@ func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCen
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
documentPage, err := trust.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
documentPage, err := publicTrustService.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list public documents: %w", err))
}
@@ -181,7 +278,7 @@ func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCen
// Audits is the resolver for the audits field.
func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) {
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.AuditOrderField]{
Field: coredata.AuditOrderFieldValidFrom,
@@ -189,7 +286,7 @@ func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
auditPage, err := trust.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
auditPage, err := publicTrustService.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list public audits: %w", err))
}
@@ -199,7 +296,7 @@ func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter
// Vendors is the resolver for the vendors field.
func (r *trustCenterResolver) Vendors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) {
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.VendorOrderField]{
Field: coredata.VendorOrderFieldName,
@@ -207,7 +304,7 @@ func (r *trustCenterResolver) Vendors(ctx context.Context, obj *types.TrustCente
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
vendorPage, err := trust.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
vendorPage, err := publicTrustService.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list public vendors: %w", err))
}

View File

@@ -16,6 +16,7 @@ package trust
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/mail"
@@ -23,8 +24,6 @@ import (
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/statelesstoken"
"github.com/getprobo/probo/pkg/usrmgr"
"go.gearno.de/kit/pg"
)
@@ -48,20 +47,12 @@ const (
func (s TrustCenterAccessService) ValidateToken(
ctx context.Context,
tokenString string,
) (*probo.TrustCenterAccessData, error) {
token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData](
s.svc.tokenSecret,
TokenTypeTrustCenterAccess,
tokenString,
)
if err != nil {
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
}
access := &coredata.TrustCenterAccess{}
err = s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, token.Data.TrustCenterID, token.Data.Email)
trustCenterID gid.GID,
email string,
) error {
return s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
access := &coredata.TrustCenterAccess{}
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email)
if err != nil {
return fmt.Errorf("cannot load trust center access: %w", err)
}
@@ -72,12 +63,6 @@ func (s TrustCenterAccessService) ValidateToken(
return nil
})
if err != nil {
return nil, err
}
return &token.Data, nil
}
func (s TrustCenterAccessService) Create(
@@ -115,14 +100,15 @@ func (s TrustCenterAccessService) Create(
}
access = &coredata.TrustCenterAccess{
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID,
Email: req.Email,
Name: req.Name,
Active: false,
CreatedAt: now,
UpdatedAt: now,
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID,
Email: req.Email,
Name: req.Name,
Active: false,
HasAcceptedNonDisclosureAgreement: false,
CreatedAt: now,
UpdatedAt: now,
}
if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
@@ -137,3 +123,48 @@ func (s TrustCenterAccessService) Create(
return access, nil
}
func (s TrustCenterAccessService) HasAcceptedNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email string) (bool, error) {
access := &coredata.TrustCenterAccess{}
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email)
if err != nil {
return fmt.Errorf("cannot load trust center access: %w", err)
}
return nil
})
if err != nil {
return false, nil
}
return access.HasAcceptedNonDisclosureAgreement, nil
}
func (s TrustCenterAccessService) AcceptNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email string) error {
return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
access := &coredata.TrustCenterAccess{}
if err := access.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, email); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err)
}
acceptationLogs, err := json.Marshal(map[string]string{
"email": email,
"timestamp": time.Now().Format(time.RFC3339),
"ip": ctx.Value(coredata.ContextKeyIPAddress).(string),
})
if err != nil {
return fmt.Errorf("cannot marshal non disclosure agreement acceptation logs: %w", err)
}
access.HasAcceptedNonDisclosureAgreement = true
access.UpdatedAt = time.Now()
access.HasAcceptedNonDisclosureAgreementMetadata = acceptationLogs
if err := access.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center access: %w", err)
}
return nil
})
}

View File

@@ -16,10 +16,14 @@ package trust
import (
"context"
"fmt"
"net/url"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"go.gearno.de/kit/pg"
)
@@ -51,3 +55,88 @@ func (s TrustCenterService) GetBySlug(
return trustCenter, nil
}
func (s TrustCenterService) Get(
ctx context.Context,
trustCenterID gid.GID,
) (*coredata.TrustCenter, *coredata.File, error) {
var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID != nil {
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
}
return nil
},
)
if err != nil {
return nil, nil, fmt.Errorf("cannot load trust center: %w", err)
}
return trustCenter, file, nil
}
func (s TrustCenterService) GenerateNDAFileURL(
ctx context.Context,
trustCenterID gid.GID,
expiresIn time.Duration,
) (string, error) {
var file *coredata.File
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
trustCenter := &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
if trustCenter.NonDisclosureAgreementFileID == nil {
return fmt.Errorf("no NDA file found")
}
file = &coredata.File{}
if err := file.LoadByID(ctx, conn, s.svc.scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
return fmt.Errorf("cannot load file: %w", err)
}
return nil
},
)
if err != nil {
return "", err
}
presignClient := s3.NewPresignClient(s.svc.s3)
encodedFilename := url.QueryEscape(file.FileName)
contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s",
encodedFilename, encodedFilename)
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(file.FileKey),
ResponseCacheControl: aws.String("max-age=3600, public"),
ResponseContentDisposition: aws.String(contentDisposition),
}, func(opts *s3.PresignOptions) {
opts.Expires = expiresIn
})
if err != nil {
return "", fmt.Errorf("cannot presign GetObject request: %w", err)
}
return presignedReq.URL, nil
}