Implement guard on empty full name before NDA is signed

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-05 17:07:58 +04:00
parent 6896c1bbb8
commit 97e957f394
19 changed files with 637 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<d32c91bec3630db95573556172f06d35>> * @generated SignedSource<<35d27a50dd067775566c69dd0e555b25>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,7 +17,6 @@ export type CompliancePageAccessPageQuery$data = {
readonly organization: { readonly organization: {
readonly __typename: "Organization"; readonly __typename: "Organization";
readonly compliancePage: { readonly compliancePage: {
readonly canCreateAccess: boolean;
readonly id: string; readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageAccessListFragment">; readonly " $fragmentSpreads": FragmentRefs<"CompliancePageAccessListFragment">;
}; };
@@ -61,20 +60,7 @@ v3 = {
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v4 = { v4 = [
"alias": "canCreateAccess",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center-access:create"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center-access:create\")"
},
v5 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
@@ -119,7 +105,6 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -168,10 +153,9 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v5/*: any*/), "args": (v4/*: any*/),
"concreteType": "TrustCenterAccessConnection", "concreteType": "TrustCenterAccessConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "accesses", "name": "accesses",
@@ -350,7 +334,7 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v5/*: any*/), "args": (v4/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy"
], ],
@@ -373,16 +357,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "c556c1f73c80950e432e5298b7aa1162", "cacheID": "b4d6f7aa127d6ee60ec6344fb291ab4e",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "CompliancePageAccessPageQuery", "name": "CompliancePageAccessPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query CompliancePageAccessPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n canCreateAccess: permission(action: \"core:trust-center-access:create\")\n ...CompliancePageAccessListFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageAccessListFragment on TrustCenter {\n accesses(first: 10, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n ...CompliancePageAccessListItemFragment\n __typename\n }\n }\n }\n id\n}\n\nfragment CompliancePageAccessListItemFragment on TrustCenterAccess {\n id\n createdAt\n profile {\n fullName\n emailAddress\n state\n id\n }\n activeCount\n pendingRequestCount\n ndaSignature {\n status\n id\n }\n canUpdate: permission(action: \"core:trust-center-access:update\")\n}\n" "text": "query CompliancePageAccessPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n ...CompliancePageAccessListFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageAccessListFragment on TrustCenter {\n accesses(first: 10, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n ...CompliancePageAccessListItemFragment\n __typename\n }\n }\n }\n id\n}\n\nfragment CompliancePageAccessListItemFragment on TrustCenterAccess {\n id\n createdAt\n profile {\n fullName\n emailAddress\n state\n id\n }\n activeCount\n pendingRequestCount\n ndaSignature {\n status\n id\n }\n canUpdate: permission(action: \"core:trust-center-access:update\")\n}\n"
} }
}; };
})(); })();
(node as any).hash = "4cc473e91fd49ddd83b6cdb0c8da0abf"; (node as any).hash = "29f59aac000603a1d11c88a441fb595b";
export default node; export default node;

View File

