diff --git a/apps/console/src/components/organizations/__generated__/InviteUserDialogMutation.graphql.ts b/apps/console/src/components/organizations/__generated__/InviteUserDialogMutation.graphql.ts index 9f747f5ee..d238b6dae 100644 --- a/apps/console/src/components/organizations/__generated__/InviteUserDialogMutation.graphql.ts +++ b/apps/console/src/components/organizations/__generated__/InviteUserDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<31b0b8ac2c59ccf3addd4508d56bd166>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -12,7 +12,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER"; export type InviteUserInput = { createPeople: boolean; - email: string; + email: any; fullName: string; organizationId: string; role: MembershipRole; @@ -27,7 +27,7 @@ export type InviteUserDialogMutation$data = { readonly node: { readonly acceptedAt: any | null | undefined; readonly createdAt: any; - readonly email: string; + readonly email: any; readonly expiresAt: any; readonly fullName: string; readonly id: string; diff --git a/apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts index 896544b28..e0e06dc12 100644 --- a/apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/DocumentGraphBulkExportDocumentsMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<9b84adc0253186c6f0a5f47837d83d7f>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type BulkExportDocumentsInput = { documentIds: ReadonlyArray; - watermarkEmail?: string | null | undefined; + watermarkEmail?: any | null | undefined; withSignatures: boolean; withWatermark: boolean; }; diff --git a/apps/console/src/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql.ts index c7b553bcf..58db5e3fe 100644 --- a/apps/console/src/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/PeopleGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<5175c19e84ee71ec4a3e9e35e515991e>> * @lightSyntaxTransform * @nogrep */ @@ -15,14 +15,14 @@ export type PeopleGraphNodeQuery$variables = { }; export type PeopleGraphNodeQuery$data = { readonly node: { - readonly additionalEmailAddresses?: ReadonlyArray; + readonly additionalEmailAddresses?: ReadonlyArray; readonly contractEndDate?: any | null | undefined; readonly contractStartDate?: any | null | undefined; readonly fullName?: string; readonly id?: string; readonly kind?: PeopleKind; readonly position?: string | null | undefined; - readonly primaryEmailAddress?: string; + readonly primaryEmailAddress?: any; }; }; export type PeopleGraphNodeQuery = { diff --git a/apps/console/src/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql.ts b/apps/console/src/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql.ts index 4ff1ba3c6..728f2901c 100644 --- a/apps/console/src/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<940911e0b7ae1ffe53a31f98216dcfe6>> + * @generated SignedSource<<336b3e8036d321c95981b2d15c0b41b9>> * @lightSyntaxTransform * @nogrep */ @@ -17,14 +17,14 @@ export type PeopleGraphPaginatedFragment$data = { readonly __id: string; readonly edges: ReadonlyArray<{ readonly node: { - readonly additionalEmailAddresses: ReadonlyArray; + readonly additionalEmailAddresses: ReadonlyArray; readonly contractEndDate: any | null | undefined; readonly contractStartDate: any | null | undefined; readonly fullName: string; readonly id: string; readonly kind: PeopleKind; readonly position: string | null | undefined; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; }>; }; diff --git a/apps/console/src/hooks/graph/__generated__/PeopleGraphQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/PeopleGraphQuery.graphql.ts index 2ac5f68b9..69bb0968e 100644 --- a/apps/console/src/hooks/graph/__generated__/PeopleGraphQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/PeopleGraphQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<3b196ff5779051fcd1ed9cca6b7be112>> + * @generated SignedSource<<8d8fcf4db72f5417db8a4f32129a687a>> * @lightSyntaxTransform * @nogrep */ @@ -23,7 +23,7 @@ export type PeopleGraphQuery$data = { readonly node: { readonly fullName: string; readonly id: string; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; }>; }; diff --git a/apps/console/src/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql.ts index 74feea799..a56eca9bd 100644 --- a/apps/console/src/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/PeopleGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<904f3388abab4e5e84dddf0528530a46>> * @lightSyntaxTransform * @nogrep */ @@ -11,14 +11,14 @@ import { ConcreteRequest } from 'relay-runtime'; export type PeopleKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT"; export type UpdatePeopleInput = { - additionalEmailAddresses?: ReadonlyArray | null | undefined; + additionalEmailAddresses?: ReadonlyArray | null | undefined; contractEndDate?: any | null | undefined; contractStartDate?: any | null | undefined; fullName?: string | null | undefined; id: string; kind?: PeopleKind | null | undefined; position?: string | null | undefined; - primaryEmailAddress?: string | null | undefined; + primaryEmailAddress?: any | null | undefined; }; export type PeopleGraphUpdateMutation$variables = { input: UpdatePeopleInput; @@ -26,14 +26,14 @@ export type PeopleGraphUpdateMutation$variables = { export type PeopleGraphUpdateMutation$data = { readonly updatePeople: { readonly people: { - readonly additionalEmailAddresses: ReadonlyArray; + readonly additionalEmailAddresses: ReadonlyArray; readonly contractEndDate: any | null | undefined; readonly contractStartDate: any | null | undefined; readonly fullName: string; readonly id: string; readonly kind: PeopleKind; readonly position: string | null | undefined; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; }; }; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts index 14f6c6f94..f0f973177 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<271260153dc611541b234266fbdd0c74>> + * @generated SignedSource<<213636cdb170859b30c372b29ddbf752>> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type CreateTrustCenterAccessInput = { active: boolean; - email: string; + email: any; name: string; trustCenterId: string; }; @@ -27,7 +27,7 @@ export type TrustCenterAccessGraphCreateMutation$data = { readonly active: boolean; readonly activeCount: number; readonly createdAt: any; - readonly email: string; + readonly email: any; readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly lastTokenExpiresAt: any | null | undefined; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts index 769c27fa8..5a88413e2 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<4d6afeeeed479773202c8516eeb63620>> + * @generated SignedSource<<3c70b46fb46bf10a48eb06a5e76c1d75>> * @lightSyntaxTransform * @nogrep */ @@ -31,7 +31,7 @@ export type TrustCenterAccessGraphUpdateMutation$data = { readonly active: boolean; readonly activeCount: number; readonly createdAt: any; - readonly email: string; + readonly email: any; readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly lastTokenExpiresAt: any | null | undefined; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraph_accesses.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraph_accesses.graphql.ts index 231e5a134..300d6b7a8 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraph_accesses.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraph_accesses.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<5b0e12ba40232dd4328c0fd766513fb2>> * @lightSyntaxTransform * @nogrep */ @@ -19,7 +19,7 @@ export type TrustCenterAccessGraph_accesses$data = { readonly active: boolean; readonly activeCount: number; readonly createdAt: any; - readonly email: string; + readonly email: any; readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly lastTokenExpiresAt: any | null | undefined; diff --git a/apps/console/src/layouts/__generated__/EmployeeLayoutQuery.graphql.ts b/apps/console/src/layouts/__generated__/EmployeeLayoutQuery.graphql.ts index 3fb0cd84c..9b01805d2 100644 --- a/apps/console/src/layouts/__generated__/EmployeeLayoutQuery.graphql.ts +++ b/apps/console/src/layouts/__generated__/EmployeeLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<626f9dd0bec23ddd103042d53539363a>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -21,7 +21,7 @@ export type EmployeeLayoutQuery$data = { readonly viewer: { readonly id: string; readonly user: { - readonly email: string; + readonly email: any; readonly fullName: string; }; }; diff --git a/apps/console/src/layouts/__generated__/MainLayoutQuery.graphql.ts b/apps/console/src/layouts/__generated__/MainLayoutQuery.graphql.ts index 0aee147b9..ecf13801a 100644 --- a/apps/console/src/layouts/__generated__/MainLayoutQuery.graphql.ts +++ b/apps/console/src/layouts/__generated__/MainLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<5e4ec3cfaeb52bc478d496e37349880c>> * @lightSyntaxTransform * @nogrep */ @@ -21,7 +21,7 @@ export type MainLayoutQuery$data = { readonly viewer: { readonly id: string; readonly user: { - readonly email: string; + readonly email: any; readonly fullName: string; }; }; diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageExportPDFMutation.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageExportPDFMutation.graphql.ts index 2797db967..14214fc09 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageExportPDFMutation.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageExportPDFMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<3a082179d9ea9d47824b7ee925418b95>> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type ExportDocumentVersionPDFInput = { documentVersionId: string; - watermarkEmail?: string | null | undefined; + watermarkEmail?: any | null | undefined; withSignatures: boolean; withWatermark: boolean; }; diff --git a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUserEmailQuery.graphql.ts b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUserEmailQuery.graphql.ts index 62bb6b0be..23d10303a 100644 --- a/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUserEmailQuery.graphql.ts +++ b/apps/console/src/pages/organizations/documents/__generated__/DocumentDetailPageUserEmailQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -13,7 +13,7 @@ export type DocumentDetailPageUserEmailQuery$variables = Record> + * @generated SignedSource<<959a59c16a4d27856820501f80379429>> * @lightSyntaxTransform * @nogrep */ @@ -13,7 +13,7 @@ export type DocumentsPageUserEmailQuery$variables = Record; export type DocumentsPageUserEmailQuery$data = { readonly viewer: { readonly user: { - readonly email: string; + readonly email: any; }; }; }; diff --git a/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts b/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts index a1bb8a857..cc3fbcf79 100644 --- a/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts +++ b/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<7d45f232043aa77c8e1e4ae7f8f330db>> * @lightSyntaxTransform * @nogrep */ @@ -17,7 +17,7 @@ export type DocumentSignaturesTab_signature$data = { readonly signedAt: any | null | undefined; readonly signedBy: { readonly fullName: string; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; readonly state: DocumentVersionSignatureState; readonly " $fragmentType": "DocumentSignaturesTab_signature"; diff --git a/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts b/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts index 899815702..77f90db5f 100644 --- a/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts +++ b/apps/console/src/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<328e9d55b6661d3330cb961426393d6f>> + * @generated SignedSource<<807a8f8c5c320dadd6b9672ef48583f2>> * @lightSyntaxTransform * @nogrep */ @@ -22,7 +22,7 @@ export type DocumentSignaturesTab_version$data = { readonly signedBy: { readonly fullName: string; readonly id: string; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; readonly state: DocumentVersionSignatureState; readonly " $fragmentSpreads": FragmentRefs<"DocumentSignaturesTab_signature">; diff --git a/apps/console/src/pages/organizations/people/dialogs/__generated__/CreatePeopleDialogMutation.graphql.ts b/apps/console/src/pages/organizations/people/dialogs/__generated__/CreatePeopleDialogMutation.graphql.ts index 48e8b7061..9eae59580 100644 --- a/apps/console/src/pages/organizations/people/dialogs/__generated__/CreatePeopleDialogMutation.graphql.ts +++ b/apps/console/src/pages/organizations/people/dialogs/__generated__/CreatePeopleDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<22054346a606e1ce4a26b02218f87af9>> * @lightSyntaxTransform * @nogrep */ @@ -11,14 +11,14 @@ import { ConcreteRequest } from 'relay-runtime'; export type PeopleKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT"; export type CreatePeopleInput = { - additionalEmailAddresses?: ReadonlyArray | null | undefined; + additionalEmailAddresses?: ReadonlyArray | null | undefined; contractEndDate?: any | null | undefined; contractStartDate?: any | null | undefined; fullName: string; kind: PeopleKind; organizationId: string; position?: string | null | undefined; - primaryEmailAddress: string; + primaryEmailAddress: any; }; export type CreatePeopleDialogMutation$variables = { connections: ReadonlyArray; @@ -28,12 +28,12 @@ export type CreatePeopleDialogMutation$data = { readonly createPeople: { readonly peopleEdge: { readonly node: { - readonly additionalEmailAddresses: ReadonlyArray; + readonly additionalEmailAddresses: ReadonlyArray; readonly fullName: string; readonly id: string; readonly kind: PeopleKind; readonly position: string | null | undefined; - readonly primaryEmailAddress: string; + readonly primaryEmailAddress: any; }; }; }; diff --git a/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabInvitationsFragment.graphql.ts b/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabInvitationsFragment.graphql.ts index f672acfdb..756729af2 100644 --- a/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabInvitationsFragment.graphql.ts +++ b/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabInvitationsFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -20,7 +20,7 @@ export type MembersSettingsTabInvitationsFragment$data = { readonly node: { readonly acceptedAt: any | null | undefined; readonly createdAt: any; - readonly email: string; + readonly email: any; readonly expiresAt: any; readonly fullName: string; readonly id: string; diff --git a/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabMembershipsFragment.graphql.ts b/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabMembershipsFragment.graphql.ts index 301af0ce3..30125c7e0 100644 --- a/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabMembershipsFragment.graphql.ts +++ b/apps/console/src/pages/organizations/settings/__generated__/MembersSettingsTabMembershipsFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<21ae595420b0098920e8d7f28834c7af>> * @lightSyntaxTransform * @nogrep */ @@ -20,7 +20,7 @@ export type MembersSettingsTabMembershipsFragment$data = { readonly node: { readonly authMethod: UserAuthMethod; readonly createdAt: any; - readonly emailAddress: string; + readonly emailAddress: any; readonly fullName: string; readonly id: string; readonly role: MembershipRole; diff --git a/apps/console/src/pages/organizations/vendors/dialogs/__generated__/CreateContactDialogMutation.graphql.ts b/apps/console/src/pages/organizations/vendors/dialogs/__generated__/CreateContactDialogMutation.graphql.ts index d6d0ff330..404c2c875 100644 --- a/apps/console/src/pages/organizations/vendors/dialogs/__generated__/CreateContactDialogMutation.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/dialogs/__generated__/CreateContactDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<843a80b3503ff276081e69b558dcce81>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type CreateVendorContactInput = { - email?: string | null | undefined; + email?: any | null | undefined; fullName?: string | null | undefined; phone?: string | null | undefined; role?: string | null | undefined; diff --git a/apps/console/src/pages/organizations/vendors/dialogs/__generated__/EditContactDialogUpdateMutation.graphql.ts b/apps/console/src/pages/organizations/vendors/dialogs/__generated__/EditContactDialogUpdateMutation.graphql.ts index fc6b96344..c201f3cc5 100644 --- a/apps/console/src/pages/organizations/vendors/dialogs/__generated__/EditContactDialogUpdateMutation.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/dialogs/__generated__/EditContactDialogUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<00c307339357baa16c237c563b8f32bd>> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type UpdateVendorContactInput = { - email?: string | null | undefined; + email?: any | null | undefined; fullName?: string | null | undefined; id: string; phone?: string | null | undefined; diff --git a/apps/console/src/pages/organizations/vendors/tabs/__generated__/VendorContactsTabFragment_contact.graphql.ts b/apps/console/src/pages/organizations/vendors/tabs/__generated__/VendorContactsTabFragment_contact.graphql.ts index d488e0dc6..9916d8a14 100644 --- a/apps/console/src/pages/organizations/vendors/tabs/__generated__/VendorContactsTabFragment_contact.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/tabs/__generated__/VendorContactsTabFragment_contact.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<28eef433ba6749a95468492e8ac9d1a6>> + * @generated SignedSource<<8020981cbf1e1b2b59c3ae9027973b17>> * @lightSyntaxTransform * @nogrep */ @@ -12,7 +12,7 @@ import { ReaderFragment } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type VendorContactsTabFragment_contact$data = { readonly createdAt: any; - readonly email: string | null | undefined; + readonly email: any | null | undefined; readonly fullName: string | null | undefined; readonly id: string; readonly phone: string | null | undefined; diff --git a/apps/trust/src/components/__generated__/RequestAccessDialogDocumentMutation.graphql.ts b/apps/trust/src/components/__generated__/RequestAccessDialogDocumentMutation.graphql.ts index 3912154d9..bdb06b837 100644 --- a/apps/trust/src/components/__generated__/RequestAccessDialogDocumentMutation.graphql.ts +++ b/apps/trust/src/components/__generated__/RequestAccessDialogDocumentMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<446953c5eafa3b9a465d245da3bea1af>> + * @generated SignedSource<<5344ac5d6a506485d45e399361b92299>> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type RequestDocumentAccessInput = { documentId: string; - email?: string | null | undefined; + email?: any | null | undefined; name?: string | null | undefined; trustCenterId: string; }; diff --git a/apps/trust/src/components/__generated__/RequestAccessDialogMutation.graphql.ts b/apps/trust/src/components/__generated__/RequestAccessDialogMutation.graphql.ts index 7065fa65a..3df881031 100644 --- a/apps/trust/src/components/__generated__/RequestAccessDialogMutation.graphql.ts +++ b/apps/trust/src/components/__generated__/RequestAccessDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<898167474e2680273ad8b588a63a47c5>> + * @generated SignedSource<<19ada29ea9b8f557b4fcc39f57bc3aec>> * @lightSyntaxTransform * @nogrep */ @@ -10,7 +10,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type RequestAllAccessesInput = { - email?: string | null | undefined; + email?: any | null | undefined; name?: string | null | undefined; trustCenterId: string; }; diff --git a/apps/trust/src/components/__generated__/RequestAccessDialogReportMutation.graphql.ts b/apps/trust/src/components/__generated__/RequestAccessDialogReportMutation.graphql.ts index 2b6c613af..05c0f1997 100644 --- a/apps/trust/src/components/__generated__/RequestAccessDialogReportMutation.graphql.ts +++ b/apps/trust/src/components/__generated__/RequestAccessDialogReportMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<74bc40a8998c962930b3f0b63dc6acd9>> + * @generated SignedSource<<858effd78b88a123430edcf6fcb100ed>> * @lightSyntaxTransform * @nogrep */ @@ -10,7 +10,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type RequestReportAccessInput = { - email?: string | null | undefined; + email?: any | null | undefined; name?: string | null | undefined; reportId: string; trustCenterId: string; diff --git a/apps/trust/src/components/__generated__/RequestAccessDialogTrustCenterFileMutation.graphql.ts b/apps/trust/src/components/__generated__/RequestAccessDialogTrustCenterFileMutation.graphql.ts index 391dff9cc..f4c55b39f 100644 --- a/apps/trust/src/components/__generated__/RequestAccessDialogTrustCenterFileMutation.graphql.ts +++ b/apps/trust/src/components/__generated__/RequestAccessDialogTrustCenterFileMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<4c07d5c8e345a8e3015f512403927890>> + * @generated SignedSource<<32242c5e0b4dc9c9368c0e2f4034d95e>> * @lightSyntaxTransform * @nogrep */ @@ -10,7 +10,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type RequestTrustCenterFileAccessInput = { - email?: string | null | undefined; + email?: any | null | undefined; name?: string | null | undefined; trustCenterFileId: string; trustCenterId: string; diff --git a/pkg/auth/saml_mapper.go b/pkg/auth/saml_mapper.go index 9fa2fd130..612aa19e4 100644 --- a/pkg/auth/saml_mapper.go +++ b/pkg/auth/saml_mapper.go @@ -20,6 +20,7 @@ import ( "github.com/crewjam/saml" "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/mail" ) func ExtractAttributeValue(assertion *saml.Assertion, attributeName string) (string, error) { @@ -98,25 +99,32 @@ func isValidRole(role string) bool { func ExtractUserAttributes( assertion *saml.Assertion, attributeEmail, attributeFirstname, attributeLastname, attributeRole string, -) (email, fullname, role string, err error) { +) (email mail.Addr, fullname, role string, err error) { if len(assertion.AttributeStatements) == 0 { if assertion.Subject != nil && assertion.Subject.NameID != nil { - email = assertion.Subject.NameID.Value - fullname = email + email, err = mail.ParseAddr(assertion.Subject.NameID.Value) + if err != nil { + return mail.Nil, "", "", fmt.Errorf("invalid nameID as email address") + } + fullname = email.String() role = "" return email, fullname, role, nil } - return "", "", "", fmt.Errorf("no attribute statement and no NameID in assertion") + return mail.Nil, "", "", fmt.Errorf("no attribute statement and no NameID in assertion") } - email, err = ExtractAttributeValue(assertion, attributeEmail) + emailString, err := ExtractAttributeValue(assertion, attributeEmail) if err != nil { if assertion.Subject != nil && assertion.Subject.NameID != nil { - email = assertion.Subject.NameID.Value + emailString = assertion.Subject.NameID.Value } else { - return "", "", "", fmt.Errorf("cannot extract email: %w", err) + return mail.Nil, "", "", fmt.Errorf("cannot extract email: %w", err) } } + email, err = mail.ParseAddr(emailString) + if err != nil { + return mail.Nil, "", "", fmt.Errorf("invalid attribute email") + } firstname, err := ExtractAttributeValue(assertion, attributeFirstname) if err != nil { @@ -135,7 +143,7 @@ func ExtractUserAttributes( } else if lastname != "" { fullname = lastname } else { - fullname = email + fullname = email.String() } role, err = ExtractAttributeValue(assertion, attributeRole) diff --git a/pkg/auth/saml_service.go b/pkg/auth/saml_service.go index c19685596..0c19ccecb 100644 --- a/pkg/auth/saml_service.go +++ b/pkg/auth/saml_service.go @@ -32,6 +32,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" ) type ( @@ -372,7 +373,7 @@ func (s *SAMLService) InitiateSAMLLogin( } type SAMLUserInfo struct { - Email string + Email mail.Addr FullName string Role *coredata.MembershipRole SAMLSubject string @@ -516,13 +517,8 @@ func (s *SAMLService) HandleSAMLAssertion( return nil, ErrCannotExtractUserAttributes{Err: err} } - actualEmailDomain, err := ExtractEmailDomain(email) - if err != nil { - return nil, ErrCannotExtractUserAttributes{Err: fmt.Errorf("cannot extract domain from email: %w", err)} - } - - if actualEmailDomain != config.EmailDomain { - return nil, fmt.Errorf("email domain mismatch: assertion contains email with domain %s but SAML config is for domain %s", actualEmailDomain, config.EmailDomain) + if email.Domain() != config.EmailDomain { + return nil, fmt.Errorf("email domain mismatch: assertion contains email with domain %s but SAML config is for domain %s", email.Domain(), config.EmailDomain) } systemRole := MapSAMLRoleToSystemRole(samlRole) diff --git a/pkg/auth/service.go b/pkg/auth/service.go index 93f8d76e6..9c6690fa7 100644 --- a/pkg/auth/service.go +++ b/pkg/auth/service.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "net" - "net/mail" "time" "github.com/jackc/pgx/v5" @@ -32,6 +31,7 @@ import ( "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/crypto/passwdhash" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/statelesstoken" ) @@ -102,8 +102,8 @@ type ( ErrSAMLAutoSignupDisabled struct{} EmailConfirmationData struct { - UserID gid.GID `json:"uid"` - Email string `json:"email"` + UserID gid.GID `json:"uid"` + Email mail.Addr `json:"email"` } InvitationData struct { @@ -113,7 +113,7 @@ type ( CreatePeople bool `json:"create_people"` } PasswordResetData struct { - Email string `json:"email"` + Email mail.Addr `json:"email"` } ErrSAMLAuthRequired struct { @@ -262,7 +262,7 @@ func (s Service) CanCreateOrganization(ctx context.Context, userID gid.GID) erro func (s Service) ForgetPassword( ctx context.Context, - email string, + email mail.Addr, ) error { // Always generate a new token to avoid timing attacks and leaking information // about existing emails @@ -329,7 +329,7 @@ func (s Service) ForgetPassword( func (s Service) SignUp( ctx context.Context, - emailAddress string, + emailAddress mail.Addr, password string, fullName string, ) (*coredata.User, *coredata.Session, error) { @@ -337,11 +337,6 @@ func (s Service) SignUp( return nil, nil, &ErrSignupDisabled{} } - emailAddress2, err := mail.ParseAddress(emailAddress) - if err != nil { - return nil, nil, &ErrInvalidEmail{emailAddress} - } - if len(password) < 8 || len(password) > 128 { return nil, nil, &ErrInvalidPassword{minLength: 8, maxLength: 128} } @@ -358,7 +353,7 @@ func (s Service) SignUp( now := time.Now() user := &coredata.User{ ID: gid.New(gid.NilTenant, coredata.UserEntityType), - EmailAddress: emailAddress2.Address, + EmailAddress: emailAddress, HashedPassword: hashedPassword, EmailAddressVerified: false, FullName: fullName, @@ -453,16 +448,12 @@ func (s Service) ProvisionSAMLUser( ctx context.Context, samlConfigID gid.GID, organizationID gid.GID, - emailAddress string, + emailAddress mail.Addr, fullName string, samlSubject string, existingSession *coredata.Session, sessionDuration time.Duration, ) (*coredata.Session, *coredata.User, error) { - if _, err := mail.ParseAddress(emailAddress); err != nil { - return nil, nil, &ErrInvalidEmail{emailAddress} - } - if fullName == "" { return nil, nil, &ErrInvalidFullName{fullName} } @@ -566,14 +557,10 @@ func (s Service) ProvisionSAMLUser( func (s Service) SignIn( ctx context.Context, - emailAddress string, + emailAddress mail.Addr, password string, existingSession *coredata.Session, ) (*coredata.Session, *coredata.User, error) { - if _, err := mail.ParseAddress(emailAddress); err != nil { - return nil, nil, &ErrInvalidCredentials{"invalid email or password"} - } - user := &coredata.User{} session := &coredata.Session{} @@ -868,10 +855,6 @@ func (s Service) SignupFromInvitation( return nil, nil, &ErrInvalidPassword{minLength: 8, maxLength: 128} } - if _, err := mail.ParseAddress(payload.Data.Email); err != nil { - return nil, nil, &ErrInvalidEmail{payload.Data.Email} - } - if fullName == "" { fullName = payload.Data.FullName } @@ -980,7 +963,7 @@ func (s *TenantAuthService) GetUserAuthMethod( } // User has SAML subject - check if there's SAML config for this org + user's domain - domain := extractDomain(user.EmailAddress) + domain := user.EmailAddress.Domain() if domain == "" { authMethod = coredata.UserAuthMethodPassword return nil @@ -1005,20 +988,6 @@ func (s *TenantAuthService) GetUserAuthMethod( return authMethod, err } -func extractDomain(email string) string { - atIndex := -1 - for i := 0; i < len(email); i++ { - if email[i] == '@' { - atIndex = i - break - } - } - if atIndex == -1 || atIndex == len(email)-1 { - return "" - } - return email[atIndex+1:] -} - // CheckOrganizationAccess checks access to multiple organizations in a single database query // Always uses batch processing to avoid N+1 query problems func (s Service) CheckOrganizationAccess( @@ -1029,7 +998,7 @@ func (s Service) CheckOrganizationAccess( ) (map[gid.GID]AccessResult, error) { results := make(map[gid.GID]AccessResult, len(organizationIDs)) - domain := extractDomain(user.EmailAddress) + domain := user.EmailAddress.Domain() if domain == "" { // All organizations fail with invalid email for _, orgID := range organizationIDs { diff --git a/pkg/authz/service.go b/pkg/authz/service.go index 0548a43bc..00084a45c 100644 --- a/pkg/authz/service.go +++ b/pkg/authz/service.go @@ -26,6 +26,7 @@ import ( "go.probo.inc/probo/packages/emails" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/statelesstoken" ) @@ -242,7 +243,7 @@ func (s *Service) AcceptInvitationByID( type UserInvitation struct { ID gid.GID - Email string + Email mail.Addr FullName string Role coredata.MembershipRole ExpiresAt time.Time @@ -259,7 +260,7 @@ type OrganizationSummary struct { func (s *Service) GetUserPendingInvitations( ctx context.Context, - email string, + email mail.Addr, ) ([]*UserInvitation, error) { userInvitations := []*UserInvitation{} @@ -679,7 +680,7 @@ func (s *TenantAuthzService) UpdateMembershipRole( func (s *TenantAuthzService) InviteUserToOrganization( ctx context.Context, organizationID gid.GID, - emailAddress string, + emailAddress mail.Addr, fullName string, role coredata.MembershipRole, ) (*coredata.Invitation, error) { diff --git a/pkg/coredata/document.go b/pkg/coredata/document.go index 22b98257e..cc3da80b4 100644 --- a/pkg/coredata/document.go +++ b/pkg/coredata/document.go @@ -24,6 +24,7 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -701,7 +702,7 @@ func (p *Document) IsLastSignableVersionSignedByUserEmail( conn pg.Conn, scope Scoper, documentID gid.GID, - userEmail string, + userEmail mail.Addr, ) (bool, error) { q := ` WITH last_signable_version AS ( diff --git a/pkg/coredata/document_filter.go b/pkg/coredata/document_filter.go index 1e6d60487..57dc18e21 100644 --- a/pkg/coredata/document_filter.go +++ b/pkg/coredata/document_filter.go @@ -16,6 +16,7 @@ package coredata import ( "github.com/jackc/pgx/v5" + "go.probo.inc/probo/pkg/mail" ) type ( @@ -23,7 +24,7 @@ type ( query *string trustCenterVisibilities []TrustCenterVisibility published *bool - userEmail *string + userEmail *mail.Addr } ) @@ -47,7 +48,7 @@ func (f *DocumentFilter) WithPublished(published *bool) *DocumentFilter { return f } -func (f *DocumentFilter) WithUserEmail(userEmail *string) *DocumentFilter { +func (f *DocumentFilter) WithUserEmail(userEmail *mail.Addr) *DocumentFilter { f.userEmail = userEmail return f } diff --git a/pkg/coredata/document_version_filter.go b/pkg/coredata/document_version_filter.go index 7a2aaad7f..7c127306f 100644 --- a/pkg/coredata/document_version_filter.go +++ b/pkg/coredata/document_version_filter.go @@ -16,11 +16,12 @@ package coredata import ( "github.com/jackc/pgx/v5" + "go.probo.inc/probo/pkg/mail" ) type ( DocumentVersionFilter struct { - userEmail *string + userEmail *mail.Addr } ) @@ -28,7 +29,7 @@ func NewDocumentVersionFilter() *DocumentVersionFilter { return &DocumentVersionFilter{} } -func (f *DocumentVersionFilter) WithUserEmail(userEmail *string) *DocumentVersionFilter { +func (f *DocumentVersionFilter) WithUserEmail(userEmail *mail.Addr) *DocumentVersionFilter { f.userEmail = userEmail return f } diff --git a/pkg/coredata/document_version_signature.go b/pkg/coredata/document_version_signature.go index 0b4fa9ee1..5c0d9cec4 100644 --- a/pkg/coredata/document_version_signature.go +++ b/pkg/coredata/document_version_signature.go @@ -25,6 +25,7 @@ import ( "github.com/jackc/pgx/v5/pgconn" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -424,7 +425,7 @@ func (pvs *DocumentVersionSignature) IsSignedByUserEmail( conn pg.Conn, scope Scoper, documentVersionID gid.GID, - userEmail string, + userEmail mail.Addr, ) (bool, error) { q := ` SELECT EXISTS ( diff --git a/pkg/coredata/email.go b/pkg/coredata/email.go index 769c9c590..0426cec07 100644 --- a/pkg/coredata/email.go +++ b/pkg/coredata/email.go @@ -23,6 +23,7 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" ) type ( @@ -45,7 +46,7 @@ var ( func NewEmail( recipientName string, - recipientEmail string, + recipientEmail mail.Addr, subject string, textBody string, htmlBody *string, @@ -54,7 +55,7 @@ func NewEmail( return &Email{ ID: gid.New(gid.NilTenant, EmailEntityType), RecipientName: recipientName, - RecipientEmail: recipientEmail, + RecipientEmail: recipientEmail.String(), Subject: subject, TextBody: textBody, HtmlBody: htmlBody, diff --git a/pkg/coredata/export_job.go b/pkg/coredata/export_job.go index cf50e0ea7..0df5235b2 100644 --- a/pkg/coredata/export_job.go +++ b/pkg/coredata/export_job.go @@ -11,31 +11,32 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" ) type ( ExportJob struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - Type ExportJobType `db:"type"` - Arguments json.RawMessage `db:"arguments"` - Error *string `db:"error"` - Status ExportJobStatus `db:"status"` - FileID *gid.GID `db:"file_id"` - RecipientEmail string `db:"recipient_email"` - RecipientName string `db:"recipient_name"` - CreatedAt time.Time `db:"created_at"` - StartedAt *time.Time `db:"started_at"` - CompletedAt *time.Time `db:"completed_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + Type ExportJobType `db:"type"` + Arguments json.RawMessage `db:"arguments"` + Error *string `db:"error"` + Status ExportJobStatus `db:"status"` + FileID *gid.GID `db:"file_id"` + RecipientEmail mail.Addr `db:"recipient_email"` + RecipientName string `db:"recipient_name"` + CreatedAt time.Time `db:"created_at"` + StartedAt *time.Time `db:"started_at"` + CompletedAt *time.Time `db:"completed_at"` } ExportJobs []*ExportJob DocumentExportArguments struct { - DocumentIDs []gid.GID `json:"document_ids"` - WithWatermark bool `json:"with_watermark"` - WatermarkEmail *string `json:"watermark_email"` - WithSignatures bool `json:"with_signatures"` + DocumentIDs []gid.GID `json:"document_ids"` + WithWatermark bool `json:"with_watermark"` + WatermarkEmail *mail.Addr `json:"watermark_email"` + WithSignatures bool `json:"with_signatures"` } FrameworkExportArguments struct { diff --git a/pkg/coredata/invitation.go b/pkg/coredata/invitation.go index 2cbe2b000..07709e932 100644 --- a/pkg/coredata/invitation.go +++ b/pkg/coredata/invitation.go @@ -24,6 +24,7 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -31,9 +32,9 @@ type ( Invitation struct { ID gid.GID `db:"id"` OrganizationID gid.GID `db:"organization_id"` - Email string `db:"email"` + Email mail.Addr `db:"email"` FullName string `db:"full_name"` - Role MembershipRole `db:"role"` + Role MembershipRole `db:"role"` Status InvitationStatus `db:"status"` ExpiresAt time.Time `db:"expires_at"` AcceptedAt *time.Time `db:"accepted_at"` @@ -43,10 +44,10 @@ type ( Invitations []*Invitation InvitationData struct { - InvitationID gid.GID `json:"invitation_id"` - OrganizationID gid.GID `json:"organization_id"` - Email string `json:"email"` - FullName string `json:"full_name"` + InvitationID gid.GID `json:"invitation_id"` + OrganizationID gid.GID `json:"organization_id"` + Email mail.Addr `json:"email"` + FullName string `json:"full_name"` Role MembershipRole `json:"role"` } @@ -241,7 +242,7 @@ func (i *Invitations) LoadByEmail( ctx context.Context, conn pg.Conn, scope Scoper, - email string, + email mail.Addr, cursor *page.Cursor[InvitationOrderField], filter *InvitationFilter, ) error { diff --git a/pkg/coredata/membership.go b/pkg/coredata/membership.go index bacfb3bd7..f76836457 100644 --- a/pkg/coredata/membership.go +++ b/pkg/coredata/membership.go @@ -26,6 +26,7 @@ import ( "github.com/jackc/pgx/v5/pgconn" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -36,7 +37,7 @@ type ( OrganizationID gid.GID `db:"organization_id"` Role MembershipRole `db:"role"` FullName string `db:"full_name"` - EmailAddress string `db:"email_address"` + EmailAddress mail.Addr `db:"email_address"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } diff --git a/pkg/coredata/people.go b/pkg/coredata/people.go index e4cd8d658..7de3a3bdc 100644 --- a/pkg/coredata/people.go +++ b/pkg/coredata/people.go @@ -24,22 +24,23 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) type ( People struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - Kind PeopleKind `db:"kind"` - FullName string `db:"full_name"` - PrimaryEmailAddress string `db:"primary_email_address"` - AdditionalEmailAddresses []string `db:"additional_email_addresses"` - Position *string `db:"position"` - ContractStartDate *time.Time `db:"contract_start_date"` - ContractEndDate *time.Time `db:"contract_end_date"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + Kind PeopleKind `db:"kind"` + FullName string `db:"full_name"` + PrimaryEmailAddress mail.Addr `db:"primary_email_address"` + AdditionalEmailAddresses []mail.Addr `db:"additional_email_addresses"` + Position *string `db:"position"` + ContractStartDate *time.Time `db:"contract_start_date"` + ContractEndDate *time.Time `db:"contract_end_date"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } Peoples []*People @@ -180,7 +181,7 @@ func (p *People) LoadByEmailAndOrganizationID( ctx context.Context, conn pg.Conn, scope Scoper, - primaryEmailAddress string, + primaryEmailAddress mail.Addr, organizationID gid.GID, ) error { q := ` @@ -221,7 +222,7 @@ LIMIT 1; people, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[People]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return &ErrPeopleNotFound{Identifier: primaryEmailAddress} + return &ErrPeopleNotFound{Identifier: primaryEmailAddress.String()} } return fmt.Errorf("cannot collect people: %w", err) diff --git a/pkg/coredata/saml_configuration.go b/pkg/coredata/saml_configuration.go index 14e7f00d9..ce04c7dba 100644 --- a/pkg/coredata/saml_configuration.go +++ b/pkg/coredata/saml_configuration.go @@ -20,9 +20,9 @@ import ( "maps" "time" - "go.probo.inc/probo/pkg/gid" "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" ) type SAMLConfiguration struct { diff --git a/pkg/coredata/slack_message.go b/pkg/coredata/slack_message.go index 8d99d8d48..4df02d71a 100644 --- a/pkg/coredata/slack_message.go +++ b/pkg/coredata/slack_message.go @@ -24,23 +24,24 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" ) type ( SlackMessage struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - Type SlackMessageType `db:"type"` - Body map[string]any `db:"body"` - MessageTS *string `db:"message_ts"` - ChannelID *string `db:"channel_id"` - RequesterEmail *string `db:"requester_email"` - Metadata map[string]any `db:"metadata"` - InitialSlackMessageID gid.GID `db:"initial_slack_message_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - SentAt *time.Time `db:"sent_at"` - Error *string `db:"error"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + Type SlackMessageType `db:"type"` + Body map[string]any `db:"body"` + MessageTS *string `db:"message_ts"` + ChannelID *string `db:"channel_id"` + RequesterEmail *mail.Addr `db:"requester_email"` + Metadata map[string]any `db:"metadata"` + InitialSlackMessageID gid.GID `db:"initial_slack_message_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + SentAt *time.Time `db:"sent_at"` + Error *string `db:"error"` } ErrNoUnsentSlackMessage struct{} @@ -61,7 +62,6 @@ func NewSlackMessage( organizationID gid.GID, messageType SlackMessageType, body map[string]any, - requesterEmail *string, ) *SlackMessage { now := time.Now() id := gid.New(scope.GetTenantID(), SlackMessageEntityType) @@ -70,7 +70,6 @@ func NewSlackMessage( OrganizationID: organizationID, Type: messageType, Body: body, - RequesterEmail: requesterEmail, InitialSlackMessageID: id, CreatedAt: now, UpdatedAt: now, @@ -438,7 +437,7 @@ func (s *SlackMessage) LoadLatestByRequesterEmailAndType( conn pg.Conn, scope Scoper, organizationID gid.GID, - requesterEmail string, + requesterEmail mail.Addr, messageType SlackMessageType, since time.Time, ) error { diff --git a/pkg/coredata/trust_center_access.go b/pkg/coredata/trust_center_access.go index 00376faaa..129ae6019 100644 --- a/pkg/coredata/trust_center_access.go +++ b/pkg/coredata/trust_center_access.go @@ -26,24 +26,25 @@ import ( "github.com/jackc/pgx/v5/pgconn" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) type ( TrustCenterAccess struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - TenantID gid.TenantID `db:"tenant_id"` - TrustCenterID gid.GID `db:"trust_center_id"` - Email string `db:"email"` - Name string `db:"name"` - Active bool `db:"active"` - HasAcceptedNonDisclosureAgreement bool `db:"has_accepted_non_disclosure_agreement"` - HasAcceptedNonDisclosureAgreementMetadata json.RawMessage `db:"has_accepted_non_disclosure_agreement_metadata"` - NDAFileID *gid.GID `db:"nda_file_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - LastTokenExpiresAt *time.Time `db:"last_token_expires_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + TenantID gid.TenantID `db:"tenant_id"` + TrustCenterID gid.GID `db:"trust_center_id"` + Email mail.Addr `db:"email"` + Name string `db:"name"` + Active bool `db:"active"` + HasAcceptedNonDisclosureAgreement bool `db:"has_accepted_non_disclosure_agreement"` + HasAcceptedNonDisclosureAgreementMetadata json.RawMessage `db:"has_accepted_non_disclosure_agreement_metadata"` + NDAFileID *gid.GID `db:"nda_file_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + LastTokenExpiresAt *time.Time `db:"last_token_expires_at"` } TrustCenterAccesses []*TrustCenterAccess @@ -132,7 +133,7 @@ func (tca *TrustCenterAccess) LoadByTrustCenterIDAndEmail( conn pg.Conn, scope Scoper, trustCenterID gid.GID, - email string, + email mail.Addr, ) error { q := ` SELECT diff --git a/pkg/coredata/user.go b/pkg/coredata/user.go index 2d9999d12..6e55cc8fb 100644 --- a/pkg/coredata/user.go +++ b/pkg/coredata/user.go @@ -22,23 +22,24 @@ import ( "strings" "time" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" + "go.probo.inc/probo/pkg/page" ) type ( User struct { ID gid.GID `db:"id"` - EmailAddress string `db:"email_address"` - HashedPassword []byte `db:"hashed_password"` - FullName string `db:"fullname"` - EmailAddressVerified bool `db:"email_address_verified"` - SAMLSubject *string `db:"saml_subject"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + EmailAddress mail.Addr `db:"email_address"` + HashedPassword []byte `db:"hashed_password"` + FullName string `db:"fullname"` + EmailAddressVerified bool `db:"email_address_verified"` + SAMLSubject *string `db:"saml_subject"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } Users []*User @@ -150,7 +151,7 @@ WHERE func (u *User) LoadByEmail( ctx context.Context, conn pg.Conn, - email string, + email mail.Addr, ) error { q := ` SELECT @@ -179,7 +180,7 @@ LIMIT 1; user, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[User]) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return &ErrUserNotFound{Identifier: email} + return &ErrUserNotFound{Identifier: email.String()} } return fmt.Errorf("cannot collect user: %w", err) @@ -362,11 +363,11 @@ WHERE ` args := pgx.StrictNamedArgs{ - "user_id": u.ID, - "email_address": u.EmailAddress, - "email_address_verified": u.EmailAddressVerified, - "saml_subject": u.SAMLSubject, - "updated_at": u.UpdatedAt, + "user_id": u.ID, + "email_address": u.EmailAddress, + "email_address_verified": u.EmailAddressVerified, + "saml_subject": u.SAMLSubject, + "updated_at": u.UpdatedAt, } _, err := conn.Exec(ctx, q, args) diff --git a/pkg/coredata/vendor_contact.go b/pkg/coredata/vendor_contact.go index 45b6c6f69..ee28288fa 100644 --- a/pkg/coredata/vendor_contact.go +++ b/pkg/coredata/vendor_contact.go @@ -24,22 +24,23 @@ import ( "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) type ( VendorContact struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - VendorID gid.GID `db:"vendor_id"` - FullName *string `db:"full_name"` - Email *string `db:"email"` - Phone *string `db:"phone"` - Role *string `db:"role"` - SnapshotID *gid.GID `db:"snapshot_id"` - SourceID *gid.GID `db:"source_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + VendorID gid.GID `db:"vendor_id"` + FullName *string `db:"full_name"` + Email *mail.Addr `db:"email"` + Phone *string `db:"phone"` + Role *string `db:"role"` + SnapshotID *gid.GID `db:"snapshot_id"` + SourceID *gid.GID `db:"source_id"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } VendorContacts []*VendorContact diff --git a/pkg/mail/addr.go b/pkg/mail/addr.go new file mode 100644 index 000000000..eb08815cb --- /dev/null +++ b/pkg/mail/addr.go @@ -0,0 +1,74 @@ +package mail + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "net/mail" + "strings" +) + +type Addr string + +var Nil Addr + +func (a Addr) String() string { + return string(a) +} + +func (a *Addr) Domain() string { + return strings.Split(a.String(), "@")[1] +} + +func ParseAddr(s string) (Addr, error) { + netMailAddr, err := mail.ParseAddress(s) + if err != nil { + return Nil, fmt.Errorf("cannot parse address %s: %w", s, err) + } + + parts := strings.Split(netMailAddr.Address, "@") + if len(parts) != 2 { + return Nil, fmt.Errorf("invalid email address format") + } + + return Addr(netMailAddr.Address), nil +} + +func (a Addr) Value() (driver.Value, error) { + return a.String(), nil +} + +func (a *Addr) Scan(value any) error { + switch v := value.(type) { + case string: + parsed, err := ParseAddr(v) + if err != nil { + return err + } + + *a = parsed + default: + return fmt.Errorf("invalid type for mail.Addr: expected string, got %T", value) + } + return nil +} + +func (a *Addr) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("cannot unmarshal email string from JSON") + } + + parsed, err := ParseAddr(s) + if err != nil { + return fmt.Errorf("cannot parse email address") + } + + *a = parsed + + return nil +} + +func (a Addr) MarshalJSON() ([]byte, error) { + return json.Marshal(a.String()) +} diff --git a/pkg/probo/connector_service.go b/pkg/probo/connector_service.go index 62b589cdf..84ab436f1 100644 --- a/pkg/probo/connector_service.go +++ b/pkg/probo/connector_service.go @@ -148,7 +148,7 @@ func (s *ConnectorService) Create( return fmt.Errorf("cannot parse template JSON: %w", err) } - slackMessage := coredata.NewSlackMessage(s.svc.scope, req.OrganizationID, coredata.SlackMessageTypeWelcome, body, nil) + slackMessage := coredata.NewSlackMessage(s.svc.scope, req.OrganizationID, coredata.SlackMessageTypeWelcome, body) if err := slackMessage.Insert(ctx, conn, s.svc.scope); err != nil { return fmt.Errorf("cannot insert slack message: %w", err) } diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index c0e424bea..0aada966c 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "net/mail" "net/url" "os" "regexp" @@ -24,6 +23,7 @@ import ( "go.probo.inc/probo/pkg/docgen" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/html2pdf" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/statelesstoken" "go.probo.inc/probo/pkg/validator" @@ -608,7 +608,7 @@ func (s *DocumentService) SignDocumentVersion( func (s *DocumentService) SignDocumentVersionByEmail( ctx context.Context, documentVersionID gid.GID, - userEmail string, + userEmail mail.Addr, ) (*coredata.DocumentVersionSignature, error) { var documentVersionSignature *coredata.DocumentVersionSignature @@ -887,7 +887,7 @@ func (s *DocumentService) ListSignatures( func (s *DocumentService) IsVersionSignedByUserEmail( ctx context.Context, documentVersionID gid.GID, - userEmail string, + userEmail mail.Addr, ) (bool, error) { documentVersionSignature := &coredata.DocumentVersionSignature{} @@ -1032,7 +1032,7 @@ func (s *DocumentService) BulkSoftDelete( func (s *DocumentService) RequestExport( ctx context.Context, documentIDs []gid.GID, - recipientEmail string, + recipientEmail mail.Addr, recipientName string, options ExportPDFOptions, ) (*coredata.ExportJob, error) { @@ -1043,9 +1043,6 @@ func (s *DocumentService) RequestExport( if options.WatermarkEmail == nil { return nil, fmt.Errorf("watermark email is required when with watermark is true") } - if _, err := mail.ParseAddress(*options.WatermarkEmail); err != nil { - return nil, fmt.Errorf("invalid email address") - } } err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error { @@ -1149,7 +1146,7 @@ func (s *DocumentService) GetVersion( func (s *DocumentService) IsSigned( ctx context.Context, documentID gid.GID, - userEmail string, + userEmail mail.Addr, ) (bool, error) { document := &coredata.Document{} @@ -1438,7 +1435,7 @@ func (s *DocumentService) CancelSignatureRequest( type ExportPDFOptions struct { WithWatermark bool - WatermarkEmail *string + WatermarkEmail *mail.Addr WithSignatures bool } @@ -1695,9 +1692,6 @@ func exportDocumentPDF( if options.WatermarkEmail == nil { return nil, fmt.Errorf("watermark email is required with watermark enabled") } - if _, err := mail.ParseAddress(*options.WatermarkEmail); err != nil { - return nil, fmt.Errorf("invalid email address") - } watermarkedPDF, err := watermarkpdf.AddConfidentialWithTimestamp(pdfData, *options.WatermarkEmail) if err != nil { @@ -1770,7 +1764,7 @@ func (s *DocumentService) SendExportEmail( ctx context.Context, fileID gid.GID, recipientName string, - recipientEmail string, + recipientEmail mail.Addr, ) error { return s.svc.pg.WithTx( ctx, diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go index 326aca9e7..65a55459e 100644 --- a/pkg/probo/framework_service.go +++ b/pkg/probo/framework_service.go @@ -32,6 +32,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/html2pdf" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/slug" "go.probo.inc/probo/pkg/soagen" @@ -97,7 +98,7 @@ func (ufr *UpdateFrameworkRequest) Validate() error { func (s FrameworkService) RequestExport( ctx context.Context, frameworkID gid.GID, - recipientEmail string, + recipientEmail mail.Addr, recipientName string, ) (error, *coredata.ExportJob) { var exportJobID gid.GID @@ -725,7 +726,7 @@ func (s FrameworkService) SendExportEmail( ctx context.Context, fileID gid.GID, recipientName string, - recipientEmail string, + recipientEmail mail.Addr, ) error { return s.svc.pg.WithTx( ctx, diff --git a/pkg/probo/people_service.go b/pkg/probo/people_service.go index 3cb6b7a10..dc31d9c5e 100644 --- a/pkg/probo/people_service.go +++ b/pkg/probo/people_service.go @@ -22,6 +22,7 @@ import ( "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/validator" ) @@ -34,8 +35,8 @@ type ( CreatePeopleRequest struct { OrganizationID gid.GID FullName string - PrimaryEmailAddress string - AdditionalEmailAddresses []string + PrimaryEmailAddress mail.Addr + AdditionalEmailAddresses []mail.Addr Kind coredata.PeopleKind Position *string ContractStartDate *time.Time @@ -46,8 +47,8 @@ type ( ID gid.GID Kind *coredata.PeopleKind FullName *string - PrimaryEmailAddress *string - AdditionalEmailAddresses *[]string + PrimaryEmailAddress *mail.Addr + AdditionalEmailAddresses *[]mail.Addr Position **string ContractStartDate **time.Time ContractEndDate **time.Time @@ -59,9 +60,9 @@ func (cpr *CreatePeopleRequest) Validate() error { v.Check(cpr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType)) v.Check(cpr.FullName, "full_name", validator.SafeTextNoNewLine(NameMaxLength)) - v.Check(cpr.PrimaryEmailAddress, "primary_email_address", validator.Required(), validator.NotEmpty(), validator.Email()) + v.Check(cpr.PrimaryEmailAddress, "primary_email_address", validator.Required(), validator.NotEmpty()) v.CheckEach(cpr.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) { - v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty(), validator.Email()) + v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty()) }) v.Check(cpr.Kind, "kind", validator.Required(), validator.OneOfSlice(coredata.PeopleKinds())) v.Check(cpr.Position, "position", validator.SafeText(TitleMaxLength)) @@ -77,9 +78,9 @@ func (upr *UpdatePeopleRequest) Validate() error { v.Check(upr.ID, "id", validator.Required(), validator.GID(coredata.PeopleEntityType)) v.Check(upr.Kind, "kind", validator.OneOfSlice(coredata.PeopleKinds())) v.Check(upr.FullName, "full_name", validator.SafeTextNoNewLine(NameMaxLength)) - v.Check(upr.PrimaryEmailAddress, "primary_email_address", validator.NotEmpty(), validator.Email()) + v.Check(upr.PrimaryEmailAddress, "primary_email_address", validator.NotEmpty()) v.CheckEach(upr.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) { - v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty(), validator.Email()) + v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty()) }) v.Check(upr.Position, "position", validator.SafeText(TitleMaxLength)) v.Check(upr.ContractStartDate, "contract_start_date", validator.Before(upr.ContractEndDate)) @@ -110,7 +111,7 @@ func (s PeopleService) Get( func (s PeopleService) GetByEmailAndOrganizationID( ctx context.Context, - primaryEmailAddress string, + primaryEmailAddress mail.Addr, organizationID gid.GID, ) (*coredata.People, error) { people := &coredata.People{} diff --git a/pkg/probo/service.go b/pkg/probo/service.go index 1b8c9857e..5b9dce06f 100644 --- a/pkg/probo/service.go +++ b/pkg/probo/service.go @@ -31,6 +31,7 @@ import ( "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/filemanager" "go.probo.inc/probo/pkg/filevalidation" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/slack" @@ -44,7 +45,7 @@ const ( type ExportService interface { BuildAndUploadExport(ctx context.Context, exportJobID gid.GID) (*coredata.ExportJob, error) - SendExportEmail(ctx context.Context, fileID gid.GID, recipientName, recipientEmail string) error + SendExportEmail(ctx context.Context, fileID gid.GID, recipientName string, recipientEmail mail.Addr) error } type ( diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index 0bafaf5f1..dd20bbb5a 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -24,6 +24,7 @@ import ( "go.probo.inc/probo/packages/emails" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/statelesstoken" "go.probo.inc/probo/pkg/validator" @@ -36,7 +37,7 @@ type ( CreateTrustCenterAccessRequest struct { TrustCenterID gid.GID - Email string + Email mail.Addr Name string } @@ -55,8 +56,8 @@ type ( } TrustCenterAccessData struct { - TrustCenterID gid.GID `json:"trust_center_id"` - Email string `json:"email"` + TrustCenterID gid.GID `json:"trust_center_id"` + Email mail.Addr `json:"email"` } ) @@ -64,7 +65,7 @@ func (ctcar *CreateTrustCenterAccessRequest) Validate() error { v := validator.New() v.Check(ctcar.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType)) - v.Check(ctcar.Email, "email", validator.Required(), validator.Email()) + v.Check(ctcar.Email, "email", validator.Required(), validator.NotEmpty()) v.Check(ctcar.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength)) return v.Error() @@ -469,7 +470,7 @@ func (s TrustCenterAccessService) sendTrustCenterAccessEmail( ctx context.Context, tx pg.Conn, name string, - email string, + email mail.Addr, companyName string, accessURL string, ) error { diff --git a/pkg/probo/vendor_contact_service.go b/pkg/probo/vendor_contact_service.go index 3352883f8..3f0d98484 100644 --- a/pkg/probo/vendor_contact_service.go +++ b/pkg/probo/vendor_contact_service.go @@ -22,6 +22,7 @@ import ( "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/validator" ) @@ -34,7 +35,7 @@ type ( CreateVendorContactRequest struct { VendorID gid.GID FullName *string - Email *string + Email *mail.Addr Phone *string Role *string } @@ -42,7 +43,7 @@ type ( UpdateVendorContactRequest struct { ID gid.GID FullName **string - Email **string + Email **mail.Addr Phone **string Role **string } @@ -53,7 +54,6 @@ func (cvcr *CreateVendorContactRequest) Validate() error { v.Check(cvcr.VendorID, "vendor_id", validator.Required(), validator.GID(coredata.VendorEntityType)) v.Check(cvcr.FullName, "fullName", validator.SafeTextNoNewLine(TitleMaxLength)) - v.Check(cvcr.Email, "email", validator.Email()) v.Check(cvcr.Phone, "phone", validator.SafeText(NameMaxLength)) v.Check(cvcr.Role, "role", validator.SafeText(TitleMaxLength)) @@ -65,7 +65,6 @@ func (uvcr *UpdateVendorContactRequest) Validate() error { v.Check(uvcr.ID, "id", validator.Required(), validator.GID(coredata.VendorContactEntityType)) v.Check(uvcr.FullName, "fullName", validator.SafeTextNoNewLine(TitleMaxLength)) - v.Check(uvcr.Email, "email", validator.Email()) v.Check(uvcr.Phone, "phone", validator.SafeText(NameMaxLength)) v.Check(uvcr.Role, "role", validator.SafeText(TitleMaxLength)) diff --git a/pkg/server/api/console/v1/gqlgen.yaml b/pkg/server/api/console/v1/gqlgen.yaml index ad4a03096..3cd7aa628 100644 --- a/pkg/server/api/console/v1/gqlgen.yaml +++ b/pkg/server/api/console/v1/gqlgen.yaml @@ -37,3 +37,6 @@ models: BigInt: model: - "go.probo.inc/probo/pkg/server/gqlutils/types/bigint.BigIntScalar" + EmailAddr: + model: + - "go.probo.inc/probo/pkg/server/gqlutils/types/mail.AddrScalar" diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index cc480fdd9..7acb44b17 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -19,6 +19,7 @@ scalar Datetime scalar Upload scalar Duration scalar BigInt +scalar EmailAddr # Interfaces interface Node { @@ -99,7 +100,8 @@ enum MembershipRole EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee") VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer") - AUDITOR @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") + AUDITOR + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") } enum APIRole @goModel(model: "go.probo.inc/probo/pkg/coredata.APIRole") { @@ -1687,7 +1689,7 @@ type Organization implements Node { type User implements Node { id: ID! fullName: String! - email: String! + email: EmailAddr! createdAt: Datetime! updatedAt: Datetime! } @@ -1698,7 +1700,7 @@ type Membership implements Node { organizationID: ID! role: MembershipRole! fullName: String! - emailAddress: String! + emailAddress: EmailAddr! authMethod: UserAuthMethod! @goField(forceResolver: true) createdAt: Datetime! updatedAt: Datetime! @@ -1706,7 +1708,7 @@ type Membership implements Node { type Invitation implements Node { id: ID! - email: String! + email: EmailAddr! fullName: String! role: MembershipRole! status: InvitationStatus! @@ -1737,8 +1739,8 @@ type SlackConnectionEdge { type People implements Node { id: ID! fullName: String! - primaryEmailAddress: String! - additionalEmailAddresses: [String!]! + primaryEmailAddress: EmailAddr! + additionalEmailAddresses: [EmailAddr!]! kind: PeopleKind! position: String contractStartDate: Datetime @@ -1841,7 +1843,7 @@ type VendorContact implements Node { id: ID! vendor: Vendor! @goField(forceResolver: true) fullName: String - email: String + email: EmailAddr phone: String role: String createdAt: Datetime! @@ -2342,7 +2344,7 @@ type TrustCenterEdge { type TrustCenterAccess implements Node { id: ID! - email: String! + email: EmailAddr! name: String! active: Boolean! hasAcceptedNonDisclosureAgreement: Boolean! @@ -3185,7 +3187,7 @@ input DeleteTrustCenterNDAInput { input CreateTrustCenterAccessInput { trustCenterId: ID! - email: String! + email: EmailAddr! name: String! active: Boolean! } @@ -3306,7 +3308,7 @@ input DeleteVendorInput { input CreateVendorContactInput { vendorId: ID! fullName: String - email: String + email: EmailAddr phone: String role: String } @@ -3314,7 +3316,7 @@ input CreateVendorContactInput { input UpdateVendorContactInput { id: ID! fullName: String @goField(omittable: true) - email: String @goField(omittable: true) + email: EmailAddr @goField(omittable: true) phone: String @goField(omittable: true) role: String @goField(omittable: true) } @@ -3346,8 +3348,8 @@ input DeleteVendorServiceInput { input CreatePeopleInput { organizationId: ID! fullName: String! - primaryEmailAddress: String! - additionalEmailAddresses: [String!] + primaryEmailAddress: EmailAddr! + additionalEmailAddresses: [EmailAddr!] kind: PeopleKind! position: String contractStartDate: Datetime @@ -3357,8 +3359,8 @@ input CreatePeopleInput { input UpdatePeopleInput { id: ID! fullName: String - primaryEmailAddress: String - additionalEmailAddresses: [String!] + primaryEmailAddress: EmailAddr + additionalEmailAddresses: [EmailAddr!] kind: PeopleKind position: String @goField(omittable: true) contractStartDate: Datetime @goField(omittable: true) @@ -3646,7 +3648,7 @@ input UpdateDocumentInput { input ExportDocumentVersionPDFInput { documentVersionId: ID! withWatermark: Boolean! - watermarkEmail: String + watermarkEmail: EmailAddr withSignatures: Boolean! } @@ -3684,7 +3686,7 @@ input ConfirmEmailInput { input InviteUserInput { organizationId: ID! - email: String! + email: EmailAddr! fullName: String! role: MembershipRole! createPeople: Boolean! @@ -4459,7 +4461,7 @@ input BulkDeleteDocumentsInput { input BulkExportDocumentsInput { documentIds: [ID!]! withWatermark: Boolean! - watermarkEmail: String + watermarkEmail: EmailAddr withSignatures: Boolean! } diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 7b0e2fa13..f11ba92ec 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -18,11 +18,13 @@ import ( "github.com/vektah/gqlparser/v2/ast" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/console/v1/types" "go.probo.inc/probo/pkg/server/gqlutils/types/bigint" "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" gid1 "go.probo.inc/probo/pkg/server/gqlutils/types/gid" + mail1 "go.probo.inc/probo/pkg/server/gqlutils/types/mail" ) // region ************************** generated!.gotpl ************************** @@ -9573,6 +9575,7 @@ scalar Datetime scalar Upload scalar Duration scalar BigInt +scalar EmailAddr # Interfaces interface Node { @@ -9653,7 +9656,8 @@ enum MembershipRole EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleEmployee") VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer") - AUDITOR @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") + AUDITOR + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor") } enum APIRole @goModel(model: "go.probo.inc/probo/pkg/coredata.APIRole") { @@ -11241,7 +11245,7 @@ type Organization implements Node { type User implements Node { id: ID! fullName: String! - email: String! + email: EmailAddr! createdAt: Datetime! updatedAt: Datetime! } @@ -11252,7 +11256,7 @@ type Membership implements Node { organizationID: ID! role: MembershipRole! fullName: String! - emailAddress: String! + emailAddress: EmailAddr! authMethod: UserAuthMethod! @goField(forceResolver: true) createdAt: Datetime! updatedAt: Datetime! @@ -11260,7 +11264,7 @@ type Membership implements Node { type Invitation implements Node { id: ID! - email: String! + email: EmailAddr! fullName: String! role: MembershipRole! status: InvitationStatus! @@ -11291,8 +11295,8 @@ type SlackConnectionEdge { type People implements Node { id: ID! fullName: String! - primaryEmailAddress: String! - additionalEmailAddresses: [String!]! + primaryEmailAddress: EmailAddr! + additionalEmailAddresses: [EmailAddr!]! kind: PeopleKind! position: String contractStartDate: Datetime @@ -11395,7 +11399,7 @@ type VendorContact implements Node { id: ID! vendor: Vendor! @goField(forceResolver: true) fullName: String - email: String + email: EmailAddr phone: String role: String createdAt: Datetime! @@ -11896,7 +11900,7 @@ type TrustCenterEdge { type TrustCenterAccess implements Node { id: ID! - email: String! + email: EmailAddr! name: String! active: Boolean! hasAcceptedNonDisclosureAgreement: Boolean! @@ -12739,7 +12743,7 @@ input DeleteTrustCenterNDAInput { input CreateTrustCenterAccessInput { trustCenterId: ID! - email: String! + email: EmailAddr! name: String! active: Boolean! } @@ -12860,7 +12864,7 @@ input DeleteVendorInput { input CreateVendorContactInput { vendorId: ID! fullName: String - email: String + email: EmailAddr phone: String role: String } @@ -12868,7 +12872,7 @@ input CreateVendorContactInput { input UpdateVendorContactInput { id: ID! fullName: String @goField(omittable: true) - email: String @goField(omittable: true) + email: EmailAddr @goField(omittable: true) phone: String @goField(omittable: true) role: String @goField(omittable: true) } @@ -12900,8 +12904,8 @@ input DeleteVendorServiceInput { input CreatePeopleInput { organizationId: ID! fullName: String! - primaryEmailAddress: String! - additionalEmailAddresses: [String!] + primaryEmailAddress: EmailAddr! + additionalEmailAddresses: [EmailAddr!] kind: PeopleKind! position: String contractStartDate: Datetime @@ -12911,8 +12915,8 @@ input CreatePeopleInput { input UpdatePeopleInput { id: ID! fullName: String - primaryEmailAddress: String - additionalEmailAddresses: [String!] + primaryEmailAddress: EmailAddr + additionalEmailAddresses: [EmailAddr!] kind: PeopleKind position: String @goField(omittable: true) contractStartDate: Datetime @goField(omittable: true) @@ -13200,7 +13204,7 @@ input UpdateDocumentInput { input ExportDocumentVersionPDFInput { documentVersionId: ID! withWatermark: Boolean! - watermarkEmail: String + watermarkEmail: EmailAddr withSignatures: Boolean! } @@ -13238,7 +13242,7 @@ input ConfirmEmailInput { input InviteUserInput { organizationId: ID! - email: String! + email: EmailAddr! fullName: String! role: MembershipRole! createPeople: Boolean! @@ -14013,7 +14017,7 @@ input BulkDeleteDocumentsInput { input BulkExportDocumentsInput { documentIds: [ID!]! withWatermark: Boolean! - watermarkEmail: String + watermarkEmail: EmailAddr withSignatures: Boolean! } @@ -28689,7 +28693,7 @@ func (ec *executionContext) _Invitation_email(ctx context.Context, field graphql return obj.Email, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -28702,7 +28706,7 @@ func (ec *executionContext) fieldContext_Invitation_email(_ context.Context, fie IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -30443,7 +30447,7 @@ func (ec *executionContext) _Membership_emailAddress(ctx context.Context, field return obj.EmailAddress, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -30456,7 +30460,7 @@ func (ec *executionContext) fieldContext_Membership_emailAddress(_ context.Conte IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -40421,7 +40425,7 @@ func (ec *executionContext) _People_primaryEmailAddress(ctx context.Context, fie return obj.PrimaryEmailAddress, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -40434,7 +40438,7 @@ func (ec *executionContext) fieldContext_People_primaryEmailAddress(_ context.Co IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -40450,7 +40454,7 @@ func (ec *executionContext) _People_additionalEmailAddresses(ctx context.Context return obj.AdditionalEmailAddresses, nil }, nil, - ec.marshalNString2ᚕstringᚄ, + ec.marshalNEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ, true, true, ) @@ -40463,7 +40467,7 @@ func (ec *executionContext) fieldContext_People_additionalEmailAddresses(_ conte IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -46593,7 +46597,7 @@ func (ec *executionContext) _TrustCenterAccess_email(ctx context.Context, field return obj.Email, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -46606,7 +46610,7 @@ func (ec *executionContext) fieldContext_TrustCenterAccess_email(_ context.Conte IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -50525,7 +50529,7 @@ func (ec *executionContext) _User_email(ctx context.Context, field graphql.Colle return obj.Email, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -50538,7 +50542,7 @@ func (ec *executionContext) fieldContext_User_email(_ context.Context, field gra IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -52953,7 +52957,7 @@ func (ec *executionContext) _VendorContact_email(ctx context.Context, field grap return obj.Email, nil }, nil, - ec.marshalOString2ᚖstring, + ec.marshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, false, ) @@ -52966,7 +52970,7 @@ func (ec *executionContext) fieldContext_VendorContact_email(_ context.Context, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -56539,7 +56543,7 @@ func (ec *executionContext) unmarshalInputBulkExportDocumentsInput(ctx context.C it.WithWatermark = data case "watermarkEmail": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("watermarkEmail")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -57848,14 +57852,14 @@ func (ec *executionContext) unmarshalInputCreatePeopleInput(ctx context.Context, it.FullName = data case "primaryEmailAddress": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("primaryEmailAddress")) - data, err := ec.unmarshalNString2string(ctx, v) + data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } it.PrimaryEmailAddress = data case "additionalEmailAddresses": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("additionalEmailAddresses")) - data, err := ec.unmarshalOString2ᚕstringᚄ(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx, v) if err != nil { return it, err } @@ -58497,7 +58501,7 @@ func (ec *executionContext) unmarshalInputCreateTrustCenterAccessInput(ctx conte it.TrustCenterID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalNString2string(ctx, v) + data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -58662,7 +58666,7 @@ func (ec *executionContext) unmarshalInputCreateVendorContactInput(ctx context.C it.FullName = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -60475,7 +60479,7 @@ func (ec *executionContext) unmarshalInputExportDocumentVersionPDFInput(ctx cont it.WithWatermark = data case "watermarkEmail": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("watermarkEmail")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -60896,7 +60900,7 @@ func (ec *executionContext) unmarshalInputInviteUserInput(ctx context.Context, o it.OrganizationID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalNString2string(ctx, v) + data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -62825,14 +62829,14 @@ func (ec *executionContext) unmarshalInputUpdatePeopleInput(ctx context.Context, it.FullName = data case "primaryEmailAddress": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("primaryEmailAddress")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } it.PrimaryEmailAddress = data case "additionalEmailAddresses": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("additionalEmailAddresses")) - data, err := ec.unmarshalOString2ᚕstringᚄ(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx, v) if err != nil { return it, err } @@ -63564,7 +63568,7 @@ func (ec *executionContext) unmarshalInputUpdateVendorContactInput(ctx context.C it.FullName = graphql.OmittableOf(data) case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -86860,6 +86864,74 @@ var ( } ) +func (ec *executionContext) unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, v any) (mail.Addr, error) { + res, err := mail1.UnmarshalAddrScalar(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, sel ast.SelectionSet, v mail.Addr) graphql.Marshaler { + _ = sel + res := mail1.MarshalAddrScalar(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +func (ec *executionContext) unmarshalNEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx context.Context, v any) ([]*mail.Addr, error) { + var vSlice []any + vSlice = graphql.CoerceList(v) + var err error + res := make([]*mail.Addr, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx context.Context, sel ast.SelectionSet, v []*mail.Addr) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, v any) (*mail.Addr, error) { + res, err := mail1.UnmarshalAddrScalar(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, sel ast.SelectionSet, v *mail.Addr) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + _ = sel + res := mail1.MarshalAddrScalar(*v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) unmarshalNEnableSAMLInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐEnableSAMLInput(ctx context.Context, v any) (types.EnableSAMLInput, error) { res, err := ec.unmarshalInputEnableSAMLInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -92570,6 +92642,60 @@ func (ec *executionContext) marshalODuration2ᚖtimeᚐDuration(ctx context.Cont return res } +func (ec *executionContext) unmarshalOEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx context.Context, v any) ([]*mail.Addr, error) { + if v == nil { + return nil, nil + } + var vSlice []any + vSlice = graphql.CoerceList(v) + var err error + res := make([]*mail.Addr, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalOEmailAddr2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx context.Context, sel ast.SelectionSet, v []*mail.Addr) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, v any) (*mail.Addr, error) { + if v == nil { + return nil, nil + } + res, err := mail1.UnmarshalAddrScalar(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, sel ast.SelectionSet, v *mail.Addr) graphql.Marshaler { + if v == nil { + return graphql.Null + } + _ = sel + _ = ctx + res := mail1.MarshalAddrScalar(*v) + return res +} + func (ec *executionContext) unmarshalOEvidenceOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceOrderBy(ctx context.Context, v any) (*types.EvidenceOrderBy, error) { if v == nil { return nil, nil diff --git a/pkg/server/api/console/v1/types/people.go b/pkg/server/api/console/v1/types/people.go index bd1d41999..8b4718d38 100644 --- a/pkg/server/api/console/v1/types/people.go +++ b/pkg/server/api/console/v1/types/people.go @@ -17,6 +17,7 @@ package types import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -64,11 +65,16 @@ func NewPeopleEdge(p *coredata.People, orderBy coredata.PeopleOrderField) *Peopl } func NewPeople(p *coredata.People) *People { + var emailAddresses []*mail.Addr + for _, emailAddress := range p.AdditionalEmailAddresses { + emailAddresses = append(emailAddresses, &emailAddress) + } + return &People{ ID: p.ID, FullName: p.FullName, PrimaryEmailAddress: p.PrimaryEmailAddress, - AdditionalEmailAddresses: p.AdditionalEmailAddresses, + AdditionalEmailAddresses: emailAddresses, Kind: p.Kind, Position: p.Position, ContractStartDate: p.ContractStartDate, diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index d02972d94..b2c176ce9 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -12,6 +12,7 @@ import ( "github.com/99designs/gqlgen/graphql" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -105,10 +106,10 @@ type BulkDeleteDocumentsPayload struct { } type BulkExportDocumentsInput struct { - DocumentIds []gid.GID `json:"documentIds"` - WithWatermark bool `json:"withWatermark"` - WatermarkEmail *string `json:"watermarkEmail,omitempty"` - WithSignatures bool `json:"withSignatures"` + DocumentIds []gid.GID `json:"documentIds"` + WithWatermark bool `json:"withWatermark"` + WatermarkEmail *mail.Addr `json:"watermarkEmail,omitempty"` + WithSignatures bool `json:"withSignatures"` } type BulkExportDocumentsPayload struct { @@ -419,8 +420,8 @@ type CreateOrganizationPayload struct { type CreatePeopleInput struct { OrganizationID gid.GID `json:"organizationId"` FullName string `json:"fullName"` - PrimaryEmailAddress string `json:"primaryEmailAddress"` - AdditionalEmailAddresses []string `json:"additionalEmailAddresses,omitempty"` + PrimaryEmailAddress mail.Addr `json:"primaryEmailAddress"` + AdditionalEmailAddresses []*mail.Addr `json:"additionalEmailAddresses,omitempty"` Kind coredata.PeopleKind `json:"kind"` Position *string `json:"position,omitempty"` ContractStartDate *time.Time `json:"contractStartDate,omitempty"` @@ -551,10 +552,10 @@ type CreateTaskPayload struct { } type CreateTrustCenterAccessInput struct { - TrustCenterID gid.GID `json:"trustCenterId"` - Email string `json:"email"` - Name string `json:"name"` - Active bool `json:"active"` + TrustCenterID gid.GID `json:"trustCenterId"` + Email mail.Addr `json:"email"` + Name string `json:"name"` + Active bool `json:"active"` } type CreateTrustCenterAccessPayload struct { @@ -586,11 +587,11 @@ type CreateTrustCenterReferencePayload struct { } type CreateVendorContactInput struct { - VendorID gid.GID `json:"vendorId"` - FullName *string `json:"fullName,omitempty"` - Email *string `json:"email,omitempty"` - Phone *string `json:"phone,omitempty"` - Role *string `json:"role,omitempty"` + VendorID gid.GID `json:"vendorId"` + FullName *string `json:"fullName,omitempty"` + Email *mail.Addr `json:"email,omitempty"` + Phone *string `json:"phone,omitempty"` + Role *string `json:"role,omitempty"` } type CreateVendorContactPayload struct { @@ -1135,10 +1136,10 @@ type EvidenceEdge struct { } type ExportDocumentVersionPDFInput struct { - DocumentVersionID gid.GID `json:"documentVersionId"` - WithWatermark bool `json:"withWatermark"` - WatermarkEmail *string `json:"watermarkEmail,omitempty"` - WithSignatures bool `json:"withSignatures"` + DocumentVersionID gid.GID `json:"documentVersionId"` + WithWatermark bool `json:"withWatermark"` + WatermarkEmail *mail.Addr `json:"watermarkEmail,omitempty"` + WithSignatures bool `json:"withSignatures"` } type ExportDocumentVersionPDFPayload struct { @@ -1254,7 +1255,7 @@ type InitiateDomainVerificationPayload struct { type Invitation struct { ID gid.GID `json:"id"` - Email string `json:"email"` + Email mail.Addr `json:"email"` FullName string `json:"fullName"` Role coredata.MembershipRole `json:"role"` Status coredata.InvitationStatus `json:"status"` @@ -1283,7 +1284,7 @@ type InvitationOrder struct { type InviteUserInput struct { OrganizationID gid.GID `json:"organizationId"` - Email string `json:"email"` + Email mail.Addr `json:"email"` FullName string `json:"fullName"` Role coredata.MembershipRole `json:"role"` CreatePeople bool `json:"createPeople"` @@ -1345,7 +1346,7 @@ type Membership struct { OrganizationID gid.GID `json:"organizationID"` Role coredata.MembershipRole `json:"role"` FullName string `json:"fullName"` - EmailAddress string `json:"emailAddress"` + EmailAddress mail.Addr `json:"emailAddress"` AuthMethod coredata.UserAuthMethod `json:"authMethod"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` @@ -1493,8 +1494,8 @@ type PageInfo struct { type People struct { ID gid.GID `json:"id"` FullName string `json:"fullName"` - PrimaryEmailAddress string `json:"primaryEmailAddress"` - AdditionalEmailAddresses []string `json:"additionalEmailAddresses"` + PrimaryEmailAddress mail.Addr `json:"primaryEmailAddress"` + AdditionalEmailAddresses []*mail.Addr `json:"additionalEmailAddresses"` Kind coredata.PeopleKind `json:"kind"` Position *string `json:"position,omitempty"` ContractStartDate *time.Time `json:"contractStartDate,omitempty"` @@ -1772,7 +1773,7 @@ func (this TrustCenter) GetID() gid.GID { return this.ID } type TrustCenterAccess struct { ID gid.GID `json:"id"` - Email string `json:"email"` + Email mail.Addr `json:"email"` Name string `json:"name"` Active bool `json:"active"` HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"` @@ -2056,8 +2057,8 @@ type UpdateOrganizationPayload struct { type UpdatePeopleInput struct { ID gid.GID `json:"id"` FullName *string `json:"fullName,omitempty"` - PrimaryEmailAddress *string `json:"primaryEmailAddress,omitempty"` - AdditionalEmailAddresses []string `json:"additionalEmailAddresses,omitempty"` + PrimaryEmailAddress *mail.Addr `json:"primaryEmailAddress,omitempty"` + AdditionalEmailAddresses []*mail.Addr `json:"additionalEmailAddresses,omitempty"` Kind *coredata.PeopleKind `json:"kind,omitempty"` Position graphql.Omittable[*string] `json:"position,omitempty"` ContractStartDate graphql.Omittable[*time.Time] `json:"contractStartDate,omitempty"` @@ -2201,11 +2202,11 @@ type UpdateVendorBusinessAssociateAgreementPayload struct { } type UpdateVendorContactInput struct { - ID gid.GID `json:"id"` - FullName graphql.Omittable[*string] `json:"fullName,omitempty"` - Email graphql.Omittable[*string] `json:"email,omitempty"` - Phone graphql.Omittable[*string] `json:"phone,omitempty"` - Role graphql.Omittable[*string] `json:"role,omitempty"` + ID gid.GID `json:"id"` + FullName graphql.Omittable[*string] `json:"fullName,omitempty"` + Email graphql.Omittable[*mail.Addr] `json:"email,omitempty"` + Phone graphql.Omittable[*string] `json:"phone,omitempty"` + Role graphql.Omittable[*string] `json:"role,omitempty"` } type UpdateVendorContactPayload struct { @@ -2329,7 +2330,7 @@ type UploadVendorDataPrivacyAgreementPayload struct { type User struct { ID gid.GID `json:"id"` FullName string `json:"fullName"` - Email string `json:"email"` + Email mail.Addr `json:"email"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } @@ -2419,14 +2420,14 @@ type VendorComplianceReportEdge struct { } type VendorContact struct { - ID gid.GID `json:"id"` - Vendor *Vendor `json:"vendor"` - FullName *string `json:"fullName,omitempty"` - Email *string `json:"email,omitempty"` - Phone *string `json:"phone,omitempty"` - Role *string `json:"role,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + Vendor *Vendor `json:"vendor"` + FullName *string `json:"fullName,omitempty"` + Email *mail.Addr `json:"email,omitempty"` + Phone *string `json:"phone,omitempty"` + Role *string `json:"role,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (VendorContact) IsNode() {} diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index f41b7bec6..f55f68a3a 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -19,6 +19,7 @@ import ( "go.probo.inc/probo/pkg/authz" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/server/api/console/v1/schema" @@ -1458,7 +1459,7 @@ func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.C OrganizationID: organization.ID, FullName: currentUser.FullName, PrimaryEmailAddress: currentUser.EmailAddress, - AdditionalEmailAddresses: []string{}, + AdditionalEmailAddresses: []mail.Addr{}, Kind: coredata.PeopleKindEmployee, }, ) @@ -1903,7 +1904,7 @@ func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUse OrganizationID: input.OrganizationID, FullName: input.FullName, PrimaryEmailAddress: input.Email, - AdditionalEmailAddresses: []string{}, + AdditionalEmailAddresses: []mail.Addr{}, Kind: coredata.PeopleKindEmployee, }) if err != nil { @@ -1993,7 +1994,7 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP OrganizationID: input.OrganizationID, FullName: input.FullName, PrimaryEmailAddress: input.PrimaryEmailAddress, - AdditionalEmailAddresses: []string{}, + AdditionalEmailAddresses: []mail.Addr{}, Kind: input.Kind, Position: input.Position, ContractStartDate: input.ContractStartDate, @@ -2019,11 +2020,15 @@ func (r *mutationResolver) UpdatePeople(ctx context.Context, input types.UpdateP prb := r.ProboService(ctx, input.ID.TenantID()) + var emailAddresses []mail.Addr + for _, emailAddress := range input.AdditionalEmailAddresses { + emailAddresses = append(emailAddresses, *emailAddress) + } people, err := prb.Peoples.Update(ctx, probo.UpdatePeopleRequest{ ID: input.ID, FullName: input.FullName, PrimaryEmailAddress: input.PrimaryEmailAddress, - AdditionalEmailAddresses: &input.AdditionalEmailAddresses, + AdditionalEmailAddresses: &emailAddresses, Kind: input.Kind, Position: UnwrapOmittable(input.Position), ContractStartDate: UnwrapOmittable(input.ContractStartDate), diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml index c635e41b8..6c7ab8bc4 100644 --- a/pkg/server/api/mcp/v1/specification.yaml +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -5,6 +5,11 @@ info: components: schemas: + EmailAddress: + type: string + format: string + go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/mail.Addr + Duration: type: string description: A duration @@ -89,12 +94,12 @@ components: type: string description: Full name primary_email_address: - type: string + $ref: "#/components/schemas/EmailAddress" description: Primary email address additional_email_addresses: type: array items: - type: string + $ref: "#/components/schemas/EmailAddress" description: Additional email addresses kind: $ref: "#/components/schemas/PeopleKind" @@ -372,12 +377,12 @@ components: type: string description: Full name primary_email_address: - type: string + $ref: "#/components/schemas/EmailAddress" description: Primary email address additional_email_addresses: type: array items: - type: string + $ref: "#/components/schemas/EmailAddress" description: Additional email addresses kind: $ref: "#/components/schemas/PeopleKind" @@ -414,13 +419,13 @@ components: type: string description: Full name primary_email_address: - type: string + $ref: "#/components/schemas/EmailAddress" description: Primary email address additional_email_addresses: anyOf: - type: array items: - type: string + $ref: "#/components/schemas/EmailAddress" - type: "null" description: No additional email addresses description: Additional email addresses diff --git a/pkg/server/api/mcp/v1/types/types.go b/pkg/server/api/mcp/v1/types/types.go index f1a912f2f..806b49cd5 100644 --- a/pkg/server/api/mcp/v1/types/types.go +++ b/pkg/server/api/mcp/v1/types/types.go @@ -6,6 +6,7 @@ import ( "go.probo.inc/mcpgen/mcp" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "time" ) @@ -32,8 +33,8 @@ var ( AddNonconformityToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["nonconformity"],"properties":{"nonconformity":{"type":"object","required":["id","organization_id","reference_id","root_cause","owner_id","status","created_at","updated_at"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No audit"}]},"corrective_action":{"description":"Corrective action"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"date_identified":{"description":"Date identified","format":"date-time"},"description":{"description":"Description"},"due_date":{"description":"Due date","format":"date-time"},"effectiveness_check":{"description":"Effectiveness check"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) AddObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","owner_id","status"],"properties":{"actions_to_be_implemented":{"type":"string","description":"Actions to be implemented"},"area":{"type":"string","description":"Area"},"due_date":{"type":"string","description":"Due date","format":"date-time"},"last_review_date":{"type":"string","description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"type":"string","description":"Regulator"},"requirement":{"type":"string","description":"Requirement"},"source":{"type":"string","description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]}}}`) AddObligationToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["obligation"],"properties":{"obligation":{"type":"object","required":["id","organization_id","owner_id","status","created_at","updated_at"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"source":{"description":"Source"},"source_id":{"description":"Source ID"},"status":{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) - AddPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","full_name","primary_email_address","kind"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"type":"string","description":"Contract end date","format":"date-time"},"contract_start_date":{"type":"string","description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"type":"string","description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"}}}`) - AddPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) + AddPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","full_name","primary_email_address","kind"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"type":"string","description":"Contract end date","format":"date-time"},"contract_start_date":{"type":"string","description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"type":"string","description":"Position"},"primary_email_address":{"type":"string","format":"string"}}}`) + AddPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) AddRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name","category","treatment","inherent_likelihood","inherent_impact"],"properties":{"category":{"type":"string","description":"Risk category"},"description":{"type":"string","description":"Risk description"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]}}}`) AddRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) AddTaskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id","name"],"properties":{"assigned_to_id":{"description":"Assigned to person ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"Not assigned"}]},"deadline":{"type":"string","description":"Deadline","format":"date-time"},"description":{"type":"string","description":"Task description"},"measure_id":{"description":"Measure ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No measure"}]},"name":{"type":"string","description":"Task name"},"organization_id":{"type":"string","format":"string"},"time_estimate":{"type":"string","description":"A duration"}}}`) @@ -75,7 +76,7 @@ var ( GetObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`) GetObligationToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["obligation"],"properties":{"obligation":{"type":"object","required":["id","organization_id","owner_id","status","created_at","updated_at"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"source":{"description":"Source"},"source_id":{"description":"Source ID"},"status":{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) GetPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`) - GetPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) + GetPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) GetRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`) GetRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) GetSnapshotToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"string"}}}`) @@ -117,7 +118,7 @@ var ( ListOrganizationsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organization_id":{"type":"string","format":"string"}}}`) ListOrganizationsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"organizations":{"type":"array","items":{"type":"object","required":["id","name","description","created_at","updated_at"],"properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Organization description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Organization name"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) ListPeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"exclude_contract_ended":{"type":"boolean","description":"Exclude people with ended contracts"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","FULL_NAME","KIND"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`) - ListPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"next_cursor":{"type":"string","format":"string"},"people":{"type":"array","items":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) + ListPeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"next_cursor":{"type":"string","format":"string"},"people":{"type":"array","items":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) ListRisksToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME","CATEGORY","TREATMENT","INHERENT_RISK_SCORE","RESIDUAL_RISK_SCORE","OWNER_FULL_NAME"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`) ListRisksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risks"],"properties":{"next_cursor":{"type":"string","format":"string"},"risks":{"type":"array","items":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}}`) ListSnapshotsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["organization_id"],"properties":{"cursor":{"type":"string","format":"string"},"order_by":{"type":"object","required":["field","direction"],"properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","NAME","TYPE"]}}},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}}}`) @@ -164,8 +165,8 @@ var ( UpdateNonconformityToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["nonconformity"],"properties":{"nonconformity":{"type":"object","required":["id","organization_id","reference_id","root_cause","owner_id","status","created_at","updated_at"],"properties":{"audit_id":{"description":"Audit ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No audit"}]},"corrective_action":{"description":"Corrective action"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"date_identified":{"description":"Date identified","format":"date-time"},"description":{"description":"Description"},"due_date":{"description":"Due date","format":"date-time"},"effectiveness_check":{"description":"Effectiveness check"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"reference_id":{"type":"string","description":"Reference ID"},"root_cause":{"type":"string","description":"Root cause"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"status":{"type":"string","enum":["OPEN","IN_PROGRESS","CLOSED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) UpdateObligationToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"source":{"description":"Source"},"status":{"description":"Status","anyOf":[{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},{"type":"null","description":"No status"}]}}}`) UpdateObligationToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["obligation"],"properties":{"obligation":{"type":"object","required":["id","organization_id","owner_id","status","created_at","updated_at"],"properties":{"actions_to_be_implemented":{"description":"Actions to be implemented"},"area":{"description":"Area"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"due_date":{"description":"Due date","format":"date-time"},"id":{"type":"string","format":"string"},"last_review_date":{"description":"Last review date","format":"date-time"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"regulator":{"description":"Regulator"},"requirement":{"description":"Requirement"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"source":{"description":"Source"},"source_id":{"description":"Source ID"},"status":{"type":"string","enum":["NON_COMPLIANT","PARTIALLY_COMPLIANT","COMPLIANT"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) - UpdatePeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"additional_email_addresses":{"description":"Additional email addresses","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null","description":"No additional email addresses"}]},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"}}}`) - UpdatePeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","description":"Primary email address"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) + UpdatePeopleToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"additional_email_addresses":{"description":"Additional email addresses","anyOf":[{"type":"array","items":{"type":"string","format":"string"}},{"type":"null","description":"No additional email addresses"}]},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"}}}`) + UpdatePeopleToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["people"],"properties":{"people":{"type":"object","required":["id","organization_id","full_name","primary_email_address","additional_email_addresses","kind","created_at","updated_at"],"properties":{"additional_email_addresses":{"type":"array","description":"Additional email addresses","items":{"type":"string","format":"string"}},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"primary_email_address":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) UpdateRiskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"category":{"type":"string","description":"Risk category"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]}}}`) UpdateRiskToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["risk"],"properties":{"risk":{"type":"object","required":["id","organization_id","name","category","treatment","inherent_likelihood","inherent_impact","inherent_risk_score","residual_likelihood","residual_impact","residual_risk_score","note","created_at","updated_at"],"properties":{"category":{"type":"string","description":"Risk category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Risk description"},"id":{"type":"string","format":"string"},"inherent_impact":{"type":"integer","description":"Inherent impact"},"inherent_likelihood":{"type":"integer","description":"Inherent likelihood"},"inherent_risk_score":{"type":"integer","description":"Inherent risk score"},"name":{"type":"string","description":"Risk name"},"note":{"type":"string","description":"Risk note"},"organization_id":{"type":"string","format":"string"},"owner_id":{"anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No owner"}]},"residual_impact":{"type":"integer","description":"Residual impact"},"residual_likelihood":{"type":"integer","description":"Residual likelihood"},"residual_risk_score":{"type":"integer","description":"Residual risk score"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"treatment":{"type":"string","enum":["MITIGATED","ACCEPTED","AVOIDED","TRANSFERRED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}}}}}`) UpdateTaskToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","required":["id"],"properties":{"deadline":{"description":"Deadline","anyOf":[{"type":"string","description":"Deadline","format":"date-time"},{"type":"null","description":"No deadline"}]},"description":{"description":"Task description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Task name"},"state":{"description":"Task state","anyOf":[{"type":"string","enum":["TODO","DONE"]},{"type":"null","description":"No state"}]},"time_estimate":{"description":"Time estimate","anyOf":[{"type":"string","description":"A duration"},{"type":"null","description":"No time estimate"}]}}}`) @@ -404,7 +405,7 @@ type AddObligationOutput struct { // AddPeopleInput represents the schema type AddPeopleInput struct { // Additional email addresses - AdditionalEmailAddresses []string `json:"additional_email_addresses,omitempty"` + AdditionalEmailAddresses []mail.Addr `json:"additional_email_addresses,omitempty"` // Contract end date ContractEndDate *time.Time `json:"contract_end_date,omitempty"` // Contract start date @@ -418,7 +419,7 @@ type AddPeopleInput struct { // Position Position *string `json:"position,omitempty"` // Primary email address - PrimaryEmailAddress string `json:"primary_email_address"` + PrimaryEmailAddress mail.Addr `json:"primary_email_address"` } // AddPeopleOutput represents the schema @@ -1548,7 +1549,7 @@ type Organization struct { // People represents the schema type People struct { // Additional email addresses - AdditionalEmailAddresses []string `json:"additional_email_addresses"` + AdditionalEmailAddresses []mail.Addr `json:"additional_email_addresses"` // Contract end date ContractEndDate *time.Time `json:"contract_end_date,omitempty"` // Contract start date @@ -1566,7 +1567,7 @@ type People struct { // Position Position *string `json:"position,omitempty"` // Primary email address - PrimaryEmailAddress string `json:"primary_email_address"` + PrimaryEmailAddress mail.Addr `json:"primary_email_address"` // Update timestamp UpdatedAt time.Time `json:"updated_at"` } @@ -2025,7 +2026,7 @@ type UpdateObligationOutput struct { // UpdatePeopleInput represents the schema type UpdatePeopleInput struct { // Additional email addresses - AdditionalEmailAddresses *[]string `json:"additional_email_addresses,omitempty"` + AdditionalEmailAddresses *[]mail.Addr `json:"additional_email_addresses,omitempty"` // Contract end date ContractEndDate mcp.Omittable[*time.Time] `json:"contract_end_date,omitempty"` // Contract start date @@ -2039,7 +2040,7 @@ type UpdatePeopleInput struct { // Position Position mcp.Omittable[*string] `json:"position,omitempty"` // Primary email address - PrimaryEmailAddress *string `json:"primary_email_address,omitempty"` + PrimaryEmailAddress *mail.Addr `json:"primary_email_address,omitempty"` } // UpdatePeopleOutput represents the schema diff --git a/pkg/server/api/slack/v1/slack_handler.go b/pkg/server/api/slack/v1/slack_handler.go index 1e8d062ae..b34db9ffd 100644 --- a/pkg/server/api/slack/v1/slack_handler.go +++ b/pkg/server/api/slack/v1/slack_handler.go @@ -146,7 +146,7 @@ func SlackHandler(slackSvc *slack.Service, slackSigningSecret string, logger *lo return } - if initialSlackMessage.RequesterEmail == nil || *initialSlackMessage.RequesterEmail == "" { + if initialSlackMessage.RequesterEmail == nil { logger.ErrorCtx(ctx, "missing requester email", log.String("slack_message_id", initialSlackMessage.ID.String())) httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"}) return diff --git a/pkg/server/api/trust/v1/gqlgen.yaml b/pkg/server/api/trust/v1/gqlgen.yaml index 61450ba5e..16e1ddeae 100644 --- a/pkg/server/api/trust/v1/gqlgen.yaml +++ b/pkg/server/api/trust/v1/gqlgen.yaml @@ -27,3 +27,6 @@ models: CursorKey: model: - "go.probo.inc/probo/pkg/server/gqlutils/types/cursor.CursorKeyScalar" + EmailAddr: + model: + - "go.probo.inc/probo/pkg/server/gqlutils/types/mail.AddrScalar" diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/schema.graphql index 07436d699..d24aacf9f 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/schema.graphql @@ -19,8 +19,9 @@ enum Role { USER } -scalar Datetime scalar CursorKey +scalar Datetime +scalar EmailAddr interface Node { id: ID! @@ -46,11 +47,9 @@ type Organization implements Node { enum DocumentType @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS") - POLICY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") PROCEDURE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") } @@ -357,9 +356,7 @@ enum CountryCode enum VendorCategory @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { ANALYTICS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") CLOUD_MONITORING @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" @@ -389,28 +386,21 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" ) ENGINEERING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") FINANCE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") IDENTITY_PROVIDER @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" ) IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") MARKETING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") OFFICE_OPERATIONS @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" ) - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") PASSWORD_MANAGEMENT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" @@ -424,15 +414,10 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" ) RECRUITING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" - ) - SALES - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") SECURITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") VERSION_CONTROL @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" @@ -542,7 +527,7 @@ type TrustCenter implements Node { type TrustCenterAccess implements Node { id: ID! - email: String! + email: EmailAddr! name: String! createdAt: Datetime! updatedAt: Datetime! @@ -550,7 +535,7 @@ type TrustCenterAccess implements Node { input RequestAllAccessesInput { trustCenterId: ID! - email: String + email: EmailAddr name: String } @@ -573,21 +558,21 @@ input AcceptNonDisclosureAgreementInput { input RequestDocumentAccessInput { trustCenterId: ID! documentId: ID! - email: String + email: EmailAddr name: String } input RequestReportAccessInput { trustCenterId: ID! reportId: ID! - email: String + email: EmailAddr name: String } input RequestTrustCenterFileAccessInput { trustCenterId: ID! trustCenterFileId: ID! - email: String + email: EmailAddr name: String } diff --git a/pkg/server/api/trust/v1/schema/schema.go b/pkg/server/api/trust/v1/schema/schema.go index 165b1b541..9957c8eaf 100644 --- a/pkg/server/api/trust/v1/schema/schema.go +++ b/pkg/server/api/trust/v1/schema/schema.go @@ -18,10 +18,12 @@ import ( "github.com/vektah/gqlparser/v2/ast" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/trust/v1/types" "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" gid1 "go.probo.inc/probo/pkg/server/gqlutils/types/gid" + mail1 "go.probo.inc/probo/pkg/server/gqlutils/types/mail" ) // region ************************** generated!.gotpl ************************** @@ -1115,8 +1117,9 @@ enum Role { USER } -scalar Datetime scalar CursorKey +scalar Datetime +scalar EmailAddr interface Node { id: ID! @@ -1142,11 +1145,9 @@ type Organization implements Node { enum DocumentType @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS") - POLICY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") PROCEDURE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") } @@ -1453,9 +1454,7 @@ enum CountryCode enum VendorCategory @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { ANALYTICS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") CLOUD_MONITORING @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" @@ -1485,28 +1484,21 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" ) ENGINEERING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") FINANCE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") IDENTITY_PROVIDER @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" ) IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") MARKETING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") OFFICE_OPERATIONS @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" ) - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") PASSWORD_MANAGEMENT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" @@ -1520,15 +1512,10 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" ) RECRUITING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" - ) - SALES - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") SECURITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") VERSION_CONTROL @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" @@ -1638,7 +1625,7 @@ type TrustCenter implements Node { type TrustCenterAccess implements Node { id: ID! - email: String! + email: EmailAddr! name: String! createdAt: Datetime! updatedAt: Datetime! @@ -1646,7 +1633,7 @@ type TrustCenterAccess implements Node { input RequestAllAccessesInput { trustCenterId: ID! - email: String + email: EmailAddr name: String } @@ -1669,21 +1656,21 @@ input AcceptNonDisclosureAgreementInput { input RequestDocumentAccessInput { trustCenterId: ID! documentId: ID! - email: String + email: EmailAddr name: String } input RequestReportAccessInput { trustCenterId: ID! reportId: ID! - email: String + email: EmailAddr name: String } input RequestTrustCenterFileAccessInput { trustCenterId: ID! trustCenterFileId: ID! - email: String + email: EmailAddr name: String } @@ -4584,7 +4571,7 @@ func (ec *executionContext) _TrustCenterAccess_email(ctx context.Context, field return obj.Email, nil }, nil, - ec.marshalNString2string, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, true, true, ) @@ -4597,7 +4584,7 @@ func (ec *executionContext) fieldContext_TrustCenterAccess_email(_ context.Conte IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") + return nil, errors.New("field of type EmailAddr does not have child fields") }, } return fc, nil @@ -7165,7 +7152,7 @@ func (ec *executionContext) unmarshalInputRequestAllAccessesInput(ctx context.Co it.TrustCenterID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -7213,7 +7200,7 @@ func (ec *executionContext) unmarshalInputRequestDocumentAccessInput(ctx context it.DocumentID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -7261,7 +7248,7 @@ func (ec *executionContext) unmarshalInputRequestReportAccessInput(ctx context.C it.ReportID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -7309,7 +7296,7 @@ func (ec *executionContext) unmarshalInputRequestTrustCenterFileAccessInput(ctx it.TrustCenterFileID = data case "email": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) if err != nil { return it, err } @@ -10647,6 +10634,22 @@ var ( } ) +func (ec *executionContext) unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, v any) (mail.Addr, error) { + res, err := mail1.UnmarshalAddrScalar(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, sel ast.SelectionSet, v mail.Addr) graphql.Marshaler { + _ = sel + res := mail1.MarshalAddrScalar(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + func (ec *executionContext) unmarshalNExportDocumentPDFInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportDocumentPDFInput(ctx context.Context, v any) (types.ExportDocumentPDFInput, error) { res, err := ec.unmarshalInputExportDocumentPDFInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -11431,6 +11434,24 @@ func (ec *executionContext) marshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkg return res } +func (ec *executionContext) unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, v any) (*mail.Addr, error) { + if v == nil { + return nil, nil + } + res, err := mail1.UnmarshalAddrScalar(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx context.Context, sel ast.SelectionSet, v *mail.Addr) graphql.Marshaler { + if v == nil { + return graphql.Null + } + _ = sel + _ = ctx + res := mail1.MarshalAddrScalar(*v) + return res +} + func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v any) (*int, error) { if v == nil { return nil, nil diff --git a/pkg/server/api/trust/v1/trustauth/auth.go b/pkg/server/api/trust/v1/trustauth/auth.go index 5ee0b9d37..aad4a8a07 100644 --- a/pkg/server/api/trust/v1/trustauth/auth.go +++ b/pkg/server/api/trust/v1/trustauth/auth.go @@ -21,20 +21,17 @@ import ( "github.com/99designs/gqlgen/graphql" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/server/api/trust/v1/types" ) type TokenAccessData struct { TrustCenterID gid.GID - Email string + Email mail.Addr TenantID gid.TenantID Scope string } -func (t *TokenAccessData) GetEmail() string { - return t.Email -} - type ContextAccessor interface { UserFromContext(ctx context.Context) *coredata.User TokenAccessFromContext(ctx context.Context) *TokenAccessData diff --git a/pkg/server/api/trust/v1/types/types.go b/pkg/server/api/trust/v1/types/types.go index 73b978c90..4cb54aa06 100644 --- a/pkg/server/api/trust/v1/types/types.go +++ b/pkg/server/api/trust/v1/types/types.go @@ -11,6 +11,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" ) @@ -140,30 +141,30 @@ type RequestAccessesPayload struct { } type RequestAllAccessesInput struct { - TrustCenterID gid.GID `json:"trustCenterId"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` + TrustCenterID gid.GID `json:"trustCenterId"` + Email *mail.Addr `json:"email,omitempty"` + Name *string `json:"name,omitempty"` } type RequestDocumentAccessInput struct { - TrustCenterID gid.GID `json:"trustCenterId"` - DocumentID gid.GID `json:"documentId"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` + TrustCenterID gid.GID `json:"trustCenterId"` + DocumentID gid.GID `json:"documentId"` + Email *mail.Addr `json:"email,omitempty"` + Name *string `json:"name,omitempty"` } type RequestReportAccessInput struct { - TrustCenterID gid.GID `json:"trustCenterId"` - ReportID gid.GID `json:"reportId"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` + TrustCenterID gid.GID `json:"trustCenterId"` + ReportID gid.GID `json:"reportId"` + Email *mail.Addr `json:"email,omitempty"` + Name *string `json:"name,omitempty"` } type RequestTrustCenterFileAccessInput struct { - TrustCenterID gid.GID `json:"trustCenterId"` - TrustCenterFileID gid.GID `json:"trustCenterFileId"` - Email *string `json:"email,omitempty"` - Name *string `json:"name,omitempty"` + TrustCenterID gid.GID `json:"trustCenterId"` + TrustCenterFileID gid.GID `json:"trustCenterFileId"` + Email *mail.Addr `json:"email,omitempty"` + Name *string `json:"name,omitempty"` } type TrustCenter struct { @@ -187,7 +188,7 @@ func (this TrustCenter) GetID() gid.GID { return this.ID } type TrustCenterAccess struct { ID gid.GID `json:"id"` - Email string `json:"email"` + Email mail.Addr `json:"email"` Name string `json:"name"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index c5e255167..b4b0ed7dd 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -13,6 +13,7 @@ import ( "github.com/vektah/gqlparser/v2/gqlerror" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/trust/v1/schema" "go.probo.inc/probo/pkg/server/api/trust/v1/types" @@ -82,7 +83,7 @@ func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Docu tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - documentAccess, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + documentAccess, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil } @@ -108,7 +109,7 @@ func (r *documentResolver) HasUserRequestedAccess(ctx context.Context, obj *type tokenData := TokenAccessFromContext(ctx) if tokenData != nil { // Try to load document access - if it exists (regardless of active status), user has requested it - _, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + _, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil // No access requested or error } @@ -133,8 +134,8 @@ func (r *mutationResolver) RequestAllAccesses(ctx context.Context, input types.R if email != nil || input.Name != nil { return nil, fmt.Errorf("email and name are not allowed for authenticated users") } - emailValue := tokenData.GetEmail() - email = &emailValue + + *email = tokenData.Email } if email == nil { return nil, fmt.Errorf("email is required for unauthenticated users") @@ -202,13 +203,13 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex if ndaExists { tokenData := TokenAccessFromContext(ctx) - hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail()) + hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.Email) if err != nil { panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err)) } } - documentAccess, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), input.DocumentID) + documentAccess, err := privateTrustService.TrustCenterAccesses.LoadDocumentAccess(ctx, tokenData.TrustCenterID, tokenData.Email, input.DocumentID) if err != nil { panic(fmt.Errorf("cannot check document access: %w", err)) } @@ -223,12 +224,12 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex } userData := UserFromContext(ctx) - userEmail := "" + var userEmail mail.Addr if userData != nil { userEmail = userData.EmailAddress } if tokenData != nil { - userEmail = tokenData.GetEmail() + userEmail = tokenData.Email } pdf, err := privateTrustService.Documents.ExportPDF(ctx, input.DocumentID, userEmail) @@ -280,13 +281,13 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo } if ndaExists { - hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail()) + hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.Email) if err != nil { panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err)) } } - reportAccess, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), input.ReportID) + reportAccess, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.Email, input.ReportID) if err != nil { panic(fmt.Errorf("cannot check report access: %w", err)) } @@ -301,12 +302,12 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo } userData := UserFromContext(ctx) - userEmail := "" + var userEmail mail.Addr if userData != nil { userEmail = userData.EmailAddress } if tokenData != nil { - userEmail = tokenData.GetEmail() + userEmail = tokenData.Email } pdf, err := privateTrustService.Reports.ExportPDF(ctx, input.ReportID, userEmail) @@ -331,7 +332,7 @@ func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, inp return nil, fmt.Errorf("token not found") } - err = privateTrustService.TrustCenterAccesses.AcceptNonDisclosureAgreement(ctx, input.TrustCenterID, tokenData.GetEmail()) + err = privateTrustService.TrustCenterAccesses.AcceptNonDisclosureAgreement(ctx, input.TrustCenterID, tokenData.Email) if err != nil { return nil, fmt.Errorf("cannot accept NDA: %w", err) } @@ -360,11 +361,7 @@ func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input type email := input.Email tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - if email != nil || input.Name != nil { - return nil, fmt.Errorf("email and name are not allowed for authenticated users") - } - emailValue := tokenData.GetEmail() - email = &emailValue + *email = tokenData.Email } if email == nil { return nil, fmt.Errorf("email is required for unauthenticated users") @@ -413,11 +410,7 @@ func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types. email := input.Email tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - if email != nil || input.Name != nil { - return nil, fmt.Errorf("email and name are not allowed for authenticated users") - } - emailValue := tokenData.GetEmail() - email = &emailValue + *email = tokenData.Email } if email == nil { return nil, fmt.Errorf("email is required for unauthenticated users") @@ -466,11 +459,7 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp email := input.Email tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - if email != nil || input.Name != nil { - return nil, fmt.Errorf("email and name are not allowed for authenticated users") - } - emailValue := tokenData.GetEmail() - email = &emailValue + *email = tokenData.Email } if email == nil { return nil, fmt.Errorf("email is required for unauthenticated users") @@ -538,13 +527,13 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type } if ndaExists { - hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail()) + hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.Email) if err != nil { panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err)) } } - fileAccess, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), input.TrustCenterFileID) + fileAccess, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.Email, input.TrustCenterFileID) if err != nil { panic(fmt.Errorf("cannot check trust center file access: %w", err)) } @@ -559,12 +548,12 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type } userData := UserFromContext(ctx) - userEmail := "" + var userEmail mail.Addr if userData != nil { userEmail = userData.EmailAddress } if tokenData != nil { - userEmail = tokenData.GetEmail() + userEmail = tokenData.Email } fileData, err := privateTrustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, userEmail) @@ -743,7 +732,7 @@ func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - reportAccess, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + reportAccess, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil } @@ -768,7 +757,7 @@ func (r *reportResolver) HasUserRequestedAccess(ctx context.Context, obj *types. tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - _, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + _, err := privateTrustService.TrustCenterAccesses.LoadReportAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil } @@ -822,7 +811,7 @@ func (r *trustCenterResolver) HasAcceptedNonDisclosureAgreement(ctx context.Cont tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - hasAcceptedNDA, err := privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, obj.ID, tokenData.GetEmail()) + hasAcceptedNDA, err := privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, obj.ID, tokenData.Email) if err != nil { panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err)) } @@ -947,7 +936,7 @@ func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *typ tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - fileAccess, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + fileAccess, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil } @@ -972,7 +961,7 @@ func (r *trustCenterFileResolver) HasUserRequestedAccess(ctx context.Context, ob tokenData := TokenAccessFromContext(ctx) if tokenData != nil { - _, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.GetEmail(), obj.ID) + _, err := privateTrustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, tokenData.TrustCenterID, tokenData.Email, obj.ID) if err != nil { return false, nil } diff --git a/pkg/server/auth/accept_invitation_handler.go b/pkg/server/auth/accept_invitation_handler.go index f535fe85a..fb9847cc8 100644 --- a/pkg/server/auth/accept_invitation_handler.go +++ b/pkg/server/auth/accept_invitation_handler.go @@ -87,9 +87,7 @@ func AcceptInvitationHandler(authSvc *authsvc.Service, authzSvc *authz.Service, return } - response := AcceptInvitationResponse{ - InvitationID: req.InvitationID, - } + response := AcceptInvitationResponse(req) httpserver.RenderJSON(w, http.StatusOK, response) } diff --git a/pkg/server/auth/delete_api_key_handler.go b/pkg/server/auth/delete_api_key_handler.go index 97d5f92f9..8c13f9874 100644 --- a/pkg/server/auth/delete_api_key_handler.go +++ b/pkg/server/auth/delete_api_key_handler.go @@ -75,9 +75,7 @@ func DeleteUserAPIKeyHandler(authSvc *authsvc.Service) http.HandlerFunc { return } - response := DeleteUserAPIKeyResponse{ - ID: req.ID, - } + response := DeleteUserAPIKeyResponse(req) httpserver.RenderJSON(w, http.StatusOK, response) } diff --git a/pkg/server/auth/forget_password_handler.go b/pkg/server/auth/forget_password_handler.go index a8af191c9..aba3e1ba7 100644 --- a/pkg/server/auth/forget_password_handler.go +++ b/pkg/server/auth/forget_password_handler.go @@ -19,13 +19,14 @@ import ( "fmt" "net/http" - authsvc "go.probo.inc/probo/pkg/auth" "go.gearno.de/kit/httpserver" + authsvc "go.probo.inc/probo/pkg/auth" + "go.probo.inc/probo/pkg/mail" ) type ( ForgetPasswordRequest struct { - Email string `json:"email"` + Email mail.Addr `json:"email"` } ForgetPasswordResponse struct { diff --git a/pkg/server/auth/list_invitations_handler.go b/pkg/server/auth/list_invitations_handler.go index 65cb0b18f..5c787a4c1 100644 --- a/pkg/server/auth/list_invitations_handler.go +++ b/pkg/server/auth/list_invitations_handler.go @@ -18,9 +18,10 @@ import ( "fmt" "net/http" + "go.gearno.de/kit/httpserver" "go.probo.inc/probo/pkg/authz" "go.probo.inc/probo/pkg/gid" - "go.gearno.de/kit/httpserver" + "go.probo.inc/probo/pkg/mail" ) type ( @@ -30,7 +31,7 @@ type ( InvitationResponse struct { ID gid.GID `json:"id"` - Email string `json:"email"` + Email mail.Addr `json:"email"` FullName string `json:"fullName"` Role string `json:"role"` ExpiresAt string `json:"expiresAt"` diff --git a/pkg/server/auth/sign_in_handler.go b/pkg/server/auth/sign_in_handler.go index 9ac9ed1a5..f3bee8a11 100644 --- a/pkg/server/auth/sign_in_handler.go +++ b/pkg/server/auth/sign_in_handler.go @@ -25,13 +25,14 @@ import ( authsvc "go.probo.inc/probo/pkg/auth" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/securecookie" ) type ( SignInRequest struct { - Email string `json:"email"` - Password string `json:"password"` + Email mail.Addr `json:"email"` + Password string `json:"password"` } SignInResponse struct { @@ -40,7 +41,7 @@ type ( UserResponse struct { ID gid.GID `json:"id"` - Email string `json:"email"` + Email mail.Addr `json:"email"` FullName string `json:"fullName"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` diff --git a/pkg/server/auth/sign_up_handler.go b/pkg/server/auth/sign_up_handler.go index dc7cf5aab..c0e63ef72 100644 --- a/pkg/server/auth/sign_up_handler.go +++ b/pkg/server/auth/sign_up_handler.go @@ -22,14 +22,15 @@ import ( "go.gearno.de/kit/httpserver" authsvc "go.probo.inc/probo/pkg/auth" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/securecookie" ) type ( SignUpRequest struct { - Email string `json:"email"` - Password string `json:"password"` - FullName string `json:"fullName"` + Email mail.Addr `json:"email"` + Password string `json:"password"` + FullName string `json:"fullName"` } SignUpResponse struct { diff --git a/pkg/server/gqlutils/types/mail/addr.go b/pkg/server/gqlutils/types/mail/addr.go new file mode 100644 index 000000000..f3839b635 --- /dev/null +++ b/pkg/server/gqlutils/types/mail/addr.go @@ -0,0 +1,46 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 mail + +import ( + "errors" + "io" + "strconv" + + "github.com/99designs/gqlgen/graphql" + "go.probo.inc/probo/pkg/mail" +) + +type AddrScalar = mail.Addr + +func MarshalAddrScalar(a mail.Addr) graphql.Marshaler { + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte(strconv.Quote(a.String()))) + }) +} + +func UnmarshalAddrScalar(v interface{}) (mail.Addr, error) { + s, ok := v.(string) + if !ok { + return mail.Nil, errors.New("must be a string") + } + + a, err := mail.ParseAddr(s) + if err != nil { + return mail.Nil, err + } + + return a, nil +} diff --git a/pkg/slack/slack_message_service.go b/pkg/slack/slack_message_service.go index 9d4c39650..4c1bed752 100644 --- a/pkg/slack/slack_message_service.go +++ b/pkg/slack/slack_message_service.go @@ -26,6 +26,7 @@ import ( "go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" ) const ( @@ -101,7 +102,7 @@ func (s *SlackMessageService) UpdateSlackAccessMessage( ctx context.Context, slackMessageID gid.GID, responseURL string, - requesterEmail string, + requesterEmail mail.Addr, ) error { return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { var slackMessage coredata.SlackMessage @@ -174,7 +175,7 @@ func (s *SlackMessageService) UpdateSlackAccessMessage( func (s *SlackMessageService) QueueSlackNotification( ctx context.Context, - requesterEmail string, + requesterEmail mail.Addr, trustCenterID gid.GID, ) error { return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { @@ -287,7 +288,7 @@ func (s *SlackMessageService) QueueSlackNotification( func (s *SlackMessageService) QueueSlackAccessMessageUpdate( ctx context.Context, - requesterEmail string, + requesterEmail mail.Addr, trustCenterID gid.GID, ) error { return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { @@ -461,7 +462,7 @@ func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses( func (s *SlackMessageService) buildAccessRequestMessage( slackMessageID gid.GID, requesterName string, - requesterEmail string, + requesterEmail mail.Addr, organizationID gid.GID, documents []SlackMessageDocument, reports []SlackMessageReport, @@ -483,7 +484,7 @@ func (s *SlackMessageService) buildAccessRequestMessage( Files []SlackMessageFile }{ RequesterName: requesterName, - RequesterEmail: requesterEmail, + RequesterEmail: requesterEmail.String(), OrganizationID: organizationID.String(), Domain: base.Host(), SlackMessageID: slackMessageID.String(), diff --git a/pkg/trust/document_service.go b/pkg/trust/document_service.go index d93099038..5fcb6b0d9 100644 --- a/pkg/trust/document_service.go +++ b/pkg/trust/document_service.go @@ -19,13 +19,14 @@ import ( "fmt" "io" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/docgen" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/html2pdf" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/watermarkpdf" - "go.gearno.de/kit/pg" ) type ( @@ -88,7 +89,7 @@ func (s *DocumentService) ListForOrganizationId( func (s *DocumentService) ExportPDF( ctx context.Context, documentID gid.GID, - email string, + email mail.Addr, ) ([]byte, error) { pdfData, err := s.exportPDFData(ctx, documentID) if err != nil { diff --git a/pkg/trust/report_service.go b/pkg/trust/report_service.go index acdbbc7db..8966a5e2e 100644 --- a/pkg/trust/report_service.go +++ b/pkg/trust/report_service.go @@ -22,10 +22,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/watermarkpdf" - "go.gearno.de/kit/pg" ) type ReportService struct { @@ -88,7 +89,7 @@ func (s ReportService) GenerateDownloadURL( func (s ReportService) ExportPDF( ctx context.Context, reportID gid.GID, - email string, + email mail.Addr, ) ([]byte, error) { pdfData, err := s.exportPDFData(ctx, reportID) if err != nil { diff --git a/pkg/trust/trust_center_access_service.go b/pkg/trust/trust_center_access_service.go index e3ca66eef..fb34eb911 100644 --- a/pkg/trust/trust_center_access_service.go +++ b/pkg/trust/trust_center_access_service.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "net/mail" "net/url" "time" @@ -29,6 +28,7 @@ import ( "go.probo.inc/probo/pkg/auth" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/statelesstoken" "go.probo.inc/probo/pkg/validator" @@ -43,7 +43,7 @@ type ( TrustCenterAccessRequest struct { TrustCenterID gid.GID - Email string + Email mail.Addr Name *string DocumentIDs []gid.GID ReportIDs []gid.GID @@ -66,7 +66,7 @@ func (tcar *TrustCenterAccessRequest) Validate() error { func (s TrustCenterAccessService) ValidateToken( ctx context.Context, trustCenterID gid.GID, - email string, + email mail.Addr, ) error { return s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { access := &coredata.TrustCenterAccess{} @@ -87,6 +87,10 @@ func (s TrustCenterAccessService) Request( ctx context.Context, req *TrustCenterAccessRequest, ) (*coredata.TrustCenterAccess, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request arguments") + } + now := time.Now() var access *coredata.TrustCenterAccess @@ -161,10 +165,6 @@ func (s TrustCenterAccessService) Request( return fmt.Errorf("name is required for new access requests") } - if _, err := mail.ParseAddress(req.Email); err != nil { - return fmt.Errorf("invalid email address") - } - access = &coredata.TrustCenterAccess{ ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType), OrganizationID: organizationID, @@ -248,7 +248,7 @@ func (s TrustCenterAccessService) Request( return access, nil } -func (s TrustCenterAccessService) HasAcceptedNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email string) (bool, error) { +func (s TrustCenterAccessService) HasAcceptedNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email mail.Addr) (bool, error) { access := &coredata.TrustCenterAccess{} err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email) @@ -266,7 +266,7 @@ func (s TrustCenterAccessService) HasAcceptedNonDisclosureAgreement(ctx context. return access.HasAcceptedNonDisclosureAgreement, nil } -func (s TrustCenterAccessService) AcceptNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email string) error { +func (s TrustCenterAccessService) AcceptNonDisclosureAgreement(ctx context.Context, trustCenterID gid.GID, email mail.Addr) error { return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { access := &coredata.TrustCenterAccess{} if err := access.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, email); err != nil { @@ -279,7 +279,7 @@ func (s TrustCenterAccessService) AcceptNonDisclosureAgreement(ctx context.Conte } acceptationLogs, err := json.Marshal(map[string]string{ - "email": email, + "email": email.String(), "timestamp": time.Now().Format(time.RFC3339), "ip": ctx.Value(coredata.ContextKeyIPAddress).(string), }) @@ -302,7 +302,7 @@ func (s TrustCenterAccessService) AcceptNonDisclosureAgreement(ctx context.Conte func (s TrustCenterAccessService) LoadDocumentAccess( ctx context.Context, trustCenterID gid.GID, - email string, + email mail.Addr, documentID gid.GID, ) (*coredata.TrustCenterDocumentAccess, error) { var documentAccess *coredata.TrustCenterDocumentAccess @@ -337,7 +337,7 @@ func (s TrustCenterAccessService) LoadDocumentAccess( func (s TrustCenterAccessService) LoadReportAccess( ctx context.Context, trustCenterID gid.GID, - email string, + email mail.Addr, reportID gid.GID, ) (*coredata.TrustCenterDocumentAccess, error) { var reportAccess *coredata.TrustCenterDocumentAccess @@ -372,7 +372,7 @@ func (s TrustCenterAccessService) LoadReportAccess( func (s TrustCenterAccessService) LoadTrustCenterFileAccess( ctx context.Context, trustCenterID gid.GID, - email string, + email mail.Addr, trustCenterFileID gid.GID, ) (*coredata.TrustCenterDocumentAccess, error) { var fileAccess *coredata.TrustCenterDocumentAccess @@ -407,7 +407,7 @@ func (s TrustCenterAccessService) LoadTrustCenterFileAccess( func (s *TrustCenterAccessService) GrantByIDs( ctx context.Context, organizationID gid.GID, - email string, + email mail.Addr, documentIDs []gid.GID, reportIDs []gid.GID, fileIDs []gid.GID, @@ -533,7 +533,7 @@ func (s *TrustCenterAccessService) sendTrustCenterAccessEmail( ctx context.Context, tx pg.Conn, name string, - email string, + email mail.Addr, companyName string, accessURL string, ) error { @@ -565,7 +565,7 @@ func (s *TrustCenterAccessService) sendTrustCenterAccessEmail( func (s *TrustCenterAccessService) RejectOrRevokeByIDs( ctx context.Context, organizationID gid.GID, - email string, + email mail.Addr, documentIDs []gid.GID, reportIDs []gid.GID, fileIDs []gid.GID, diff --git a/pkg/trust/trust_center_file_service.go b/pkg/trust/trust_center_file_service.go index 85bc55bc8..f6d47c940 100644 --- a/pkg/trust/trust_center_file_service.go +++ b/pkg/trust/trust_center_file_service.go @@ -21,11 +21,12 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/watermarkpdf" - "go.gearno.de/kit/pg" ) type TrustCenterFileService struct { @@ -86,7 +87,7 @@ func (s *TrustCenterFileService) ListForOrganizationId( func (s *TrustCenterFileService) ExportFile( ctx context.Context, trustCenterFileID gid.GID, - email string, + email mail.Addr, ) ([]byte, error) { pdfData, err := s.exportFileData(ctx, trustCenterFileID) if err != nil { diff --git a/pkg/validator/validation_bench_test.go b/pkg/validator/validation_bench_test.go index 9f88e221e..615f46094 100644 --- a/pkg/validator/validation_bench_test.go +++ b/pkg/validator/validation_bench_test.go @@ -25,7 +25,7 @@ func BenchmarkValidate_SingleField(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { v := New() - v.Check(&email, "email", Required(), Email()) + v.Check(&email, "email", Required(), NotEmpty()) } } @@ -37,7 +37,7 @@ func BenchmarkValidate_MultipleFields(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { v := New() - v.Check(&email, "email", Required(), Email()) + v.Check(&email, "email", Required(), NotEmpty()) v.Check(&password, "password", Required(), MinLen(8)) v.Check(&age, "age", Min(18), Max(120)) } @@ -108,16 +108,6 @@ func BenchmarkValidate_ArrayValidation(b *testing.B) { } } -func BenchmarkEmail(b *testing.B) { - email := "test@example.com" - validator := Email() - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = validator(&email) - } -} - func BenchmarkURL(b *testing.B) { urlStr := "https://example.com" validator := URL() @@ -214,7 +204,7 @@ func BenchmarkValidate_WithErrors(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { v := New() - v.Check(&email, "email", Required(), Email()) + v.Check(&email, "email", Required(), NotEmpty()) if !v.HasErrors() { b.Fatal("expected validation error") } @@ -256,7 +246,7 @@ func BenchmarkValidate_ComplexForm(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { v := New() - v.Check(&user.Email, "email", Required(), Email()) + v.Check(&user.Email, "email", Required(), NotEmpty()) v.Check(&user.Name, "name", Required(), MinLen(2)) v.Check(&user.Age, "age", Min(18), Max(120)) v.Check(user.Website, "website", URL()) diff --git a/pkg/validator/validation_test.go b/pkg/validator/validation_test.go index 2f2fc13c5..0a8c55f63 100644 --- a/pkg/validator/validation_test.go +++ b/pkg/validator/validation_test.go @@ -19,13 +19,15 @@ import ( "testing" "go.gearno.de/x/ref" + "go.probo.inc/probo/pkg/mail" ) func TestValidator_Validate(t *testing.T) { t.Run("single field validation", func(t *testing.T) { v := New() - email := "test@example.com" - v.Check(&email, "email", Required(), Email()) + email := mail.Addr("test@example.com") + + v.Check(&email, "email", Required(), NotEmpty()) if v.HasErrors() { t.Errorf("expected no errors, got: %v", v.Errors()) @@ -34,10 +36,10 @@ func TestValidator_Validate(t *testing.T) { t.Run("multiple field validations", func(t *testing.T) { v := New() - email := "" + email := mail.Nil password := "123" - v.Check(&email, "email", Required(), Email()) + v.Check(email, "email", NotEmpty()) v.Check(&password, "password", Required(), MinLen(8)) if !v.HasErrors() { @@ -69,8 +71,8 @@ func TestValidator_CheckNested(t *testing.T) { v := New() v.CheckNested("user", func(nv *Validator) { - email := "invalid" - nv.Check(&email, "email", Email()) + email := mail.Nil + nv.Check(&email, "email", NotEmpty()) nv.CheckNested("address", func(av *Validator) { city := "" @@ -186,7 +188,7 @@ func TestOptionalFieldExample(t *testing.T) { v := New() - v.Check(&req.Email, "email", Required(), Email()) + v.Check(&req.Email, "email", Required(), NotEmpty()) v.Check(&req.Name, "name", Required(), MinLen(2)) v.Check(req.Website, "website", URL()) v.Check(req.PhoneNumber, "phoneNumber", MinLen(10)) @@ -232,7 +234,7 @@ func TestRealWorldExample(t *testing.T) { } user := User{ - Email: "invalid-email", + Email: "", Password: "123", Age: 15, Website: ref.Ref("not-a-url"), @@ -245,7 +247,7 @@ func TestRealWorldExample(t *testing.T) { v := New() // Validate user fields - v.Check(&user.Email, "email", Required(), Email()) + v.Check(&user.Email, "email", Required(), NotEmpty()) v.Check(&user.Password, "password", Required(), MinLen(8)) v.Check(&user.Age, "age", Min(18), Max(120)) v.Check(user.Website, "website", URL()) @@ -262,7 +264,7 @@ func TestRealWorldExample(t *testing.T) { errors := v.Errors() expectedErrors := map[string]ErrorCode{ - "email": ErrorCodeInvalidEmail, + "email": ErrorCodeRequired, "password": ErrorCodeTooShort, "age": ErrorCodeOutOfRange, "website": ErrorCodeInvalidURL, @@ -373,10 +375,10 @@ func TestDuplicateValidators(t *testing.T) { } }) - t.Run("duplicate Email creates two errors", func(t *testing.T) { + t.Run("duplicate NotEmpty creates two errors", func(t *testing.T) { v := New() - email := "invalid" - v.Check(&email, "email", Email(), Email()) + email := mail.Nil + v.Check(&email, "email", NotEmpty(), NotEmpty()) errors := v.Errors() if len(errors) != 2 { @@ -405,22 +407,23 @@ func TestDuplicateValidators(t *testing.T) { func TestStandardErrorPattern(t *testing.T) { // Simulates a typical validation function - validateUser := func(email, password string) error { + validateUser := func(email mail.Addr, password string) error { v := New() - v.Check(&email, "email", Required(), Email()) + v.Check(&email, "email", Required(), NotEmpty()) v.Check(&password, "password", Required(), MinLen(8)) return v.Error() } t.Run("valid data returns nil", func(t *testing.T) { - err := validateUser("user@example.com", "password123") - if err != nil { + email := mail.Addr("user@example.com") + + if err := validateUser(email, "password123"); err != nil { t.Errorf("expected nil, got: %v", err) } }) t.Run("invalid data returns ValidationErrors as error", func(t *testing.T) { - err := validateUser("", "123") + err := validateUser(mail.Nil, "123") if err == nil { t.Fatal("expected validation errors") } diff --git a/pkg/validator/validator_common.go b/pkg/validator/validator_common.go index 8d3eb2bdc..bdb381064 100644 --- a/pkg/validator/validator_common.go +++ b/pkg/validator/validator_common.go @@ -17,6 +17,8 @@ package validator import ( "reflect" "strings" + + "go.probo.inc/probo/pkg/mail" ) // Required validates that a field has a value. @@ -59,6 +61,10 @@ func NotEmpty() ValidatorFunc { if strings.TrimSpace(v) == "" { return newValidationError(ErrorCodeRequired, "field cannot be empty") } + case mail.Addr: + if v == mail.Nil { + return newValidationError(ErrorCodeRequired, "field cannot be empty") + } default: rv := reflect.ValueOf(actualValue) if rv.Kind() == reflect.Slice && rv.Len() == 0 { diff --git a/pkg/validator/validator_format.go b/pkg/validator/validator_format.go index 6c7b49a85..8214a0e8b 100644 --- a/pkg/validator/validator_format.go +++ b/pkg/validator/validator_format.go @@ -23,36 +23,10 @@ import ( var ( emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) - gidRegex = regexp.MustCompile(`^gid://[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+$`) uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`) ) -// Email validates that a string is a valid email address. -func Email() ValidatorFunc { - return func(value any) *ValidationError { - actualValue, isNil := dereferenceValue(value) - if isNil { - return nil - } - - str, ok := actualValue.(string) - if !ok { - return newValidationError(ErrorCodeInvalidEmail, "value must be a string") - } - - if str == "" { - return nil - } - - if !emailRegex.MatchString(str) { - return newValidationError(ErrorCodeInvalidEmail, "invalid email address") - } - - return nil - } -} - // URL validates that a string is a valid URL with http or https scheme. func URL() ValidatorFunc { return func(value any) *ValidationError { diff --git a/pkg/validator/validator_format_test.go b/pkg/validator/validator_format_test.go index 7aa17c4f7..5bb3e6c73 100644 --- a/pkg/validator/validator_format_test.go +++ b/pkg/validator/validator_format_test.go @@ -21,35 +21,6 @@ import ( "go.probo.inc/probo/pkg/gid" ) -func TestEmail(t *testing.T) { - tests := []struct { - name string - value any - wantError bool - }{ - {"valid email", "test@example.com", false}, - {"valid email with plus", "test+tag@example.com", false}, - {"invalid email no @", "testexample.com", true}, - {"invalid email no domain", "test@", true}, - {"invalid email no TLD", "test@example", true}, - {"empty string", "", false}, // Empty is allowed, use Required() to enforce - {"nil pointer", (*string)(nil), false}, // Skip validation - {"non-string", 123, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := Email()(tt.value) - if (err != nil) != tt.wantError { - t.Errorf("Email() error = %v, wantError %v", err, tt.wantError) - } - if err != nil && err.Code != ErrorCodeInvalidEmail { - t.Errorf("Expected error code %s, got %s", ErrorCodeInvalidEmail, err.Code) - } - }) - } -} - func TestURL(t *testing.T) { tests := []struct { name string diff --git a/pkg/watermarkpdf/watermarkpdf.go b/pkg/watermarkpdf/watermarkpdf.go index ce309a5a1..799e6ddeb 100644 --- a/pkg/watermarkpdf/watermarkpdf.go +++ b/pkg/watermarkpdf/watermarkpdf.go @@ -25,6 +25,7 @@ import ( "github.com/pdfcpu/pdfcpu/pkg/api" "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" + "go.probo.inc/probo/pkg/mail" "golang.org/x/image/font" "golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/opentype" @@ -46,12 +47,12 @@ var ( fontColor = color.RGBA{0, 0, 0, 255} ) -func AddConfidentialWithTimestamp(pdfData []byte, email string) ([]byte, error) { +func AddConfidentialWithTimestamp(pdfData []byte, email mail.Addr) ([]byte, error) { reader := bytes.NewReader(pdfData) watermarkLines := []string{ "Confidential", - email, + email.String(), time.Now().Format("2006-01-02"), }