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 email
name name
active active
hasAcceptedNonDisclosureAgreement
createdAt createdAt
} }
} }
@@ -48,6 +49,7 @@ export const createTrustCenterAccessMutation = graphql`
email email
name name
active active
hasAcceptedNonDisclosureAgreement
createdAt createdAt
} }
} }
@@ -65,6 +67,7 @@ export const updateTrustCenterAccessMutation = graphql`
email email
name name
active active
hasAcceptedNonDisclosureAgreement
createdAt createdAt
updatedAt updatedAt
} }

View File

@@ -12,6 +12,8 @@ export const trustCenterQuery = graphql`
id id
active active
slug slug
ndaFileName
ndaFileUrl
createdAt createdAt
updatedAt 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 * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -27,6 +27,7 @@ export type TrustCenterAccessGraphCreateMutation$data = {
readonly active: boolean; readonly active: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly email: string; readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
}; };
@@ -107,6 +108,13 @@ v3 = {
"name": "active", "name": "active",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -186,16 +194,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "eafddcd0263963235d3249c22eb50593", "cacheID": "fa88b100be7598cf46159cf79199c398",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "TrustCenterAccessGraphCreateMutation", "name": "TrustCenterAccessGraphCreateMutation",
"operationKind": "mutation", "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; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<2333a6a7d5415f1a08a5612dcaceee8f>> * @generated SignedSource<<e9cdda9f586cee5ffe66f238216060f6>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -22,6 +22,7 @@ export type TrustCenterAccessGraphQuery$data = {
readonly active: boolean; readonly active: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly email: string; readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
}; };
@@ -163,6 +164,13 @@ v5 = [
"name": "active", "name": "active",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -290,7 +298,7 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "5b83cd4ae2434ce2e00de7230d264432", "cacheID": "0286ec03fb4ae012ada7bae8a2234152",
"id": null, "id": null,
"metadata": { "metadata": {
"connection": [ "connection": [
@@ -307,11 +315,11 @@ return {
}, },
"name": "TrustCenterAccessGraphQuery", "name": "TrustCenterAccessGraphQuery",
"operationKind": "query", "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; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<6de2ecb63a58c88c3008368061943b49>> * @generated SignedSource<<d2f40d8fc2bd9c7627d308660dedb1e5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -23,6 +23,7 @@ export type TrustCenterAccessGraphUpdateMutation$data = {
readonly active: boolean; readonly active: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly email: string; readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
readonly updatedAt: any; readonly updatedAt: any;
@@ -93,6 +94,13 @@ v1 = [
"name": "active", "name": "active",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -132,16 +140,16 @@ return {
"selections": (v1/*: any*/) "selections": (v1/*: any*/)
}, },
"params": { "params": {
"cacheID": "683ee01fb5173b49b0a7f3c2d99cf002", "cacheID": "223169ee8a4f65008047097ecff68fc5",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "TrustCenterAccessGraphUpdateMutation", "name": "TrustCenterAccessGraphUpdateMutation",
"operationKind": "mutation", "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; 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 * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -37,6 +37,8 @@ export type TrustCenterGraphQuery$data = {
readonly active: boolean; readonly active: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly id: string; readonly id: string;
readonly ndaFileName: string | null | undefined;
readonly ndaFileUrl: string | null | undefined;
readonly slug: string; readonly slug: string;
readonly updatedAt: any; readonly updatedAt: any;
} | null | undefined; } | null | undefined;
@@ -114,6 +116,20 @@ v5 = {
"name": "slug", "name": "slug",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "ndaFileUrl",
"storageKey": null
},
(v4/*: any*/), (v4/*: any*/),
{ {
"alias": null, "alias": null,
@@ -544,16 +560,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "28c1a32327e2fee9320fd091100ec800", "cacheID": "8dff30a639470418ba7151cb5743d3cc",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "TrustCenterGraphQuery", "name": "TrustCenterGraphQuery",
"operationKind": "query", "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; 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; active: boolean;
slug: string; slug: string;
isUserAuthenticated: boolean; isUserAuthenticated: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
ndaFileName: string | null;
ndaFileUrl: string | null;
organization: { organization: {
id: string; id: string;
name: 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>( async function trustCenterGraphQLRequest<T = unknown>(
operationName: string, operationName: string,
@@ -127,12 +142,14 @@ async function trustCenterGraphQLRequest<T = unknown>(
function isCriticalError(error: GraphQLError): boolean { function isCriticalError(error: GraphQLError): boolean {
const message = error.message?.toLowerCase() || ''; const message = error.message?.toLowerCase() || '';
const path = error.path || [];
if (message.includes('access denied') || message.includes('authentication required')) { if (
if (path.length > 2) { message.includes('access denied') ||
return false; message.includes('authentication required') ||
} message.includes('user has not accepted nda') ||
message.includes('no nda file found')
) {
return false;
} }
return true; return true;
@@ -145,6 +162,9 @@ const TRUST_CENTER_QUERY = `
active active
slug slug
isUserAuthenticated isUserAuthenticated
hasAcceptedNonDisclosureAgreement
ndaFileName
ndaFileUrl
organization { organization {
id id
name 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) { export function useTrustCenterQuery(slug: string) {
return useQuery<TrustCenterQueryData>({ return useQuery<TrustCenterQueryData>({
queryKey: ["trust-center", slug], 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) { if (error) {
const isTokenError = error.toLowerCase().includes('token') || const isTokenError = error.toLowerCase().includes('token') ||
error.toLowerCase().includes('expired') || error.toLowerCase().includes('expired') ||
error.toLowerCase().includes('invalid'); error.toLowerCase().includes('invalid') ||
error.toLowerCase().includes('401') ||
error.toLowerCase().includes('unauthorized');
if (isTokenError) { if (isTokenError) {
return <TokenErrorPage error={error} />; return <TokenErrorPage error={error} />;

View File

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

View File

@@ -4,6 +4,7 @@ import {
Button, Button,
Card, Card,
Checkbox, Checkbox,
Dropzone,
Field, Field,
Input, Input,
PageHeader, PageHeader,
@@ -12,9 +13,10 @@ import {
Tabs, Tabs,
TabLink, TabLink,
TabItem, TabItem,
IconTrashCan,
} from "@probo/ui"; } from "@probo/ui";
import { usePreloadedQuery, type PreloadedQuery } from "react-relay"; 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 type { TrustCenterGraphQuery } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
import { useState } from "react"; import { useState } from "react";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
@@ -32,6 +34,8 @@ export default function TrustCenterPage({ queryRef }: Props) {
const { organization } = usePreloadedQuery(trustCenterQuery, queryRef); const { organization } = usePreloadedQuery(trustCenterQuery, queryRef);
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation(); const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
const [isActive, setIsActive] = useState(organization.trustCenter?.active || false); const [isActive, setIsActive] = useState(organization.trustCenter?.active || false);
const [slug, setSlug] = useState(organization.trustCenter?.slug || ""); const [slug, setSlug] = useState(organization.trustCenter?.slug || "");
const [isUpdatingSlug, setIsUpdatingSlug] = useState(false); 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 const trustCenterUrl = organization.trustCenter?.slug
? `${window.location.origin}/trust/${organization.trustCenter.slug}` ? `${window.location.origin}/trust/${organization.trustCenter.slug}`
: null; : null;
@@ -211,6 +266,68 @@ export default function TrustCenterPage({ queryRef }: Props) {
</div> </div>
</Card> </Card>
</div> </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"> <div className="space-y-4">
<Tabs> <Tabs>
<TabItem <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 { PublicTrustCenterAudits } from "../components/PublicTrustCenterAudits";
import { PublicTrustCenterVendors } from "../components/PublicTrustCenterVendors"; import { PublicTrustCenterVendors } from "../components/PublicTrustCenterVendors";
import { PublicTrustCenterDocuments } from "../components/PublicTrustCenterDocuments"; import { PublicTrustCenterDocuments } from "../components/PublicTrustCenterDocuments";
import { NDAAcceptanceDialog } from "../components/NDAAcceptanceDialog";
import { Spinner } from "@probo/ui"; import { Spinner } from "@probo/ui";
import { useTrustCenterQuery, type TrustCenterDocument, type TrustCenterAudit, type TrustCenterVendor } from "/hooks/useTrustCenterQueries"; import { useTrustCenterQuery, type TrustCenterDocument, type TrustCenterAudit, type TrustCenterVendor } from "/hooks/useTrustCenterQueries";
@@ -66,36 +67,49 @@ export default function PublicTrustCenterPage() {
} }
const { trustCenterBySlug } = data; 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 trustCenterDocuments = documents.edges.map((edge) => edge.node) as TrustCenterDocument[];
const trustCenterAudits = audits.edges.map((edge) => edge.node) as TrustCenterAudit[]; const trustCenterAudits = audits.edges.map((edge) => edge.node) as TrustCenterAudit[];
const trustCenterVendors = vendors.edges.map((edge) => edge.node) as TrustCenterVendor[]; const trustCenterVendors = vendors.edges.map((edge) => edge.node) as TrustCenterVendor[];
const showNdaDialog = isUserAuthenticated && !hasAcceptedNonDisclosureAgreement;
return ( return (
<PublicTrustCenterLayout <>
organizationName={organizationName} {showNdaDialog && (
organizationLogo={organization?.logoUrl} <NDAAcceptanceDialog
isAuthenticated={isUserAuthenticated}
>
<div className="space-y-12">
<PublicTrustCenterAudits
audits={trustCenterAudits}
organizationName={organizationName}
isAuthenticated={isUserAuthenticated}
trustCenterId={trustCenterBySlug.id} trustCenterId={trustCenterBySlug.id}
/>
<PublicTrustCenterDocuments
documents={trustCenterDocuments}
organizationName={organizationName} organizationName={organizationName}
isAuthenticated={isUserAuthenticated} ndaFileName={trustCenterBySlug.ndaFileName}
trustCenterId={trustCenterBySlug.id} ndaFileUrl={trustCenterBySlug.ndaFileUrl}
/> />
<PublicTrustCenterVendors )}
vendors={trustCenterVendors}
organizationName={organizationName} <PublicTrustCenterLayout
/> organizationName={organizationName}
</div> organizationLogo={organization?.logoUrl}
</PublicTrustCenterLayout> 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; className?: string;
ref?: DialogRef; ref?: DialogRef;
onClose?: () => void; onClose?: () => void;
closable?: boolean;
}; };
export const useDialogRef = (): DialogRef => { export const useDialogRef = (): DialogRef => {
@@ -64,6 +65,7 @@ export function Dialog({
ref, ref,
defaultOpen, defaultOpen,
onClose, onClose,
closable = true,
}: Props) { }: Props) {
const { overlay, content, header, title: titleClassname } = dialog(); const { overlay, content, header, title: titleClassname } = dialog();
const [open, setOpen] = useState(!!defaultOpen); const [open, setOpen] = useState(!!defaultOpen);
@@ -80,41 +82,55 @@ export function Dialog({
} }
const onOpenChange = (open: boolean) => { const onOpenChange = (open: boolean) => {
if (!open && !closable) {
return;
}
setOpen(open); setOpen(open);
if (!open) { if (!open) {
onClose?.(); onClose?.();
} }
}; };
const contentProps = closable ? {} : {
onEscapeKeyDown: (e: Event) => e.preventDefault(),
onPointerDownOutside: (e: Event) => e.preventDefault(),
onInteractOutside: (e: Event) => e.preventDefault(),
};
return ( return (
<Root open={open} onOpenChange={onOpenChange}> <Root open={open} onOpenChange={closable ? onOpenChange : undefined}>
{trigger && <Trigger asChild>{trigger}</Trigger>} {trigger && <Trigger asChild>{trigger}</Trigger>}
<Portal> <Portal>
<Overlay className={overlay()} /> <Overlay className={overlay()} />
<Content <Content
aria-describedby={undefined} aria-describedby={undefined}
className={content({ className })} className={content({ className })}
{...contentProps}
> >
{title ? ( {title ? (
<div className={header()}> <div className={header()}>
<Title className={titleClassname()}> {title}</Title> <Title className={titleClassname()}> {title}</Title>
{closable && (
<Close asChild>
<Button
tabIndex={-1}
variant="tertiary"
icon={IconCrossLargeX}
/>
</Close>
)}
</div>
) : (
closable && (
<Close asChild> <Close asChild>
<Button <Button
tabIndex={-1} tabIndex={-1}
variant="tertiary" variant="tertiary"
className="absolute top-4 right-4"
icon={IconCrossLargeX} icon={IconCrossLargeX}
/> />
</Close> </Close>
</div> )
) : (
<Close asChild>
<Button
tabIndex={-1}
variant="tertiary"
className="absolute top-4 right-4"
icon={IconCrossLargeX}
/>
</Close>
)} )}
{children} {children}
</Content> </Content>

View File

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

View File

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

View File

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

View File

@@ -17,10 +17,17 @@ package probo
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"mime"
"net/url"
"path/filepath"
"time" "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/coredata"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"go.gearno.de/crypto/uuid"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
) )
@@ -30,71 +37,100 @@ type (
} }
UpdateTrustCenterRequest struct { UpdateTrustCenterRequest struct {
ID gid.GID ID gid.GID
Active *bool Active *bool
Slug *string Slug *string
NonDisclosureAgreementFileID *gid.GID
}
UploadTrustCenterNDARequest struct {
TrustCenterID gid.GID
File io.Reader
FileName string
}
DeleteTrustCenterNDARequest struct {
TrustCenterID gid.GID
} }
) )
func (s TrustCenterService) Get( func (s TrustCenterService) Get(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
) (*coredata.TrustCenter, error) { ) (*coredata.TrustCenter, *coredata.File, error) {
trustCenter := &coredata.TrustCenter{} var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithConn( err := s.svc.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID) trustCenter = &coredata.TrustCenter{}
if err != nil { if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err) 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 return nil
}, },
) )
if err != 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( func (s TrustCenterService) GetByOrganizationID(
ctx context.Context, ctx context.Context,
organizationID gid.GID, organizationID gid.GID,
) (*coredata.TrustCenter, error) { ) (*coredata.TrustCenter, *coredata.File, error) {
trustCenter := &coredata.TrustCenter{} var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithConn( err := s.svc.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
err := trustCenter.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID) trustCenter = &coredata.TrustCenter{}
if err != nil { if err := trustCenter.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err) 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 return nil
}, },
) )
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return trustCenter, nil return trustCenter, file, nil
} }
func (s TrustCenterService) Update( func (s TrustCenterService) Update(
ctx context.Context, ctx context.Context,
req *UpdateTrustCenterRequest, req *UpdateTrustCenterRequest,
) (*coredata.TrustCenter, error) { ) (*coredata.TrustCenter, *coredata.File, error) {
trustCenter := &coredata.TrustCenter{} var trustCenter *coredata.TrustCenter
var file *coredata.File
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil { if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err) 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) 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 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 { if err != nil {
return nil, err 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! id: ID!
active: Boolean! active: Boolean!
slug: String! slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
organization: Organization! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true)
@@ -1889,6 +1891,7 @@ type TrustCenterAccess implements Node {
email: String! email: String!
name: String! name: String!
active: Boolean! active: Boolean!
hasAcceptedNonDisclosureAgreement: Boolean!
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
} }
@@ -2218,6 +2221,14 @@ type Mutation {
input: UpdateTrustCenterInput! input: UpdateTrustCenterInput!
): UpdateTrustCenterPayload! ): UpdateTrustCenterPayload!
uploadTrustCenterNDA(
input: UploadTrustCenterNDAInput!
): UploadTrustCenterNDAPayload!
deleteTrustCenterNDA(
input: DeleteTrustCenterNDAInput!
): DeleteTrustCenterNDAPayload!
# Trust Center Access CRUD mutations # Trust Center Access CRUD mutations
createTrustCenterAccess( createTrustCenterAccess(
input: CreateTrustCenterAccessInput! input: CreateTrustCenterAccessInput!
@@ -2503,6 +2514,16 @@ input UpdateTrustCenterInput {
slug: String slug: String
} }
input UploadTrustCenterNDAInput {
trustCenterId: ID!
fileName: String!
file: Upload!
}
input DeleteTrustCenterNDAInput {
trustCenterId: ID!
}
input CreateTrustCenterAccessInput { input CreateTrustCenterAccessInput {
trustCenterId: ID! trustCenterId: ID!
email: String! email: String!
@@ -3135,6 +3156,14 @@ type UpdateTrustCenterPayload {
trustCenter: TrustCenter! trustCenter: TrustCenter!
} }
type UploadTrustCenterNDAPayload {
trustCenter: TrustCenter!
}
type DeleteTrustCenterNDAPayload {
trustCenter: TrustCenter!
}
type CreateTrustCenterAccessPayload { type CreateTrustCenterAccessPayload {
trustCenterAccessEdge: TrustCenterAccessEdge! 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" "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{ return &TrustCenter{
ID: tc.ID, ID: tc.ID,
Active: tc.Active, Active: tc.Active,
Slug: tc.Slug, Slug: tc.Slug,
CreatedAt: tc.CreatedAt, NdaFileName: ndaFileName,
UpdatedAt: tc.UpdatedAt, CreatedAt: tc.CreatedAt,
UpdatedAt: tc.UpdatedAt,
} }
} }

View File

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

View File

@@ -828,6 +828,14 @@ type DeleteTrustCenterAccessPayload struct {
DeletedTrustCenterAccessID gid.GID `json:"deletedTrustCenterAccessId"` DeletedTrustCenterAccessID gid.GID `json:"deletedTrustCenterAccessId"`
} }
type DeleteTrustCenterNDAInput struct {
TrustCenterID gid.GID `json:"trustCenterId"`
}
type DeleteTrustCenterNDAPayload struct {
TrustCenter *TrustCenter `json:"trustCenter"`
}
type DeleteVendorBusinessAssociateAgreementInput struct { type DeleteVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"` VendorID gid.GID `json:"vendorId"`
} }
@@ -1436,6 +1444,8 @@ type TrustCenter struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Active bool `json:"active"` Active bool `json:"active"`
Slug string `json:"slug"` Slug string `json:"slug"`
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
Organization *Organization `json:"organization"` Organization *Organization `json:"organization"`
@@ -1446,12 +1456,13 @@ func (TrustCenter) IsNode() {}
func (this TrustCenter) GetID() gid.GID { return this.ID } func (this TrustCenter) GetID() gid.GID { return this.ID }
type TrustCenterAccess struct { type TrustCenterAccess struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Email string `json:"email"` Email string `json:"email"`
Name string `json:"name"` Name string `json:"name"`
Active bool `json:"active"` Active bool `json:"active"`
CreatedAt time.Time `json:"createdAt"` HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
UpdatedAt time.Time `json:"updatedAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
} }
func (TrustCenterAccess) IsNode() {} func (TrustCenterAccess) IsNode() {}
@@ -1831,6 +1842,16 @@ type UploadTaskEvidencePayload struct {
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"` 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 { type UploadVendorBusinessAssociateAgreementInput struct {
VendorID gid.GID `json:"vendorId"` VendorID gid.GID `json:"vendorId"`
ValidFrom *time.Time `json:"validFrom,omitempty"` 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) { func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.UpdateTrustCenterInput) (*types.UpdateTrustCenterPayload, error) {
prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) 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, ID: input.TrustCenterID,
Active: input.Active, Active: input.Active,
Slug: input.Slug, Slug: input.Slug,
@@ -1155,7 +1155,41 @@ func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.Up
} }
return &types.UpdateTrustCenterPayload{ 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 }, 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) { func (r *organizationResolver) TrustCenter(ctx context.Context, obj *types.Organization) (*types.TrustCenter, error) {
prb := r.ProboService(ctx, obj.ID.TenantID()) 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 { if err != nil {
return nil, fmt.Errorf("cannot get trust center: %w", err) 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. // 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 return types.NewSnapshot(snapshot), nil
case coredata.TrustCenterEntityType: case coredata.TrustCenterEntityType:
trustCenter, err := prb.TrustCenters.Get(ctx, id) trustCenter, file, err := prb.TrustCenters.Get(ctx, id)
if err != nil { 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: default:
} }
@@ -4462,6 +4497,18 @@ func (r *taskConnectionResolver) TotalCount(ctx context.Context, obj *types.Task
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) 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. // Organization is the resolver for the organization field.
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
prb := r.ProboService(ctx, obj.ID.TenantID()) prb := r.ProboService(ctx, obj.ID.TenantID())

View File

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

View File

@@ -20,6 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/99designs/gqlgen/graphql/handler" "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) return WithSession(usrmgrSvc, trustSvc, authCfg, trustAuthCfg, srv.ServeHTTP)
} }
// TrustService returns a trust service scoped to the given tenant func (r *Resolver) RootTrustService(ctx context.Context) *trust.TenantService {
func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *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) return r.trustCenterSvc.WithTenant(tenantID)
} }
// GetTenantService returns a tenant service for the given tenant ID func (r *Resolver) PrivateTrustService(ctx context.Context, tenantID gid.TenantID) (*trust.TenantService, error) {
func (r *Resolver) GetTenantService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService { if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, tenantID); err != nil {
return r.trustCenterSvc.WithTenant(tenantID) 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 { 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) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
ip := extractIPAddress(r)
ctx = context.WithValue(ctx, coredata.ContextKeyIPAddress, ip)
if authCtx := tryTokenAuth(ctx, w, r, trustSvc, trustAuthCfg); authCtx != nil { if authCtx := tryTokenAuth(ctx, w, r, trustSvc, trustAuthCfg); authCtx != nil {
next(w, r.WithContext(authCtx)) next(w, r.WithContext(authCtx))
return return
@@ -219,15 +229,14 @@ func tryTokenAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, t
tenantID := basicPayload.Data.TrustCenterID.TenantID() tenantID := basicPayload.Data.TrustCenterID.TenantID()
tenantSvc := trustSvc.WithTenant(tenantID) tenantSvc := trustSvc.WithTenant(tenantID)
payload, err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, cookie.Value) if err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, basicPayload.Data.TrustCenterID, basicPayload.Data.Email); err != nil {
if err != nil {
clearTokenCookie(w, trustAuthCfg) clearTokenCookie(w, trustAuthCfg)
return nil return nil
} }
tokenAccess := &auth.TokenAccessData{ tokenAccess := &auth.TokenAccessData{
TrustCenterID: payload.TrustCenterID, TrustCenterID: basicPayload.Data.TrustCenterID,
Email: payload.Email, Email: basicPayload.Data.Email,
TenantID: tenantID, TenantID: tenantID,
Scope: trustAuthCfg.Scope, 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! id: ID!
active: Boolean! active: Boolean!
slug: String! slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true) isUserAuthenticated: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents( documents(
first: Int first: Int
@@ -246,10 +249,18 @@ input ExportDocumentPDFInput {
documentId: ID! documentId: ID!
} }
input AcceptNonDisclosureAgreementInput {
trustCenterId: ID!
}
type ExportDocumentPDFPayload { type ExportDocumentPDFPayload {
data: String! data: String!
} }
type AcceptNonDisclosureAgreementPayload{
success: Boolean!
}
type Query { type Query {
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE) trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
} }
@@ -262,4 +273,8 @@ type Mutation {
exportDocumentPDF( exportDocumentPDF(
input: ExportDocumentPDFInput! input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER) ): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)
} }