@@ -1,4 +1,4 @@
import { NDASignatureRequiredError, UnAuthenticatedError } from "@probo/relay"; import { FullNameRequiredError, NDASignatureRequiredError, UnAuthenticatedError } from "@probo/relay";
import { Navigate, useLocation, useRouteError } from "react-router"; import { Navigate, useLocation, useRouteError } from "react-router";
import { getPathPrefix } from "#/utils/pathPrefix"; import { getPathPrefix } from "#/utils/pathPrefix";
@@ -29,6 +29,18 @@ export function RootErrorBoundary() {
); );
} }
if (error instanceof FullNameRequiredError) {
return (
<Navigate
replace
to={{
pathname: "/full-name",
search: queryString ? "?" + queryString : "",
}}
/>
);
}
if (error instanceof NDASignatureRequiredError) { if (error instanceof NDASignatureRequiredError) {
return ( return (
<Navigate <Navigate

View File

@@ -37,7 +37,6 @@ const sendMagicLinkMutation = graphql`
`; `;
const schema = z.object({ const schema = z.object({
fullName: z.string().min(2),
email: z.string().email(), email: z.string().email(),
}); });
@@ -104,7 +103,6 @@ export function ConnectPage(props: {
} = useFormWithSchema(schema, { } = useFormWithSchema(schema, {
defaultValues: { defaultValues: {
email: "", email: "",
fullName: "",
}, },
}); });
@@ -112,8 +110,8 @@ export function ConnectPage(props: {
sendMagicLinkMutation, sendMagicLinkMutation,
); );
const handleSubmit = handleSubmitWrapper(({ email, fullName }: FormData) => { const handleSubmit = handleSubmitWrapper(({ email }: FormData) => {
const input: SendMagicLinkInput = { email, fullName }; const input: SendMagicLinkInput = { email };
if (safeContinueUrl) { if (safeContinueUrl) {
input.continue = safeContinueUrl; input.continue = safeContinueUrl;
} }
@@ -121,7 +119,6 @@ export function ConnectPage(props: {
variables: { variables: {
input: { input: {
email, email,
fullName,
continue: safeContinueUrl, continue: safeContinueUrl,
}, },
}, },
@@ -173,14 +170,6 @@ export function ConnectPage(props: {
</div> </div>
<form onSubmit={e => void handleSubmit(e)} className="space-y-6"> <form onSubmit={e => void handleSubmit(e)} className="space-y-6">
<Field
label={__("Full Name")}
placeholder="John Doe"
{...register("fullName")}
type="text"
required
error={formState.errors.fullName?.message}
/>
<Field <Field
label={__("Email")} label={__("Email")}
placeholder="john.doe@acme.com" placeholder="john.doe@acme.com"

View File

@@ -0,0 +1,135 @@
import type { GraphQLError } from "@probo/helpers";
import { useTranslate } from "@probo/i18n";
import { Button, Field, useToast } from "@probo/ui";
import {
useMutation,
} from "react-relay";
import { useSearchParams } from "react-router";
import { graphql } from "relay-runtime";
import { z } from "zod";
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { getPathPrefix } from "#/utils/pathPrefix";
import type { FullNamePageMutation } from "./__generated__/FullNamePageMutation.graphql";
const updateMutation = graphql`
mutation FullNamePageMutation($input: UpdateFullNameInput!) {
updateFullName(input: $input) {
success
}
}
`;
const schema = z.object({
fullName: z.string().min(2),
});
type FormData = z.infer<typeof schema>;
export default function FullNamePage() {
const { __ } = useTranslate();
const { toast } = useToast();
const [searchParams] = useSearchParams();
const continueUrlParam = searchParams.get("continue");
let safeContinueUrl: string;
if (continueUrlParam) {
try {
const continueUrl = new URL(continueUrlParam, window.location.origin);
if (continueUrl.origin === window.location.origin && continueUrl.pathname.startsWith(`${getPathPrefix()}/`)) {
safeContinueUrl = window.location.origin + continueUrl.pathname + continueUrl.search;
} else {
safeContinueUrl = window.location.origin + (getPathPrefix() || "/");
}
} catch {
safeContinueUrl = window.location.origin + (getPathPrefix() || "/");
}
} else {
safeContinueUrl = window.location.origin + (getPathPrefix() || "/");
}
const {
handleSubmit: handleSubmitWrapper,
register,
formState,
} = useFormWithSchema(schema, {
defaultValues: {
fullName: "",
},
});
const [update] = useMutation<FullNamePageMutation>(
updateMutation,
);
const handleSubmit = handleSubmitWrapper(({ fullName }: FormData) => {
update({
variables: {
input: {
fullName,
},
},
onCompleted: (_, errors: GraphQLError[] | null) => {
if (errors) {
for (const err of errors) {
if (err.extensions?.code === "ALREADY_AUTHENTICATED") {
window.location.href = getPathPrefix() || "/";
return;
}
}
toast({
title: __("Error"),
description: __("Cannot send magic link"),
variant: "error",
});
return;
}
toast({
title: __("Success"),
description: __("Full name updated!"),
variant: "success",
});
window.location.href = safeContinueUrl;
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message,
variant: "error",
});
},
});
});
return (
<div className="space-y-6 w-full max-w-md mx-auto pt-8">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">
{__("Please set your profile's full name")}
</h1>
</div>
<form onSubmit={e => void handleSubmit(e)} className="space-y-6">
<Field
label={__("Full Name")}
placeholder="John Doe"
{...register("fullName")}
type="text"
required
error={formState.errors.fullName?.message}
/>
<Button
type="submit"
className="w-xs h-10 mx-auto"
disabled={formState.isSubmitting}
>
{__("Continue")}
</Button>
</form>
</div>
);
}

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<f567da0a977d6ae788cdd5d6eff1d59f>> * @generated SignedSource<<711ecaa392c23004a3bd1dfb24a5751f>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -12,7 +12,6 @@ import { ConcreteRequest } from 'relay-runtime';
export type SendMagicLinkInput = { export type SendMagicLinkInput = {
continue?: string | null | undefined; continue?: string | null | undefined;
email: any; email: any;
fullName: string;
}; };
export type ConnectPageMutation$variables = { export type ConnectPageMutation$variables = {
input: SendMagicLinkInput; input: SendMagicLinkInput;

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<7861673355d6d647183c2a4425e9a5b9>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateFullNameInput = {
fullName: string;
};
export type FullNamePageMutation$variables = {
input: UpdateFullNameInput;
};
export type FullNamePageMutation$data = {
readonly updateFullName: {
readonly success: boolean;
} | null | undefined;
};
export type FullNamePageMutation = {
response: FullNamePageMutation$data;
variables: FullNamePageMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateFullNamePayload",
"kind": "LinkedField",
"name": "updateFullName",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "FullNamePageMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "FullNamePageMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "502db67e5bab72aff71fec9a0be0eeaf",
"id": null,
"metadata": {},
"name": "FullNamePageMutation",
"operationKind": "mutation",
"text": "mutation FullNamePageMutation(\n $input: UpdateFullNameInput!\n) {\n updateFullName(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "3d6352889babf98a37fe06a1bcaead15";
export default node;

View File

@@ -10,32 +10,6 @@ import {
import { getPathPrefix } from "#/utils/pathPrefix"; import { getPathPrefix } from "#/utils/pathPrefix";
export class UnAuthenticatedError extends Error {
constructor() {
super("UNAUTHENTICATED");
this.name = "UnAuthenticatedError";
}
}
export class InvalidError extends Error {
field?: string;
cause?: string;
constructor(message?: string, field?: string, cause?: string) {
super(message || "INVALID");
this.name = "InvalidError";
this.field = field;
this.cause = cause;
}
}
export class InternalServerError extends Error {
constructor() {
super("INTERNAL_SERVER_ERROR");
this.name = "InternalServerError";
}
}
export function buildEndpoint(): string { export function buildEndpoint(): string {
let host = import.meta.env.VITE_API_URL; let host = import.meta.env.VITE_API_URL;

View File

@@ -37,6 +37,10 @@ const routes = [
path: "/verify-magic-link", path: "/verify-magic-link",
Component: lazy(() => import("#/pages/auth/VerifyMagicLinkPage")), Component: lazy(() => import("#/pages/auth/VerifyMagicLinkPage")),
}, },
{
path: "/full-name",
Component: lazy(() => import("#/pages/auth/FullNamePage")),
},
], ],
}, },
{ {

View File

@@ -6,6 +6,14 @@ export class UnAuthenticatedError extends Error {
} }
} }
export class FullNameRequiredError extends Error {
constructor(message?: string) {
super(message || "FULL_NAME_REQUIRED");
this.name = "FullNameRequiredError";
Object.setPrototypeOf(this, FullNameRequiredError.prototype);
}
}
export class NDASignatureRequiredError extends Error { export class NDASignatureRequiredError extends Error {
constructor(message?: string) { constructor(message?: string) {
super(message || "NDA_SIGNATURE_REQUIRED"); super(message || "NDA_SIGNATURE_REQUIRED");

View File

@@ -5,12 +5,16 @@ import {
ForbiddenError, ForbiddenError,
AssumptionRequiredError, AssumptionRequiredError,
NDASignatureRequiredError, NDASignatureRequiredError,
FullNameRequiredError,
} from "./errors"; } from "./errors";
import { GraphQLError } from "graphql"; import { GraphQLError } from "graphql";
const hasUnauthenticatedError = (error: GraphQLError) => const hasUnauthenticatedError = (error: GraphQLError) =>
error.extensions?.code == "UNAUTHENTICATED"; error.extensions?.code == "UNAUTHENTICATED";
const hasFullNameRequiredError = (error: GraphQLError) =>
error.extensions?.code == "FULL_NAME_REQUIRED";
const hasAssumptionRequiredError = (error: GraphQLError) => const hasAssumptionRequiredError = (error: GraphQLError) =>
error.extensions?.code == "ASSUMPTION_REQUIRED"; error.extensions?.code == "ASSUMPTION_REQUIRED";
@@ -83,6 +87,11 @@ export const makeFetchQuery = (endpoint: string): FetchFunction => {
throw new UnAuthenticatedError(unauthenticatedError.message); throw new UnAuthenticatedError(unauthenticatedError.message);
} }
const fullNameRequiredError = errors.find(hasFullNameRequiredError);
if (fullNameRequiredError) {
throw new FullNameRequiredError(fullNameRequiredError.message);
}
const assumptionRequiredError = errors.find(hasAssumptionRequiredError); const assumptionRequiredError = errors.find(hasAssumptionRequiredError);
if (assumptionRequiredError) { if (assumptionRequiredError) {
throw new AssumptionRequiredError(assumptionRequiredError.message) throw new AssumptionRequiredError(assumptionRequiredError.message)

View File

@@ -47,6 +47,15 @@ type (
IdentityID gid.GID `json:"uid"` IdentityID gid.GID `json:"uid"`
Email mail.Addr `json:"email"` Email mail.Addr `json:"email"`
} }
ChangeEmailRequest struct {
NewEmail mail.Addr
Password string
}
UpdateIdentityRequest struct {
FullName string `json:"fullName"`
}
) )
const ( const (
@@ -57,11 +66,6 @@ func NewAccountService(svc *Service) *AccountService {
return &AccountService{Service: svc} return &AccountService{Service: svc}
} }
type ChangeEmailRequest struct {
NewEmail mail.Addr
Password string
}
func (req ChangeEmailRequest) Validate() error { func (req ChangeEmailRequest) Validate() error {
v := validator.New() v := validator.New()
@@ -70,6 +74,14 @@ func (req ChangeEmailRequest) Validate() error {
return v.Error() return v.Error()
} }
func (req UpdateIdentityRequest) Validate() error {
v := validator.New()
v.Check(req.FullName, "full_name", validator.NotEmpty(), validator.MinLen(2), validator.MaxLen(255))
return v.Error()
}
func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req *ChangeEmailRequest) error { func (s AccountService) ChangeEmail(ctx context.Context, identityID gid.GID, req *ChangeEmailRequest) error {
if err := req.Validate(); err != nil { if err := req.Validate(); err != nil {
return fmt.Errorf("invalid request: %w", err) return fmt.Errorf("invalid request: %w", err)
@@ -339,6 +351,42 @@ func (s AccountService) GetIdentity(ctx context.Context, identityID gid.GID) (*c
return identity, nil return identity, nil
} }
func (s AccountService) UpdateIdentity(ctx context.Context, identityID gid.GID, req *UpdateIdentityRequest) (*coredata.Identity, error) {
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("invalid request: %w", err)
}
identity := &coredata.Identity{}
err := s.pg.WithTx(
ctx,
func(tx pg.Conn) error {
err := identity.LoadByID(ctx, tx, identityID)
if err != nil {
if err == coredata.ErrResourceNotFound {
return NewIdentityNotFoundError(identityID)
}
return fmt.Errorf("cannot load identity: %w", err)
}
identity.FullName = req.FullName
identity.UpdatedAt = time.Now()
if err := identity.Update(ctx, tx); err != nil {
return fmt.Errorf("cannot update identity: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return identity, nil
}
func (s AccountService) ListPersonalAPIKeys( func (s AccountService) ListPersonalAPIKeys(
ctx context.Context, ctx context.Context,
identityID gid.GID, identityID gid.GID,

View File

@@ -61,7 +61,6 @@ type (
} }
SendMagicLinkRequest struct { SendMagicLinkRequest struct {
FullName string
Email mail.Addr Email mail.Addr
URLPath string URLPath string
OrganizationID gid.GID OrganizationID gid.GID
@@ -75,7 +74,6 @@ type (
} }
MagicLinkData struct { MagicLinkData struct {
FullName string `json:"fullName"`
Email mail.Addr `json:"email"` Email mail.Addr `json:"email"`
Continue *string `json:"continue"` Continue *string `json:"continue"`
} }
@@ -552,7 +550,6 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
TokenTypeMagicLink, TokenTypeMagicLink,
s.magicLinkTokenValidity, s.magicLinkTokenValidity,
MagicLinkData{ MagicLinkData{
FullName: req.FullName,
Email: req.Email, Email: req.Email,
Continue: req.Continue, Continue: req.Continue,
}, },
@@ -574,9 +571,20 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques
return fmt.Errorf("cannot insert token: %w", err) return fmt.Errorf("cannot insert token: %w", err)
} }
fullName := req.FullName fullName := req.Email.Username()
identity := &coredata.Identity{}
organization := &coredata.Organization{} organization := &coredata.Organization{}
if err := identity.LoadByEmail(ctx, tx, req.Email); err == nil {
if identity.FullName != "" {
fullName = identity.FullName
}
} else {
if !errors.Is(err, coredata.ErrResourceNotFound) {
return fmt.Errorf("cannot load identity: %w", err)
}
}
if err := organization.LoadByID(ctx, tx, coredata.NewNoScope(), req.OrganizationID); err != nil { if err := organization.LoadByID(ctx, tx, coredata.NewNoScope(), req.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err) return fmt.Errorf("cannot load organization: %w", err)
} }
@@ -675,10 +683,6 @@ func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString s
UpdatedAt: now, UpdatedAt: now,
} }
if identity.FullName == "" {
identity.FullName = payload.Data.FullName
}
if err := identity.Insert(ctx, tx); err != nil { if err := identity.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot create identity: %w", err) return fmt.Errorf("cannot create identity: %w", err)
} }

View File

@@ -65,6 +65,11 @@ func newNDADirective(
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)
} }
// We need full name before user signs NDA
if identity.FullName == "" {
return nil, gqlutils.FullNameRequiredf(ctx, "full name is required")
}
if sig.Status != coredata.ElectronicSignatureStatusCompleted { if sig.Status != coredata.ElectronicSignatureStatusCompleted {
return nil, gqlutils.NDASignatureRequiredf(ctx, "NDA signature required") return nil, gqlutils.NDASignatureRequiredf(ctx, "NDA signature required")
} }

View File

@@ -611,7 +611,6 @@ type DocumentAccess implements Node {
} }
input SendMagicLinkInput { input SendMagicLinkInput {
fullName: String!
email: EmailAddr! email: EmailAddr!
continue: String continue: String
} }
@@ -628,6 +627,14 @@ type VerifyMagicLinkPayload {
continue: String continue: String
} }
input UpdateFullNameInput {
fullName: String!
}
type UpdateFullNamePayload {
success: Boolean!
}
type RequestDocumentAccessPayload { type RequestDocumentAccessPayload {
document: Document document: Document
} }
@@ -827,6 +834,8 @@ type Mutation {
@session(required: OPTIONAL) @session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: OPTIONAL) @session(required: OPTIONAL)
updateFullName(input: UpdateFullNameInput!): UpdateFullNamePayload
@session(required: PRESENT)
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda

View File

@@ -166,6 +166,7 @@ type ComplexityRoot struct {
RequestReportAccess func(childComplexity int, input types.RequestReportAccessInput) int RequestReportAccess func(childComplexity int, input types.RequestReportAccessInput) int
RequestTrustCenterFileAccess func(childComplexity int, input types.RequestTrustCenterFileAccessInput) int RequestTrustCenterFileAccess func(childComplexity int, input types.RequestTrustCenterFileAccessInput) int
SendMagicLink func(childComplexity int, input types.SendMagicLinkInput) int SendMagicLink func(childComplexity int, input types.SendMagicLinkInput) int
UpdateFullName func(childComplexity int, input types.UpdateFullNameInput) int
VerifyMagicLink func(childComplexity int, input types.VerifyMagicLinkInput) int VerifyMagicLink func(childComplexity int, input types.VerifyMagicLinkInput) int
} }
@@ -289,6 +290,10 @@ type ComplexityRoot struct {
Node func(childComplexity int) int Node func(childComplexity int) int
} }
UpdateFullNamePayload struct {
Success func(childComplexity int) int
}
Vendor struct { Vendor struct {
Category func(childComplexity int) int Category func(childComplexity int) int
Countries func(childComplexity int) int Countries func(childComplexity int) int
@@ -333,6 +338,7 @@ type FrameworkResolver interface {
type MutationResolver interface { type MutationResolver interface {
SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error) SendMagicLink(ctx context.Context, input types.SendMagicLinkInput) (*types.SendMagicLinkPayload, error)
VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error)
UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error)
RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error)
@@ -800,6 +806,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
} }
return e.ComplexityRoot.Mutation.SendMagicLink(childComplexity, args["input"].(types.SendMagicLinkInput)), true return e.ComplexityRoot.Mutation.SendMagicLink(childComplexity, args["input"].(types.SendMagicLinkInput)), true
case "Mutation.updateFullName":
if e.ComplexityRoot.Mutation.UpdateFullName == nil {
break
}
args, err := ec.field_Mutation_updateFullName_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.ComplexityRoot.Mutation.UpdateFullName(childComplexity, args["input"].(types.UpdateFullNameInput)), true
case "Mutation.verifyMagicLink": case "Mutation.verifyMagicLink":
if e.ComplexityRoot.Mutation.VerifyMagicLink == nil { if e.ComplexityRoot.Mutation.VerifyMagicLink == nil {
break break
@@ -1245,6 +1262,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.ComplexityRoot.TrustCenterReferenceEdge.Node(childComplexity), true return e.ComplexityRoot.TrustCenterReferenceEdge.Node(childComplexity), true
case "UpdateFullNamePayload.success":
if e.ComplexityRoot.UpdateFullNamePayload.Success == nil {
break
}
return e.ComplexityRoot.UpdateFullNamePayload.Success(childComplexity), true
case "Vendor.category": case "Vendor.category":
if e.ComplexityRoot.Vendor.Category == nil { if e.ComplexityRoot.Vendor.Category == nil {
break break
@@ -1344,6 +1368,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputRequestReportAccessInput, ec.unmarshalInputRequestReportAccessInput,
ec.unmarshalInputRequestTrustCenterFileAccessInput, ec.unmarshalInputRequestTrustCenterFileAccessInput,
ec.unmarshalInputSendMagicLinkInput, ec.unmarshalInputSendMagicLinkInput,
ec.unmarshalInputUpdateFullNameInput,
ec.unmarshalInputVerifyMagicLinkInput, ec.unmarshalInputVerifyMagicLinkInput,
) )
first := true first := true
@@ -2033,7 +2058,6 @@ type DocumentAccess implements Node {
} }
input SendMagicLinkInput { input SendMagicLinkInput {
fullName: String!
email: EmailAddr! email: EmailAddr!
continue: String continue: String
} }
@@ -2050,6 +2074,14 @@ type VerifyMagicLinkPayload {
continue: String continue: String
} }
input UpdateFullNameInput {
fullName: String!
}
type UpdateFullNamePayload {
success: Boolean!
}
type RequestDocumentAccessPayload { type RequestDocumentAccessPayload {
document: Document document: Document
} }
@@ -2249,6 +2281,8 @@ type Mutation {
@session(required: OPTIONAL) @session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: OPTIONAL) @session(required: OPTIONAL)
updateFullName(input: UpdateFullNameInput!): UpdateFullNamePayload
@session(required: PRESENT)
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda
@@ -2439,6 +2473,17 @@ func (ec *executionContext) field_Mutation_sendMagicLink_args(ctx context.Contex
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_updateFullName_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.unmarshalNUpdateFullNameInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUpdateFullNameInput)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_verifyMagicLink_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_verifyMagicLink_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -4390,6 +4435,69 @@ func (ec *executionContext) fieldContext_Mutation_verifyMagicLink(ctx context.Co
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_updateFullName(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_updateFullName,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.Resolvers.Mutation().UpdateFullName(ctx, fc.Args["input"].(types.UpdateFullNameInput))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT")
if err != nil {
var zeroVal *types.UpdateFullNamePayload
return zeroVal, err
}
if ec.Directives.Session == nil {
var zeroVal *types.UpdateFullNamePayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.Directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalOUpdateFullNamePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUpdateFullNamePayload,
true,
false,
)
}
func (ec *executionContext) fieldContext_Mutation_updateFullName(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "success":
return ec.fieldContext_UpdateFullNamePayload_success(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type UpdateFullNamePayload", 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_updateFullName_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -7405,6 +7513,35 @@ func (ec *executionContext) fieldContext_TrustCenterReferenceEdge_node(_ context
return fc, nil return fc, nil
} }
func (ec *executionContext) _UpdateFullNamePayload_success(ctx context.Context, field graphql.CollectedField, obj *types.UpdateFullNamePayload) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_UpdateFullNamePayload_success,
func(ctx context.Context) (any, error) {
return obj.Success, nil
},
nil,
ec.marshalNBoolean2bool,
true,
true,
)
}
func (ec *executionContext) fieldContext_UpdateFullNamePayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "UpdateFullNamePayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Vendor_id(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { func (ec *executionContext) _Vendor_id(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) {
return graphql.ResolveField( return graphql.ResolveField(
ctx, ctx,
@@ -9508,20 +9645,13 @@ func (ec *executionContext) unmarshalInputSendMagicLinkInput(ctx context.Context
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"fullName", "email", "continue"} fieldsInOrder := [...]string{"email", "continue"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
continue continue
} }
switch k { 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
case "email": case "email":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v)
@@ -9541,6 +9671,32 @@ func (ec *executionContext) unmarshalInputSendMagicLinkInput(ctx context.Context
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputUpdateFullNameInput(ctx context.Context, obj any) (types.UpdateFullNameInput, error) {
var it types.UpdateFullNameInput
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) unmarshalInputVerifyMagicLinkInput(ctx context.Context, obj any) (types.VerifyMagicLinkInput, error) { func (ec *executionContext) unmarshalInputVerifyMagicLinkInput(ctx context.Context, obj any) (types.VerifyMagicLinkInput, error) {
var it types.VerifyMagicLinkInput var it types.VerifyMagicLinkInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -10722,6 +10878,10 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_verifyMagicLink(ctx, field) return ec._Mutation_verifyMagicLink(ctx, field)
}) })
case "updateFullName":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_updateFullName(ctx, field)
})
case "requestAllAccesses": case "requestAllAccesses":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_requestAllAccesses(ctx, field) return ec._Mutation_requestAllAccesses(ctx, field)
@@ -12331,6 +12491,45 @@ func (ec *executionContext) _TrustCenterReferenceEdge(ctx context.Context, sel a
return out return out
} }
var updateFullNamePayloadImplementors = []string{"UpdateFullNamePayload"}
func (ec *executionContext) _UpdateFullNamePayload(ctx context.Context, sel ast.SelectionSet, obj *types.UpdateFullNamePayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, updateFullNamePayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("UpdateFullNamePayload")
case "success":
out.Values[i] = ec._UpdateFullNamePayload_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.Deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.ProcessDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var vendorImplementors = []string{"Vendor", "Node"} var vendorImplementors = []string{"Vendor", "Node"}
func (ec *executionContext) _Vendor(ctx context.Context, sel ast.SelectionSet, obj *types.Vendor) graphql.Marshaler { func (ec *executionContext) _Vendor(ctx context.Context, sel ast.SelectionSet, obj *types.Vendor) graphql.Marshaler {
@@ -14226,6 +14425,11 @@ func (ec *executionContext) marshalNTrustCenterReferenceEdge2ᚖgoᚗproboᚗinc
return ec._TrustCenterReferenceEdge(ctx, sel, v) return ec._TrustCenterReferenceEdge(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNUpdateFullNameInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUpdateFullNameInput(ctx context.Context, v any) (types.UpdateFullNameInput, error) {
res, err := ec.unmarshalInputUpdateFullNameInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNVendor2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVendor(ctx context.Context, sel ast.SelectionSet, v *types.Vendor) graphql.Marshaler { func (ec *executionContext) marshalNVendor2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVendor(ctx context.Context, sel ast.SelectionSet, v *types.Vendor) graphql.Marshaler {
if v == nil { if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@@ -14712,6 +14916,13 @@ func (ec *executionContext) marshalOTrustCenterFile2ᚖgoᚗproboᚗincᚋprobo
return ec._TrustCenterFile(ctx, sel, v) return ec._TrustCenterFile(ctx, sel, v)
} }
func (ec *executionContext) marshalOUpdateFullNamePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUpdateFullNamePayload(ctx context.Context, sel ast.SelectionSet, v *types.UpdateFullNamePayload) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._UpdateFullNamePayload(ctx, sel, v)
}
func (ec *executionContext) marshalOVerifyMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, v *types.VerifyMagicLinkPayload) graphql.Marshaler { func (ec *executionContext) marshalOVerifyMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkPayload(ctx context.Context, sel ast.SelectionSet, v *types.VerifyMagicLinkPayload) graphql.Marshaler {
if v == nil { if v == nil {
return graphql.Null return graphql.Null

View File

@@ -213,7 +213,6 @@ type RequestTrustCenterFileAccessInput struct {
} }
type SendMagicLinkInput struct { type SendMagicLinkInput struct {
FullName string `json:"fullName"`
Email mail.Addr `json:"email"` Email mail.Addr `json:"email"`
Continue *string `json:"continue,omitempty"` Continue *string `json:"continue,omitempty"`
} }
@@ -294,6 +293,14 @@ type TrustCenterReferenceEdge struct {
Node *TrustCenterReference `json:"node"` Node *TrustCenterReference `json:"node"`
} }
type UpdateFullNameInput struct {
FullName string `json:"fullName"`
}
type UpdateFullNamePayload struct {
Success bool `json:"success"`
}
type Vendor struct { type Vendor struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`

View File

@@ -192,7 +192,6 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa
} }
req := &iam.SendMagicLinkRequest{ req := &iam.SendMagicLinkRequest{
FullName: input.FullName,
Email: input.Email, Email: input.Email,
CompliancePageID: &trustCenter.ID, CompliancePageID: &trustCenter.ID,
OrganizationID: trustCenter.OrganizationID, OrganizationID: trustCenter.OrganizationID,
@@ -273,6 +272,49 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
}, nil }, nil
} }
// UpdateFullName is the resolver for the updateFullName field.
func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.UpdateFullNameInput) (*types.UpdateFullNamePayload, error) {
identity := authn.IdentityFromContext(ctx)
if identity == nil {
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
identity, err := r.iam.AccountService.UpdateIdentity(ctx, identity.ID, &iam.UpdateIdentityRequest{
FullName: input.FullName,
})
if err != nil {
r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
compliancePage := compliancepage.CompliancePageFromContext(ctx)
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID)
if err != nil {
if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok {
r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
}
if profile.Source == coredata.ProfileSourceManual {
if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{
ID: profile.ID,
FullName: identity.FullName,
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
Kind: profile.Kind,
Position: profile.Position,
ContractStartDate: &profile.ContractStartDate,
ContractEndDate: &profile.ContractEndDate,
}); err != nil {
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
}
return &types.UpdateFullNamePayload{Success: true}, nil
}
// RequestAllAccesses is the resolver for the requestAllAccesses field. // RequestAllAccesses is the resolver for the requestAllAccesses field.
func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) { func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) {
trustCenter := compliancepage.CompliancePageFromContext(ctx) trustCenter := compliancepage.CompliancePageFromContext(ctx)

View File

@@ -66,6 +66,20 @@ func AssumptionRequiredf(ctx context.Context, format string, a ...any) *gqlerror
return AssumptionRequired(ctx, fmt.Errorf(format, a...)) return AssumptionRequired(ctx, fmt.Errorf(format, a...))
} }
func FullNameRequired(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{
Message: err.Error(),
Path: graphql.GetPath(ctx),
Extensions: map[string]any{
"code": "FULL_NAME_REQUIRED",
},
}
}
func FullNameRequiredf(ctx context.Context, format string, a ...any) *gqlerror.Error {
return FullNameRequired(ctx, fmt.Errorf(format, a...))
}
func NDASignatureRequired(ctx context.Context, err error) *gqlerror.Error { func NDASignatureRequired(ctx context.Context, err error) *gqlerror.Error {
return &gqlerror.Error{ return &gqlerror.Error{
Message: err.Error(), Message: err.Error(),

View File

@@ -187,7 +187,7 @@ func (s TrustCenterAccessService) Request(
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
} }
access := &coredata.TrustCenterAccess{} access = &coredata.TrustCenterAccess{}
if err := access.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, req.TrustCenterID, req.IdentityID); err != nil { if err := access.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, req.TrustCenterID, req.IdentityID); err != nil {
return fmt.Errorf("cannot load compliance page membership: %w", err) return fmt.Errorf("cannot load compliance page membership: %w", err)
} }