diff --git a/apps/trust/src/layouts/MainLayout.tsx b/apps/trust/src/layouts/MainLayout.tsx
index 10fe501ef..ea2f06a19 100644
--- a/apps/trust/src/layouts/MainLayout.tsx
+++ b/apps/trust/src/layouts/MainLayout.tsx
@@ -28,7 +28,7 @@ export function MainLayout(props: Props) {
}
const nda = trustCenter.nonDisclosureAgreement;
- const hasPendingNDA = nda
+ const hasPendingNDA = data.viewer && nda
&& (!nda.viewerSignature || nda.viewerSignature.status === "PENDING" || nda.viewerSignature.status === "FAILED");
if (hasPendingNDA) {
diff --git a/apps/trust/src/pages/NDAPage.tsx b/apps/trust/src/pages/NDAPage.tsx
index c1271bcc3..4bc0703be 100644
--- a/apps/trust/src/pages/NDAPage.tsx
+++ b/apps/trust/src/pages/NDAPage.tsx
@@ -47,7 +47,7 @@ const ndaPageFragment = graphql`
fragment NDAPageFragment on TrustCenter
@refetchable(queryName: "NDAPageRefetchQuery") {
nonDisclosureAgreement @required(action: THROW) {
- viewerSignature @required(action: THROW) {
+ viewerSignature {
id
status
consentText
@@ -124,11 +124,11 @@ export function NDAPage(props: {
);
const isProcessing
- = ndaSignature.status === "ACCEPTED"
- || ndaSignature.status === "PROCESSING";
+ = ndaSignature?.status === "ACCEPTED"
+ || ndaSignature?.status === "PROCESSING";
- const isFailed = ndaSignature.status === "FAILED";
- const isCompleted = ndaSignature.status === "COMPLETED";
+ const isFailed = ndaSignature?.status === "FAILED";
+ const isCompleted = ndaSignature?.status === "COMPLETED";
useEffect(() => {
if (isCompleted) {
@@ -200,11 +200,11 @@ export function NDAPage(props: {
});
const nda = trustCenter.nonDisclosureAgreement;
- if (
- !nda
- || !nda.viewerSignature
- || nda.viewerSignature.status === "COMPLETED"
- ) {
+ if (!viewer) {
+ return ;
+ }
+
+ if (!nda || !nda.viewerSignature || nda.viewerSignature.status === "COMPLETED") {
return ;
}
diff --git a/apps/trust/src/pages/NDAPageLoader.tsx b/apps/trust/src/pages/NDAPageLoader.tsx
index ea7f5bd9e..52389997f 100644
--- a/apps/trust/src/pages/NDAPageLoader.tsx
+++ b/apps/trust/src/pages/NDAPageLoader.tsx
@@ -1,4 +1,4 @@
-import { Suspense, useEffect } from "react";
+import { useEffect } from "react";
import { useQueryLoader } from "react-relay";
import { RelayProvider } from "#/providers/RelayProviders";
diff --git a/apps/trust/src/pages/__generated__/NDAPageFragment.graphql.ts b/apps/trust/src/pages/__generated__/NDAPageFragment.graphql.ts
index 1915f4026..b545091fe 100644
--- a/apps/trust/src/pages/__generated__/NDAPageFragment.graphql.ts
+++ b/apps/trust/src/pages/__generated__/NDAPageFragment.graphql.ts
@@ -1,5 +1,5 @@
/**
- * @generated SignedSource<<99908411212decf464eafa45b1fc0e04>>
+ * @generated SignedSource<>
* @lightSyntaxTransform
* @nogrep
*/
@@ -19,7 +19,7 @@ export type NDAPageFragment$data = {
readonly id: string;
readonly lastError: string | null | undefined;
readonly status: ElectronicSignatureStatus;
- };
+ } | null | undefined;
};
readonly " $fragmentType": "NDAPageFragment";
};
@@ -67,41 +67,37 @@ return {
"plural": false,
"selections": [
{
- "kind": "RequiredField",
- "field": {
- "alias": null,
- "args": null,
- "concreteType": "ElectronicSignature",
- "kind": "LinkedField",
- "name": "viewerSignature",
- "plural": false,
- "selections": [
- (v0/*: any*/),
- {
- "alias": null,
- "args": null,
- "kind": "ScalarField",
- "name": "status",
- "storageKey": null
- },
- {
- "alias": null,
- "args": null,
- "kind": "ScalarField",
- "name": "consentText",
- "storageKey": null
- },
- {
- "alias": null,
- "args": null,
- "kind": "ScalarField",
- "name": "lastError",
- "storageKey": null
- }
- ],
- "storageKey": null
- },
- "action": "THROW"
+ "alias": null,
+ "args": null,
+ "concreteType": "ElectronicSignature",
+ "kind": "LinkedField",
+ "name": "viewerSignature",
+ "plural": false,
+ "selections": [
+ (v0/*: any*/),
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "status",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "consentText",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "lastError",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
}
],
"storageKey": null
@@ -115,6 +111,6 @@ return {
};
})();
-(node as any).hash = "503859b8c00b27214f9f3253bffcb670";
+(node as any).hash = "37f6ca36229313e341797c28d789035d";
export default node;
diff --git a/apps/trust/src/pages/__generated__/NDAPageRefetchQuery.graphql.ts b/apps/trust/src/pages/__generated__/NDAPageRefetchQuery.graphql.ts
index e1158a4f9..67d45ca30 100644
--- a/apps/trust/src/pages/__generated__/NDAPageRefetchQuery.graphql.ts
+++ b/apps/trust/src/pages/__generated__/NDAPageRefetchQuery.graphql.ts
@@ -1,5 +1,5 @@
/**
- * @generated SignedSource<<5c6134b4d6615f24a63ea534c80e76c1>>
+ * @generated SignedSource<<2b8f94503fe145fb141255a7ba79ddac>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -161,6 +161,6 @@ return {
};
})();
-(node as any).hash = "503859b8c00b27214f9f3253bffcb670";
+(node as any).hash = "37f6ca36229313e341797c28d789035d";
export default node;
diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go
index 4c91126cd..500c2b311 100644
--- a/pkg/server/api/trust/v1/v1_resolver.go
+++ b/pkg/server/api/trust/v1/v1_resolver.go
@@ -174,7 +174,7 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa
// VerifyMagicLink is the resolver for the verifyMagicLink field.
func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) {
- _, session, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
+ identity, session, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token)
if err != nil {
var errInvalidToken *iam.ErrInvalidToken
if errors.As(err, &errInvalidToken) {
@@ -185,6 +185,13 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
return nil, gqlutils.Internal(ctx)
}
+ trustCenter := compliancepage.CompliancePageFromContext(ctx)
+ trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
+
+ if _, err := trustService.TrustCenterAccesses.EnsureAccess(ctx, trustCenter.ID, identity.EmailAddress, identity.FullName); err != nil {
+ r.logger.ErrorCtx(ctx, "cannot ensure trust center access", log.Error(err))
+ }
+
w := gqlutils.HTTPResponseWriterFromContext(ctx)
r.sessionCookie.Set(w, session)
diff --git a/pkg/trust/trust_center_access_service.go b/pkg/trust/trust_center_access_service.go
index 0db27e036..4d2d08cc9 100644
--- a/pkg/trust/trust_center_access_service.go
+++ b/pkg/trust/trust_center_access_service.go
@@ -60,6 +60,87 @@ func (tcar *TrustCenterAccessRequest) Validate() error {
return v.Error()
}
+func (s TrustCenterAccessService) ensureAccessInTx(
+ ctx context.Context,
+ tx pg.Conn,
+ trustCenterID gid.GID,
+ email mail.Addr,
+ fullName string,
+) (*coredata.TrustCenterAccess, *coredata.TrustCenter, error) {
+ now := time.Now()
+
+ trustCenter := &coredata.TrustCenter{}
+ if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, trustCenterID); err != nil {
+ return nil, nil, fmt.Errorf("cannot load trust center: %w", err)
+ }
+
+ existingAccess := &coredata.TrustCenterAccess{}
+ err := existingAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, email)
+ if err == nil {
+ return existingAccess, trustCenter, nil
+ }
+
+ if !errors.Is(err, coredata.ErrResourceNotFound) {
+ return nil, nil, fmt.Errorf("cannot load trust center access: %w", err)
+ }
+
+ access := &coredata.TrustCenterAccess{
+ ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
+ OrganizationID: trustCenter.OrganizationID,
+ TenantID: s.svc.scope.GetTenantID(),
+ TrustCenterID: trustCenterID,
+ Email: email,
+ Name: fullName,
+ State: coredata.TrustCenterAccessStateActive,
+ HasAcceptedNonDisclosureAgreement: false,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+
+ if trustCenter.NonDisclosureAgreementFileID != nil && s.svc.esign != nil {
+ sig, err := s.svc.esign.CreateSignature(
+ ctx,
+ tx,
+ &esign.CreateSignatureRequest{
+ OrganizationID: access.OrganizationID,
+ DocumentType: coredata.ElectronicSignatureDocumentTypeNDA,
+ FileID: *trustCenter.NonDisclosureAgreementFileID,
+ SignerEmail: access.Email,
+ },
+ )
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot create pending signature: %w", err)
+ }
+ access.ElectronicSignatureID = &sig.ID
+ }
+
+ if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
+ return nil, nil, fmt.Errorf("cannot insert trust center access: %w", err)
+ }
+
+ return access, trustCenter, nil
+}
+
+func (s TrustCenterAccessService) EnsureAccess(
+ ctx context.Context,
+ trustCenterID gid.GID,
+ email mail.Addr,
+ fullName string,
+) (*coredata.TrustCenterAccess, error) {
+ var access *coredata.TrustCenterAccess
+
+ err := s.svc.pg.WithTx(
+ ctx,
+ func(tx pg.Conn) error {
+ var err error
+ access, _, err = s.ensureAccessInTx(ctx, tx, trustCenterID, email, fullName)
+ return err
+ },
+ )
+
+ return access, err
+}
+
func (s TrustCenterAccessService) Request(
ctx context.Context,
req *TrustCenterAccessRequest,
@@ -69,20 +150,20 @@ func (s TrustCenterAccessService) Request(
}
var (
- now = time.Now()
- access *coredata.TrustCenterAccess
- trustCenter *coredata.TrustCenter
+ now = time.Now()
+ access *coredata.TrustCenterAccess
)
err := s.svc.pg.WithTx(
ctx,
func(tx pg.Conn) error {
- trustCenter = &coredata.TrustCenter{}
- if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, req.TrustCenterID); err != nil {
- return fmt.Errorf("cannot load trust center: %w", err)
- }
+ var trustCenter *coredata.TrustCenter
+ var err error
- // TODO: load document to ensure they are requestable
+ access, trustCenter, err = s.ensureAccessInTx(ctx, tx, req.TrustCenterID, req.Email, req.FullName)
+ if err != nil {
+ return err
+ }
organizationID := trustCenter.OrganizationID
@@ -132,50 +213,6 @@ func (s TrustCenterAccessService) Request(
}
}
- existingAccess := &coredata.TrustCenterAccess{}
- err := existingAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, req.TrustCenterID, req.Email)
- if err == nil {
- access = existingAccess
- } else {
- if !errors.Is(err, coredata.ErrResourceNotFound) {
- return fmt.Errorf("cannot load trust center access: %w", err)
- }
-
- access = &coredata.TrustCenterAccess{
- ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
- OrganizationID: organizationID,
- TenantID: s.svc.scope.GetTenantID(),
- TrustCenterID: req.TrustCenterID,
- Email: req.Email,
- Name: req.FullName,
- State: coredata.TrustCenterAccessStateActive,
- HasAcceptedNonDisclosureAgreement: false,
- CreatedAt: now,
- UpdatedAt: now,
- }
-
- if trustCenter.NonDisclosureAgreementFileID != nil && s.svc.esign != nil {
- sig, err := s.svc.esign.CreateSignature(
- ctx,
- tx,
- &esign.CreateSignatureRequest{
- OrganizationID: access.OrganizationID,
- DocumentType: coredata.ElectronicSignatureDocumentTypeNDA,
- FileID: *trustCenter.NonDisclosureAgreementFileID,
- SignerEmail: access.Email,
- },
- )
- if err != nil {
- return fmt.Errorf("cannot create pending signature: %w", err)
- }
- access.ElectronicSignatureID = &sig.ID
- }
-
- if err := access.Insert(ctx, tx, s.svc.scope); err != nil {
- return fmt.Errorf("cannot insert trust center access: %w", err)
- }
- }
-
var existingAccesses coredata.TrustCenterDocumentAccesses
if err := existingAccesses.LoadAllByTrustCenterAccessID(ctx, tx, s.svc.scope, access.ID); err != nil {
return fmt.Errorf("cannot load existing access records: %w", err)