diff --git a/apps/trust/src/components/NDADialog.tsx b/apps/trust/src/components/NDADialog.tsx index 2888db771..d6259cf46 100644 --- a/apps/trust/src/components/NDADialog.tsx +++ b/apps/trust/src/components/NDADialog.tsx @@ -1,27 +1,34 @@ -import { Button, Card, Logo, Spinner } from "@probo/ui"; +import { Button, Card, Field, Logo, Spinner } from "@probo/ui"; import { sprintf } from "@probo/helpers"; import { useTranslate } from "@probo/i18n"; -import { useEffect } from "react"; +import { use, useEffect } from "react"; import { PDFPreview } from "./PDFPreview"; import { useWindowSize } from "usehooks-ts"; import clsx from "clsx"; import { graphql } from "relay-runtime"; import { useMutationWithToasts } from "/hooks/useMutationWithToast"; +import z from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { Viewer } from "/providers/Viewer"; const signMutation = graphql` - mutation NDADialogSignMutation { - acceptNonDisclosureAgreement { + mutation NDADialogSignMutation($input: AcceptNonDisclosureAgreementInput!) { + acceptNonDisclosureAgreement(input: $input) { success } } `; +const schema = z.object({ + fullName: z.string(), +}); + export function NDADialog({ - name, + organizationName, url, fileName, }: { - name: string; + organizationName: string; url?: string | null; fileName?: string | null; }) { @@ -35,12 +42,34 @@ export function NDADialog({ const { width } = useWindowSize(); const isMobile = width < 1100; const isDesktop = !isMobile; + const viewer = use(Viewer); + + const { + handleSubmit: handleSubmitWrapper, + register, + formState, + } = useFormWithSchema(schema, { + defaultValues: { + fullName: viewer?.fullName, + }, + }); + const [commitSigning, isSigning] = useMutationWithToasts(signMutation, { onSuccess: () => { window.location.reload(); }, }); + const handleSubmit = handleSubmitWrapper(({ fullName }) => { + commitSigning({ + variables: { + input: { + fullName, + }, + }, + }); + }); + return (
@@ -56,7 +85,7 @@ export function NDADialog({ __( "Access to %s Trust Center documents requires signing a Non-Disclosure Agreement (NDA). Please review the agreement below. Once signed, you’ll receive immediate access to the requested documents.", ), - name, + organizationName, )}

{isMobile && url && ( @@ -69,14 +98,25 @@ export function NDADialog({ )} - +
+
+ + +
+

{__( "By clicking Review & Sign, you agree to the terms of this NDA. If you have questions about the NDA, please contact security@probo.com.", diff --git a/apps/trust/src/components/__generated__/NDADialogSignMutation.graphql.ts b/apps/trust/src/components/__generated__/NDADialogSignMutation.graphql.ts index 6f8d7b722..547deab5e 100644 --- a/apps/trust/src/components/__generated__/NDADialogSignMutation.graphql.ts +++ b/apps/trust/src/components/__generated__/NDADialogSignMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<3ef589b34a5cb80406c517d6e5eb938c>> + * @generated SignedSource<<22d97ce5cead5a1cc6c23e89fca72cb1>> * @lightSyntaxTransform * @nogrep */ @@ -9,11 +9,16 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; -export type NDADialogSignMutation$variables = Record; +export type AcceptNonDisclosureAgreementInput = { + fullName: string; +}; +export type NDADialogSignMutation$variables = { + input: AcceptNonDisclosureAgreementInput; +}; export type NDADialogSignMutation$data = { readonly acceptNonDisclosureAgreement: { readonly success: boolean; - }; + } | null | undefined; }; export type NDADialogSignMutation = { response: NDADialogSignMutation$data; @@ -22,9 +27,22 @@ export type NDADialogSignMutation = { const node: ConcreteRequest = (function(){ var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ { "alias": null, - "args": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], "concreteType": "AcceptNonDisclosureAgreementPayload", "kind": "LinkedField", "name": "acceptNonDisclosureAgreement", @@ -43,32 +61,32 @@ var v0 = [ ]; return { "fragment": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, "name": "NDADialogSignMutation", - "selections": (v0/*: any*/), + "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null }, "kind": "Request", "operation": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Operation", "name": "NDADialogSignMutation", - "selections": (v0/*: any*/) + "selections": (v1/*: any*/) }, "params": { - "cacheID": "bca0dcb3f227c89d8339b0238aa2e42a", + "cacheID": "130cfc307dca0525194e0103a0548bd0", "id": null, "metadata": {}, "name": "NDADialogSignMutation", "operationKind": "mutation", - "text": "mutation NDADialogSignMutation {\n acceptNonDisclosureAgreement {\n success\n }\n}\n" + "text": "mutation NDADialogSignMutation(\n $input: AcceptNonDisclosureAgreementInput!\n) {\n acceptNonDisclosureAgreement(input: $input) {\n success\n }\n}\n" } }; })(); -(node as any).hash = "814a623444a446730f260e6b7bbf0083"; +(node as any).hash = "1b9447e5cbb2ec7dce4f3f9c68493555"; export default node; diff --git a/apps/trust/src/layouts/MainLayout.tsx b/apps/trust/src/layouts/MainLayout.tsx index 0e68cf5f8..9b73cea64 100644 --- a/apps/trust/src/layouts/MainLayout.tsx +++ b/apps/trust/src/layouts/MainLayout.tsx @@ -30,7 +30,7 @@ export function MainLayout(props: Props) { {showNDADialog && ( diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 124261fd6..79498516e 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -653,3 +653,35 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, token string) return identity, session, err } + +func (s *AuthService) UpdateIdentity(ctx context.Context, identityID gid.GID, fullName string) (*coredata.Identity, error) { + identity := &coredata.Identity{} + + err := s.pg.WithTx( + ctx, + func(tx pg.Conn) error { + if err := identity.LoadByID(ctx, tx, identityID); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return NewIdentityNotFoundError(identityID) + } + + return fmt.Errorf("cannot load identity: %w", err) + } + + identity.FullName = fullName + identity.UpdatedAt = time.Now() + + if err := identity.Update(ctx, tx); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return NewIdentityNotFoundError(identityID) + } + + return fmt.Errorf("cannot update identity: %w", err) + } + + return nil + }, + ) + + return identity, err +} diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/schema.graphql index 8680e8f32..4b7c99376 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/schema.graphql @@ -600,6 +600,10 @@ type ExportTrustCenterFilePayload { data: String! } +input AcceptNonDisclosureAgreementInput { + fullName: String! +} + type AcceptNonDisclosureAgreementPayload { success: Boolean! } @@ -626,7 +630,9 @@ type Mutation { @session(required: PRESENT) @membersOnly - acceptNonDisclosureAgreement: AcceptNonDisclosureAgreementPayload! + acceptNonDisclosureAgreement( + input: AcceptNonDisclosureAgreementInput! + ): AcceptNonDisclosureAgreementPayload @session(required: PRESENT) @membersOnly diff --git a/pkg/server/api/trust/v1/schema/schema.go b/pkg/server/api/trust/v1/schema/schema.go index 139c18036..8d339f719 100644 --- a/pkg/server/api/trust/v1/schema/schema.go +++ b/pkg/server/api/trust/v1/schema/schema.go @@ -132,7 +132,7 @@ type ComplexityRoot struct { } Mutation struct { - AcceptNonDisclosureAgreement func(childComplexity int) int + AcceptNonDisclosureAgreement func(childComplexity int, input types.AcceptNonDisclosureAgreementInput) int ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int ExportReportPDF func(childComplexity int, input types.ExportReportPDFInput) int ExportTrustCenterFile func(childComplexity int, input types.ExportTrustCenterFileInput) int @@ -284,7 +284,7 @@ type MutationResolver interface { RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) - AcceptNonDisclosureAgreement(ctx context.Context) (*types.AcceptNonDisclosureAgreementPayload, error) + AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) RequestDocumentAccess(ctx context.Context, input types.RequestDocumentAccessInput) (*types.RequestAccessesPayload, error) RequestReportAccess(ctx context.Context, input types.RequestReportAccessInput) (*types.RequestAccessesPayload, error) RequestTrustCenterFileAccess(ctx context.Context, input types.RequestTrustCenterFileAccessInput) (*types.RequestAccessesPayload, error) @@ -537,7 +537,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin break } - return e.complexity.Mutation.AcceptNonDisclosureAgreement(childComplexity), true + args, err := ec.field_Mutation_acceptNonDisclosureAgreement_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AcceptNonDisclosureAgreement(childComplexity, args["input"].(types.AcceptNonDisclosureAgreementInput)), true case "Mutation.exportDocumentPDF": if e.complexity.Mutation.ExportDocumentPDF == nil { break @@ -1091,6 +1096,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { opCtx := graphql.GetOperationContext(ctx) ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)} inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputAcceptNonDisclosureAgreementInput, ec.unmarshalInputExportDocumentPDFInput, ec.unmarshalInputExportReportPDFInput, ec.unmarshalInputExportTrustCenterFileInput, @@ -1798,6 +1804,10 @@ type ExportTrustCenterFilePayload { data: String! } +input AcceptNonDisclosureAgreementInput { + fullName: String! +} + type AcceptNonDisclosureAgreementPayload { success: Boolean! } @@ -1824,7 +1834,9 @@ type Mutation { @session(required: PRESENT) @membersOnly - acceptNonDisclosureAgreement: AcceptNonDisclosureAgreementPayload! + acceptNonDisclosureAgreement( + input: AcceptNonDisclosureAgreementInput! + ): AcceptNonDisclosureAgreementPayload @session(required: PRESENT) @membersOnly @@ -1902,6 +1914,17 @@ func (ec *executionContext) dir_session_args(ctx context.Context, rawArgs map[st return args, nil } +func (ec *executionContext) field_Mutation_acceptNonDisclosureAgreement_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "input", ec.unmarshalNAcceptNonDisclosureAgreementInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementInput) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_exportDocumentPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -3460,7 +3483,8 @@ func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.C field, ec.fieldContext_Mutation_acceptNonDisclosureAgreement, func(ctx context.Context) (any, error) { - return ec.resolvers.Mutation().AcceptNonDisclosureAgreement(ctx) + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Mutation().AcceptNonDisclosureAgreement(ctx, fc.Args["input"].(types.AcceptNonDisclosureAgreementInput)) }, func(ctx context.Context, next graphql.Resolver) graphql.Resolver { directive0 := next @@ -3488,13 +3512,13 @@ func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.C next = directive2 return next }, - ec.marshalNAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload, - true, + ec.marshalOAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload, true, + false, ) } -func (ec *executionContext) fieldContext_Mutation_acceptNonDisclosureAgreement(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Mutation_acceptNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Mutation", Field: field, @@ -3508,6 +3532,17 @@ func (ec *executionContext) fieldContext_Mutation_acceptNonDisclosureAgreement(_ return nil, fmt.Errorf("no field named %q was found under type AcceptNonDisclosureAgreementPayload", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_acceptNonDisclosureAgreement_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -7525,6 +7560,33 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputAcceptNonDisclosureAgreementInput(ctx context.Context, obj any) (types.AcceptNonDisclosureAgreementInput, error) { + var it types.AcceptNonDisclosureAgreementInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"fullName"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "fullName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.FullName = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputExportDocumentPDFInput(ctx context.Context, obj any) (types.ExportDocumentPDFInput, error) { var it types.ExportDocumentPDFInput asMap := map[string]any{} @@ -8622,9 +8684,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_acceptNonDisclosureAgreement(ctx, field) }) - if out.Values[i] == graphql.Null { - out.Invalids++ - } case "requestDocumentAccess": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_requestDocumentAccess(ctx, field) @@ -10448,18 +10507,9 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o // region ***************************** type.gotpl ***************************** -func (ec *executionContext) marshalNAcceptNonDisclosureAgreementPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, v types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler { - return ec._AcceptNonDisclosureAgreementPayload(ctx, sel, &v) -} - -func (ec *executionContext) marshalNAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, v *types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") - } - return graphql.Null - } - return ec._AcceptNonDisclosureAgreementPayload(ctx, sel, v) +func (ec *executionContext) unmarshalNAcceptNonDisclosureAgreementInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementInput(ctx context.Context, v any) (types.AcceptNonDisclosureAgreementInput, error) { + res, err := ec.unmarshalInputAcceptNonDisclosureAgreementInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) } func (ec *executionContext) marshalNAudit2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAudit(ctx context.Context, sel ast.SelectionSet, v *types.Audit) graphql.Marshaler { @@ -12066,6 +12116,13 @@ func (ec *executionContext) marshalN__TypeKind2string(ctx context.Context, sel a return res } +func (ec *executionContext) marshalOAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload(ctx context.Context, sel ast.SelectionSet, v *types.AcceptNonDisclosureAgreementPayload) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._AcceptNonDisclosureAgreementPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v any) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/server/api/trust/v1/types/types.go b/pkg/server/api/trust/v1/types/types.go index 2d1de4ec0..d3843cf6f 100644 --- a/pkg/server/api/trust/v1/types/types.go +++ b/pkg/server/api/trust/v1/types/types.go @@ -20,6 +20,10 @@ type Node interface { GetID() gid.GID } +type AcceptNonDisclosureAgreementInput struct { + FullName string `json:"fullName"` +} + type AcceptNonDisclosureAgreementPayload struct { Success bool `json:"success"` } diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index 85cd98028..47e7e270a 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -423,7 +423,7 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo } // AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field. -func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context) (*types.AcceptNonDisclosureAgreementPayload, error) { +func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) { identity := authn.IdentityFromContext(ctx) if identity == nil { return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") @@ -433,6 +433,16 @@ func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context) (*t httpReq := gqlutils.HTTPRequestFromContext(ctx) + if _, err := r.iam.AuthService.UpdateIdentity(ctx, identity.ID, input.FullName); err != nil { + var errNotFound *iam.ErrIdentityNotFound + if errors.As(err, &errNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + if err := trustService.TrustCenterAccesses.AcceptNonDisclosureAgreement( ctx, &trust.AcceptNDARequest{