View File

@@ -57,6 +57,10 @@ type DirectiveRoot struct {
} }
type ComplexityRoot struct { type ComplexityRoot struct {
AcceptNonDisclosureAgreementPayload struct {
Success func(childComplexity int) int
}
Audit struct { Audit struct {
Framework func(childComplexity int) int Framework func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
@@ -103,8 +107,9 @@ type ComplexityRoot struct {
} }
Mutation struct { Mutation struct {
CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int AcceptNonDisclosureAgreement func(childComplexity int, input types.AcceptNonDisclosureAgreementInput) int
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
} }
Organization struct { Organization struct {
@@ -131,14 +136,17 @@ type ComplexityRoot struct {
} }
TrustCenter struct { TrustCenter struct {
Active func(childComplexity int) int Active func(childComplexity int) int
Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) 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 Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
ID func(childComplexity int) int HasAcceptedNonDisclosureAgreement func(childComplexity int) int
IsUserAuthenticated func(childComplexity int) int ID func(childComplexity int) int
Organization func(childComplexity int) int IsUserAuthenticated func(childComplexity int) int
Slug func(childComplexity int) int NdaFileName func(childComplexity int) int
Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) 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 { TrustCenterAccess struct {
@@ -175,6 +183,7 @@ type AuditResolver interface {
type MutationResolver interface { type MutationResolver interface {
CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error)
} }
type OrganizationResolver interface { type OrganizationResolver interface {
LogoURL(ctx context.Context, obj *types.Organization) (*string, error) 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) DownloadURL(ctx context.Context, obj *types.Report) (*string, error)
} }
type TrustCenterResolver interface { type TrustCenterResolver interface {
NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error)
Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error)
IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, 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) 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) 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) 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 _ = ec
switch typeName + "." + field { switch typeName + "." + field {
case "AcceptNonDisclosureAgreementPayload.success":
if e.complexity.AcceptNonDisclosureAgreementPayload.Success == nil {
break
}
return e.complexity.AcceptNonDisclosureAgreementPayload.Success(childComplexity), true
case "Audit.framework": case "Audit.framework":
if e.complexity.Audit.Framework == nil { if e.complexity.Audit.Framework == nil {
break break
@@ -338,6 +356,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Framework.Name(childComplexity), true 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": case "Mutation.createTrustCenterAccess":
if e.complexity.Mutation.CreateTrustCenterAccess == nil { if e.complexity.Mutation.CreateTrustCenterAccess == nil {
break 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 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": case "TrustCenter.id":
if e.complexity.TrustCenter.ID == nil { if e.complexity.TrustCenter.ID == nil {
break break
@@ -489,6 +526,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.TrustCenter.IsUserAuthenticated(childComplexity), true 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": case "TrustCenter.organization":
if e.complexity.TrustCenter.Organization == nil { if e.complexity.TrustCenter.Organization == nil {
break break
@@ -621,6 +672,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
opCtx := graphql.GetOperationContext(ctx) opCtx := graphql.GetOperationContext(ctx)
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)} ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap( inputUnmarshalMap := graphql.BuildUnmarshalerMap(
ec.unmarshalInputAcceptNonDisclosureAgreementInput,
ec.unmarshalInputCreateTrustCenterAccessInput, ec.unmarshalInputCreateTrustCenterAccessInput,
ec.unmarshalInputExportDocumentPDFInput, ec.unmarshalInputExportDocumentPDFInput,
) )
@@ -921,8 +973,11 @@ type TrustCenter implements Node {
id: ID! id: ID!
active: Boolean! active: Boolean!
slug: String! slug: String!
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true) isUserAuthenticated: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents( documents(
first: Int first: Int
@@ -968,10 +1023,18 @@ input ExportDocumentPDFInput {
documentId: ID! documentId: ID!
} }
input AcceptNonDisclosureAgreementInput {
trustCenterId: ID!
}
type ExportDocumentPDFPayload { type ExportDocumentPDFPayload {
data: String! data: String!
} }
type AcceptNonDisclosureAgreementPayload{
success: Boolean!
}
type Query { type Query {
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE) trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
} }
@@ -984,6 +1047,10 @@ type Mutation {
exportDocumentPDF( exportDocumentPDF(
input: ExportDocumentPDFInput! input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER) ): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)
} }
`, BuiltIn: false}, `, BuiltIn: false},
} }
@@ -1021,6 +1088,29 @@ func (ec *executionContext) dir_mustBeAuthenticated_argsRole(
return zeroVal, nil 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) { func (ec *executionContext) field_Mutation_createTrustCenterAccess_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -1444,6 +1534,50 @@ func (ec *executionContext) field___Type_fields_argsIncludeDeprecated(
// region **************************** field.gotpl ***************************** // 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) { 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) fc, err := ec.fieldContext_Audit_id(ctx, field)
if err != nil { if err != nil {
@@ -2479,6 +2613,92 @@ func (ec *executionContext) fieldContext_Mutation_exportDocumentPDF(ctx context.
return fc, nil 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) { 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) fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil { if err != nil {
@@ -2847,10 +3067,16 @@ func (ec *executionContext) fieldContext_Query_trustCenterBySlug(ctx context.Con
return ec.fieldContext_TrustCenter_active(ctx, field) return ec.fieldContext_TrustCenter_active(ctx, field)
case "slug": case "slug":
return ec.fieldContext_TrustCenter_slug(ctx, field) 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": case "organization":
return ec.fieldContext_TrustCenter_organization(ctx, field) return ec.fieldContext_TrustCenter_organization(ctx, field)
case "isUserAuthenticated": case "isUserAuthenticated":
return ec.fieldContext_TrustCenter_isUserAuthenticated(ctx, field) return ec.fieldContext_TrustCenter_isUserAuthenticated(ctx, field)
case "hasAcceptedNonDisclosureAgreement":
return ec.fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field)
case "documents": case "documents":
return ec.fieldContext_TrustCenter_documents(ctx, field) return ec.fieldContext_TrustCenter_documents(ctx, field)
case "audits": case "audits":
@@ -3294,6 +3520,88 @@ func (ec *executionContext) fieldContext_TrustCenter_slug(_ context.Context, fie
return fc, nil 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) { 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) fc, err := ec.fieldContext_TrustCenter_organization(ctx, field)
if err != nil { if err != nil {
@@ -3390,6 +3698,50 @@ func (ec *executionContext) fieldContext_TrustCenter_isUserAuthenticated(_ conte
return fc, nil 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) { 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) fc, err := ec.fieldContext_TrustCenter_documents(ctx, field)
if err != nil { if err != nil {
@@ -6162,6 +6514,33 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field
// region **************************** input.gotpl ***************************** // 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) { func (ec *executionContext) unmarshalInputCreateTrustCenterAccessInput(ctx context.Context, obj any) (types.CreateTrustCenterAccessInput, error) {
var it types.CreateTrustCenterAccessInput var it types.CreateTrustCenterAccessInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -6303,6 +6682,45 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj
// region **************************** object.gotpl **************************** // 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"} var auditImplementors = []string{"Audit", "Node"}
func (ec *executionContext) _Audit(ctx context.Context, sel ast.SelectionSet, obj *types.Audit) graphql.Marshaler { 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 { if out.Values[i] == graphql.Null {
out.Invalids++ 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: default:
panic("unknown field " + strconv.Quote(field.Name)) 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 { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) 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": case "organization":
field := field field := field
@@ -7182,6 +7642,42 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
continue 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) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "documents": case "documents":
field := field field := field
@@ -7849,6 +8345,25 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl ***************************** // 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 { 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 v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { 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) return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
} }
tenantID := token.Data.TrustCenterID.TenantID() tenantSvc := trustSvc.WithTenant(token.Data.TrustCenterID.TenantID())
tenantSvc := trustSvc.WithTenant(tenantID) if err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, token.Data.TrustCenterID, token.Data.Email); err != nil {
accessData, err := tenantSvc.TrustCenterAccesses.ValidateToken(ctx, tokenString)
if err != nil {
return nil, fmt.Errorf("cannot validate trust center access token: %w", err) 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 { func trustCenterLogoutHandler(authCfg console_v1.AuthConfig, trustAuthCfg TrustAuthConfig) http.HandlerFunc {

View File

@@ -18,10 +18,16 @@ import (
"github.com/getprobo/probo/pkg/coredata" "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{ return &TrustCenter{
ID: tc.ID, ID: tc.ID,
Active: tc.Active, Active: tc.Active,
Slug: tc.Slug, Slug: tc.Slug,
NdaFileName: ndaFileName,
} }
} }

View File

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

View File

@@ -11,9 +11,7 @@ import (
"time" "time"
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page" "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/schema"
"github.com/getprobo/probo/pkg/server/api/trust/v1/types" "github.com/getprobo/probo/pkg/server/api/trust/v1/types"
"github.com/getprobo/probo/pkg/trust" "github.com/getprobo/probo/pkg/trust"
@@ -21,14 +19,14 @@ import (
// Framework is the resolver for the framework field. // Framework is the resolver for the framework field.
func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) { 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 { if err != nil {
panic(fmt.Errorf("cannot load audit: %w", err)) 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 { if err != nil {
panic(fmt.Errorf("cannot load framework: %w", err)) 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. // Report is the resolver for the report field.
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) { 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 { if err != nil {
panic(fmt.Errorf("cannot load audit: %w", err)) 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 return nil, nil
} }
report, err := trust.Reports.Get(ctx, *audit.ReportID) report, err := publicTrustService.Reports.Get(ctx, *audit.ReportID)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot load report: %w", err)) 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. // CreateTrustCenterAccess is the resolver for the createTrustCenterAccess field.
func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) { 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, TrustCenterID: input.TrustCenterID,
Email: input.Email, Email: input.Email,
Name: input.Name, Name: input.Name,
@@ -83,34 +81,72 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
// ExportDocumentPDF is the resolver for the exportDocumentPDF field. // ExportDocumentPDF is the resolver for the exportDocumentPDF field.
func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) { 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 { privateTrustService, err := r.PrivateTrustService(ctx, input.DocumentID.TenantID())
return nil, err
}
trust := r.trustCenterSvc.WithTenant(input.DocumentID.TenantID())
pdf, err := trust.Documents.ExportPDF(ctx, input.DocumentID)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot export document PDF: %w", err) 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{ return &types.ExportDocumentPDFPayload{
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)), Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
}, nil }, 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. // LogoURL is the resolver for the logoUrl field.
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { 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. // TrustCenterBySlug is the resolver for the trustCenterBySlug field.
func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) { 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 { if err != nil {
return nil, nil return nil, nil
} }
@@ -119,28 +155,48 @@ func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*ty
return nil, nil 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 := publicTrustService.Organizations.Get(ctx, trustCenter.OrganizationID)
org, err := orgTrust.Organizations.Get(ctx, trustCenter.OrganizationID)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err)) panic(fmt.Errorf("cannot get organization: %w", err))
} }
response := types.NewTrustCenter(trustCenter, file)
response.Organization = types.NewOrganization(org)
result.Organization = types.NewOrganization(org) return response, nil
return result, nil
} }
// DownloadURL is the resolver for the downloadUrl field. // DownloadURL is the resolver for the downloadUrl field.
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) { func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
if err := auth.ValidateTenantAccess(ctx, r, userTenantContextKey, obj.ID.TenantID()); err != nil { privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
return nil, err 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 { if err != nil {
panic(fmt.Errorf("cannot generate download URL: %w", err)) 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 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. // Organization is the resolver for the organization field.
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
return obj.Organization, nil 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. // IsUserAuthenticated is the resolver for the isUserAuthenticated field.
func (r *trustCenterResolver) IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, error) { 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 false, nil
} }
return true, 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. // 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) { 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]{ pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
Field: coredata.DocumentOrderFieldTitle, 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) 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 { if err != nil {
panic(fmt.Errorf("cannot list public documents: %w", err)) 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. // 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) { 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]{ pageOrderBy := page.OrderBy[coredata.AuditOrderField]{
Field: coredata.AuditOrderFieldValidFrom, 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) 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 { if err != nil {
panic(fmt.Errorf("cannot list public audits: %w", err)) 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. // 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) { 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]{ pageOrderBy := page.OrderBy[coredata.VendorOrderField]{
Field: coredata.VendorOrderFieldName, 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) 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 { if err != nil {
panic(fmt.Errorf("cannot list public vendors: %w", err)) panic(fmt.Errorf("cannot list public vendors: %w", err))
} }

View File

@@ -16,6 +16,7 @@ package trust
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/mail" "net/mail"
@@ -23,8 +24,6 @@ import (
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/statelesstoken"
"github.com/getprobo/probo/pkg/usrmgr" "github.com/getprobo/probo/pkg/usrmgr"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
) )
@@ -48,20 +47,12 @@ const (
func (s TrustCenterAccessService) ValidateToken( func (s TrustCenterAccessService) ValidateToken(
ctx context.Context, ctx context.Context,
tokenString string, trustCenterID gid.GID,
) (*probo.TrustCenterAccessData, error) { email string,
token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData]( ) error {
s.svc.tokenSecret, return s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
TokenTypeTrustCenterAccess, access := &coredata.TrustCenterAccess{}
tokenString, err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email)
)
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)
if err != nil { if err != nil {
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
@@ -72,12 +63,6 @@ func (s TrustCenterAccessService) ValidateToken(
return nil return nil
}) })
if err != nil {
return nil, err
}
return &token.Data, nil
} }
func (s TrustCenterAccessService) Create( func (s TrustCenterAccessService) Create(
@@ -115,14 +100,15 @@ func (s TrustCenterAccessService) Create(
} }
access = &coredata.TrustCenterAccess{ access = &coredata.TrustCenterAccess{
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType), ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
TenantID: s.svc.scope.GetTenantID(), TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID, TrustCenterID: req.TrustCenterID,
Email: req.Email, Email: req.Email,
Name: req.Name, Name: req.Name,
Active: false, Active: false,
CreatedAt: now, HasAcceptedNonDisclosureAgreement: false,
UpdatedAt: now, CreatedAt: now,
UpdatedAt: now,
} }
if err := access.Insert(ctx, tx, s.svc.scope); err != nil { if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
@@ -137,3 +123,48 @@ func (s TrustCenterAccessService) Create(
return access, nil 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 ( import (
"context" "context"
"fmt" "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/coredata"
"github.com/getprobo/probo/pkg/gid"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
) )
@@ -51,3 +55,88 @@ func (s TrustCenterService) GetBySlug(
return trustCenter, nil 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
}