Add trust center front
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -3,5 +3,8 @@
|
|||||||
"schema": "../../pkg/server/api/console/v1/schema.graphql",
|
"schema": "../../pkg/server/api/console/v1/schema.graphql",
|
||||||
"language": "typescript",
|
"language": "typescript",
|
||||||
"eagerEsModules": true,
|
"eagerEsModules": true,
|
||||||
"noFutureProofEnums": true
|
"noFutureProofEnums": true,
|
||||||
|
"excludes": [
|
||||||
|
"**/PublicTrustCenterGraph.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Tr,
|
||||||
|
Td,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Th,
|
||||||
|
Button,
|
||||||
|
IconArrowDown,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { sprintf } from "@probo/helpers";
|
||||||
|
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||||
|
|
||||||
|
type Audit = {
|
||||||
|
id: string;
|
||||||
|
framework: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
validFrom: string;
|
||||||
|
validUntil: string | null;
|
||||||
|
state: string;
|
||||||
|
createdAt: string;
|
||||||
|
report: {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
downloadUrl: string | null;
|
||||||
|
} | null;
|
||||||
|
reportUrl: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
audits: Audit[];
|
||||||
|
organizationName: string;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PublicTrustCenterAudits({ audits, organizationName, isAuthenticated }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
if (audits.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary mb-2">
|
||||||
|
{__("Compliance")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{__("No compliance reports are currently available.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card padded className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary">
|
||||||
|
{__("Compliance")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
|
{sprintf(__("%s is compliant with the following frameworks"), organizationName)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Framework")}</Th>
|
||||||
|
<Th>{__("Report")}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{audits.map((audit) => {
|
||||||
|
const hasReport = audit.report || audit.reportUrl;
|
||||||
|
const downloadUrl = audit.report?.downloadUrl || audit.reportUrl;
|
||||||
|
const reportName = audit.report?.filename || __("Compliance Report");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={audit.id}>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<div className="w-8 h-8 [&>img]:w-8 [&>img]:h-8 [&>div]:w-8 [&>div]:h-8">
|
||||||
|
<FrameworkLogo name={audit.framework.name} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="font-medium">
|
||||||
|
{audit.framework.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{!hasReport ? (
|
||||||
|
<span className="text-txt-tertiary text-sm">
|
||||||
|
{__("No report")}
|
||||||
|
</span>
|
||||||
|
) : !isAuthenticated ? (
|
||||||
|
<span className="text-txt-tertiary text-sm">
|
||||||
|
{__("Not available")}
|
||||||
|
</span>
|
||||||
|
) : downloadUrl ? (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={IconArrowDown}
|
||||||
|
onClick={() => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = reportName;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__("Download")}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<span className="text-txt-tertiary text-sm">
|
||||||
|
{__("Not available")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Tr,
|
||||||
|
Td,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Th,
|
||||||
|
DocumentTypeBadge,
|
||||||
|
Button,
|
||||||
|
IconArrowDown,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { useMutation } from "react-relay";
|
||||||
|
import type { PublicTrustCenterDocumentsExportPDFMutation } from "./__generated__/PublicTrustCenterDocumentsExportPDFMutation.graphql";
|
||||||
|
|
||||||
|
const exportDocumentVersionPDFMutation = graphql`
|
||||||
|
mutation PublicTrustCenterDocumentsExportPDFMutation(
|
||||||
|
$input: ExportDocumentVersionPDFInput!
|
||||||
|
) {
|
||||||
|
exportDocumentVersionPDF(input: $input) {
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Document = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
documentType: string;
|
||||||
|
versions: {
|
||||||
|
edges: Array<{
|
||||||
|
node: {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
documents: Document[];
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PublicTrustCenterDocuments({ documents, isAuthenticated }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [exportDocumentVersionPDF] = useMutation<PublicTrustCenterDocumentsExportPDFMutation>(exportDocumentVersionPDFMutation);
|
||||||
|
|
||||||
|
const handleDownload = (document: Document) => {
|
||||||
|
const latestVersion = document.versions.edges[0]?.node;
|
||||||
|
if (!latestVersion) return;
|
||||||
|
|
||||||
|
exportDocumentVersionPDF({
|
||||||
|
variables: {
|
||||||
|
input: { documentVersionId: latestVersion.id },
|
||||||
|
},
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (data.exportDocumentVersionPDF?.data) {
|
||||||
|
const link = window.document.createElement("a");
|
||||||
|
link.href = data.exportDocumentVersionPDF.data;
|
||||||
|
link.download = `${document.title}.pdf`;
|
||||||
|
window.document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
window.document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (documents.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary mb-2">
|
||||||
|
{__("Documents")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{__("No documents are currently available.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card padded className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary">
|
||||||
|
{__("Documents")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
|
{__("Security and compliance documentation")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="w-1/2">{__("Document")}</Th>
|
||||||
|
<Th className="w-1/4">{__("Type")}</Th>
|
||||||
|
<Th className="w-1/4">{__("Download")}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{documents.map((document) => {
|
||||||
|
const latestVersion = document.versions.edges[0]?.node;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={document.id}>
|
||||||
|
<Td>
|
||||||
|
<div className="font-medium">
|
||||||
|
{document.title}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<DocumentTypeBadge type={document.documentType} />
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{!latestVersion ? (
|
||||||
|
<span className="text-txt-tertiary text-sm">
|
||||||
|
{__("No version available")}
|
||||||
|
</span>
|
||||||
|
) : !isAuthenticated ? (
|
||||||
|
<span className="text-txt-tertiary text-sm">
|
||||||
|
{__("Not available")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
icon={IconArrowDown}
|
||||||
|
onClick={() => handleDownload(document)}
|
||||||
|
>
|
||||||
|
{__("Download")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Tr,
|
||||||
|
Td,
|
||||||
|
Table,
|
||||||
|
Thead,
|
||||||
|
Tbody,
|
||||||
|
Th,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { faviconUrl, sprintf } from "@probo/helpers";
|
||||||
|
|
||||||
|
type Vendor = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
description: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
privacyPolicyUrl?: string | null;
|
||||||
|
websiteUrl?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
vendors: Vendor[];
|
||||||
|
organizationName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PublicTrustCenterVendors({ vendors, organizationName }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
if (vendors.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary mb-2">
|
||||||
|
{__("Vendors")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{__("No vendor information is currently available.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card padded className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold text-txt-primary">
|
||||||
|
{__("Vendors")}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
|
{sprintf(__("Third-party vendors and service providers %s work with"), organizationName)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Company")}</Th>
|
||||||
|
<Th>{__("Website")}</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{vendors.map((vendor) => {
|
||||||
|
const url = vendor.privacyPolicyUrl || vendor.websiteUrl;
|
||||||
|
const logo = faviconUrl(vendor.websiteUrl);
|
||||||
|
|
||||||
|
const getCleanUrl = (url: string) => {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(url);
|
||||||
|
return parsedUrl.hostname + parsedUrl.pathname + parsedUrl.search;
|
||||||
|
} catch {
|
||||||
|
return url.replace(/^https?:\/\//, '');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={vendor.id}>
|
||||||
|
<Td>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
{logo && (
|
||||||
|
<img
|
||||||
|
src={logo}
|
||||||
|
alt={`${vendor.name} logo`}
|
||||||
|
className="w-8 h-8 object-contain rounded-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="font-medium">
|
||||||
|
{vendor.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{url ? (
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-600 hover:text-blue-800 underline"
|
||||||
|
>
|
||||||
|
{getCleanUrl(url)}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-txt-secondary text-sm">
|
||||||
|
{__("No website available")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<24fd0dcf15c98ad3d20762e4ccc83a1b>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ExportDocumentVersionPDFInput = {
|
||||||
|
documentVersionId: string;
|
||||||
|
};
|
||||||
|
export type PublicTrustCenterDocumentsExportPDFMutation$variables = {
|
||||||
|
input: ExportDocumentVersionPDFInput;
|
||||||
|
};
|
||||||
|
export type PublicTrustCenterDocumentsExportPDFMutation$data = {
|
||||||
|
readonly exportDocumentVersionPDF: {
|
||||||
|
readonly data: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type PublicTrustCenterDocumentsExportPDFMutation = {
|
||||||
|
response: PublicTrustCenterDocumentsExportPDFMutation$data;
|
||||||
|
variables: PublicTrustCenterDocumentsExportPDFMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "ExportDocumentVersionPDFPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "exportDocumentVersionPDF",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "data",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "b5d33d4c301cfa811bb74d52f61f52e8",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "PublicTrustCenterDocumentsExportPDFMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation PublicTrustCenterDocumentsExportPDFMutation(\n $input: ExportDocumentVersionPDFInput!\n) {\n exportDocumentVersionPDF(input: $input) {\n data\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "b6a173895aea2450ad4ac1a4ec6aeb4e";
|
||||||
|
|
||||||
|
export default node;
|
||||||
64
apps/console/src/hooks/graph/PublicTrustCenterGraph.ts
Normal file
64
apps/console/src/hooks/graph/PublicTrustCenterGraph.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Manual query definition for trust API (not processed by relay compiler)
|
||||||
|
export const publicTrustCenterQuery = {
|
||||||
|
params: {
|
||||||
|
name: "PublicTrustCenterGraphQuery",
|
||||||
|
operationKind: "query",
|
||||||
|
text: `
|
||||||
|
query PublicTrustCenterGraphQuery($slug: String!) {
|
||||||
|
trustCenterBySlug(slug: $slug) {
|
||||||
|
id
|
||||||
|
active
|
||||||
|
slug
|
||||||
|
organization {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
logoUrl
|
||||||
|
}
|
||||||
|
documents(first: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
documentType
|
||||||
|
versions(first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
audits(first: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
framework {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
report {
|
||||||
|
id
|
||||||
|
filename
|
||||||
|
downloadUrl
|
||||||
|
}
|
||||||
|
reportUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vendors(first: 100) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
category
|
||||||
|
websiteUrl
|
||||||
|
privacyPolicyUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
95
apps/console/src/hooks/graph/TrustCenterAccessGraph.ts
Normal file
95
apps/console/src/hooks/graph/TrustCenterAccessGraph.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { graphql } from 'react-relay';
|
||||||
|
import { useLazyLoadQuery } from 'react-relay';
|
||||||
|
|
||||||
|
export const trustCenterAccessesQuery = graphql`
|
||||||
|
query TrustCenterAccessGraphQuery($trustCenterId: ID!) {
|
||||||
|
node(id: $trustCenterId) {
|
||||||
|
... on TrustCenter {
|
||||||
|
id
|
||||||
|
accesses(first: 100, orderBy: { field: CREATED_AT, direction: DESC })
|
||||||
|
@connection(key: "TrustCenterAccessTab_accesses") {
|
||||||
|
__id
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
|
startCursor
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
cursor
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
active
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const revokeTrustCenterAccessMutation = graphql`
|
||||||
|
mutation TrustCenterAccessGraphRevokeMutation($input: RevokeTrustCenterAccessInput!) {
|
||||||
|
revokeTrustCenterAccess(input: $input) {
|
||||||
|
trustCenterAccess {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
active
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const createTrustCenterAccessMutation = graphql`
|
||||||
|
mutation TrustCenterAccessGraphCreateMutation(
|
||||||
|
$input: CreateTrustCenterAccessInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
createTrustCenterAccess(input: $input) {
|
||||||
|
trustCenterAccessEdge @prependEdge(connections: $connections) {
|
||||||
|
cursor
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
active
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const updateTrustCenterAccessMutation = graphql`
|
||||||
|
mutation TrustCenterAccessGraphUpdateMutation($input: UpdateTrustCenterAccessInput!) {
|
||||||
|
updateTrustCenterAccess(input: $input) {
|
||||||
|
trustCenterAccess {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
name
|
||||||
|
active
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const deleteTrustCenterAccessMutation = graphql`
|
||||||
|
mutation TrustCenterAccessGraphDeleteMutation(
|
||||||
|
$input: DeleteTrustCenterAccessInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
deleteTrustCenterAccess(input: $input) {
|
||||||
|
deletedTrustCenterAccessId @deleteEdge(connections: $connections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function useTrustCenterAccesses(trustCenterId: string) {
|
||||||
|
return useLazyLoadQuery(trustCenterAccessesQuery, { trustCenterId });
|
||||||
|
}
|
||||||
17
apps/console/src/hooks/graph/TrustCenterAccessTokenGraph.ts
Normal file
17
apps/console/src/hooks/graph/TrustCenterAccessTokenGraph.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { graphql } from 'react-relay';
|
||||||
|
|
||||||
|
export const trustCenterByIdQuery = graphql`
|
||||||
|
query TrustCenterAccessTokenGraphQuery($trustCenterId: ID!) {
|
||||||
|
node(id: $trustCenterId) {
|
||||||
|
... on TrustCenter {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
active
|
||||||
|
organization {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
201
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts
generated
Normal file
201
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<79b53f51663ada6e9899523400d54996>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type CreateTrustCenterAccessInput = {
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
sendEmail?: boolean;
|
||||||
|
trustCenterId: string;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphCreateMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: CreateTrustCenterAccessInput;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphCreateMutation$data = {
|
||||||
|
readonly createTrustCenterAccess: {
|
||||||
|
readonly trustCenterAccessEdge: {
|
||||||
|
readonly cursor: any;
|
||||||
|
readonly node: {
|
||||||
|
readonly active: boolean;
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly email: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphCreateMutation = {
|
||||||
|
response: TrustCenterAccessGraphCreateMutation$data;
|
||||||
|
variables: TrustCenterAccessGraphCreateMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "connections"
|
||||||
|
},
|
||||||
|
v1 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
},
|
||||||
|
v2 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v3 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccessEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "trustCenterAccessEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "cursor",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccess",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "email",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "active",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessGraphCreateMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessGraphCreateMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "prependEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "trustCenterAccessEdge",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "eafddcd0263963235d3249c22eb50593",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "TrustCenterAccessGraphCreateMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation TrustCenterAccessGraphCreateMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccessEdge {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n }\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "99676fee0b2de06a92cdad66c577eee7";
|
||||||
|
|
||||||
|
export default node;
|
||||||
132
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphDeleteMutation.graphql.ts
generated
Normal file
132
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphDeleteMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<509cd7c9db3cbf15931c386b13a13987>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type DeleteTrustCenterAccessInput = {
|
||||||
|
accessId: string;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphDeleteMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: DeleteTrustCenterAccessInput;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphDeleteMutation$data = {
|
||||||
|
readonly deleteTrustCenterAccess: {
|
||||||
|
readonly deletedTrustCenterAccessId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphDeleteMutation = {
|
||||||
|
response: TrustCenterAccessGraphDeleteMutation$data;
|
||||||
|
variables: TrustCenterAccessGraphDeleteMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "connections"
|
||||||
|
},
|
||||||
|
v1 = {
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
},
|
||||||
|
v2 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v3 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "deletedTrustCenterAccessId",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessGraphDeleteMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "DeleteTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "deleteTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessGraphDeleteMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "DeleteTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "deleteTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "deleteEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "ScalarHandle",
|
||||||
|
"name": "deletedTrustCenterAccessId",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "3f55c9ce5cac2874b769ec994977367a",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "TrustCenterAccessGraphDeleteMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation TrustCenterAccessGraphDeleteMutation(\n $input: DeleteTrustCenterAccessInput!\n) {\n deleteTrustCenterAccess(input: $input) {\n deletedTrustCenterAccessId\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "0d0a44eb6db912eeb533d24718c49b35";
|
||||||
|
|
||||||
|
export default node;
|
||||||
317
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts
generated
Normal file
317
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<2333a6a7d5415f1a08a5612dcaceee8f>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type TrustCenterAccessGraphQuery$variables = {
|
||||||
|
trustCenterId: string;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphQuery$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly accesses?: {
|
||||||
|
readonly __id: string;
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly cursor: any;
|
||||||
|
readonly node: {
|
||||||
|
readonly active: boolean;
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly email: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
readonly pageInfo: {
|
||||||
|
readonly endCursor: any | null | undefined;
|
||||||
|
readonly hasNextPage: boolean;
|
||||||
|
readonly hasPreviousPage: boolean;
|
||||||
|
readonly startCursor: any | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
readonly id?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphQuery = {
|
||||||
|
response: TrustCenterAccessGraphQuery$data;
|
||||||
|
variables: TrustCenterAccessGraphQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "trustCenterId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "trustCenterId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "orderBy",
|
||||||
|
"value": {
|
||||||
|
"direction": "DESC",
|
||||||
|
"field": "CREATED_AT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
v4 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v5 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "PageInfo",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "pageInfo",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "hasNextPage",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "hasPreviousPage",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "startCursor",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "endCursor",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccessEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "cursor",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccess",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "email",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "active",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v4/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "ClientExtension",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v6 = [
|
||||||
|
{
|
||||||
|
"kind": "Literal",
|
||||||
|
"name": "first",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
(v3/*: any*/)
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessGraphQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": "accesses",
|
||||||
|
"args": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"concreteType": "TrustCenterAccessConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "__TrustCenterAccessTab_accesses_connection",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v5/*: any*/),
|
||||||
|
"storageKey": "__TrustCenterAccessTab_accesses_connection(orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "TrustCenter",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessGraphQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v6/*: any*/),
|
||||||
|
"concreteType": "TrustCenterAccessConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "accesses",
|
||||||
|
"plural": false,
|
||||||
|
"selections": (v5/*: any*/),
|
||||||
|
"storageKey": "accesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v6/*: any*/),
|
||||||
|
"filters": [
|
||||||
|
"orderBy"
|
||||||
|
],
|
||||||
|
"handle": "connection",
|
||||||
|
"key": "TrustCenterAccessTab_accesses",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "accesses"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "TrustCenter",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "5b83cd4ae2434ce2e00de7230d264432",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {
|
||||||
|
"connection": [
|
||||||
|
{
|
||||||
|
"count": null,
|
||||||
|
"cursor": null,
|
||||||
|
"direction": "forward",
|
||||||
|
"path": [
|
||||||
|
"node",
|
||||||
|
"accesses"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": "TrustCenterAccessGraphQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n accesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "af598fd2af198e63ed84fd618857a985";
|
||||||
|
|
||||||
|
export default node;
|
||||||
137
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphRevokeMutation.graphql.ts
generated
Normal file
137
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphRevokeMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<a234c36ae13e75cd1b0d893c4e232fc4>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type RevokeTrustCenterAccessInput = {
|
||||||
|
accessId: string;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphRevokeMutation$variables = {
|
||||||
|
input: RevokeTrustCenterAccessInput;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphRevokeMutation$data = {
|
||||||
|
readonly revokeTrustCenterAccess: {
|
||||||
|
readonly trustCenterAccess: {
|
||||||
|
readonly active: boolean;
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly email: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphRevokeMutation = {
|
||||||
|
response: TrustCenterAccessGraphRevokeMutation$data;
|
||||||
|
variables: TrustCenterAccessGraphRevokeMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "RevokeTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "revokeTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccess",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "trustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "email",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "active",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessGraphRevokeMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessGraphRevokeMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "b26927a2b8d02eb2e3754850b0a44d8b",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "TrustCenterAccessGraphRevokeMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation TrustCenterAccessGraphRevokeMutation(\n $input: RevokeTrustCenterAccessInput!\n) {\n revokeTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n createdAt\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "269cce2fe60fac04d807f1004ef0777f";
|
||||||
|
|
||||||
|
export default node;
|
||||||
141
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts
generated
Normal file
141
apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<54ac79c4d292eb19f73bbf2363015dd9>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type UpdateTrustCenterAccessInput = {
|
||||||
|
accessId: string;
|
||||||
|
active?: boolean | null | undefined;
|
||||||
|
email?: string | null | undefined;
|
||||||
|
name?: string | null | undefined;
|
||||||
|
sendEmail?: boolean;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphUpdateMutation$variables = {
|
||||||
|
input: UpdateTrustCenterAccessInput;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphUpdateMutation$data = {
|
||||||
|
readonly updateTrustCenterAccess: {
|
||||||
|
readonly trustCenterAccess: {
|
||||||
|
readonly active: boolean;
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly email: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessGraphUpdateMutation = {
|
||||||
|
response: TrustCenterAccessGraphUpdateMutation$data;
|
||||||
|
variables: TrustCenterAccessGraphUpdateMutation$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "input",
|
||||||
|
"variableName": "input"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "UpdateTrustCenterAccessPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "updateTrustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenterAccess",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "trustCenterAccess",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "email",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "active",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessGraphUpdateMutation",
|
||||||
|
"selections": (v1/*: any*/),
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessGraphUpdateMutation",
|
||||||
|
"selections": (v1/*: any*/)
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "8e284c8129de02d689385cae38cc3876",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "TrustCenterAccessGraphUpdateMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation TrustCenterAccessGraphUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n createdAt\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "235754d40be56785bd0e7a36ac8c4ec2";
|
||||||
|
|
||||||
|
export default node;
|
||||||
169
apps/console/src/hooks/graph/__generated__/TrustCenterAccessTokenGraphQuery.graphql.ts
generated
Normal file
169
apps/console/src/hooks/graph/__generated__/TrustCenterAccessTokenGraphQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<6e43a07222fde7acc3d588bac24a8077>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type TrustCenterAccessTokenGraphQuery$variables = {
|
||||||
|
trustCenterId: string;
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessTokenGraphQuery$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly active?: boolean;
|
||||||
|
readonly id?: string;
|
||||||
|
readonly organization?: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
};
|
||||||
|
readonly slug?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type TrustCenterAccessTokenGraphQuery = {
|
||||||
|
response: TrustCenterAccessTokenGraphQuery$data;
|
||||||
|
variables: TrustCenterAccessTokenGraphQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "trustCenterId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "trustCenterId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "slug",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v4 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "active",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v5 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Organization",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "organization",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "TrustCenterAccessTokenGraphQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
|
],
|
||||||
|
"type": "TrustCenter",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "TrustCenterAccessTokenGraphQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
|
],
|
||||||
|
"type": "TrustCenter",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "7d3d891944391d31a722364f8b1f1cbb",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "TrustCenterAccessTokenGraphQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query TrustCenterAccessTokenGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n slug\n active\n organization {\n id\n name\n }\n }\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "e273d4e8c8b756a5aba5271520df8602";
|
||||||
|
|
||||||
|
export default node;
|
||||||
87
apps/console/src/layouts/PublicTrustCenterLayout.tsx
Normal file
87
apps/console/src/layouts/PublicTrustCenterLayout.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { Outlet } from "react-router";
|
||||||
|
import { Logo, Button, IconArrowBoxLeft } from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
organizationName: string;
|
||||||
|
organizationLogo?: string | null;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PublicTrustCenterLayout({ organizationName, organizationLogo, children }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await fetch(buildEndpoint('/api/trust/v1/trust-center-access/logout'), {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout failed:', error);
|
||||||
|
} finally {
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-tertiary">
|
||||||
|
<header className="bg-surface border-b border-border-solid">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex items-center justify-between h-16">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
{organizationLogo ? (
|
||||||
|
<img
|
||||||
|
src={organizationLogo}
|
||||||
|
alt={organizationName}
|
||||||
|
className="h-8 w-8 rounded"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Logo className="h-8 w-8" />
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<h1 className="text-lg font-semibold text-txt-primary">
|
||||||
|
{organizationName}
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-txt-secondary">Trust Center</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<div className="text-sm text-txt-tertiary">
|
||||||
|
<a
|
||||||
|
href="https://www.getprobo.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:text-txt-secondary transition-colors flex items-center space-x-1"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/favicons/favicon.ico"
|
||||||
|
alt="Probo"
|
||||||
|
className="h-4 w-4"
|
||||||
|
/>
|
||||||
|
<span>Powered by Probo</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
icon={IconArrowBoxLeft}
|
||||||
|
onClick={handleLogout}
|
||||||
|
title={__("Logout")}
|
||||||
|
className="text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
{children || <Outlet />}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
218
apps/console/src/pages/PublicTrustCenterPage.tsx
Normal file
218
apps/console/src/pages/PublicTrustCenterPage.tsx
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import { useParams, Navigate } from "react-router";
|
||||||
|
import { usePageTitle } from "@probo/hooks";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { sprintf } from "@probo/helpers";
|
||||||
|
import { publicTrustCenterQuery } from "/hooks/graph/PublicTrustCenterGraph";
|
||||||
|
import { PublicTrustCenterLayout } from "/layouts/PublicTrustCenterLayout";
|
||||||
|
import { PublicTrustCenterAudits } from "../components/trustCenter/PublicTrustCenterAudits";
|
||||||
|
import { PublicTrustCenterVendors } from "../components/trustCenter/PublicTrustCenterVendors";
|
||||||
|
import { PublicTrustCenterDocuments } from "../components/trustCenter/PublicTrustCenterDocuments";
|
||||||
|
import { PageError } from "/components/PageError";
|
||||||
|
import { TrustRelayProvider } from "/providers/TrustRelayProvider";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
|
|
||||||
|
interface GraphQLError {
|
||||||
|
message: string;
|
||||||
|
path?: string[];
|
||||||
|
locations?: Array<{ line: number; column: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphQLResponse<T> {
|
||||||
|
data?: T;
|
||||||
|
errors?: GraphQLError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Organization {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
logoUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Framework {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentVersion {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentVersionConnection {
|
||||||
|
edges: Array<{
|
||||||
|
node: DocumentVersion;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Document {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
documentType: string;
|
||||||
|
versions: DocumentVersionConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Audit {
|
||||||
|
id: string;
|
||||||
|
framework: Framework;
|
||||||
|
validFrom: string;
|
||||||
|
validUntil: string | null;
|
||||||
|
state: string;
|
||||||
|
createdAt: string;
|
||||||
|
report: {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
downloadUrl: string | null;
|
||||||
|
} | null;
|
||||||
|
reportUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Vendor {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
description: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
websiteUrl?: string | null;
|
||||||
|
privacyPolicyUrl?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Connection<T> {
|
||||||
|
edges: Array<{
|
||||||
|
node: T;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrustCenter {
|
||||||
|
id: string;
|
||||||
|
active: boolean;
|
||||||
|
slug: string;
|
||||||
|
organization: Organization;
|
||||||
|
documents: Connection<Document>;
|
||||||
|
audits: Connection<Audit>;
|
||||||
|
vendors: Connection<Vendor>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PublicTrustCenterData {
|
||||||
|
trustCenterBySlug?: TrustCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PublicTrustCenterContent() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { slug } = useParams<{ slug: string }>();
|
||||||
|
const [data, setData] = useState<PublicTrustCenterData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
const organizationName = data?.trustCenterBySlug?.organization?.name;
|
||||||
|
usePageTitle(organizationName ? `${organizationName} - Trust Center` : "Trust Center");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!slug) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
fetch(buildEndpoint("/api/trust/v1/graphql"), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify({
|
||||||
|
operationName: publicTrustCenterQuery.params.name,
|
||||||
|
query: publicTrustCenterQuery.params.text,
|
||||||
|
variables: { slug },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((result: GraphQLResponse<PublicTrustCenterData>) => {
|
||||||
|
if (result.errors && result.errors.some((error: GraphQLError) =>
|
||||||
|
!error.message.includes('access denied: authentication required')
|
||||||
|
)) {
|
||||||
|
throw new Error(result.errors[0].message);
|
||||||
|
}
|
||||||
|
setData(result.data || null);
|
||||||
|
})
|
||||||
|
.catch(setError)
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [slug]);
|
||||||
|
|
||||||
|
if (!slug) {
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <PageError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { trustCenterBySlug } = data || {};
|
||||||
|
|
||||||
|
if (!trustCenterBySlug) {
|
||||||
|
return <PageError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trustCenterBySlug.active) {
|
||||||
|
return <PageError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { organization } = trustCenterBySlug;
|
||||||
|
|
||||||
|
const documents = trustCenterBySlug.documents.edges
|
||||||
|
.map(edge => edge.node);
|
||||||
|
|
||||||
|
const audits = trustCenterBySlug.audits.edges
|
||||||
|
.map(edge => edge.node);
|
||||||
|
|
||||||
|
const vendors = trustCenterBySlug.vendors.edges
|
||||||
|
.map(edge => edge.node);
|
||||||
|
|
||||||
|
const isAuthenticated = audits.some((audit: Audit) =>
|
||||||
|
audit.report?.downloadUrl || audit.reportUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PublicTrustCenterLayout
|
||||||
|
organizationName={organization.name}
|
||||||
|
organizationLogo={organization.logoUrl}
|
||||||
|
>
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-3xl font-bold text-txt-primary mb-4">
|
||||||
|
{sprintf(__("%s Trust Center"), organization.name)}
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-txt-secondary max-w-2xl mx-auto">
|
||||||
|
{__("Explore our security practices, compliance certifications, and transparency reports.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<PublicTrustCenterAudits
|
||||||
|
audits={audits}
|
||||||
|
organizationName={organization.name}
|
||||||
|
isAuthenticated={isAuthenticated}
|
||||||
|
/>
|
||||||
|
<PublicTrustCenterDocuments
|
||||||
|
documents={documents}
|
||||||
|
isAuthenticated={isAuthenticated}
|
||||||
|
/>
|
||||||
|
<PublicTrustCenterVendors
|
||||||
|
vendors={vendors}
|
||||||
|
organizationName={organization.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PublicTrustCenterLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PublicTrustCenterPage() {
|
||||||
|
return (
|
||||||
|
<TrustRelayProvider>
|
||||||
|
<PublicTrustCenterContent />
|
||||||
|
</TrustRelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
130
apps/console/src/pages/TrustCenterAccessPage.tsx
Normal file
130
apps/console/src/pages/TrustCenterAccessPage.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useParams, useNavigate, useSearchParams } from "react-router";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||||
|
import { PageError } from "/components/PageError";
|
||||||
|
import { buildEndpoint } from "/providers/RelayProviders";
|
||||||
|
import { IconClock, IconWarning } from "@probo/ui";
|
||||||
|
|
||||||
|
function TokenErrorPage({ error }: { error: string }) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const isExpiredToken = error.toLowerCase().includes('expired');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-level-0 flex items-center justify-center p-4">
|
||||||
|
<div className="max-w-md w-full text-center space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{isExpiredToken ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-amber-100 rounded-full">
|
||||||
|
<IconClock size={32} className="text-amber-600" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-semibold text-txt-primary">
|
||||||
|
{__("Access Link Expired")}
|
||||||
|
</h1>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{__("This access link has expired. Trust center access links are valid for 7 days for security reasons.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-red-100 rounded-full">
|
||||||
|
<IconWarning size={32} className="text-red-600" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-semibold text-txt-primary">
|
||||||
|
{__("Invalid Access Link")}
|
||||||
|
</h1>
|
||||||
|
<p className="text-txt-secondary">
|
||||||
|
{__("This access link is not valid. It may have been revoked or the link might be incorrect.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-level-1 border border-border-low rounded-lg p-4 space-y-3">
|
||||||
|
<h3 className="font-medium text-txt-primary">{__("What can you do?")}</h3>
|
||||||
|
<ul className="text-sm text-txt-secondary space-y-2 text-left">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-primary-600 mt-1">•</span>
|
||||||
|
<span>{__("Contact the person who sent you this link to request a new access invitation")}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-primary-600 mt-1">•</span>
|
||||||
|
<span>{__("Check if you received a newer email with an updated access link")}</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="text-primary-600 mt-1">•</span>
|
||||||
|
<span>{__("Verify that you copied the entire link correctly from the email")}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TrustCenterAccessPage() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { slug } = useParams<{ slug: string }>();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const token = searchParams.get('token');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!slug) {
|
||||||
|
setError(__("Invalid trust center"));
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
setError(__("Invalid or missing access token"));
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(buildEndpoint('/api/trust/v1/trust-center-access/authenticate'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify({ token }),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
navigate(`/trust/${slug}`);
|
||||||
|
} else {
|
||||||
|
setError(data.message || __("Authentication failed"));
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setError(__("Authentication failed"));
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, [slug, token, __, navigate]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <PageSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const isTokenError = error.toLowerCase().includes('token') ||
|
||||||
|
error.toLowerCase().includes('expired') ||
|
||||||
|
error.toLowerCase().includes('invalid');
|
||||||
|
|
||||||
|
if (isTokenError) {
|
||||||
|
return <TokenErrorPage error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PageError error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>{__("Redirecting to trust center...")}</div>;
|
||||||
|
}
|
||||||
@@ -212,8 +212,7 @@ export default function TrustCenterPage({ queryRef }: Props) {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-base font-medium">{__("Content")}</h2>
|
<Tabs>
|
||||||
<Tabs>
|
|
||||||
<TabItem
|
<TabItem
|
||||||
asChild
|
asChild
|
||||||
active={
|
active={
|
||||||
@@ -231,6 +230,9 @@ export default function TrustCenterPage({ queryRef }: Props) {
|
|||||||
<TabLink to={`/organizations/${organizationId}/trust-center/documents`}>
|
<TabLink to={`/organizations/${organizationId}/trust-center/documents`}>
|
||||||
{__("Documents")}
|
{__("Documents")}
|
||||||
</TabLink>
|
</TabLink>
|
||||||
|
<TabLink to={`/organizations/${organizationId}/trust-center/access`}>
|
||||||
|
{__("Access")}
|
||||||
|
</TabLink>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Outlet context={{ organization }} />
|
<Outlet context={{ organization }} />
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
import { Badge, Button, Card, Dialog, DialogContent, DialogFooter, Field, Input, Spinner, Table, Tbody, Td, Th, Thead, Tr, useDialogRef, useToast, IconCheckmark1, IconCrossLargeX, IconTrashCan } from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useOutletContext } from "react-router";
|
||||||
|
import { useState, useCallback } from "react";
|
||||||
|
import {
|
||||||
|
useTrustCenterAccesses,
|
||||||
|
createTrustCenterAccessMutation,
|
||||||
|
updateTrustCenterAccessMutation,
|
||||||
|
deleteTrustCenterAccessMutation
|
||||||
|
} from "/hooks/graph/TrustCenterAccessGraph";
|
||||||
|
import { useMutation } from "react-relay";
|
||||||
|
import type { TrustCenterAccessGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql";
|
||||||
|
|
||||||
|
type ContextType = {
|
||||||
|
organization: {
|
||||||
|
id: string;
|
||||||
|
trustCenter?: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TrustCenterAccessTab() {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { organization } = useOutletContext<ContextType>();
|
||||||
|
|
||||||
|
const [createInvitation, isCreating] = useMutation(createTrustCenterAccessMutation);
|
||||||
|
const [updateInvitation, isUpdating] = useMutation(updateTrustCenterAccessMutation);
|
||||||
|
const [deleteInvitation, isDeleting] = useMutation(deleteTrustCenterAccessMutation);
|
||||||
|
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
|
type AccessType = {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = useTrustCenterAccesses(organization.trustCenter?.id || "");
|
||||||
|
|
||||||
|
if (!organization.trustCenter?.id) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-base font-medium">{__("External Access")}</h3>
|
||||||
|
<p className="text-sm text-txt-tertiary">
|
||||||
|
{__("Manage who can access your trust center with time-limited tokens")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Card padded>
|
||||||
|
<div className="text-center text-txt-tertiary py-8">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trustCenterData = data as TrustCenterAccessGraphQuery$data | null;
|
||||||
|
const accesses: AccessType[] = trustCenterData?.node?.accesses?.edges ?
|
||||||
|
trustCenterData.node.accesses.edges.map(edge => ({
|
||||||
|
id: edge.node.id,
|
||||||
|
email: edge.node.email,
|
||||||
|
name: edge.node.name,
|
||||||
|
active: edge.node.active,
|
||||||
|
createdAt: new Date(edge.node.createdAt)
|
||||||
|
})) : [];
|
||||||
|
|
||||||
|
const handleInvite = useCallback(async () => {
|
||||||
|
if (!organization.trustCenter?.id) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Trust center not found"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!email.trim() || !name.trim()) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Email and name are required"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionId = trustCenterData?.node?.accesses?.__id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
createInvitation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
trustCenterId: organization.trustCenter.id,
|
||||||
|
email: email.trim(),
|
||||||
|
name: name.trim(),
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
connections: connectionId ? [connectionId] : [],
|
||||||
|
},
|
||||||
|
onCompleted: (_, errors) => {
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: errors[0]?.message || __("Failed to send invitation"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogRef.current) {
|
||||||
|
dialogRef.current.close();
|
||||||
|
}
|
||||||
|
setEmail("");
|
||||||
|
setName("");
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("Access invitation sent successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: error.message || __("Failed to send invitation. Please try again."),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("An unexpected error occurred. Please try again."),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [organization.trustCenter?.id, email, name, trustCenterData, createInvitation, toast, __, dialogRef]);
|
||||||
|
|
||||||
|
const handleRevoke = useCallback(async (accessId: string) => {
|
||||||
|
updateInvitation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
accessId,
|
||||||
|
active: false,
|
||||||
|
sendEmail: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("Access revoked successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: error.message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [updateInvitation, toast, __]);
|
||||||
|
|
||||||
|
const handleReinvite = useCallback(async (access: AccessType) => {
|
||||||
|
if (!organization.trustCenter?.id) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("Trust center not found"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateInvitation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
accessId: access.id,
|
||||||
|
active: true,
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCompleted: (_, errors) => {
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: errors[0]?.message || __("Failed to send reinvitation"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("Reinvitation sent successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: error.message || __("Failed to send reinvitation. Please try again."),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: __("An unexpected error occurred. Please try again."),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [organization.trustCenter?.id, updateInvitation, toast, __]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(async (accessId: string) => {
|
||||||
|
const connectionId = trustCenterData?.node?.accesses?.__id;
|
||||||
|
|
||||||
|
deleteInvitation({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
accessId,
|
||||||
|
},
|
||||||
|
connections: connectionId ? [connectionId] : [],
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: __("Access deleted successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: error.message,
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [deleteInvitation, toast, __, trustCenterData]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-base font-medium">{__("External Access")}</h3>
|
||||||
|
<p className="text-sm text-txt-tertiary">
|
||||||
|
{__("Manage who can access your trust center with time-limited tokens")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={() => dialogRef.current?.open()}>
|
||||||
|
{__("Invite")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card padded>
|
||||||
|
{accesses.length === 0 ? (
|
||||||
|
<div className="text-center text-txt-tertiary py-8">
|
||||||
|
{__("No external access granted yet")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Table>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Name")}</Th>
|
||||||
|
<Th>{__("Email")}</Th>
|
||||||
|
<Th>{__("Status")}</Th>
|
||||||
|
<Th>{__("Date")}</Th>
|
||||||
|
<Th></Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{accesses.map((access) => (
|
||||||
|
<Tr key={access.id}>
|
||||||
|
<Td className="font-medium">{access.name}</Td>
|
||||||
|
<Td>{access.email}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={access.active ? "success" : "neutral"}>
|
||||||
|
{access.active ? __("Active") : __("Revoked")}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{access.createdAt.toLocaleDateString()}
|
||||||
|
</Td>
|
||||||
|
<Td noLink width={120} className="text-end">
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => access.active ? handleRevoke(access.id) : handleReinvite(access)}
|
||||||
|
disabled={isUpdating}
|
||||||
|
icon={access.active ? IconCrossLargeX : IconCheckmark1}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => handleDelete(access.id)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
icon={IconTrashCan}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
ref={dialogRef}
|
||||||
|
title={__("Invite External Access")}
|
||||||
|
>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<p className="text-txt-secondary text-sm">
|
||||||
|
{__("Send a 7-day access token to an external person to view your trust center")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Field label={__("Full Name")} required>
|
||||||
|
<Input
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder={__("John Doe")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label={__("Email Address")} required>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder={__("john@example.com")}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => dialogRef.current?.close()}
|
||||||
|
disabled={isCreating}
|
||||||
|
>
|
||||||
|
{__("Cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleInvite} disabled={isCreating}>
|
||||||
|
{isCreating && <Spinner />}
|
||||||
|
{__("Send Invitation")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
80
apps/console/src/providers/TrustRelayProvider.tsx
Normal file
80
apps/console/src/providers/TrustRelayProvider.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
type FetchFunction,
|
||||||
|
Network,
|
||||||
|
RecordSource,
|
||||||
|
Store,
|
||||||
|
} from "relay-runtime";
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from "react";
|
||||||
|
import { RelayEnvironmentProvider } from "react-relay";
|
||||||
|
import { buildEndpoint } from "./RelayProviders";
|
||||||
|
|
||||||
|
export class TrustCenterError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "TrustCenterError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchTrustRelay: FetchFunction = async (request, variables) => {
|
||||||
|
const requestInit: RequestInit = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept:
|
||||||
|
"application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
credentials: "include", // Include cookies for authentication
|
||||||
|
body: JSON.stringify({
|
||||||
|
operationName: request.name,
|
||||||
|
query: request.text,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
buildEndpoint("/api/trust/v1/graphql"),
|
||||||
|
requestInit
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 500) {
|
||||||
|
throw new TrustCenterError("Internal server error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (json.errors) {
|
||||||
|
throw new TrustCenterError(
|
||||||
|
`Error fetching GraphQL query '${
|
||||||
|
request.name
|
||||||
|
}' with variables '${JSON.stringify(variables)}': ${JSON.stringify(
|
||||||
|
json.errors
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
|
const trustSource = new RecordSource();
|
||||||
|
const trustStore = new Store(trustSource, {
|
||||||
|
queryCacheExpirationTime: 5 * 60 * 1000, // 5 minutes for trust center content
|
||||||
|
gcReleaseBufferSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const trustRelayEnvironment = new Environment({
|
||||||
|
network: Network.create(fetchTrustRelay),
|
||||||
|
store: trustStore,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for trust center Relay environment (public API)
|
||||||
|
*/
|
||||||
|
export function TrustRelayProvider({ children }: PropsWithChildren) {
|
||||||
|
return (
|
||||||
|
<RelayEnvironmentProvider environment={trustRelayEnvironment}>
|
||||||
|
{children}
|
||||||
|
</RelayEnvironmentProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
} from "react-router";
|
} from "react-router";
|
||||||
import { MainLayout } from "./layouts/MainLayout";
|
import { MainLayout } from "./layouts/MainLayout";
|
||||||
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
|
||||||
import { Fragment, Suspense, type FC, type LazyExoticComponent } from "react";
|
import { Fragment, Suspense } from "react";
|
||||||
import {
|
import {
|
||||||
relayEnvironment,
|
relayEnvironment,
|
||||||
UnAuthenticatedError,
|
UnAuthenticatedError,
|
||||||
@@ -31,6 +31,13 @@ import { auditRoutes } from "./routes/auditRoutes.ts";
|
|||||||
import { trustCenterRoutes } from "./routes/trustCenterRoutes.ts";
|
import { trustCenterRoutes } from "./routes/trustCenterRoutes.ts";
|
||||||
import { lazy } from "@probo/react-lazy";
|
import { lazy } from "@probo/react-lazy";
|
||||||
|
|
||||||
|
export type AppRoute = Omit<RouteObject, "Component" | "children"> & {
|
||||||
|
Component?: React.ComponentType<any>;
|
||||||
|
children?: AppRoute[];
|
||||||
|
fallback?: React.ComponentType;
|
||||||
|
queryLoader?: (params: any) => PreloadedQuery<any>;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level error boundary
|
* Top level error boundary
|
||||||
*/
|
*/
|
||||||
@@ -44,13 +51,6 @@ function ErrorBoundary({ error: propsError }: { error?: string }) {
|
|||||||
return <PageError error={error?.toString()} />;
|
return <PageError error={error?.toString()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppRoute = {
|
|
||||||
Component: FC<any> | LazyExoticComponent<FC<any>>;
|
|
||||||
children?: AppRoute[];
|
|
||||||
fallback?: FC;
|
|
||||||
queryLoader?: (params: Record<string, string>) => PreloadedQuery<any>;
|
|
||||||
} & Omit<RouteObject, "Component" | "children">;
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/auth",
|
path: "/auth",
|
||||||
@@ -106,6 +106,18 @@ const routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/trust/:slug",
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
Component: lazy(() => import("./pages/PublicTrustCenterPage")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/trust/:slug/access",
|
||||||
|
ErrorBoundary: ErrorBoundary,
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
Component: lazy(() => import("./pages/TrustCenterAccessPage")),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/organizations/:organizationId",
|
path: "/organizations/:organizationId",
|
||||||
Component: MainLayout,
|
Component: MainLayout,
|
||||||
@@ -160,17 +172,19 @@ function routeTransformer({
|
|||||||
...route
|
...route
|
||||||
}: AppRoute): RouteObject {
|
}: AppRoute): RouteObject {
|
||||||
let result = { ...route };
|
let result = { ...route };
|
||||||
if (FallbackComponent) {
|
if (FallbackComponent && route.Component) {
|
||||||
|
const OriginalComponent = route.Component;
|
||||||
result = {
|
result = {
|
||||||
...result,
|
...result,
|
||||||
Component: (props) => (
|
Component: (props) => (
|
||||||
<Suspense fallback={<FallbackComponent />}>
|
<Suspense fallback={<FallbackComponent />}>
|
||||||
<route.Component {...props} />
|
<OriginalComponent {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (queryLoader) {
|
if (queryLoader && route.Component) {
|
||||||
|
const OriginalComponent = route.Component;
|
||||||
result = {
|
result = {
|
||||||
...result,
|
...result,
|
||||||
loader: ({ params }) => {
|
loader: ({ params }) => {
|
||||||
@@ -187,7 +201,7 @@ function routeTransformer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={FallbackComponent ? <FallbackComponent /> : null}>
|
<Suspense fallback={FallbackComponent ? <FallbackComponent /> : null}>
|
||||||
<route.Component queryRef={queryRef} />
|
<OriginalComponent queryRef={queryRef} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ export const trustCenterRoutes = [
|
|||||||
() => import("/pages/organizations/trustCenter/TrustCenterDocumentsTab")
|
() => import("/pages/organizations/trustCenter/TrustCenterDocumentsTab")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "access",
|
||||||
|
fallback: LinkCardSkeleton,
|
||||||
|
Component: lazy(
|
||||||
|
() => import("/pages/organizations/trustCenter/TrustCenterAccessTab")
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ func (a *Audits) LoadByOrganizationID(
|
|||||||
scope Scoper,
|
scope Scoper,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
cursor *page.Cursor[AuditOrderField],
|
cursor *page.Cursor[AuditOrderField],
|
||||||
|
filter *AuditFilter,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -161,12 +162,14 @@ WHERE
|
|||||||
%s
|
%s
|
||||||
AND organization_id = @organization_id
|
AND organization_id = @organization_id
|
||||||
AND %s
|
AND %s
|
||||||
|
AND %s
|
||||||
`
|
`
|
||||||
|
|
||||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||||
maps.Copy(args, scope.SQLArguments())
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, filter.SQLArguments())
|
||||||
maps.Copy(args, cursor.SQLArguments())
|
maps.Copy(args, cursor.SQLArguments())
|
||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
|||||||
54
pkg/coredata/audit_filter.go
Normal file
54
pkg/coredata/audit_filter.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AuditFilter struct {
|
||||||
|
showOnTrustCenter *bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAuditFilter() *AuditFilter {
|
||||||
|
return &AuditFilter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuditTrustCenterFilter() *AuditFilter {
|
||||||
|
showOnTrustCenter := true
|
||||||
|
return &AuditFilter{
|
||||||
|
showOnTrustCenter: &showOnTrustCenter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AuditFilter) SQLArguments() pgx.NamedArgs {
|
||||||
|
args := pgx.NamedArgs{}
|
||||||
|
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
args["show_on_trust_center"] = *f.showOnTrustCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AuditFilter) SQLFragment() string {
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
return "show_on_trust_center = @show_on_trust_center"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
DocumentFilter struct {
|
DocumentFilter struct {
|
||||||
query *string
|
query *string
|
||||||
|
showOnTrustCenter *bool
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,21 +31,52 @@ func NewDocumentFilter(query *string) *DocumentFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *DocumentFilter) SQLArguments() pgx.NamedArgs {
|
func NewDocumentTrustCenterFilter() *DocumentFilter {
|
||||||
return pgx.NamedArgs{
|
showOnTrustCenter := true
|
||||||
"query": f.query,
|
return &DocumentFilter{
|
||||||
|
showOnTrustCenter: &showOnTrustCenter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *DocumentFilter) SQLArguments() pgx.NamedArgs {
|
||||||
|
args := pgx.NamedArgs{}
|
||||||
|
|
||||||
|
if f.query != nil {
|
||||||
|
args["query"] = *f.query
|
||||||
|
}
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
args["show_on_trust_center"] = *f.showOnTrustCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
func (f *DocumentFilter) SQLFragment() string {
|
func (f *DocumentFilter) SQLFragment() string {
|
||||||
if f.query == nil || *f.query == "" {
|
conditions := []string{}
|
||||||
return "TRUE"
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
if f.query != nil && *f.query != "" {
|
||||||
|
conditions = append(conditions, `
|
||||||
search_vector @@ (
|
search_vector @@ (
|
||||||
SELECT to_tsquery('simple', string_agg(lexeme || ':*', ' & '))
|
SELECT to_tsquery('simple', string_agg(lexeme || ':*', ' & '))
|
||||||
FROM unnest(regexp_split_to_array(trim(@query), '\s+')) AS lexeme
|
FROM unnest(regexp_split_to_array(trim(@query), '\s+')) AS lexeme
|
||||||
)
|
)`)
|
||||||
`
|
}
|
||||||
|
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
conditions = append(conditions, "show_on_trust_center = @show_on_trust_center")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(conditions) == 0 {
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
for i, condition := range conditions {
|
||||||
|
if i > 0 {
|
||||||
|
result += " AND "
|
||||||
|
}
|
||||||
|
result += condition
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,5 @@ const (
|
|||||||
AuditEntityType
|
AuditEntityType
|
||||||
ReportEntityType
|
ReportEntityType
|
||||||
TrustCenterEntityType
|
TrustCenterEntityType
|
||||||
|
TrustCenterAccessEntityType
|
||||||
)
|
)
|
||||||
|
|||||||
11
pkg/coredata/migrations/20250728T194402Z.sql
Normal file
11
pkg/coredata/migrations/20250728T194402Z.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE trust_center_accesses (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tenant_id TEXT NOT NULL,
|
||||||
|
trust_center_id TEXT NOT NULL REFERENCES trust_centers(id) ON DELETE CASCADE,
|
||||||
|
email CITEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
UNIQUE(trust_center_id, email)
|
||||||
|
);
|
||||||
@@ -135,6 +135,44 @@ LIMIT 1;
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tc *TrustCenter) LoadBySlug(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
slug string,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
tenant_id,
|
||||||
|
active,
|
||||||
|
slug,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
trust_centers
|
||||||
|
WHERE
|
||||||
|
slug = @slug
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"slug": slug}
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query trust center: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustCenter, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenter])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect trust center: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*tc = trustCenter
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (tc *TrustCenter) Insert(
|
func (tc *TrustCenter) Insert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
|
|||||||
318
pkg/coredata/trust_center_access.go
Normal file
318
pkg/coredata/trust_center_access.go
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
TrustCenterAccess struct {
|
||||||
|
ID gid.GID `db:"id"`
|
||||||
|
TenantID gid.TenantID `db:"tenant_id"`
|
||||||
|
TrustCenterID gid.GID `db:"trust_center_id"`
|
||||||
|
Email string `db:"email"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Active bool `db:"active"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustCenterAccesses []*TrustCenterAccess
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) CursorKey(orderBy TrustCenterAccessOrderField) page.CursorKey {
|
||||||
|
switch orderBy {
|
||||||
|
case TrustCenterAccessOrderFieldCreatedAt:
|
||||||
|
return page.NewCursorKey(tca.ID, tca.CreatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) LoadByID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
accessID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
tenant_id,
|
||||||
|
trust_center_id,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
trust_center_accesses
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @access_id
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"access_id": accessID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
access, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenterAccess])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*tca = access
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) LoadByTrustCenterIDAndEmail(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
email string,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
tenant_id,
|
||||||
|
trust_center_id,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
trust_center_accesses
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND trust_center_id = @trust_center_id
|
||||||
|
AND email = @email
|
||||||
|
LIMIT 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"trust_center_id": trustCenterID,
|
||||||
|
"email": email,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
access, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenterAccess])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*tca = access
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) Insert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO trust_center_accesses (
|
||||||
|
id,
|
||||||
|
tenant_id,
|
||||||
|
trust_center_id,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
@id,
|
||||||
|
@tenant_id,
|
||||||
|
@trust_center_id,
|
||||||
|
@email,
|
||||||
|
@name,
|
||||||
|
@active,
|
||||||
|
@created_at,
|
||||||
|
@updated_at
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": tca.ID,
|
||||||
|
"tenant_id": tca.TenantID,
|
||||||
|
"trust_center_id": tca.TrustCenterID,
|
||||||
|
"email": tca.Email,
|
||||||
|
"name": tca.Name,
|
||||||
|
"active": tca.Active,
|
||||||
|
"created_at": tca.CreatedAt,
|
||||||
|
"updated_at": tca.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot insert trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
UPDATE trust_center_accesses
|
||||||
|
SET
|
||||||
|
active = @active,
|
||||||
|
updated_at = @updated_at
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": tca.ID,
|
||||||
|
"active": tca.Active,
|
||||||
|
"updated_at": tca.UpdatedAt,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca *TrustCenterAccess) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE FROM trust_center_accesses
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND id = @id
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"id": tca.ID,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot delete trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcas *TrustCenterAccesses) LoadByTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
cursor *page.Cursor[TrustCenterAccessOrderField],
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
tenant_id,
|
||||||
|
trust_center_id,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
active,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
trust_center_accesses
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND trust_center_id = @trust_center_id
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"trust_center_id": trustCenterID,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, cursor.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query trust center accesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accesses, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TrustCenterAccess])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect trust center accesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*tcas = accesses
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
TrustCenterAccessOrderField string
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TrustCenterAccessOrderFieldCreatedAt TrustCenterAccessOrderField = "CREATED_AT"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tcaof TrustCenterAccessOrderField) String() string {
|
||||||
|
return string(tcaof)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcaof TrustCenterAccessOrderField) Column() string {
|
||||||
|
switch tcaof {
|
||||||
|
case TrustCenterAccessOrderFieldCreatedAt:
|
||||||
|
return "created_at"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unsupported order by: %s", tcaof))
|
||||||
|
}
|
||||||
@@ -278,45 +278,48 @@ func (v *Vendors) LoadByOrganizationID(
|
|||||||
scope Scoper,
|
scope Scoper,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
cursor *page.Cursor[VendorOrderField],
|
cursor *page.Cursor[VendorOrderField],
|
||||||
|
filter *VendorFilter,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
tenant_id,
|
tenant_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
category,
|
category,
|
||||||
headquarter_address,
|
headquarter_address,
|
||||||
legal_name,
|
legal_name,
|
||||||
website_url,
|
website_url,
|
||||||
privacy_policy_url,
|
privacy_policy_url,
|
||||||
service_level_agreement_url,
|
service_level_agreement_url,
|
||||||
data_processing_agreement_url,
|
data_processing_agreement_url,
|
||||||
business_associate_agreement_url,
|
business_associate_agreement_url,
|
||||||
subprocessors_list_url,
|
subprocessors_list_url,
|
||||||
certifications,
|
certifications,
|
||||||
business_owner_id,
|
business_owner_id,
|
||||||
security_owner_id,
|
security_owner_id,
|
||||||
status_page_url,
|
status_page_url,
|
||||||
terms_of_service_url,
|
terms_of_service_url,
|
||||||
security_page_url,
|
security_page_url,
|
||||||
trust_page_url,
|
trust_page_url,
|
||||||
show_on_trust_center,
|
show_on_trust_center,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
vendors
|
vendors
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND organization_id = @organization_id
|
AND organization_id = @organization_id
|
||||||
AND %s
|
AND %s
|
||||||
|
AND %s
|
||||||
`
|
`
|
||||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||||
maps.Copy(args, cursor.SQLArguments())
|
|
||||||
maps.Copy(args, scope.SQLArguments())
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
maps.Copy(args, filter.SQLArguments())
|
||||||
|
maps.Copy(args, cursor.SQLArguments())
|
||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
54
pkg/coredata/vendor_filter.go
Normal file
54
pkg/coredata/vendor_filter.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package coredata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
VendorFilter struct {
|
||||||
|
showOnTrustCenter *bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVendorFilter() *VendorFilter {
|
||||||
|
return &VendorFilter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorTrustCenterFilter() *VendorFilter {
|
||||||
|
showOnTrustCenter := true
|
||||||
|
return &VendorFilter{
|
||||||
|
showOnTrustCenter: &showOnTrustCenter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *VendorFilter) SQLArguments() pgx.NamedArgs {
|
||||||
|
args := pgx.NamedArgs{}
|
||||||
|
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
args["show_on_trust_center"] = *f.showOnTrustCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *VendorFilter) SQLFragment() string {
|
||||||
|
if f.showOnTrustCenter != nil {
|
||||||
|
return "show_on_trust_center = @show_on_trust_center"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "TRUE"
|
||||||
|
}
|
||||||
@@ -201,7 +201,8 @@ func (s AuditService) ListForOrganizationID(
|
|||||||
err := s.svc.pg.WithConn(
|
err := s.svc.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
err := audits.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor)
|
filter := coredata.NewAuditFilter()
|
||||||
|
err := audits.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot load audits: %w", err)
|
return fmt.Errorf("cannot load audits: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/getprobo/probo/pkg/filevalidation"
|
"github.com/getprobo/probo/pkg/filevalidation"
|
||||||
"github.com/getprobo/probo/pkg/gid"
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
"github.com/getprobo/probo/pkg/html2pdf"
|
"github.com/getprobo/probo/pkg/html2pdf"
|
||||||
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ type (
|
|||||||
tokenSecret string
|
tokenSecret string
|
||||||
agentConfig agents.Config
|
agentConfig agents.Config
|
||||||
html2pdfConverter *html2pdf.Converter
|
html2pdfConverter *html2pdf.Converter
|
||||||
|
usrmgr *usrmgr.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
TenantService struct {
|
TenantService struct {
|
||||||
@@ -66,6 +68,7 @@ type (
|
|||||||
Audits *AuditService
|
Audits *AuditService
|
||||||
Reports *ReportService
|
Reports *ReportService
|
||||||
TrustCenters *TrustCenterService
|
TrustCenters *TrustCenterService
|
||||||
|
TrustCenterAccesses *TrustCenterAccessService
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,6 +82,7 @@ func NewService(
|
|||||||
tokenSecret string,
|
tokenSecret string,
|
||||||
agentConfig agents.Config,
|
agentConfig agents.Config,
|
||||||
html2pdfConverter *html2pdf.Converter,
|
html2pdfConverter *html2pdf.Converter,
|
||||||
|
usrmgrService *usrmgr.Service,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
return nil, fmt.Errorf("bucket is required")
|
return nil, fmt.Errorf("bucket is required")
|
||||||
@@ -93,11 +97,16 @@ func NewService(
|
|||||||
tokenSecret: tokenSecret,
|
tokenSecret: tokenSecret,
|
||||||
agentConfig: agentConfig,
|
agentConfig: agentConfig,
|
||||||
html2pdfConverter: html2pdfConverter,
|
html2pdfConverter: html2pdfConverter,
|
||||||
|
usrmgr: usrmgrService,
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetEncryptionKey() cipher.EncryptionKey {
|
||||||
|
return s.encryptionKey
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||||
tenantService := &TenantService{
|
tenantService := &TenantService{
|
||||||
pg: s.pg,
|
pg: s.pg,
|
||||||
@@ -146,5 +155,9 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
|||||||
tenantService.Audits = &AuditService{svc: tenantService}
|
tenantService.Audits = &AuditService{svc: tenantService}
|
||||||
tenantService.Reports = &ReportService{svc: tenantService}
|
tenantService.Reports = &ReportService{svc: tenantService}
|
||||||
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
|
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
|
||||||
|
tenantService.TrustCenterAccesses = &TrustCenterAccessService{
|
||||||
|
svc: tenantService,
|
||||||
|
usrmgr: s.usrmgr,
|
||||||
|
}
|
||||||
return tenantService
|
return tenantService
|
||||||
}
|
}
|
||||||
|
|||||||
341
pkg/probo/trust_center_access_service.go
Normal file
341
pkg/probo/trust_center_access_service.go
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package probo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||||
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
TrustCenterAccessService struct {
|
||||||
|
svc *TenantService
|
||||||
|
usrmgr *usrmgr.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateTrustCenterAccessRequest struct {
|
||||||
|
TrustCenterID gid.GID
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
SendEmail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTrustCenterAccessRequest struct {
|
||||||
|
AccessID gid.GID
|
||||||
|
Email *string
|
||||||
|
Name *string
|
||||||
|
Active *bool
|
||||||
|
SendEmail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteTrustCenterAccessRequest struct {
|
||||||
|
AccessID gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
RevokeTrustCenterAccessRequest struct {
|
||||||
|
AccessID gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustCenterAccessData struct {
|
||||||
|
TrustCenterID gid.GID `json:"trust_center_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenTypeTrustCenterAccess = "trust_center_access"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) RevokeAccess(
|
||||||
|
ctx context.Context,
|
||||||
|
req *RevokeTrustCenterAccessRequest,
|
||||||
|
) (*coredata.TrustCenterAccess, error) {
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
if err := access.LoadByID(ctx, tx, s.svc.scope, req.AccessID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
access.Active = false
|
||||||
|
access.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := access.Update(ctx, tx, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) ListForTrustCenterID(
|
||||||
|
ctx context.Context,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.TrustCenterAccessOrderField],
|
||||||
|
) (*page.Page[*coredata.TrustCenterAccess, coredata.TrustCenterAccessOrderField], error) {
|
||||||
|
var accesses coredata.TrustCenterAccesses
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return accesses.LoadByTrustCenterID(ctx, conn, s.svc.scope, trustCenterID, cursor)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(accesses, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) ValidateToken(
|
||||||
|
ctx context.Context,
|
||||||
|
tokenString string,
|
||||||
|
) (*TrustCenterAccessData, error) {
|
||||||
|
token, err := statelesstoken.ValidateToken[TrustCenterAccessData](
|
||||||
|
s.svc.tokenSecret,
|
||||||
|
TokenTypeTrustCenterAccess,
|
||||||
|
tokenString,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
err = s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, token.Data.TrustCenterID, token.Data.Email)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("access not found or revoked: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !access.Active {
|
||||||
|
return nil, fmt.Errorf("access has been revoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) IsAccessActive(
|
||||||
|
ctx context.Context,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
email string,
|
||||||
|
) (bool, error) {
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return access.Active, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
req *CreateTrustCenterAccessRequest,
|
||||||
|
) (*coredata.TrustCenterAccess, error) {
|
||||||
|
if !strings.Contains(req.Email, "@") {
|
||||||
|
return nil, fmt.Errorf("invalid email address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name == "" {
|
||||||
|
return nil, fmt.Errorf("name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
existingAccess := &coredata.TrustCenterAccess{}
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return existingAccess.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, req.TrustCenterID, req.Email)
|
||||||
|
})
|
||||||
|
|
||||||
|
var access *coredata.TrustCenterAccess
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
access = existingAccess
|
||||||
|
access.Name = req.Name
|
||||||
|
access.Active = true
|
||||||
|
access.UpdatedAt = now
|
||||||
|
|
||||||
|
err = s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
if err := access.Update(ctx, tx, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
access = &coredata.TrustCenterAccess{
|
||||||
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
|
||||||
|
TenantID: s.svc.scope.GetTenantID(),
|
||||||
|
TrustCenterID: req.TrustCenterID,
|
||||||
|
Email: req.Email,
|
||||||
|
Name: req.Name,
|
||||||
|
Active: true,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert trust center access: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.SendEmail {
|
||||||
|
if err := s.sendAccessEmail(ctx, access); err != nil {
|
||||||
|
fmt.Printf("Failed to send access email\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
req *UpdateTrustCenterAccessRequest,
|
||||||
|
) (*coredata.TrustCenterAccess, error) {
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
if err := access.LoadByID(ctx, tx, s.svc.scope, req.AccessID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Email != nil {
|
||||||
|
if !strings.Contains(*req.Email, "@") {
|
||||||
|
return fmt.Errorf("invalid email address")
|
||||||
|
}
|
||||||
|
access.Email = *req.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
if *req.Name == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
access.Name = *req.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Active != nil {
|
||||||
|
access.Active = *req.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
access.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if err := access.Update(ctx, tx, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.SendEmail && access.Active {
|
||||||
|
if err := s.sendAccessEmail(ctx, access); err != nil {
|
||||||
|
fmt.Printf("Failed to send access email\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return access, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
req *DeleteTrustCenterAccessRequest,
|
||||||
|
) error {
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
|
||||||
|
if err := access.LoadByID(ctx, tx, s.svc.scope, req.AccessID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := access.Delete(ctx, tx, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, access *coredata.TrustCenterAccess) error {
|
||||||
|
accessToken, err := statelesstoken.NewToken(
|
||||||
|
s.svc.tokenSecret,
|
||||||
|
TokenTypeTrustCenterAccess,
|
||||||
|
7*24*time.Hour,
|
||||||
|
TrustCenterAccessData{
|
||||||
|
TrustCenterID: access.TrustCenterID,
|
||||||
|
Email: access.Email,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot generate access token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustCenter := &coredata.TrustCenter{}
|
||||||
|
err = s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return trustCenter.LoadByID(ctx, conn, s.svc.scope, access.TrustCenterID)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
organization := &coredata.Organization{}
|
||||||
|
err = s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return organization.LoadByID(ctx, conn, s.svc.scope, trustCenter.OrganizationID)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessURL := url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: s.svc.hostname,
|
||||||
|
Path: "/trust/" + trustCenter.Slug + "/access",
|
||||||
|
RawQuery: "token=" + url.QueryEscape(accessToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.usrmgr.SendTrustCenterAccessEmail(ctx, access.Name, access.Email, organization.Name, accessURL.String())
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ func (s TrustCenterService) GetByOrganizationID(
|
|||||||
return trustCenter, nil
|
return trustCenter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TrustCenterService) Update(
|
func (s TrustCenterService) Update(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateTrustCenterRequest,
|
req *UpdateTrustCenterRequest,
|
||||||
) (*coredata.TrustCenter, error) {
|
) (*coredata.TrustCenter, error) {
|
||||||
|
|||||||
@@ -131,12 +131,14 @@ func (s VendorService) ListForOrganizationID(
|
|||||||
return fmt.Errorf("cannot load organization: %w", err)
|
return fmt.Errorf("cannot load organization: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter := coredata.NewVendorFilter()
|
||||||
return vendors.LoadByOrganizationID(
|
return vendors.LoadByOrganizationID(
|
||||||
ctx,
|
ctx,
|
||||||
conn,
|
conn,
|
||||||
s.svc.scope,
|
s.svc.scope,
|
||||||
organization.ID,
|
organization.ID,
|
||||||
cursor,
|
cursor,
|
||||||
|
filter,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ import (
|
|||||||
"github.com/getprobo/probo/pkg/probo"
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
"github.com/getprobo/probo/pkg/saferedirect"
|
"github.com/getprobo/probo/pkg/saferedirect"
|
||||||
"github.com/getprobo/probo/pkg/server"
|
"github.com/getprobo/probo/pkg/server"
|
||||||
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
|
"github.com/getprobo/probo/pkg/server/api"
|
||||||
|
"github.com/getprobo/probo/pkg/trust"
|
||||||
"github.com/getprobo/probo/pkg/usrmgr"
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"go.gearno.de/kit/httpclient"
|
"go.gearno.de/kit/httpclient"
|
||||||
@@ -226,22 +227,34 @@ func (impl *Implm) Run(
|
|||||||
impl.cfg.Auth.Cookie.Secret,
|
impl.cfg.Auth.Cookie.Secret,
|
||||||
agentConfig,
|
agentConfig,
|
||||||
html2pdfConverter,
|
html2pdfConverter,
|
||||||
|
usrmgrService,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create probo service: %w", err)
|
return fmt.Errorf("cannot create probo service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trustService := trust.NewService(
|
||||||
|
pgClient,
|
||||||
|
s3Client,
|
||||||
|
impl.cfg.AWS.Bucket,
|
||||||
|
impl.cfg.EncryptionKey,
|
||||||
|
impl.cfg.Auth.Cookie.Secret,
|
||||||
|
usrmgrService,
|
||||||
|
html2pdfConverter,
|
||||||
|
)
|
||||||
|
|
||||||
serverHandler, err := server.NewServer(
|
serverHandler, err := server.NewServer(
|
||||||
server.Config{
|
server.Config{
|
||||||
AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins,
|
AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins,
|
||||||
ExtraHeaderFields: impl.cfg.Api.ExtraHeaderFields,
|
ExtraHeaderFields: impl.cfg.Api.ExtraHeaderFields,
|
||||||
Probo: proboService,
|
Probo: proboService,
|
||||||
Usrmgr: usrmgrService,
|
Usrmgr: usrmgrService,
|
||||||
|
Trust: trustService,
|
||||||
ConnectorRegistry: defaultConnectorRegistry,
|
ConnectorRegistry: defaultConnectorRegistry,
|
||||||
Agent: agent,
|
Agent: agent,
|
||||||
SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname},
|
SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname},
|
||||||
Logger: l.Named("http.server"),
|
Logger: l.Named("http.server"),
|
||||||
Auth: console_v1.AuthConfig{
|
Auth: api.AuthConfig{
|
||||||
CookieName: impl.cfg.Auth.Cookie.Name,
|
CookieName: impl.cfg.Auth.Cookie.Name,
|
||||||
CookieDomain: impl.cfg.Auth.Cookie.Domain,
|
CookieDomain: impl.cfg.Auth.Cookie.Domain,
|
||||||
SessionDuration: time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour,
|
SessionDuration: time.Duration(impl.cfg.Auth.Cookie.Duration) * time.Hour,
|
||||||
|
|||||||
@@ -18,10 +18,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/getprobo/probo/pkg/connector"
|
"github.com/getprobo/probo/pkg/connector"
|
||||||
"github.com/getprobo/probo/pkg/probo"
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
"github.com/getprobo/probo/pkg/saferedirect"
|
"github.com/getprobo/probo/pkg/saferedirect"
|
||||||
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
|
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
|
||||||
|
trust_v1 "github.com/getprobo/probo/pkg/server/api/trust/v1"
|
||||||
|
"github.com/getprobo/probo/pkg/trust"
|
||||||
"github.com/getprobo/probo/pkg/usrmgr"
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
@@ -30,11 +34,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
AuthConfig struct {
|
||||||
|
CookieName string
|
||||||
|
CookieDomain string
|
||||||
|
SessionDuration time.Duration
|
||||||
|
CookieSecret string
|
||||||
|
}
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
AllowedOrigins []string
|
AllowedOrigins []string
|
||||||
Probo *probo.Service
|
Probo *probo.Service
|
||||||
Usrmgr *usrmgr.Service
|
Usrmgr *usrmgr.Service
|
||||||
Auth console_v1.AuthConfig
|
Trust *trust.Service
|
||||||
|
Auth AuthConfig
|
||||||
ConnectorRegistry *connector.ConnectorRegistry
|
ConnectorRegistry *connector.ConnectorRegistry
|
||||||
SafeRedirect *saferedirect.SafeRedirect
|
SafeRedirect *saferedirect.SafeRedirect
|
||||||
Logger *log.Logger
|
Logger *log.Logger
|
||||||
@@ -122,11 +134,32 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.cfg.Logger.Named("console.v1"),
|
s.cfg.Logger.Named("console.v1"),
|
||||||
s.cfg.Probo,
|
s.cfg.Probo,
|
||||||
s.cfg.Usrmgr,
|
s.cfg.Usrmgr,
|
||||||
s.cfg.Auth,
|
console_v1.AuthConfig{
|
||||||
|
CookieName: s.cfg.Auth.CookieName,
|
||||||
|
CookieDomain: s.cfg.Auth.CookieDomain,
|
||||||
|
SessionDuration: s.cfg.Auth.SessionDuration,
|
||||||
|
CookieSecret: s.cfg.Auth.CookieSecret,
|
||||||
|
},
|
||||||
s.cfg.ConnectorRegistry,
|
s.cfg.ConnectorRegistry,
|
||||||
s.cfg.SafeRedirect,
|
s.cfg.SafeRedirect,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Mount the trust API with authentication
|
||||||
|
router.Mount(
|
||||||
|
"/trust/v1",
|
||||||
|
trust_v1.NewMux(
|
||||||
|
s.cfg.Logger.Named("trust.v1"),
|
||||||
|
s.cfg.Usrmgr,
|
||||||
|
s.cfg.Trust,
|
||||||
|
trust_v1.AuthConfig{
|
||||||
|
CookieName: s.cfg.Auth.CookieName,
|
||||||
|
CookieDomain: s.cfg.Auth.CookieDomain,
|
||||||
|
SessionDuration: s.cfg.Auth.SessionDuration,
|
||||||
|
CookieSecret: s.cfg.Auth.CookieSecret,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
router.ServeHTTP(w, r)
|
router.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -566,6 +566,14 @@ enum AuditOrderField
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TrustCenterAccessOrderField
|
||||||
|
@goModel(model: "github.com/getprobo/probo/pkg/coredata.TrustCenterAccessOrderField") {
|
||||||
|
CREATED_AT
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterAccessOrderFieldCreatedAt"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
# Input Types
|
# Input Types
|
||||||
input UserOrder
|
input UserOrder
|
||||||
@goModel(
|
@goModel(
|
||||||
@@ -647,6 +655,14 @@ input AuditOrder
|
|||||||
field: AuditOrderField!
|
field: AuditOrderField!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input TrustCenterAccessOrder
|
||||||
|
@goModel(
|
||||||
|
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.TrustCenterAccessOrderBy"
|
||||||
|
) {
|
||||||
|
direction: OrderDirection!
|
||||||
|
field: TrustCenterAccessOrderField!
|
||||||
|
}
|
||||||
|
|
||||||
input EvidenceOrder
|
input EvidenceOrder
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.EvidenceOrderBy"
|
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.EvidenceOrderBy"
|
||||||
@@ -702,6 +718,14 @@ input RiskFilter {
|
|||||||
query: String
|
query: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input OrganizationFilter {
|
||||||
|
trustCenterSlug: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input TrustCenterFilter {
|
||||||
|
slug: String
|
||||||
|
}
|
||||||
|
|
||||||
# Core Types
|
# Core Types
|
||||||
type TrustCenter implements Node {
|
type TrustCenter implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
@@ -709,6 +733,15 @@ type TrustCenter implements Node {
|
|||||||
slug: String!
|
slug: String!
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
updatedAt: Datetime!
|
updatedAt: Datetime!
|
||||||
|
organization: Organization! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
accesses(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: TrustCenterAccessOrder
|
||||||
|
): TrustCenterAccessConnection! @goField(forceResolver: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Organization implements Node {
|
type Organization implements Node {
|
||||||
@@ -1177,6 +1210,7 @@ type Viewer {
|
|||||||
last: Int
|
last: Int
|
||||||
before: CursorKey
|
before: CursorKey
|
||||||
orderBy: OrganizationOrder
|
orderBy: OrganizationOrder
|
||||||
|
filter: OrganizationFilter
|
||||||
): OrganizationConnection! @goField(forceResolver: true)
|
): OrganizationConnection! @goField(forceResolver: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1191,6 +1225,35 @@ type OrganizationEdge {
|
|||||||
node: Organization!
|
node: Organization!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrustCenterConnection {
|
||||||
|
edges: [TrustCenterEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: TrustCenter!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterAccess implements Node {
|
||||||
|
id: ID!
|
||||||
|
email: String!
|
||||||
|
name: String!
|
||||||
|
active: Boolean!
|
||||||
|
createdAt: Datetime!
|
||||||
|
updatedAt: Datetime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterAccessConnection {
|
||||||
|
edges: [TrustCenterAccessEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterAccessEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: TrustCenterAccess!
|
||||||
|
}
|
||||||
|
|
||||||
type UserConnection {
|
type UserConnection {
|
||||||
edges: [UserEdge!]!
|
edges: [UserEdge!]!
|
||||||
pageInfo: PageInfo!
|
pageInfo: PageInfo!
|
||||||
@@ -1399,6 +1462,13 @@ type AuditEdge {
|
|||||||
type Query {
|
type Query {
|
||||||
node(id: ID!): Node!
|
node(id: ID!): Node!
|
||||||
viewer: Viewer!
|
viewer: Viewer!
|
||||||
|
trustCenters(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
filter: TrustCenterFilter
|
||||||
|
): TrustCenterConnection! @goField(forceResolver: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@@ -1414,6 +1484,23 @@ type Mutation {
|
|||||||
input: UpdateTrustCenterInput!
|
input: UpdateTrustCenterInput!
|
||||||
): UpdateTrustCenterPayload!
|
): UpdateTrustCenterPayload!
|
||||||
|
|
||||||
|
revokeTrustCenterAccess(
|
||||||
|
input: RevokeTrustCenterAccessInput!
|
||||||
|
): RevokeTrustCenterAccessPayload!
|
||||||
|
|
||||||
|
# Trust Center Access CRUD mutations
|
||||||
|
createTrustCenterAccess(
|
||||||
|
input: CreateTrustCenterAccessInput!
|
||||||
|
): CreateTrustCenterAccessPayload!
|
||||||
|
|
||||||
|
updateTrustCenterAccess(
|
||||||
|
input: UpdateTrustCenterAccessInput!
|
||||||
|
): UpdateTrustCenterAccessPayload!
|
||||||
|
|
||||||
|
deleteTrustCenterAccess(
|
||||||
|
input: DeleteTrustCenterAccessInput!
|
||||||
|
): DeleteTrustCenterAccessPayload!
|
||||||
|
|
||||||
# User mutations
|
# User mutations
|
||||||
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
|
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
|
||||||
inviteUser(input: InviteUserInput!): InviteUserPayload!
|
inviteUser(input: InviteUserInput!): InviteUserPayload!
|
||||||
@@ -1586,6 +1673,29 @@ input UpdateTrustCenterInput {
|
|||||||
slug: String
|
slug: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input RevokeTrustCenterAccessInput {
|
||||||
|
accessId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateTrustCenterAccessInput {
|
||||||
|
trustCenterId: ID!
|
||||||
|
email: String!
|
||||||
|
name: String!
|
||||||
|
sendEmail: Boolean! = true
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateTrustCenterAccessInput {
|
||||||
|
accessId: ID!
|
||||||
|
email: String
|
||||||
|
name: String
|
||||||
|
active: Boolean
|
||||||
|
sendEmail: Boolean! = false
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteTrustCenterAccessInput {
|
||||||
|
accessId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
input CreateVendorInput {
|
input CreateVendorInput {
|
||||||
organizationId: ID!
|
organizationId: ID!
|
||||||
name: String!
|
name: String!
|
||||||
@@ -1951,6 +2061,24 @@ type UpdateTrustCenterPayload {
|
|||||||
trustCenter: TrustCenter!
|
trustCenter: TrustCenter!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type RevokeTrustCenterAccessPayload {
|
||||||
|
trustCenterAccess: TrustCenterAccess!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTrustCenterAccessPayload {
|
||||||
|
trustCenterAccessEdge: TrustCenterAccessEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTrustCenterAccessPayload {
|
||||||
|
trustCenterAccess: TrustCenterAccess!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTrustCenterAccessPayload {
|
||||||
|
deletedTrustCenterAccessId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
type CreateControlPayload {
|
type CreateControlPayload {
|
||||||
controlEdge: ControlEdge!
|
controlEdge: ControlEdge!
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
57
pkg/server/api/console/v1/types/trust_center_access.go
Normal file
57
pkg/server/api/console/v1/types/trust_center_access.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrustCenterAccessOrderBy = OrderBy[coredata.TrustCenterAccessOrderField]
|
||||||
|
|
||||||
|
func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
|
||||||
|
return &TrustCenterAccess{
|
||||||
|
ID: tca.ID,
|
||||||
|
Email: tca.Email,
|
||||||
|
Name: tca.Name,
|
||||||
|
Active: tca.Active,
|
||||||
|
CreatedAt: tca.CreatedAt,
|
||||||
|
UpdatedAt: tca.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrustCenterAccessConnection(
|
||||||
|
page *page.Page[*coredata.TrustCenterAccess, coredata.TrustCenterAccessOrderField],
|
||||||
|
) *TrustCenterAccessConnection {
|
||||||
|
var edges = make([]*TrustCenterAccessEdge, len(page.Data))
|
||||||
|
|
||||||
|
for i := range edges {
|
||||||
|
edges[i] = NewTrustCenterAccessEdge(page.Data[i], page.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TrustCenterAccessConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(page),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrustCenterAccessEdge(tca *coredata.TrustCenterAccess, orderBy coredata.TrustCenterAccessOrderField) *TrustCenterAccessEdge {
|
||||||
|
return &TrustCenterAccessEdge{
|
||||||
|
Cursor: tca.CursorKey(orderBy),
|
||||||
|
Node: NewTrustCenterAccess(tca),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types are auto-generated in types.go - only helper functions remain here
|
||||||
@@ -367,6 +367,17 @@ type CreateTaskPayload struct {
|
|||||||
TaskEdge *TaskEdge `json:"taskEdge"`
|
TaskEdge *TaskEdge `json:"taskEdge"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTrustCenterAccessInput struct {
|
||||||
|
TrustCenterID gid.GID `json:"trustCenterId"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SendEmail bool `json:"sendEmail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTrustCenterAccessPayload struct {
|
||||||
|
TrustCenterAccessEdge *TrustCenterAccessEdge `json:"trustCenterAccessEdge"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateVendorInput struct {
|
type CreateVendorInput struct {
|
||||||
OrganizationID gid.GID `json:"organizationId"`
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -561,6 +572,14 @@ type DeleteTaskPayload struct {
|
|||||||
DeletedTaskID gid.GID `json:"deletedTaskId"`
|
DeletedTaskID gid.GID `json:"deletedTaskId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteTrustCenterAccessInput struct {
|
||||||
|
AccessID gid.GID `json:"accessId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTrustCenterAccessPayload struct {
|
||||||
|
DeletedTrustCenterAccessID gid.GID `json:"deletedTrustCenterAccessId"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteVendorComplianceReportInput struct {
|
type DeleteVendorComplianceReportInput struct {
|
||||||
ReportID gid.GID `json:"reportId"`
|
ReportID gid.GID `json:"reportId"`
|
||||||
}
|
}
|
||||||
@@ -836,6 +855,10 @@ type OrganizationEdge struct {
|
|||||||
Node *Organization `json:"node"`
|
Node *Organization `json:"node"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrganizationFilter struct {
|
||||||
|
TrustCenterSlug *string `json:"trustCenterSlug,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type OrganizationOrder struct {
|
type OrganizationOrder struct {
|
||||||
Direction page.OrderDirection `json:"direction"`
|
Direction page.OrderDirection `json:"direction"`
|
||||||
Field coredata.OrganizationOrderField `json:"field"`
|
Field coredata.OrganizationOrderField `json:"field"`
|
||||||
@@ -925,6 +948,14 @@ type RequestSignaturePayload struct {
|
|||||||
DocumentVersionSignatureEdge *DocumentVersionSignatureEdge `json:"documentVersionSignatureEdge"`
|
DocumentVersionSignatureEdge *DocumentVersionSignatureEdge `json:"documentVersionSignatureEdge"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RevokeTrustCenterAccessInput struct {
|
||||||
|
AccessID gid.GID `json:"accessId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RevokeTrustCenterAccessPayload struct {
|
||||||
|
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
|
||||||
|
}
|
||||||
|
|
||||||
type Risk struct {
|
type Risk struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -996,16 +1027,54 @@ type TaskEdge struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
Organization *Organization `json:"organization"`
|
||||||
|
Accesses *TrustCenterAccessConnection `json:"accesses"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (TrustCenter) IsNode() {}
|
func (TrustCenter) IsNode() {}
|
||||||
func (this TrustCenter) GetID() gid.GID { return this.ID }
|
func (this TrustCenter) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type TrustCenterAccess struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (TrustCenterAccess) IsNode() {}
|
||||||
|
func (this TrustCenterAccess) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type TrustCenterAccessConnection struct {
|
||||||
|
Edges []*TrustCenterAccessEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterAccessEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *TrustCenterAccess `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterConnection struct {
|
||||||
|
Edges []*TrustCenterEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *TrustCenter `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenterFilter struct {
|
||||||
|
Slug *string `json:"slug,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type UnassignTaskInput struct {
|
type UnassignTaskInput struct {
|
||||||
TaskID gid.GID `json:"taskId"`
|
TaskID gid.GID `json:"taskId"`
|
||||||
}
|
}
|
||||||
@@ -1167,6 +1236,18 @@ type UpdateTaskPayload struct {
|
|||||||
Task *Task `json:"task"`
|
Task *Task `json:"task"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTrustCenterAccessInput struct {
|
||||||
|
AccessID gid.GID `json:"accessId"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Active *bool `json:"active,omitempty"`
|
||||||
|
SendEmail bool `json:"sendEmail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTrustCenterAccessPayload struct {
|
||||||
|
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateTrustCenterInput struct {
|
type UpdateTrustCenterInput struct {
|
||||||
TrustCenterID gid.GID `json:"trustCenterId"`
|
TrustCenterID gid.GID `json:"trustCenterId"`
|
||||||
Active *bool `json:"active,omitempty"`
|
Active *bool `json:"active,omitempty"`
|
||||||
|
|||||||
@@ -998,6 +998,77 @@ func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.Up
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevokeTrustCenterAccess is the resolver for the revokeTrustCenterAccess field.
|
||||||
|
func (r *mutationResolver) RevokeTrustCenterAccess(ctx context.Context, input types.RevokeTrustCenterAccessInput) (*types.RevokeTrustCenterAccessPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||||
|
|
||||||
|
access, err := prb.TrustCenterAccesses.RevokeAccess(ctx, &probo.RevokeTrustCenterAccessRequest{
|
||||||
|
AccessID: input.AccessID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot revoke trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.RevokeTrustCenterAccessPayload{
|
||||||
|
TrustCenterAccess: types.NewTrustCenterAccess(access),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTrustCenterAccess is the resolver for the createTrustCenterAccess field.
|
||||||
|
func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
|
||||||
|
|
||||||
|
access, err := prb.TrustCenterAccesses.Create(ctx, &probo.CreateTrustCenterAccessRequest{
|
||||||
|
TrustCenterID: input.TrustCenterID,
|
||||||
|
Email: input.Email,
|
||||||
|
Name: input.Name,
|
||||||
|
SendEmail: input.SendEmail,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.CreateTrustCenterAccessPayload{
|
||||||
|
TrustCenterAccessEdge: types.NewTrustCenterAccessEdge(access, coredata.TrustCenterAccessOrderFieldCreatedAt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTrustCenterAccess is the resolver for the updateTrustCenterAccess field.
|
||||||
|
func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input types.UpdateTrustCenterAccessInput) (*types.UpdateTrustCenterAccessPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||||
|
|
||||||
|
access, err := prb.TrustCenterAccesses.Update(ctx, &probo.UpdateTrustCenterAccessRequest{
|
||||||
|
AccessID: input.AccessID,
|
||||||
|
Email: input.Email,
|
||||||
|
Name: input.Name,
|
||||||
|
Active: input.Active,
|
||||||
|
SendEmail: input.SendEmail,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot update trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.UpdateTrustCenterAccessPayload{
|
||||||
|
TrustCenterAccess: types.NewTrustCenterAccess(access),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTrustCenterAccess is the resolver for the deleteTrustCenterAccess field.
|
||||||
|
func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||||
|
|
||||||
|
err := prb.TrustCenterAccesses.Delete(ctx, &probo.DeleteTrustCenterAccessRequest{
|
||||||
|
AccessID: input.AccessID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot delete trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DeleteTrustCenterAccessPayload{
|
||||||
|
DeletedTrustCenterAccessID: input.AccessID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ConfirmEmail is the resolver for the confirmEmail field.
|
// ConfirmEmail is the resolver for the confirmEmail field.
|
||||||
func (r *mutationResolver) ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error) {
|
func (r *mutationResolver) ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error) {
|
||||||
err := r.usrmgrSvc.ConfirmEmail(ctx, input.Token)
|
err := r.usrmgrSvc.ConfirmEmail(ctx, input.Token)
|
||||||
@@ -2883,7 +2954,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
|||||||
}
|
}
|
||||||
return types.NewReport(report), nil
|
return types.NewReport(report), nil
|
||||||
case coredata.TrustCenterEntityType:
|
case coredata.TrustCenterEntityType:
|
||||||
trustCenter, err := prb.TrustCenters.GetByOrganizationID(ctx, id)
|
trustCenter, 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: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2905,6 +2976,11 @@ func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrustCenters is the resolver for the trustCenters field.
|
||||||
|
func (r *queryResolver) TrustCenters(ctx context.Context, first *int, after *page.CursorKey, last *int, before *page.CursorKey, filter *types.TrustCenterFilter) (*types.TrustCenterConnection, error) {
|
||||||
|
panic(fmt.Errorf("not implemented: TrustCenters - trustCenters"))
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||||
@@ -3167,6 +3243,43 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Organization is the resolver for the organization field.
|
||||||
|
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
|
||||||
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
organization, err := prb.Organizations.Get(ctx, obj.Organization.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewOrganization(organization), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accesses is the resolver for the accesses field.
|
||||||
|
func (r *trustCenterResolver) Accesses(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) (*types.TrustCenterAccessConnection, error) {
|
||||||
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.TrustCenterAccessOrderField]{
|
||||||
|
Field: coredata.TrustCenterAccessOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
if orderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.TrustCenterAccessOrderField]{
|
||||||
|
Field: orderBy.Field,
|
||||||
|
Direction: orderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
result, err := prb.TrustCenterAccesses.ListForTrustCenterID(ctx, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list trust center accesses: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewTrustCenterAccessConnection(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
// People is the resolver for the people field.
|
// People is the resolver for the people field.
|
||||||
func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) {
|
func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) {
|
||||||
prb := r.ProboService(ctx, organizationID.TenantID())
|
prb := r.ProboService(ctx, organizationID.TenantID())
|
||||||
@@ -3369,7 +3482,7 @@ func (r *vendorRiskAssessmentResolver) AssessedBy(ctx context.Context, obj *type
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Organizations is the resolver for the organizations field.
|
// Organizations is the resolver for the organizations field.
|
||||||
func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrganizationOrder) (*types.OrganizationConnection, error) {
|
func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrganizationOrder, filter *types.OrganizationFilter) (*types.OrganizationConnection, error) {
|
||||||
user := UserFromContext(ctx)
|
user := UserFromContext(ctx)
|
||||||
|
|
||||||
// For now, we're not using cursor pagination since we're loading all organizations
|
// For now, we're not using cursor pagination since we're loading all organizations
|
||||||
@@ -3496,6 +3609,9 @@ func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} }
|
|||||||
// TaskConnection returns schema.TaskConnectionResolver implementation.
|
// TaskConnection returns schema.TaskConnectionResolver implementation.
|
||||||
func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &taskConnectionResolver{r} }
|
func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &taskConnectionResolver{r} }
|
||||||
|
|
||||||
|
// TrustCenter returns schema.TrustCenterResolver implementation.
|
||||||
|
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
|
||||||
|
|
||||||
// User returns schema.UserResolver implementation.
|
// User returns schema.UserResolver implementation.
|
||||||
func (r *Resolver) User() schema.UserResolver { return &userResolver{r} }
|
func (r *Resolver) User() schema.UserResolver { return &userResolver{r} }
|
||||||
|
|
||||||
@@ -3547,6 +3663,7 @@ type riskResolver struct{ *Resolver }
|
|||||||
type riskConnectionResolver struct{ *Resolver }
|
type riskConnectionResolver struct{ *Resolver }
|
||||||
type taskResolver struct{ *Resolver }
|
type taskResolver struct{ *Resolver }
|
||||||
type taskConnectionResolver struct{ *Resolver }
|
type taskConnectionResolver struct{ *Resolver }
|
||||||
|
type trustCenterResolver struct{ *Resolver }
|
||||||
type userResolver struct{ *Resolver }
|
type userResolver struct{ *Resolver }
|
||||||
type vendorResolver struct{ *Resolver }
|
type vendorResolver struct{ *Resolver }
|
||||||
type vendorComplianceReportResolver struct{ *Resolver }
|
type vendorComplianceReportResolver struct{ *Resolver }
|
||||||
|
|||||||
29
pkg/server/api/trust/v1/gqlgen.yaml
Normal file
29
pkg/server/api/trust/v1/gqlgen.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
schema: ["schema.graphql"]
|
||||||
|
|
||||||
|
exec:
|
||||||
|
filename: "schema/schema.go"
|
||||||
|
package: "schema"
|
||||||
|
|
||||||
|
model:
|
||||||
|
filename: "types/types.go"
|
||||||
|
package: "types"
|
||||||
|
|
||||||
|
resolver:
|
||||||
|
layout: "follow-schema"
|
||||||
|
dir: "."
|
||||||
|
package: "trust_v1"
|
||||||
|
filename_template: "v1_resolver.go"
|
||||||
|
|
||||||
|
autobind: []
|
||||||
|
call_argument_directives_with_null: true
|
||||||
|
|
||||||
|
models:
|
||||||
|
ID:
|
||||||
|
model:
|
||||||
|
- "github.com/getprobo/probo/pkg/server/api/trust/v1/types.GIDScalar"
|
||||||
|
Datetime:
|
||||||
|
model:
|
||||||
|
- "github.com/99designs/gqlgen/graphql.Time"
|
||||||
|
CursorKey:
|
||||||
|
model:
|
||||||
|
- "github.com/getprobo/probo/pkg/server/api/trust/v1/types.CursorKeyScalar"
|
||||||
277
pkg/server/api/trust/v1/resolver.go
Normal file
277
pkg/server/api/trust/v1/resolver.go
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
//go:generate go run github.com/99designs/gqlgen generate
|
||||||
|
|
||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust_v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||||
|
"github.com/99designs/gqlgen/graphql/playground"
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/securecookie"
|
||||||
|
"github.com/getprobo/probo/pkg/server/api/trust/v1/schema"
|
||||||
|
"github.com/getprobo/probo/pkg/server/api/trust/v1/types"
|
||||||
|
"github.com/getprobo/probo/pkg/trust"
|
||||||
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AuthConfig struct {
|
||||||
|
CookieName string
|
||||||
|
CookieDomain string
|
||||||
|
SessionDuration time.Duration
|
||||||
|
CookieSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
Resolver struct {
|
||||||
|
trustCenterSvc *trust.Service
|
||||||
|
authCfg AuthConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxKey struct{ name string }
|
||||||
|
|
||||||
|
TokenAccessData struct {
|
||||||
|
TrustCenterID gid.GID
|
||||||
|
Email string
|
||||||
|
TenantID gid.TenantID
|
||||||
|
Scope string
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustCenterTokenData struct {
|
||||||
|
TrustCenterID gid.GID `json:"trust_center_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
TenantID gid.TenantID `json:"tenant_id"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenScopeTrustCenterReadOnly = "trust_center_readonly"
|
||||||
|
TokenCookieName = "trust_center_token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sessionContextKey = &ctxKey{name: "session"}
|
||||||
|
userContextKey = &ctxKey{name: "user"}
|
||||||
|
userTenantContextKey = &ctxKey{name: "user_tenants"}
|
||||||
|
tokenAccessContextKey = &ctxKey{name: "token_access"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SessionFromContext(ctx context.Context) *coredata.Session {
|
||||||
|
session, _ := ctx.Value(sessionContextKey).(*coredata.Session)
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserFromContext(ctx context.Context) *coredata.User {
|
||||||
|
user, _ := ctx.Value(userContextKey).(*coredata.User)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func TokenAccessFromContext(ctx context.Context) *TokenAccessData {
|
||||||
|
tokenAccess, _ := ctx.Value(tokenAccessContextKey).(*TokenAccessData)
|
||||||
|
return tokenAccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurrentUserRole(ctx context.Context) types.Role {
|
||||||
|
user := UserFromContext(ctx)
|
||||||
|
tokenAccess := TokenAccessFromContext(ctx)
|
||||||
|
|
||||||
|
if user != nil || tokenAccess != nil {
|
||||||
|
return types.RoleUser
|
||||||
|
}
|
||||||
|
return types.RoleNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMux(
|
||||||
|
logger *log.Logger,
|
||||||
|
usrmgrSvc *usrmgr.Service,
|
||||||
|
trustSvc *trust.Service,
|
||||||
|
authCfg AuthConfig,
|
||||||
|
) *chi.Mux {
|
||||||
|
r := chi.NewMux()
|
||||||
|
|
||||||
|
encryptionKey := trustSvc.GetEncryptionKey()
|
||||||
|
|
||||||
|
r.Handle("/graphql", graphqlHandler(logger, usrmgrSvc, trustSvc, authCfg, encryptionKey))
|
||||||
|
|
||||||
|
r.Handle("/playground", playground.Handler("GraphQL Playground", "/api/trust/v1/graphql"))
|
||||||
|
|
||||||
|
r.Post("/trust-center-access/authenticate", authTokenHandler(trustSvc, authCfg, encryptionKey))
|
||||||
|
r.Delete("/trust-center-access/logout", trustCenterLogoutHandler(authCfg))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey) http.HandlerFunc {
|
||||||
|
var mb int64 = 1 << 20
|
||||||
|
|
||||||
|
c := schema.Config{
|
||||||
|
Resolvers: &Resolver{
|
||||||
|
trustCenterSvc: trustSvc,
|
||||||
|
authCfg: authCfg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Directives.MustBeAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver, role *types.Role) (interface{}, error) {
|
||||||
|
currentRole := GetCurrentUserRole(ctx)
|
||||||
|
|
||||||
|
if role != nil && *role == types.RoleUser && currentRole == types.RoleNone {
|
||||||
|
return nil, fmt.Errorf("access denied: authentication required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
es := schema.NewExecutableSchema(c)
|
||||||
|
|
||||||
|
srv := handler.New(es)
|
||||||
|
|
||||||
|
srv.AddTransport(transport.POST{})
|
||||||
|
srv.AddTransport(transport.GET{})
|
||||||
|
srv.AddTransport(transport.Options{})
|
||||||
|
srv.AddTransport(
|
||||||
|
transport.MultipartForm{
|
||||||
|
MaxMemory: 32 * mb,
|
||||||
|
MaxUploadSize: 50 * mb,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
srv.Use(extension.Introspection{})
|
||||||
|
|
||||||
|
srv.SetRecoverFunc(func(ctx context.Context, err any) error {
|
||||||
|
logger := httpserver.LoggerFromContext(ctx)
|
||||||
|
logger.Error("resolver panic", log.Any("error", err), log.Any("stack", string(debug.Stack())))
|
||||||
|
|
||||||
|
return errors.New("internal server error")
|
||||||
|
})
|
||||||
|
|
||||||
|
return WithSession(usrmgrSvc, trustSvc, authCfg, encryptionKey, srv.ServeHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustService returns a trust service scoped to the given tenant
|
||||||
|
func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
|
||||||
|
return r.trustCenterSvc.WithTenant(tenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTenantService returns a tenant service for the given tenant ID
|
||||||
|
func (r *Resolver) GetTenantService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
|
||||||
|
return r.trustCenterSvc.WithTenant(tenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey, next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
cookieValue, err := securecookie.Get(r, securecookie.DefaultConfig(
|
||||||
|
authCfg.CookieName,
|
||||||
|
authCfg.CookieSecret,
|
||||||
|
))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
sessionID, err := gid.ParseGID(cookieValue)
|
||||||
|
if err == nil {
|
||||||
|
session, err := usrmgrSvc.GetSession(ctx, sessionID)
|
||||||
|
if err == nil {
|
||||||
|
user, err := usrmgrSvc.GetUserBySession(ctx, sessionID)
|
||||||
|
if err == nil {
|
||||||
|
tenantIDs, err := usrmgrSvc.ListTenantsForUserID(ctx, user.ID)
|
||||||
|
if err == nil {
|
||||||
|
ctx = context.WithValue(ctx, sessionContextKey, session)
|
||||||
|
ctx = context.WithValue(ctx, userContextKey, user)
|
||||||
|
ctx = context.WithValue(ctx, userTenantContextKey, &tenantIDs)
|
||||||
|
|
||||||
|
next(w, r.WithContext(ctx))
|
||||||
|
|
||||||
|
if err := usrmgrSvc.UpdateSession(ctx, session); err != nil {
|
||||||
|
panic(fmt.Errorf("failed to update session: %w", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
securecookie.Clear(w, securecookie.DefaultConfig(
|
||||||
|
authCfg.CookieName,
|
||||||
|
authCfg.CookieSecret,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenCookieValue, err := securecookie.Get(r, securecookie.Config{
|
||||||
|
Name: TokenCookieName,
|
||||||
|
Secret: authCfg.CookieSecret,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
encryptedData, err := base64.StdEncoding.DecodeString(tokenCookieValue)
|
||||||
|
if err == nil {
|
||||||
|
decryptedData, err := cipher.Decrypt(encryptedData, encryptionKey)
|
||||||
|
if err == nil {
|
||||||
|
var tokenData TrustCenterTokenData
|
||||||
|
if err := json.Unmarshal(decryptedData, &tokenData); err == nil {
|
||||||
|
if time.Now().Before(tokenData.ExpiresAt) {
|
||||||
|
tenantSvc := trustSvc.WithTenant(tokenData.TenantID)
|
||||||
|
isActive, err := tenantSvc.TrustCenterAccesses.IsAccessActive(ctx, tokenData.TrustCenterID, tokenData.Email)
|
||||||
|
|
||||||
|
if err == nil && isActive {
|
||||||
|
tokenAccess := &TokenAccessData{
|
||||||
|
TrustCenterID: tokenData.TrustCenterID,
|
||||||
|
Email: tokenData.Email,
|
||||||
|
TenantID: tokenData.TenantID,
|
||||||
|
Scope: tokenData.Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, tokenAccessContextKey, tokenAccess)
|
||||||
|
next(w, r.WithContext(ctx))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
securecookie.Clear(w, securecookie.Config{
|
||||||
|
Name: TokenCookieName,
|
||||||
|
Secret: authCfg.CookieSecret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
securecookie.Clear(w, securecookie.Config{
|
||||||
|
Name: TokenCookieName,
|
||||||
|
Secret: authCfg.CookieSecret,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue without authentication for public access
|
||||||
|
next(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
263
pkg/server/api/trust/v1/schema.graphql
Normal file
263
pkg/server/api/trust/v1/schema.graphql
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
# Directives
|
||||||
|
directive @goField(
|
||||||
|
forceResolver: Boolean
|
||||||
|
name: String
|
||||||
|
omittable: Boolean
|
||||||
|
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||||
|
|
||||||
|
directive @goModel(
|
||||||
|
model: String
|
||||||
|
models: [String!]
|
||||||
|
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
|
||||||
|
|
||||||
|
directive @goEnum(value: String) on ENUM_VALUE
|
||||||
|
|
||||||
|
directive @mustBeAuthenticated(role: Role = NONE) on FIELD_DEFINITION | OBJECT
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
NONE
|
||||||
|
USER
|
||||||
|
}
|
||||||
|
|
||||||
|
scalar Datetime
|
||||||
|
scalar CursorKey
|
||||||
|
|
||||||
|
interface Node {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo {
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
hasPreviousPage: Boolean!
|
||||||
|
startCursor: CursorKey
|
||||||
|
endCursor: CursorKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organization implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
logoUrl: String @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DocumentType
|
||||||
|
@goModel(model: "github.com/getprobo/probo/pkg/coredata.DocumentType") {
|
||||||
|
OTHER
|
||||||
|
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeOther")
|
||||||
|
ISMS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeISMS")
|
||||||
|
POLICY
|
||||||
|
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypePolicy")
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentVersion implements Node {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Document implements Node {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
documentType: DocumentType!
|
||||||
|
versions(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): DocumentVersionConnection! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentConnection {
|
||||||
|
edges: [DocumentEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: Document!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentVersionConnection {
|
||||||
|
edges: [DocumentVersionEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentVersionEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: DocumentVersion!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Framework implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Report implements Node {
|
||||||
|
id: ID!
|
||||||
|
filename: String!
|
||||||
|
downloadUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Audit implements Node {
|
||||||
|
id: ID!
|
||||||
|
framework: Framework! @goField(forceResolver: true)
|
||||||
|
report: Report @goField(forceResolver: true)
|
||||||
|
reportUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditConnection {
|
||||||
|
edges: [AuditEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: Audit!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VendorCategory
|
||||||
|
@goModel(model: "github.com/getprobo/probo/pkg/coredata.VendorCategory") {
|
||||||
|
ANALYTICS
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryAnalytics"
|
||||||
|
)
|
||||||
|
CLOUD_MONITORING
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||||
|
)
|
||||||
|
CLOUD_PROVIDER
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudProvider"
|
||||||
|
)
|
||||||
|
COLLABORATION
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCollaboration"
|
||||||
|
)
|
||||||
|
CUSTOMER_SUPPORT
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
||||||
|
)
|
||||||
|
DATA_STORAGE_AND_PROCESSING
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
||||||
|
)
|
||||||
|
DOCUMENT_MANAGEMENT
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
||||||
|
)
|
||||||
|
EMPLOYEE_MANAGEMENT
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||||
|
)
|
||||||
|
ENGINEERING
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEngineering"
|
||||||
|
)
|
||||||
|
FINANCE
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryFinance"
|
||||||
|
)
|
||||||
|
IDENTITY_PROVIDER
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
||||||
|
)
|
||||||
|
IT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIT")
|
||||||
|
MARKETING
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryMarketing"
|
||||||
|
)
|
||||||
|
OFFICE_OPERATIONS
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||||
|
)
|
||||||
|
OTHER
|
||||||
|
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOther")
|
||||||
|
PASSWORD_MANAGEMENT
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||||
|
)
|
||||||
|
PRODUCT_AND_DESIGN
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
||||||
|
)
|
||||||
|
PROFESSIONAL_SERVICES
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||||
|
)
|
||||||
|
RECRUITING
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryRecruiting"
|
||||||
|
)
|
||||||
|
SALES
|
||||||
|
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySales")
|
||||||
|
SECURITY
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySecurity"
|
||||||
|
)
|
||||||
|
VERSION_CONTROL
|
||||||
|
@goEnum(
|
||||||
|
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryVersionControl"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vendor implements Node {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
category: VendorCategory!
|
||||||
|
websiteUrl: String
|
||||||
|
privacyPolicyUrl: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorConnection {
|
||||||
|
edges: [VendorEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: Vendor!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustCenter implements Node {
|
||||||
|
id: ID!
|
||||||
|
active: Boolean!
|
||||||
|
slug: String!
|
||||||
|
organization: Organization! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
documents(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): DocumentConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
audits(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): AuditConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
vendors(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
): VendorConnection! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
input ExportDocumentVersionPDFInput {
|
||||||
|
documentVersionId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportDocumentVersionPDFPayload {
|
||||||
|
data: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
exportDocumentVersionPDF(
|
||||||
|
input: ExportDocumentVersionPDFInput!
|
||||||
|
): ExportDocumentVersionPDFPayload! @mustBeAuthenticated(role: USER)
|
||||||
|
}
|
||||||
9013
pkg/server/api/trust/v1/schema/schema.go
Normal file
9013
pkg/server/api/trust/v1/schema/schema.go
Normal file
File diff suppressed because it is too large
Load Diff
153
pkg/server/api/trust/v1/trust_center_access_handler.go
Normal file
153
pkg/server/api/trust/v1/trust_center_access_handler.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust_v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||||
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
|
"github.com/getprobo/probo/pkg/securecookie"
|
||||||
|
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||||
|
"github.com/getprobo/probo/pkg/trust"
|
||||||
|
"go.gearno.de/kit/httpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
AuthTokenRequest struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthTokenResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
TrustCenterID string `json:"trust_center_id,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req AuthTokenRequest
|
||||||
|
// Limit request body size to 1KB to prevent DoS attacks
|
||||||
|
limitedReader := http.MaxBytesReader(w, r.Body, 1024)
|
||||||
|
if err := json.NewDecoder(limitedReader).Decode(&req); err != nil {
|
||||||
|
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("cannot decode body: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Token == "" {
|
||||||
|
httpserver.RenderJSON(w, http.StatusBadRequest, AuthTokenResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "Token is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessData, err := validateTrustCenterAccessToken(r.Context(), trustSvc, authCfg, req.Token)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderJSON(w, http.StatusUnauthorized, AuthTokenResponse{
|
||||||
|
Success: false,
|
||||||
|
Message: "Invalid or expired token",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenData := TrustCenterTokenData{
|
||||||
|
TrustCenterID: accessData.TrustCenterID,
|
||||||
|
Email: accessData.Email,
|
||||||
|
TenantID: accessData.TrustCenterID.TenantID(),
|
||||||
|
Scope: TokenScopeTrustCenterReadOnly,
|
||||||
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenBytes, err := json.Marshal(tokenData)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to serialize token data: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedTokenData, err := cipher.Encrypt(tokenBytes, encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to encrypt token data: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedTokenString := base64.StdEncoding.EncodeToString(encryptedTokenData)
|
||||||
|
|
||||||
|
cookieConfig := securecookie.Config{
|
||||||
|
Name: TokenCookieName,
|
||||||
|
Secret: authCfg.CookieSecret,
|
||||||
|
Domain: authCfg.CookieDomain,
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: int(24 * time.Hour / time.Second), // 24 hours
|
||||||
|
Secure: true,
|
||||||
|
HTTPOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := securecookie.Set(w, cookieConfig, encryptedTokenString); err != nil {
|
||||||
|
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to set cookie: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, AuthTokenResponse{
|
||||||
|
Success: true,
|
||||||
|
TrustCenterID: accessData.TrustCenterID.String(),
|
||||||
|
Message: "Authentication successful",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service, authCfg AuthConfig, tokenString string) (*probo.TrustCenterAccessData, error) {
|
||||||
|
token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData](
|
||||||
|
authCfg.CookieSecret,
|
||||||
|
probo.TokenTypeTrustCenterAccess,
|
||||||
|
tokenString,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantID := token.Data.TrustCenterID.TenantID()
|
||||||
|
tenantSvc := trustSvc.WithTenant(tenantID)
|
||||||
|
|
||||||
|
return tenantSvc.TrustCenterAccesses.ValidateToken(ctx, tokenString)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustCenterLogoutHandler(authCfg AuthConfig) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cookieConfig := securecookie.Config{
|
||||||
|
Name: TokenCookieName,
|
||||||
|
Secret: authCfg.CookieSecret,
|
||||||
|
Domain: authCfg.CookieDomain,
|
||||||
|
Path: "/",
|
||||||
|
MaxAge: -1,
|
||||||
|
Secure: true,
|
||||||
|
HTTPOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
securecookie.Clear(w, cookieConfig)
|
||||||
|
|
||||||
|
w.Header().Set("Clear-Site-Data", "*")
|
||||||
|
|
||||||
|
httpserver.RenderJSON(w, http.StatusOK, map[string]bool{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
47
pkg/server/api/trust/v1/types/audit.go
Normal file
47
pkg/server/api/trust/v1/types/audit.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAuditConnection(
|
||||||
|
p *page.Page[*coredata.Audit, coredata.AuditOrderField],
|
||||||
|
) *AuditConnection {
|
||||||
|
edges := make([]*AuditEdge, len(p.Data))
|
||||||
|
for i, audit := range p.Data {
|
||||||
|
edges[i] = NewAuditEdge(audit, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AuditConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAudit(a *coredata.Audit) *Audit {
|
||||||
|
return &Audit{
|
||||||
|
ID: a.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuditEdge(a *coredata.Audit, orderField coredata.AuditOrderField) *AuditEdge {
|
||||||
|
return &AuditEdge{
|
||||||
|
Node: NewAudit(a),
|
||||||
|
Cursor: a.CursorKey(orderField),
|
||||||
|
}
|
||||||
|
}
|
||||||
70
pkg/server/api/trust/v1/types/cursorkey.go
Normal file
70
pkg/server/api/trust/v1/types/cursorkey.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCursor[O page.OrderField](
|
||||||
|
first *int,
|
||||||
|
after *page.CursorKey,
|
||||||
|
last *int,
|
||||||
|
before *page.CursorKey,
|
||||||
|
orderBy page.OrderBy[O],
|
||||||
|
) *page.Cursor[O] {
|
||||||
|
var (
|
||||||
|
size int
|
||||||
|
from *page.CursorKey
|
||||||
|
direction = page.Head
|
||||||
|
)
|
||||||
|
|
||||||
|
if first != nil {
|
||||||
|
size = *first
|
||||||
|
direction = page.Head
|
||||||
|
from = after
|
||||||
|
} else if last != nil {
|
||||||
|
size = *last
|
||||||
|
direction = page.Tail
|
||||||
|
from = before
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewCursor(size, from, direction, orderBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarshalCursorKeyScalar(ck page.CursorKey) graphql.Marshaler {
|
||||||
|
return graphql.WriterFunc(func(w io.Writer) {
|
||||||
|
_, _ = w.Write([]byte(strconv.Quote(ck.String())))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalCursorKeyScalar(v interface{}) (page.CursorKey, error) {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return page.CursorKeyNil, errors.New("must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
ck, err := page.ParseCursorKey(s)
|
||||||
|
if err != nil {
|
||||||
|
return page.CursorKeyNil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ck, nil
|
||||||
|
}
|
||||||
49
pkg/server/api/trust/v1/types/document.go
Normal file
49
pkg/server/api/trust/v1/types/document.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDocumentConnection(
|
||||||
|
p *page.Page[*coredata.Document, coredata.DocumentOrderField],
|
||||||
|
) *DocumentConnection {
|
||||||
|
edges := make([]*DocumentEdge, len(p.Data))
|
||||||
|
for i, document := range p.Data {
|
||||||
|
edges[i] = NewDocumentEdge(document, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DocumentConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDocument(d *coredata.Document) *Document {
|
||||||
|
return &Document{
|
||||||
|
ID: d.ID,
|
||||||
|
Title: d.Title,
|
||||||
|
DocumentType: d.DocumentType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDocumentEdge(d *coredata.Document, orderField coredata.DocumentOrderField) *DocumentEdge {
|
||||||
|
return &DocumentEdge{
|
||||||
|
Node: NewDocument(d),
|
||||||
|
Cursor: d.CursorKey(orderField),
|
||||||
|
}
|
||||||
|
}
|
||||||
47
pkg/server/api/trust/v1/types/document_version.go
Normal file
47
pkg/server/api/trust/v1/types/document_version.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDocumentVersionConnection(
|
||||||
|
p *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField],
|
||||||
|
) *DocumentVersionConnection {
|
||||||
|
edges := make([]*DocumentVersionEdge, len(p.Data))
|
||||||
|
for i, documentVersion := range p.Data {
|
||||||
|
edges[i] = NewDocumentVersionEdge(documentVersion, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DocumentVersionConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDocumentVersion(dv *coredata.DocumentVersion) *DocumentVersion {
|
||||||
|
return &DocumentVersion{
|
||||||
|
ID: dv.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDocumentVersionEdge(dv *coredata.DocumentVersion, orderField coredata.DocumentVersionOrderField) *DocumentVersionEdge {
|
||||||
|
return &DocumentVersionEdge{
|
||||||
|
Node: NewDocumentVersion(dv),
|
||||||
|
Cursor: dv.CursorKey(orderField),
|
||||||
|
}
|
||||||
|
}
|
||||||
26
pkg/server/api/trust/v1/types/framework.go
Normal file
26
pkg/server/api/trust/v1/types/framework.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFramework(f *coredata.Framework) *Framework {
|
||||||
|
return &Framework{
|
||||||
|
ID: f.ID,
|
||||||
|
Name: f.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
pkg/server/api/trust/v1/types/gid.go
Normal file
44
pkg/server/api/trust/v1/types/gid.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MarshalGIDScalar(id gid.GID) graphql.Marshaler {
|
||||||
|
return graphql.WriterFunc(func(w io.Writer) {
|
||||||
|
w.Write([]byte(strconv.Quote(id.String())))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalGIDScalar(v interface{}) (gid.GID, error) {
|
||||||
|
s, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return gid.Nil, errors.New("must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := gid.ParseGID(s)
|
||||||
|
if err != nil {
|
||||||
|
return gid.Nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
26
pkg/server/api/trust/v1/types/organization.go
Normal file
26
pkg/server/api/trust/v1/types/organization.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewOrganization(o *coredata.Organization) *Organization {
|
||||||
|
return &Organization{
|
||||||
|
ID: o.ID,
|
||||||
|
Name: o.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
39
pkg/server/api/trust/v1/types/pageinfo.go
Normal file
39
pkg/server/api/trust/v1/types/pageinfo.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
||||||
|
var (
|
||||||
|
startCursor *page.CursorKey
|
||||||
|
endCursor *page.CursorKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(p.Data) > 0 {
|
||||||
|
startCursor = ref.Ref(p.First().CursorKey(p.Cursor.OrderBy.Field))
|
||||||
|
endCursor = ref.Ref(p.Last().CursorKey(p.Cursor.OrderBy.Field))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PageInfo{
|
||||||
|
HasNextPage: p.Info.HasNext,
|
||||||
|
HasPreviousPage: p.Info.HasPrev,
|
||||||
|
StartCursor: startCursor,
|
||||||
|
EndCursor: endCursor,
|
||||||
|
}
|
||||||
|
}
|
||||||
26
pkg/server/api/trust/v1/types/report.go
Normal file
26
pkg/server/api/trust/v1/types/report.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewReport(r *coredata.Report) *Report {
|
||||||
|
return &Report{
|
||||||
|
ID: r.ID,
|
||||||
|
Filename: r.Filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
27
pkg/server/api/trust/v1/types/trust_center.go
Normal file
27
pkg/server/api/trust/v1/types/trust_center.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTrustCenter(tc *coredata.TrustCenter) *TrustCenter {
|
||||||
|
return &TrustCenter{
|
||||||
|
ID: tc.ID,
|
||||||
|
Active: tc.Active,
|
||||||
|
Slug: tc.Slug,
|
||||||
|
}
|
||||||
|
}
|
||||||
212
pkg/server/api/trust/v1/types/types.go
Normal file
212
pkg/server/api/trust/v1/types/types.go
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
IsNode()
|
||||||
|
GetID() gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Audit struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Framework *Framework `json:"framework"`
|
||||||
|
Report *Report `json:"report,omitempty"`
|
||||||
|
ReportURL *string `json:"reportUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Audit) IsNode() {}
|
||||||
|
func (this Audit) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type AuditConnection struct {
|
||||||
|
Edges []*AuditEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *Audit `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Document struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
DocumentType coredata.DocumentType `json:"documentType"`
|
||||||
|
Versions *DocumentVersionConnection `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Document) IsNode() {}
|
||||||
|
func (this Document) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type DocumentConnection struct {
|
||||||
|
Edges []*DocumentEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *Document `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentVersion struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (DocumentVersion) IsNode() {}
|
||||||
|
func (this DocumentVersion) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type DocumentVersionConnection struct {
|
||||||
|
Edges []*DocumentVersionEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentVersionEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *DocumentVersion `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportDocumentVersionPDFInput struct {
|
||||||
|
DocumentVersionID gid.GID `json:"documentVersionId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportDocumentVersionPDFPayload struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Framework struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Framework) IsNode() {}
|
||||||
|
func (this Framework) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type Mutation struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Organization struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
LogoURL *string `json:"logoUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Organization) IsNode() {}
|
||||||
|
func (this Organization) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type PageInfo struct {
|
||||||
|
HasNextPage bool `json:"hasNextPage"`
|
||||||
|
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||||
|
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
|
||||||
|
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Report struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
DownloadURL *string `json:"downloadUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Report) IsNode() {}
|
||||||
|
func (this Report) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type TrustCenter struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Organization *Organization `json:"organization"`
|
||||||
|
Documents *DocumentConnection `json:"documents"`
|
||||||
|
Audits *AuditConnection `json:"audits"`
|
||||||
|
Vendors *VendorConnection `json:"vendors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (TrustCenter) IsNode() {}
|
||||||
|
func (this TrustCenter) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type Vendor struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Category coredata.VendorCategory `json:"category"`
|
||||||
|
WebsiteURL *string `json:"websiteUrl,omitempty"`
|
||||||
|
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Vendor) IsNode() {}
|
||||||
|
func (this Vendor) GetID() gid.GID { return this.ID }
|
||||||
|
|
||||||
|
type VendorConnection struct {
|
||||||
|
Edges []*VendorEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorEdge struct {
|
||||||
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
|
Node *Vendor `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleNone Role = "NONE"
|
||||||
|
RoleUser Role = "USER"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllRole = []Role{
|
||||||
|
RoleNone,
|
||||||
|
RoleUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Role) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case RoleNone, RoleUser:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Role) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Role) UnmarshalGQL(v any) error {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("enums must be strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = Role(str)
|
||||||
|
if !e.IsValid() {
|
||||||
|
return fmt.Errorf("%s is not a valid Role", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Role) MarshalGQL(w io.Writer) {
|
||||||
|
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Role) UnmarshalJSON(b []byte) error {
|
||||||
|
s, err := strconv.Unquote(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.UnmarshalGQL(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Role) MarshalJSON() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
e.MarshalGQL(&buf)
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
51
pkg/server/api/trust/v1/types/vendor.go
Normal file
51
pkg/server/api/trust/v1/types/vendor.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewVendorConnection(
|
||||||
|
p *page.Page[*coredata.Vendor, coredata.VendorOrderField],
|
||||||
|
) *VendorConnection {
|
||||||
|
edges := make([]*VendorEdge, len(p.Data))
|
||||||
|
for i, vendor := range p.Data {
|
||||||
|
edges[i] = NewVendorEdge(vendor, p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VendorConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: NewPageInfo(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendor(v *coredata.Vendor) *Vendor {
|
||||||
|
return &Vendor{
|
||||||
|
ID: v.ID,
|
||||||
|
Name: v.Name,
|
||||||
|
Category: v.Category,
|
||||||
|
WebsiteURL: v.WebsiteURL,
|
||||||
|
PrivacyPolicyURL: v.PrivacyPolicyURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorEdge(v *coredata.Vendor, orderField coredata.VendorOrderField) *VendorEdge {
|
||||||
|
return &VendorEdge{
|
||||||
|
Node: NewVendor(v),
|
||||||
|
Cursor: v.CursorKey(orderField),
|
||||||
|
}
|
||||||
|
}
|
||||||
258
pkg/server/api/trust/v1/v1_resolver.go
Normal file
258
pkg/server/api/trust/v1/v1_resolver.go
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package trust_v1
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.76
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"github.com/getprobo/probo/pkg/server/api/trust/v1/schema"
|
||||||
|
"github.com/getprobo/probo/pkg/server/api/trust/v1/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Framework is the resolver for the framework field.
|
||||||
|
func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
framework, err := trust.Frameworks.Get(ctx, audit.FrameworkID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load framework: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewFramework(framework), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report is the resolver for the report field.
|
||||||
|
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if audit.ReportID == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := trust.Reports.Get(ctx, *audit.ReportID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewReport(report), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportURL is the resolver for the reportUrl field.
|
||||||
|
func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if audit.ReportID == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := trust.Audits.GenerateReportURL(ctx, obj.ID, 15*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate report URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Versions is the resolver for the versions field.
|
||||||
|
func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentVersionConnection, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{
|
||||||
|
Field: coredata.DocumentVersionOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
// For the public trust API, only return published versions
|
||||||
|
page, err := trust.Documents.ListVersions(ctx, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot list document versions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to only published versions and create edges directly
|
||||||
|
publishedEdges := make([]*types.DocumentVersionEdge, 0)
|
||||||
|
for _, version := range page.Data {
|
||||||
|
if version.Status == coredata.DocumentStatusPublished {
|
||||||
|
edge := &types.DocumentVersionEdge{
|
||||||
|
Cursor: version.CursorKey(pageOrderBy.Field),
|
||||||
|
Node: types.NewDocumentVersion(version),
|
||||||
|
}
|
||||||
|
publishedEdges = append(publishedEdges, edge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DocumentVersionConnection{
|
||||||
|
Edges: publishedEdges,
|
||||||
|
PageInfo: types.NewPageInfo(page),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field.
|
||||||
|
func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) {
|
||||||
|
trust := r.trustCenterSvc.WithTenant(input.DocumentVersionID.TenantID())
|
||||||
|
|
||||||
|
pdf, err := trust.Documents.ExportPDF(ctx, input.DocumentVersionID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot export document version PDF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.ExportDocumentVersionPDFPayload{
|
||||||
|
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogoURL is the resolver for the logoUrl field.
|
||||||
|
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
return trust.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustCenterBySlug is the resolver for the trustCenterBySlug field.
|
||||||
|
func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) {
|
||||||
|
trust := r.trustCenterSvc.WithTenant(gid.NewTenantID())
|
||||||
|
|
||||||
|
trustCenter, err := trust.TrustCenters.GetBySlug(ctx, slug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trustCenter.Active {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := types.NewTrustCenter(trustCenter)
|
||||||
|
|
||||||
|
orgTrust := r.trustCenterSvc.WithTenant(trustCenter.TenantID)
|
||||||
|
org, err := orgTrust.Organizations.Get(ctx, trustCenter.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Organization = types.NewOrganization(org)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadURL is the resolver for the downloadUrl field.
|
||||||
|
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
|
||||||
|
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
url, err := trust.Reports.GenerateDownloadURL(ctx, obj.ID, 5*time.Minute)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate download URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organization is the resolver for the organization field.
|
||||||
|
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
|
||||||
|
return obj.Organization, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Documents is the resolver for the documents field.
|
||||||
|
func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) {
|
||||||
|
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
|
||||||
|
Field: coredata.DocumentOrderFieldTitle,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
documentPage, err := trust.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot list public documents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewDocumentConnection(documentPage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audits is the resolver for the audits field.
|
||||||
|
func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) {
|
||||||
|
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.AuditOrderField]{
|
||||||
|
Field: coredata.AuditOrderFieldValidFrom,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
auditPage, err := trust.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot list public audits: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewAuditConnection(auditPage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendors is the resolver for the vendors field.
|
||||||
|
func (r *trustCenterResolver) Vendors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) {
|
||||||
|
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.VendorOrderField]{
|
||||||
|
Field: coredata.VendorOrderFieldName,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
}
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
vendorPage, err := trust.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot list public vendors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewVendorConnection(vendorPage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit returns schema.AuditResolver implementation.
|
||||||
|
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
|
||||||
|
|
||||||
|
// Document returns schema.DocumentResolver implementation.
|
||||||
|
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
|
||||||
|
|
||||||
|
// Mutation returns schema.MutationResolver implementation.
|
||||||
|
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
// Organization returns schema.OrganizationResolver implementation.
|
||||||
|
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
|
||||||
|
|
||||||
|
// Query returns schema.QueryResolver implementation.
|
||||||
|
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
// Report returns schema.ReportResolver implementation.
|
||||||
|
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
|
||||||
|
|
||||||
|
// TrustCenter returns schema.TrustCenterResolver implementation.
|
||||||
|
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
|
||||||
|
|
||||||
|
type auditResolver struct{ *Resolver }
|
||||||
|
type documentResolver struct{ *Resolver }
|
||||||
|
type mutationResolver struct{ *Resolver }
|
||||||
|
type organizationResolver struct{ *Resolver }
|
||||||
|
type queryResolver struct{ *Resolver }
|
||||||
|
type reportResolver struct{ *Resolver }
|
||||||
|
type trustCenterResolver struct{ *Resolver }
|
||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
"github.com/getprobo/probo/pkg/probo"
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
"github.com/getprobo/probo/pkg/saferedirect"
|
"github.com/getprobo/probo/pkg/saferedirect"
|
||||||
"github.com/getprobo/probo/pkg/server/api"
|
"github.com/getprobo/probo/pkg/server/api"
|
||||||
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
|
|
||||||
"github.com/getprobo/probo/pkg/server/web"
|
"github.com/getprobo/probo/pkg/server/web"
|
||||||
|
"github.com/getprobo/probo/pkg/trust"
|
||||||
"github.com/getprobo/probo/pkg/usrmgr"
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
@@ -37,7 +37,8 @@ type Config struct {
|
|||||||
ExtraHeaderFields map[string]string
|
ExtraHeaderFields map[string]string
|
||||||
Probo *probo.Service
|
Probo *probo.Service
|
||||||
Usrmgr *usrmgr.Service
|
Usrmgr *usrmgr.Service
|
||||||
Auth console_v1.AuthConfig
|
Trust *trust.Service
|
||||||
|
Auth api.AuthConfig
|
||||||
ConnectorRegistry *connector.ConnectorRegistry
|
ConnectorRegistry *connector.ConnectorRegistry
|
||||||
Agent *agents.Agent
|
Agent *agents.Agent
|
||||||
SafeRedirect *saferedirect.SafeRedirect
|
SafeRedirect *saferedirect.SafeRedirect
|
||||||
@@ -59,6 +60,7 @@ func NewServer(cfg Config) (*Server, error) {
|
|||||||
AllowedOrigins: cfg.AllowedOrigins,
|
AllowedOrigins: cfg.AllowedOrigins,
|
||||||
Probo: cfg.Probo,
|
Probo: cfg.Probo,
|
||||||
Usrmgr: cfg.Usrmgr,
|
Usrmgr: cfg.Usrmgr,
|
||||||
|
Trust: cfg.Trust,
|
||||||
Auth: cfg.Auth,
|
Auth: cfg.Auth,
|
||||||
ConnectorRegistry: cfg.ConnectorRegistry,
|
ConnectorRegistry: cfg.ConnectorRegistry,
|
||||||
SafeRedirect: cfg.SafeRedirect,
|
SafeRedirect: cfg.SafeRedirect,
|
||||||
|
|||||||
99
pkg/trust/audit_service.go
Normal file
99
pkg/trust/audit_service.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuditService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AuditService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
auditID gid.GID,
|
||||||
|
) (*coredata.Audit, error) {
|
||||||
|
audit := &coredata.Audit{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
return audit.LoadByID(ctx, conn, s.svc.scope, auditID)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return audit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AuditService) ListForOrganizationId(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.AuditOrderField],
|
||||||
|
) (*page.Page[*coredata.Audit, coredata.AuditOrderField], error) {
|
||||||
|
var audits coredata.Audits
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
filter := coredata.NewAuditTrustCenterFilter()
|
||||||
|
err := audits.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load audits: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(audits, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AuditService) GenerateReportURL(
|
||||||
|
ctx context.Context,
|
||||||
|
auditID gid.GID,
|
||||||
|
expiresIn time.Duration,
|
||||||
|
) (*string, error) {
|
||||||
|
audit, err := s.Get(ctx, auditID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get audit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if audit.ReportID == nil {
|
||||||
|
return nil, fmt.Errorf("audit has no report")
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := s.svc.Reports.GenerateDownloadURL(ctx, *audit.ReportID, expiresIn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate report download URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
219
pkg/trust/document_service.go
Normal file
219
pkg/trust/document_service.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/docgen"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/html2pdf"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DocumentService struct {
|
||||||
|
svc *TenantService
|
||||||
|
html2pdfConverter *html2pdf.Converter
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListVersions lists all versions of a document
|
||||||
|
func (s *DocumentService) ListVersions(
|
||||||
|
ctx context.Context,
|
||||||
|
documentID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.DocumentVersionOrderField],
|
||||||
|
) (*page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField], error) {
|
||||||
|
var documentVersions coredata.DocumentVersions
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
return documentVersions.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(documentVersions, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DocumentService) ListForOrganizationId(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.DocumentOrderField],
|
||||||
|
) (*page.Page[*coredata.Document, coredata.DocumentOrderField], error) {
|
||||||
|
var documents coredata.Documents
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
filter := coredata.NewDocumentTrustCenterFilter()
|
||||||
|
err := documents.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load documents: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(documents, cursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DocumentService) ExportPDF(
|
||||||
|
ctx context.Context,
|
||||||
|
documentVersionID gid.GID,
|
||||||
|
) ([]byte, error) {
|
||||||
|
document := &coredata.Document{}
|
||||||
|
version := &coredata.DocumentVersion{}
|
||||||
|
owner := &coredata.People{}
|
||||||
|
publishedBy := &coredata.People{}
|
||||||
|
signatures := coredata.DocumentVersionSignatures{}
|
||||||
|
peopleMap := make(map[gid.GID]*coredata.People)
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := version.LoadByID(ctx, conn, s.svc.scope, documentVersionID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load document version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := document.LoadByID(ctx, conn, s.svc.scope, version.DocumentID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load document: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !document.ShowOnTrustCenter {
|
||||||
|
return fmt.Errorf("document not visible on trust center")
|
||||||
|
}
|
||||||
|
|
||||||
|
if version.PublishedBy != nil {
|
||||||
|
if err := publishedBy.LoadByID(ctx, conn, s.svc.scope, *version.PublishedBy); err != nil {
|
||||||
|
return fmt.Errorf("cannot load published by person: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := page.NewCursor(
|
||||||
|
100,
|
||||||
|
nil,
|
||||||
|
page.Head,
|
||||||
|
page.OrderBy[coredata.DocumentVersionSignatureOrderField]{
|
||||||
|
Field: coredata.DocumentVersionSignatureOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionAsc,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := signatures.LoadByDocumentVersionID(ctx, conn, s.svc.scope, documentVersionID, cursor); err != nil {
|
||||||
|
return fmt.Errorf("cannot load document version signatures: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := owner.LoadByID(ctx, conn, s.svc.scope, document.OwnerID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load document owner: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: refactor this to use a single query
|
||||||
|
for _, sig := range signatures {
|
||||||
|
if _, ok := peopleMap[sig.SignedBy]; !ok {
|
||||||
|
people := &coredata.People{}
|
||||||
|
if err := people.LoadByID(ctx, conn, s.svc.scope, sig.SignedBy); err != nil {
|
||||||
|
return fmt.Errorf("cannot load people %q: %w", sig.SignedBy, err)
|
||||||
|
}
|
||||||
|
peopleMap[sig.SignedBy] = people
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := peopleMap[sig.RequestedBy]; !ok {
|
||||||
|
people := &coredata.People{}
|
||||||
|
if err := people.LoadByID(ctx, conn, s.svc.scope, sig.RequestedBy); err != nil {
|
||||||
|
return fmt.Errorf("cannot load people %q: %w", sig.RequestedBy, err)
|
||||||
|
}
|
||||||
|
peopleMap[sig.RequestedBy] = people
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
classification := docgen.ClassificationInternal
|
||||||
|
switch document.DocumentType {
|
||||||
|
case coredata.DocumentTypePolicy:
|
||||||
|
classification = docgen.ClassificationConfidential
|
||||||
|
case coredata.DocumentTypeISMS:
|
||||||
|
classification = docgen.ClassificationSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
docData := docgen.DocumentData{
|
||||||
|
Title: version.Title,
|
||||||
|
Content: version.Content,
|
||||||
|
Version: version.VersionNumber,
|
||||||
|
Classification: classification,
|
||||||
|
Approver: owner.FullName,
|
||||||
|
Description: version.Changelog,
|
||||||
|
PublishedAt: version.PublishedAt,
|
||||||
|
PublishedBy: publishedBy.FullName,
|
||||||
|
Signatures: make([]docgen.SignatureData, len(signatures)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sig := range signatures {
|
||||||
|
docData.Signatures[i] = docgen.SignatureData{
|
||||||
|
SignedBy: peopleMap[sig.SignedBy].FullName,
|
||||||
|
SignedAt: sig.SignedAt,
|
||||||
|
State: sig.State,
|
||||||
|
RequestedAt: sig.RequestedAt,
|
||||||
|
RequestedBy: peopleMap[sig.RequestedBy].FullName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlContent, err := docgen.RenderHTML(docData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate HTML: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := html2pdf.RenderConfig{
|
||||||
|
PageFormat: html2pdf.PageFormatA4,
|
||||||
|
Orientation: html2pdf.OrientationPortrait,
|
||||||
|
MarginTop: html2pdf.NewMarginInches(1.0),
|
||||||
|
MarginBottom: html2pdf.NewMarginInches(1.0),
|
||||||
|
MarginLeft: html2pdf.NewMarginInches(1.0),
|
||||||
|
MarginRight: html2pdf.NewMarginInches(1.0),
|
||||||
|
PrintBackground: true,
|
||||||
|
Scale: 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfReader, err := s.html2pdfConverter.GeneratePDF(ctx, htmlContent, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot generate PDF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfData, err := io.ReadAll(pdfReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read PDF data: %w", err)
|
||||||
|
}
|
||||||
|
return pdfData, nil
|
||||||
|
}
|
||||||
51
pkg/trust/framework_service.go
Normal file
51
pkg/trust/framework_service.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FrameworkService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FrameworkService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
frameworkID gid.GID,
|
||||||
|
) (*coredata.Framework, error) {
|
||||||
|
framework := &coredata.Framework{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load framework: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return framework, nil
|
||||||
|
}
|
||||||
97
pkg/trust/organization_service.go
Normal file
97
pkg/trust/organization_service.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrganizationService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s OrganizationService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
) (*coredata.Organization, error) {
|
||||||
|
organization := &coredata.Organization{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
err := organization.LoadByID(
|
||||||
|
ctx,
|
||||||
|
conn,
|
||||||
|
s.svc.scope,
|
||||||
|
organizationID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return organization, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s OrganizationService) GenerateLogoURL(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
expiresIn time.Duration,
|
||||||
|
) (*string, error) {
|
||||||
|
organization, err := s.Get(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization.LogoObjectKey == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||||
|
|
||||||
|
encodedFilename := url.QueryEscape(organization.Name)
|
||||||
|
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(organization.LogoObjectKey),
|
||||||
|
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
|
||||||
|
}
|
||||||
84
pkg/trust/report_service.go
Normal file
84
pkg/trust/report_service.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReportService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ReportService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
reportID gid.GID,
|
||||||
|
) (*coredata.Report, error) {
|
||||||
|
report := &coredata.Report{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
err := report.LoadByID(ctx, conn, s.svc.scope, reportID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ReportService) GenerateDownloadURL(
|
||||||
|
ctx context.Context,
|
||||||
|
reportID gid.GID,
|
||||||
|
expiresIn time.Duration,
|
||||||
|
) (*string, error) {
|
||||||
|
report, err := s.Get(ctx, reportID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get report: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||||
|
|
||||||
|
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(s.svc.bucket),
|
||||||
|
Key: aws.String(report.ObjectKey),
|
||||||
|
ResponseCacheControl: aws.String("max-age=3600, public"),
|
||||||
|
ResponseContentType: aws.String(report.MimeType),
|
||||||
|
ResponseContentDisposition: aws.String(fmt.Sprintf("attachment; filename=\"%s\"", report.Filename)),
|
||||||
|
}, func(opts *s3.PresignOptions) {
|
||||||
|
opts.Expires = expiresIn
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot presign GetObject request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &presignedReq.URL, nil
|
||||||
|
}
|
||||||
108
pkg/trust/service.go
Normal file
108
pkg/trust/service.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/html2pdf"
|
||||||
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Service struct {
|
||||||
|
pg *pg.Client
|
||||||
|
s3 *s3.Client
|
||||||
|
bucket string
|
||||||
|
proboSvc *probo.Service
|
||||||
|
encryptionKey cipher.EncryptionKey
|
||||||
|
tokenSecret string
|
||||||
|
usrmgr *usrmgr.Service
|
||||||
|
html2pdfConverter *html2pdf.Converter
|
||||||
|
}
|
||||||
|
|
||||||
|
TenantService struct {
|
||||||
|
pg *pg.Client
|
||||||
|
s3 *s3.Client
|
||||||
|
bucket string
|
||||||
|
scope coredata.Scoper
|
||||||
|
proboSvc *probo.Service
|
||||||
|
encryptionKey cipher.EncryptionKey
|
||||||
|
tokenSecret string
|
||||||
|
usrmgr *usrmgr.Service
|
||||||
|
html2pdfConverter *html2pdf.Converter
|
||||||
|
TrustCenters *TrustCenterService
|
||||||
|
Documents *DocumentService
|
||||||
|
Audits *AuditService
|
||||||
|
Vendors *VendorService
|
||||||
|
Frameworks *FrameworkService
|
||||||
|
TrustCenterAccesses *TrustCenterAccessService
|
||||||
|
Reports *ReportService
|
||||||
|
Organizations *OrganizationService
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewService(
|
||||||
|
pgClient *pg.Client,
|
||||||
|
s3Client *s3.Client,
|
||||||
|
bucket string,
|
||||||
|
encryptionKey cipher.EncryptionKey,
|
||||||
|
tokenSecret string,
|
||||||
|
usrmgr *usrmgr.Service,
|
||||||
|
html2pdfConverter *html2pdf.Converter,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
pg: pgClient,
|
||||||
|
s3: s3Client,
|
||||||
|
bucket: bucket,
|
||||||
|
encryptionKey: encryptionKey,
|
||||||
|
tokenSecret: tokenSecret,
|
||||||
|
usrmgr: usrmgr,
|
||||||
|
html2pdfConverter: html2pdfConverter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetEncryptionKey() cipher.EncryptionKey {
|
||||||
|
return s.encryptionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||||
|
tenantService := &TenantService{
|
||||||
|
pg: s.pg,
|
||||||
|
s3: s.s3,
|
||||||
|
bucket: s.bucket,
|
||||||
|
scope: coredata.NewScope(tenantID),
|
||||||
|
proboSvc: s.proboSvc,
|
||||||
|
encryptionKey: s.encryptionKey,
|
||||||
|
tokenSecret: s.tokenSecret,
|
||||||
|
usrmgr: s.usrmgr,
|
||||||
|
html2pdfConverter: s.html2pdfConverter,
|
||||||
|
}
|
||||||
|
|
||||||
|
tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
|
||||||
|
tenantService.Documents = &DocumentService{svc: tenantService, html2pdfConverter: s.html2pdfConverter}
|
||||||
|
tenantService.Audits = &AuditService{svc: tenantService}
|
||||||
|
tenantService.Vendors = &VendorService{svc: tenantService}
|
||||||
|
tenantService.Frameworks = &FrameworkService{svc: tenantService}
|
||||||
|
tenantService.TrustCenterAccesses = &TrustCenterAccessService{svc: tenantService, usrmgr: s.usrmgr}
|
||||||
|
tenantService.Reports = &ReportService{svc: tenantService}
|
||||||
|
tenantService.Organizations = &OrganizationService{svc: tenantService}
|
||||||
|
|
||||||
|
return tenantService
|
||||||
|
}
|
||||||
89
pkg/trust/trust_center_access_service.go
Normal file
89
pkg/trust/trust_center_access_service.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/probo"
|
||||||
|
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||||
|
"github.com/getprobo/probo/pkg/usrmgr"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
TrustCenterAccessService struct {
|
||||||
|
svc *TenantService
|
||||||
|
usrmgr *usrmgr.Service
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenTypeTrustCenterAccess = "trust_center_access"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) ValidateToken(
|
||||||
|
ctx context.Context,
|
||||||
|
tokenString string,
|
||||||
|
) (*probo.TrustCenterAccessData, error) {
|
||||||
|
token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData](
|
||||||
|
s.svc.tokenSecret,
|
||||||
|
TokenTypeTrustCenterAccess,
|
||||||
|
tokenString,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
err = s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, token.Data.TrustCenterID, token.Data.Email)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !access.Active {
|
||||||
|
return nil, fmt.Errorf("access has been revoked")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterAccessService) IsAccessActive(
|
||||||
|
ctx context.Context,
|
||||||
|
trustCenterID gid.GID,
|
||||||
|
email string,
|
||||||
|
) (bool, error) {
|
||||||
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||||
|
return access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return access.Active, nil
|
||||||
|
}
|
||||||
53
pkg/trust/trust_center_service.go
Normal file
53
pkg/trust/trust_center_service.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrustCenterService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TrustCenterService) GetBySlug(
|
||||||
|
ctx context.Context,
|
||||||
|
slug string,
|
||||||
|
) (*coredata.TrustCenter, error) {
|
||||||
|
trustCenter := &coredata.TrustCenter{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
err := trustCenter.LoadBySlug(ctx, conn, slug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load trust center: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return trustCenter, nil
|
||||||
|
}
|
||||||
81
pkg/trust/vendor_service.go
Normal file
81
pkg/trust/vendor_service.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/gid"
|
||||||
|
"github.com/getprobo/probo/pkg/page"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s VendorService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
vendorID gid.GID,
|
||||||
|
) (*coredata.Vendor, error) {
|
||||||
|
vendor := &coredata.Vendor{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
err := vendor.LoadByID(ctx, conn, s.svc.scope, vendorID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load vendor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return vendor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s VendorService) ListForOrganizationId(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.VendorOrderField],
|
||||||
|
) (*page.Page[*coredata.Vendor, coredata.VendorOrderField], error) {
|
||||||
|
var vendors coredata.Vendors
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
filter := coredata.NewVendorTrustCenterFilter()
|
||||||
|
err := vendors.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load vendors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(vendors, cursor), nil
|
||||||
|
}
|
||||||
@@ -123,6 +123,19 @@ var (
|
|||||||
|
|
||||||
[1] %s
|
[1] %s
|
||||||
`
|
`
|
||||||
|
|
||||||
|
trustCenterAccessEmailSubject = "Trust Center Access Invitation - %s"
|
||||||
|
trustCenterAccessEmailTemplate = `
|
||||||
|
You have been granted access to %s's Trust Center!
|
||||||
|
|
||||||
|
Click the link below to access it:
|
||||||
|
|
||||||
|
[1] %s
|
||||||
|
|
||||||
|
This link will expire in 7 days.
|
||||||
|
|
||||||
|
If the link above doesn't work, copy and paste the entire URL into your browser.
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e ErrInvalidCredentials) Error() string {
|
func (e ErrInvalidCredentials) Error() string {
|
||||||
@@ -909,3 +922,25 @@ func (s Service) ResetPassword(ctx context.Context, tokenString string, newPassw
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Service) SendTrustCenterAccessEmail(
|
||||||
|
ctx context.Context,
|
||||||
|
name string,
|
||||||
|
email string,
|
||||||
|
companyName string,
|
||||||
|
accessURL string,
|
||||||
|
) error {
|
||||||
|
accessEmail := coredata.NewEmail(
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
fmt.Sprintf(trustCenterAccessEmailSubject, companyName),
|
||||||
|
fmt.Sprintf(trustCenterAccessEmailTemplate, companyName, accessURL),
|
||||||
|
)
|
||||||
|
|
||||||
|
return s.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
|
if err := accessEmail.Insert(ctx, tx); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert access email: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user