Update business logic

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-04 18:47:12 +04:00
parent 909beeb606
commit 323cd602a1
13 changed files with 264 additions and 320 deletions

View File

@@ -16,7 +16,6 @@ package coredata
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"maps" "maps"
@@ -26,7 +25,6 @@ import (
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/page"
) )
@@ -35,12 +33,8 @@ type (
ID gid.GID `db:"id"` ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"` OrganizationID gid.GID `db:"organization_id"`
TenantID gid.TenantID `db:"tenant_id"` TenantID gid.TenantID `db:"tenant_id"`
IdentityID gid.GID `db:"identity_id"`
TrustCenterID gid.GID `db:"trust_center_id"` TrustCenterID gid.GID `db:"trust_center_id"`
Email mail.Addr `db:"email"`
Name string `db:"name"`
State TrustCenterAccessState `db:"state"`
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"` NDAFileID *gid.GID `db:"nda_file_id"`
ElectronicSignatureID *gid.GID `db:"electronic_signature_id"` ElectronicSignatureID *gid.GID `db:"electronic_signature_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
@@ -85,11 +79,6 @@ SELECT
organization_id, organization_id,
tenant_id, tenant_id,
trust_center_id, trust_center_id,
email,
name,
state,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
nda_file_id, nda_file_id,
electronic_signature_id, electronic_signature_id,
created_at, created_at,
@@ -126,12 +115,12 @@ LIMIT 1;
return nil return nil
} }
func (tca *TrustCenterAccess) LoadByTrustCenterIDAndEmail( func (tca *TrustCenterAccess) LoadByTrustCenterIDAndIdentityID(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,
scope Scoper, scope Scoper,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
) error { ) error {
q := ` q := `
SELECT SELECT
@@ -139,11 +128,6 @@ SELECT
organization_id, organization_id,
tenant_id, tenant_id,
trust_center_id, trust_center_id,
email,
name,
state,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
nda_file_id, nda_file_id,
electronic_signature_id, electronic_signature_id,
created_at, created_at,
@@ -153,7 +137,7 @@ FROM
WHERE WHERE
%s %s
AND trust_center_id = @trust_center_id AND trust_center_id = @trust_center_id
AND email = @email AND identity_id = @identity_id
LIMIT 1; LIMIT 1;
` `
@@ -161,7 +145,7 @@ LIMIT 1;
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"trust_center_id": trustCenterID, "trust_center_id": trustCenterID,
"email": email, "identity_id": identityID,
} }
maps.Copy(args, scope.SQLArguments()) maps.Copy(args, scope.SQLArguments())
@@ -195,10 +179,6 @@ INSERT INTO trust_center_accesses (
tenant_id, tenant_id,
organization_id, organization_id,
trust_center_id, trust_center_id,
email,
name,
state,
has_accepted_non_disclosure_agreement,
electronic_signature_id, electronic_signature_id,
created_at, created_at,
updated_at updated_at
@@ -207,10 +187,6 @@ INSERT INTO trust_center_accesses (
@tenant_id, @tenant_id,
@organization_id, @organization_id,
@trust_center_id, @trust_center_id,
@email,
@name,
@state,
@has_accepted_non_disclosure_agreement,
@electronic_signature_id, @electronic_signature_id,
@created_at, @created_at,
@updated_at @updated_at
@@ -222,10 +198,6 @@ INSERT INTO trust_center_accesses (
"tenant_id": tca.TenantID, "tenant_id": tca.TenantID,
"organization_id": tca.OrganizationID, "organization_id": tca.OrganizationID,
"trust_center_id": tca.TrustCenterID, "trust_center_id": tca.TrustCenterID,
"email": tca.Email,
"name": tca.Name,
"state": tca.State,
"has_accepted_non_disclosure_agreement": tca.HasAcceptedNonDisclosureAgreement,
"electronic_signature_id": tca.ElectronicSignatureID, "electronic_signature_id": tca.ElectronicSignatureID,
"created_at": tca.CreatedAt, "created_at": tca.CreatedAt,
"updated_at": tca.UpdatedAt, "updated_at": tca.UpdatedAt,
@@ -252,11 +224,7 @@ func (tca *TrustCenterAccess) Update(
) error { ) error {
q := ` q := `
UPDATE trust_center_accesses SET UPDATE trust_center_accesses SET
name = @name,
state = @state,
updated_at = @updated_at, updated_at = @updated_at,
has_accepted_non_disclosure_agreement = @has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata = @has_accepted_non_disclosure_agreement_metadata,
nda_file_id = @nda_file_id, nda_file_id = @nda_file_id,
electronic_signature_id = @electronic_signature_id electronic_signature_id = @electronic_signature_id
WHERE WHERE
@@ -268,11 +236,7 @@ WHERE
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
"id": tca.ID, "id": tca.ID,
"name": tca.Name,
"state": tca.State,
"updated_at": tca.UpdatedAt, "updated_at": tca.UpdatedAt,
"has_accepted_non_disclosure_agreement": tca.HasAcceptedNonDisclosureAgreement,
"has_accepted_non_disclosure_agreement_metadata": tca.HasAcceptedNonDisclosureAgreementMetadata,
"nda_file_id": tca.NDAFileID, "nda_file_id": tca.NDAFileID,
"electronic_signature_id": tca.ElectronicSignatureID, "electronic_signature_id": tca.ElectronicSignatureID,
} }
@@ -326,11 +290,6 @@ SELECT
organization_id, organization_id,
tenant_id, tenant_id,
trust_center_id, trust_center_id,
email,
name,
state,
has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata,
nda_file_id, nda_file_id,
electronic_signature_id, electronic_signature_id,
created_at, created_at,

View File

@@ -38,8 +38,7 @@ type (
CreateTrustCenterAccessRequest struct { CreateTrustCenterAccessRequest struct {
TrustCenterID gid.GID TrustCenterID gid.GID
Email mail.Addr IdentityID gid.GID
FullName string
} }
UpdateTrustCenterDocumentAccessRequest struct { UpdateTrustCenterDocumentAccessRequest struct {
@@ -49,8 +48,6 @@ type (
UpdateTrustCenterAccessRequest struct { UpdateTrustCenterAccessRequest struct {
ID gid.GID ID gid.GID
Name *string
State *coredata.TrustCenterAccessState
DocumentAccesses []UpdateTrustCenterDocumentAccessRequest DocumentAccesses []UpdateTrustCenterDocumentAccessRequest
ReportAccesses []UpdateTrustCenterDocumentAccessRequest ReportAccesses []UpdateTrustCenterDocumentAccessRequest
TrustCenterFileAccesses []UpdateTrustCenterDocumentAccessRequest TrustCenterFileAccesses []UpdateTrustCenterDocumentAccessRequest
@@ -66,9 +63,6 @@ func (ctcar *CreateTrustCenterAccessRequest) Validate() error {
v := validator.New() v := validator.New()
v.Check(ctcar.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType)) v.Check(ctcar.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
v.Check(ctcar.Email, "email", validator.Required(), validator.NotEmpty())
v.Check(ctcar.Email.Domain(), "email", validator.NotBlacklisted())
v.Check(ctcar.FullName, "name", validator.SafeTextNoNewLine(TitleMaxLength))
return v.Error() return v.Error()
} }
@@ -77,7 +71,6 @@ func (utcar *UpdateTrustCenterAccessRequest) Validate() error {
v := validator.New() v := validator.New()
v.Check(utcar.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterAccessEntityType)) v.Check(utcar.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterAccessEntityType))
v.Check(utcar.Name, "name", validator.SafeTextNoNewLine(TitleMaxLength))
for i, docAccess := range utcar.DocumentAccesses { for i, docAccess := range utcar.DocumentAccesses {
v.Check(docAccess.ID, fmt.Sprintf("documentAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.DocumentEntityType)) v.Check(docAccess.ID, fmt.Sprintf("documentAccesses[%d].ID", i), validator.Required(), validator.GID(coredata.DocumentEntityType))
} }
@@ -237,15 +230,23 @@ func (s TrustCenterAccessService) Create(
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
} }
// FIXME: need to change UX
profile := &coredata.MembershipProfile{}
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, s.svc.scope, trustCenter.OrganizationID, req.IdentityID); err != nil {
if !errors.Is(err, coredata.ErrResourceNotFound) {
return fmt.Errorf("cannot load profile: %w", err)
}
if err := profile.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot insert profile: %w", err)
}
}
access = &coredata.TrustCenterAccess{ access = &coredata.TrustCenterAccess{
ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType), ID: gid.New(s.svc.scope.GetTenantID(), coredata.TrustCenterAccessEntityType),
OrganizationID: trustCenter.OrganizationID, OrganizationID: trustCenter.OrganizationID,
TenantID: s.svc.scope.GetTenantID(), TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: req.TrustCenterID, TrustCenterID: req.TrustCenterID,
Email: req.Email,
Name: req.FullName,
State: coredata.TrustCenterAccessStateActive,
HasAcceptedNonDisclosureAgreement: false,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
@@ -258,7 +259,7 @@ func (s TrustCenterAccessService) Create(
OrganizationID: access.OrganizationID, OrganizationID: access.OrganizationID,
DocumentType: coredata.ElectronicSignatureDocumentTypeNDA, DocumentType: coredata.ElectronicSignatureDocumentTypeNDA,
FileID: *trustCenter.NonDisclosureAgreementFileID, FileID: *trustCenter.NonDisclosureAgreementFileID,
SignerEmail: access.Email, SignerEmail: profile.EmailAddress,
}, },
) )
if err != nil { if err != nil {
@@ -292,9 +293,13 @@ func (s TrustCenterAccessService) Update(
} }
now := time.Now() now := time.Now()
var access *coredata.TrustCenterAccess var (
var trustCenterAcessActivated bool access *coredata.TrustCenterAccess
var shouldUpdateSlackMessage bool profile *coredata.MembershipProfile
trustCenterAcessActivated bool
shouldUpdateSlackMessage bool
)
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
@@ -304,19 +309,24 @@ func (s TrustCenterAccessService) Update(
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
trustCenterAcessActivated = req.State != nil && *req.State == coredata.TrustCenterAccessStateActive && access.State != coredata.TrustCenterAccessStateActive
if req.Name != nil {
access.Name = *req.Name
}
if req.State != nil {
access.State = *req.State
}
access.UpdatedAt = now access.UpdatedAt = now
if err := access.Update(ctx, tx, s.svc.scope); err != nil { if err := access.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center access: %w", err) return fmt.Errorf("cannot update trust center access: %w", err)
} }
// FIXME: need to change UX
profile = &coredata.MembershipProfile{}
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, s.svc.scope, access.OrganizationID, access.IdentityID); err != nil {
if !errors.Is(err, coredata.ErrResourceNotFound) {
return fmt.Errorf("cannot load profile: %w", err)
}
if err := profile.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot insert profile: %w", err)
}
}
var tcdas coredata.TrustCenterDocumentAccesses var tcdas coredata.TrustCenterDocumentAccesses
if len(req.DocumentAccesses) > 0 { if len(req.DocumentAccesses) > 0 {
@@ -370,8 +380,7 @@ func (s TrustCenterAccessService) Update(
shouldUpdateSlackMessage = trustCenterAcessActivated || shouldUpdateSlackMessage = trustCenterAcessActivated ||
len(req.DocumentAccesses) > 0 || len(req.DocumentAccesses) > 0 ||
len(req.ReportAccesses) > 0 || len(req.ReportAccesses) > 0 ||
len(req.TrustCenterFileAccesses) > 0 || len(req.TrustCenterFileAccesses) > 0
req.Name != nil
return nil return nil
}, },
@@ -382,7 +391,7 @@ func (s TrustCenterAccessService) Update(
} }
if shouldUpdateSlackMessage { if shouldUpdateSlackMessage {
if err := s.svc.SlackMessages.QueueSlackNotification(ctx, access.Email, access.TrustCenterID); err != nil { if err := s.svc.SlackMessages.QueueSlackNotification(ctx, profile.IdentityID, access.TrustCenterID); err != nil {
if !errors.Is(err, slack.ErrNoSlackConnector) { if !errors.Is(err, slack.ErrNoSlackConnector) {
return nil, fmt.Errorf("cannot queue slack notification: %w", err) return nil, fmt.Errorf("cannot queue slack notification: %w", err)
} }
@@ -429,12 +438,23 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Con
return fmt.Errorf("cannot update trust center access with expiration: %w", err) return fmt.Errorf("cannot update trust center access with expiration: %w", err)
} }
profile := &coredata.MembershipProfile{}
if err := profile.LoadByIdentityIDAndOrganizationID(
ctx,
tx,
s.svc.scope,
access.IdentityID,
access.OrganizationID,
); err != nil {
return fmt.Errorf("cannot load profile: %w", err)
}
emailPresenterCfg, err := s.svc.TrustCenters.EmailPresenterConfig(ctx, access.TrustCenterID) emailPresenterCfg, err := s.svc.TrustCenters.EmailPresenterConfig(ctx, access.TrustCenterID)
if err != nil { if err != nil {
return fmt.Errorf("cannot get compliance page email presenter config: %w", err) return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
} }
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, access.Name) emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name) subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name)
if err != nil { if err != nil {
@@ -442,8 +462,8 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Con
} }
accessEmail := coredata.NewEmail( accessEmail := coredata.NewEmail(
access.Name, profile.FullName,
access.Email, profile.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,

View File

@@ -1,75 +0,0 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package compliancepage
import (
"context"
"errors"
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/v2/gqlerror"
"go.gearno.de/kit/httpserver"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/trust"
)
func NewMembershipMiddleware(trustSvc *trust.Service, logger *log.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
identity := authn.IdentityFromContext(r.Context())
if identity == nil {
next.ServeHTTP(w, r)
return
}
compliancePage := CompliancePageFromContext(ctx)
membership, err := trustSvc.GetMembershipByCompliancePageIDAndEmail(ctx, compliancePage.ID, identity.EmailAddress)
if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) {
next.ServeHTTP(w, r)
return
}
logger.ErrorCtx(ctx, "cannot get membership by page id and email", log.Error(err))
httpserver.RenderJSON(
w,
http.StatusInternalServerError,
&graphql.Response{
Errors: gqlerror.List{
gqlutils.Internal(ctx),
},
},
)
return
}
if membership.State == coredata.TrustCenterAccessStateActive {
ctx = context.WithValue(ctx, complianceMembershipKey, membership)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
next.ServeHTTP(w, r)
},
)
}
}

View File

@@ -46,10 +46,6 @@ func NewTrustCenterAccessEdge(tca *coredata.TrustCenterAccess, orderBy coredata.
func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess { func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
return &TrustCenterAccess{ return &TrustCenterAccess{
ID: tca.ID, ID: tca.ID,
Email: tca.Email,
Name: tca.Name,
State: tca.State,
HasAcceptedNonDisclosureAgreement: tca.HasAcceptedNonDisclosureAgreement,
CreatedAt: tca.CreatedAt, CreatedAt: tca.CreatedAt,
UpdatedAt: tca.UpdatedAt, UpdatedAt: tca.UpdatedAt,
} }

View File

@@ -1940,8 +1940,7 @@ func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input ty
ctx, ctx,
&probo.CreateTrustCenterAccessRequest{ &probo.CreateTrustCenterAccessRequest{
TrustCenterID: input.TrustCenterID, TrustCenterID: input.TrustCenterID,
Email: identity.EmailAddress, IdentityID: identity.ID,
FullName: identity.FullName,
}, },
) )
if err != nil { if err != nil {
@@ -1991,8 +1990,6 @@ func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input ty
ctx, ctx,
&probo.UpdateTrustCenterAccessRequest{ &probo.UpdateTrustCenterAccessRequest{
ID: input.ID, ID: input.ID,
Name: input.Name,
State: input.State,
DocumentAccesses: documentAccesses, DocumentAccesses: documentAccesses,
ReportAccesses: reportAccesses, ReportAccesses: reportAccesses,
TrustCenterFileAccesses: fileAccesses, TrustCenterFileAccesses: fileAccesses,

View File

@@ -40,7 +40,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *e
sessionCookie: authn.NewCookie(&cookieConfig), sessionCookie: authn.NewCookie(&cookieConfig),
}, },
Directives: schema.DirectiveRoot{ Directives: schema.DirectiveRoot{
Nda: newNDADirective(logger, esignSvc), Nda: newNDADirective(logger, trustSvc, esignSvc),
Session: session.Directive, Session: session.Directive,
}, },
} }

View File

@@ -16,6 +16,7 @@ package trust_v1
import ( import (
"context" "context"
"errors"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
"go.gearno.de/kit/log" "go.gearno.de/kit/log"
@@ -24,10 +25,12 @@ import (
"go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/compliancepage" "go.probo.inc/probo/pkg/server/api/compliancepage"
"go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/trust"
) )
func newNDADirective( func newNDADirective(
logger *log.Logger, logger *log.Logger,
trustSvc *trust.Service,
esignSvc *esign.Service, esignSvc *esign.Service,
) func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { ) func(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
return func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { return func(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
@@ -36,11 +39,22 @@ func newNDADirective(
return next(ctx) return next(ctx)
} }
membership := compliancepage.ComplianceMembershipFromContext(ctx) compliancePage := compliancepage.CompliancePageFromContext(ctx)
if membership == nil { if compliancePage == nil {
logger.ErrorCtx(ctx, "cannot get compliance page from context")
return nil, gqlutils.Internal(ctx)
}
membership, err := trustSvc.GetMembershipByCompliancePageIDAndIdentityID(ctx, compliancePage.ID, identity.ID)
if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) {
return nil, gqlutils.Unauthenticatedf(ctx, "authentication needed") return nil, gqlutils.Unauthenticatedf(ctx, "authentication needed")
} }
logger.ErrorCtx(ctx, "cannot get compliance page membership", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if membership.ElectronicSignatureID == nil { if membership.ElectronicSignatureID == nil {
return next(ctx) return next(ctx)
} }

View File

@@ -85,7 +85,6 @@ func NewMux(
r.Use(compliancepage.NewCompliancePagePresenceMiddleware()) r.Use(compliancepage.NewCompliancePagePresenceMiddleware())
r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig))
r.Use(compliancepage.NewMembershipMiddleware(trustSvc, logger))
graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, esignSvc, logger, baseURL, cookieConfig) graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, esignSvc, logger, baseURL, cookieConfig)

View File

@@ -107,17 +107,14 @@ func (r *documentResolver) IsUserAuthorized(ctx context.Context, obj *types.Docu
documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
return false, nil errors.Is(err, trust.ErrUserNotFound) ||
} errors.Is(err, trust.ErrUserInactive) ||
if errors.Is(err, trust.ErrMembershipInactive) { errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil
}
if errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil return false, nil
} }
@@ -141,15 +138,17 @@ func (r *documentResolver) Access(ctx context.Context, obj *types.Document) (*ty
access, err := trustService.TrustCenterAccesses.GetDocumentAccess( access, err := trustService.TrustCenterAccesses.GetDocumentAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) || errors.Is(err, trust.ErrDocumentAccessNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
errors.Is(err, trust.ErrUserNotFound) ||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
return nil, nil return nil, nil
} }
if errors.Is(err, trust.ErrMembershipInactive) { if errors.Is(err, trust.ErrUserInactive) {
return nil, gqlutils.Forbidden(ctx, err) return nil, gqlutils.Forbidden(ctx, err)
} }
@@ -262,7 +261,7 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri
trustCenter := compliancepage.CompliancePageFromContext(ctx) trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
if _, err := trustService.TrustCenterAccesses.EnsureAccess(ctx, trustCenter.ID, identity.EmailAddress, identity.FullName); err != nil { if _, err := trustService.TrustCenterAccesses.EnsureAccess(ctx, trustCenter.ID, identity.ID); err != nil {
r.logger.ErrorCtx(ctx, "cannot ensure trust center access", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot ensure trust center access", log.Error(err))
} }
@@ -288,8 +287,7 @@ func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.Reque
ctx, ctx,
&trust.TrustCenterAccessRequest{ &trust.TrustCenterAccessRequest{
TrustCenterID: trustCenter.ID, TrustCenterID: trustCenter.ID,
Email: identity.EmailAddress, IdentityID: identity.ID,
FullName: identity.FullName,
DocumentIDs: nil, DocumentIDs: nil,
ReportIDs: nil, ReportIDs: nil,
}, },
@@ -302,8 +300,6 @@ func (r *mutationResolver) RequestAllAccesses(ctx context.Context) (*types.Reque
return &types.RequestAccessesPayload{ return &types.RequestAccessesPayload{
TrustCenterAccess: &types.TrustCenterAccess{ TrustCenterAccess: &types.TrustCenterAccess{
ID: access.ID, ID: access.ID,
Email: access.Email,
Name: access.Name,
CreatedAt: access.CreatedAt, CreatedAt: access.CreatedAt,
UpdatedAt: access.UpdatedAt, UpdatedAt: access.UpdatedAt,
}, },
@@ -341,7 +337,7 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess( documentAccess, err := trustService.TrustCenterAccesses.GetDocumentAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
input.DocumentID, input.DocumentID,
) )
if err != nil { if err != nil {
@@ -395,7 +391,7 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo
reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess( reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
input.ReportID, input.ReportID,
) )
if err != nil { if err != nil {
@@ -447,7 +443,7 @@ func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input type
fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
input.TrustCenterFileID, input.TrustCenterFileID,
) )
if err != nil { if err != nil {
@@ -495,8 +491,7 @@ func (r *mutationResolver) RequestDocumentAccess(ctx context.Context, input type
ctx, ctx,
&trust.TrustCenterAccessRequest{ &trust.TrustCenterAccessRequest{
TrustCenterID: trustCenter.ID, TrustCenterID: trustCenter.ID,
Email: identity.EmailAddress, IdentityID: identity.ID,
FullName: identity.FullName,
DocumentIDs: []gid.GID{input.DocumentID}, DocumentIDs: []gid.GID{input.DocumentID},
ReportIDs: []gid.GID{}, ReportIDs: []gid.GID{},
TrustCenterFileIDs: []gid.GID{}, TrustCenterFileIDs: []gid.GID{},
@@ -538,8 +533,7 @@ func (r *mutationResolver) RequestReportAccess(ctx context.Context, input types.
ctx, ctx,
&trust.TrustCenterAccessRequest{ &trust.TrustCenterAccessRequest{
TrustCenterID: trustCenter.ID, TrustCenterID: trustCenter.ID,
Email: identity.EmailAddress, IdentityID: identity.ID,
FullName: identity.FullName,
DocumentIDs: []gid.GID{}, DocumentIDs: []gid.GID{},
ReportIDs: []gid.GID{input.ReportID}, ReportIDs: []gid.GID{input.ReportID},
TrustCenterFileIDs: []gid.GID{}, TrustCenterFileIDs: []gid.GID{},
@@ -581,8 +575,7 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp
ctx, ctx,
&trust.TrustCenterAccessRequest{ &trust.TrustCenterAccessRequest{
TrustCenterID: trustCenter.ID, TrustCenterID: trustCenter.ID,
Email: identity.EmailAddress, IdentityID: identity.ID,
FullName: identity.FullName,
DocumentIDs: []gid.GID{}, DocumentIDs: []gid.GID{},
ReportIDs: []gid.GID{}, ReportIDs: []gid.GID{},
TrustCenterFileIDs: []gid.GID{input.TrustCenterFileID}, TrustCenterFileIDs: []gid.GID{input.TrustCenterFileID},
@@ -676,7 +669,7 @@ func (r *nonDisclosureAgreementResolver) FileURL(ctx context.Context, obj *types
if identity := authn.IdentityFromContext(ctx); identity != nil && r.esign != nil { if identity := authn.IdentityFromContext(ctx); identity != nil && r.esign != nil {
trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.EmailAddress) access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID)
if err == nil && access.ElectronicSignatureID != nil { if err == nil && access.ElectronicSignatureID != nil {
fileURL, err := r.esign.GenerateSignatureFileURL(ctx, *access.ElectronicSignatureID, 15*time.Minute) fileURL, err := r.esign.GenerateSignatureFileURL(ctx, *access.ElectronicSignatureID, 15*time.Minute)
if err == nil { if err == nil {
@@ -707,7 +700,7 @@ func (r *nonDisclosureAgreementResolver) ViewerSignature(ctx context.Context, ob
trustCenter := compliancepage.CompliancePageFromContext(ctx) trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) trustService := r.TrustService(ctx, trustCenter.ID.TenantID())
access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.EmailAddress) access, err := trustService.TrustCenterAccesses.GetAccess(ctx, trustCenter.ID, identity.ID)
if err != nil { if err != nil {
return nil, nil return nil, nil
} }
@@ -868,17 +861,14 @@ func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report
reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(ctx, reportAccess, err := trustService.TrustCenterAccesses.GetReportAccess(ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
return false, nil errors.Is(err, trust.ErrUserNotFound) ||
} errors.Is(err, trust.ErrUserInactive) ||
if errors.Is(err, trust.ErrMembershipInactive) { errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil
}
if errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil return false, nil
} }
@@ -902,15 +892,17 @@ func (r *reportResolver) Access(ctx context.Context, obj *types.Report) (*types.
access, err := trustService.TrustCenterAccesses.GetReportAccess( access, err := trustService.TrustCenterAccesses.GetReportAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) || errors.Is(err, trust.ErrDocumentAccessNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
errors.Is(err, trust.ErrUserNotFound) ||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
return nil, nil return nil, nil
} }
if errors.Is(err, trust.ErrMembershipInactive) { if errors.Is(err, trust.ErrUserInactive) {
return nil, gqlutils.Forbidden(ctx, err) return nil, gqlutils.Forbidden(ctx, err)
} }
@@ -1107,17 +1099,14 @@ func (r *trustCenterFileResolver) IsUserAuthorized(ctx context.Context, obj *typ
fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx, fileAccess, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
return false, nil errors.Is(err, trust.ErrUserNotFound) ||
} errors.Is(err, trust.ErrUserInactive) ||
if errors.Is(err, trust.ErrMembershipInactive) { errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil
}
if errors.Is(err, trust.ErrDocumentAccessNotFound) {
return false, nil return false, nil
} }
@@ -1141,15 +1130,17 @@ func (r *trustCenterFileResolver) Access(ctx context.Context, obj *types.TrustCe
access, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess( access, err := trustService.TrustCenterAccesses.GetTrustCenterFileAccess(
ctx, ctx,
trustCenter.ID, trustCenter.ID,
identity.EmailAddress, identity.ID,
obj.ID, obj.ID,
) )
if err != nil { if err != nil {
if errors.Is(err, trust.ErrMembershipNotFound) || errors.Is(err, trust.ErrDocumentAccessNotFound) { if errors.Is(err, trust.ErrMembershipNotFound) ||
errors.Is(err, trust.ErrUserNotFound) ||
errors.Is(err, trust.ErrDocumentAccessNotFound) {
return nil, nil return nil, nil
} }
if errors.Is(err, trust.ErrMembershipInactive) { if errors.Is(err, trust.ErrUserInactive) {
return nil, gqlutils.Forbidden(ctx, err) return nil, gqlutils.Forbidden(ctx, err)
} }

View File

@@ -118,8 +118,13 @@ func (s *SlackMessageService) UpdateSlackAccessMessage(
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
} }
identity := &coredata.Identity{}
if err := identity.LoadByEmail(ctx, tx, requesterEmail); err != nil {
return fmt.Errorf("cannot load identity: %w", err)
}
var trustCenterAccess coredata.TrustCenterAccess var trustCenterAccess coredata.TrustCenterAccess
if err := trustCenterAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenter.ID, requesterEmail); err != nil { if err := trustCenterAccess.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, trustCenter.ID, identity.ID); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
@@ -132,7 +137,7 @@ func (s *SlackMessageService) UpdateSlackAccessMessage(
updatedBody, err := s.buildAccessRequestMessage( updatedBody, err := s.buildAccessRequestMessage(
newSlackMessageID, newSlackMessageID,
trustCenterAccess.Name, identity.FullName,
requesterEmail, requesterEmail,
trustCenter.OrganizationID, trustCenter.OrganizationID,
documents, documents,
@@ -179,12 +184,21 @@ func (s *SlackMessageService) UpdateSlackAccessMessage(
func (s *SlackMessageService) QueueSlackNotification( func (s *SlackMessageService) QueueSlackNotification(
ctx context.Context, ctx context.Context,
requesterEmail mail.Addr, identityID gid.GID,
trustCenterID gid.GID, trustCenterID gid.GID,
) error { ) error {
return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
var trustCenterAccess coredata.TrustCenterAccess var (
if err := trustCenterAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, requesterEmail); err != nil { identity = &coredata.Identity{}
trustCenterAccess *coredata.TrustCenterAccess
)
if err := identity.LoadByID(ctx, tx, identityID); err != nil {
return fmt.Errorf("cannot load identity: %w", err)
}
trustCenterAccess = &coredata.TrustCenterAccess{}
if err := trustCenterAccess.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, trustCenterID, identityID); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
@@ -224,8 +238,8 @@ func (s *SlackMessageService) QueueSlackNotification(
body, err := s.buildAccessRequestMessage( body, err := s.buildAccessRequestMessage(
slackMessageID, slackMessageID,
trustCenterAccess.Name, identity.FullName,
requesterEmail, identity.EmailAddress,
trustCenter.OrganizationID, trustCenter.OrganizationID,
documents, documents,
reports, reports,
@@ -247,7 +261,7 @@ func (s *SlackMessageService) QueueSlackNotification(
OrganizationID: trustCenter.OrganizationID, OrganizationID: trustCenter.OrganizationID,
Type: coredata.SlackMessageTypeTrustCenterAccessRequest, Type: coredata.SlackMessageTypeTrustCenterAccessRequest,
Body: body, Body: body,
RequesterEmail: &requesterEmail, RequesterEmail: &identity.EmailAddress,
Metadata: metadata.toMap(), Metadata: metadata.toMap(),
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
@@ -261,7 +275,7 @@ func (s *SlackMessageService) QueueSlackNotification(
tx, tx,
s.svc.scope, s.svc.scope,
trustCenter.OrganizationID, trustCenter.OrganizationID,
requesterEmail, identity.EmailAddress,
coredata.SlackMessageTypeTrustCenterAccessRequest, coredata.SlackMessageTypeTrustCenterAccessRequest,
sevenDaysAgo, sevenDaysAgo,
) )

View File

@@ -20,7 +20,8 @@ var (
ErrCustomDomainNotFound = errors.New("custom domain not found") ErrCustomDomainNotFound = errors.New("custom domain not found")
ErrPageNotFound = errors.New("page not found") ErrPageNotFound = errors.New("page not found")
ErrMembershipNotFound = errors.New("membership not found") ErrMembershipNotFound = errors.New("membership not found")
ErrMembershipInactive = errors.New("membership inactive") ErrUserNotFound = errors.New("user not found")
ErrUserInactive = errors.New("user inactive")
ErrDocumentAccessNotFound = errors.New("document access not found") ErrDocumentAccessNotFound = errors.New("document access not found")
ErrNDAFileNotFound = errors.New("NDA file not found") ErrNDAFileNotFound = errors.New("NDA file not found")
) )

View File

@@ -30,7 +30,6 @@ import (
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/slack" "go.probo.inc/probo/pkg/slack"
) )
@@ -277,18 +276,18 @@ func (s *Service) EmailPresenterConfigByOrganizationID(ctx context.Context, orgI
return s.WithTenant(orgID.TenantID()).TrustCenters.EmailPresenterConfig(ctx, trustCenter.ID) return s.WithTenant(orgID.TenantID()).TrustCenters.EmailPresenterConfig(ctx, trustCenter.ID)
} }
func (s *Service) GetMembershipByCompliancePageIDAndEmail(ctx context.Context, compliancePageID gid.GID, email mail.Addr) (*coredata.TrustCenterAccess, error) { func (s *Service) GetMembershipByCompliancePageIDAndIdentityID(ctx context.Context, compliancePageID gid.GID, identityID gid.GID) (*coredata.TrustCenterAccess, error) {
membership := &coredata.TrustCenterAccess{} membership := &coredata.TrustCenterAccess{}
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(conn pg.Conn) error { func(conn pg.Conn) error {
return membership.LoadByTrustCenterIDAndEmail( return membership.LoadByTrustCenterIDAndIdentityID(
ctx, ctx,
conn, conn,
coredata.NewScopeFromObjectID(compliancePageID), coredata.NewScopeFromObjectID(compliancePageID),
compliancePageID, compliancePageID,
email, identityID,
) )
}, },
) )

View File

@@ -28,7 +28,6 @@ import (
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/validator"
) )
type ( type (
@@ -40,8 +39,7 @@ type (
TrustCenterAccessRequest struct { TrustCenterAccessRequest struct {
TrustCenterID gid.GID TrustCenterID gid.GID
Email mail.Addr IdentityID gid.GID
FullName string
DocumentIDs []gid.GID DocumentIDs []gid.GID
ReportIDs []gid.GID ReportIDs []gid.GID
TrustCenterFileIDs []gid.GID TrustCenterFileIDs []gid.GID
@@ -52,20 +50,11 @@ const (
TrustCenterAccessURLFormat = "https://%s/organizations/%s/trust-center/access" TrustCenterAccessURLFormat = "https://%s/organizations/%s/trust-center/access"
) )
func (tcar *TrustCenterAccessRequest) Validate() error {
v := validator.New()
v.Check(tcar.Email.Domain(), "email", validator.NotBlacklisted())
return v.Error()
}
func (s TrustCenterAccessService) ensureAccessInTx( func (s TrustCenterAccessService) ensureAccessInTx(
ctx context.Context, ctx context.Context,
tx pg.Conn, tx pg.Conn,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
fullName string,
) (*coredata.TrustCenterAccess, *coredata.TrustCenter, error) { ) (*coredata.TrustCenterAccess, *coredata.TrustCenter, error) {
now := time.Now() now := time.Now()
@@ -74,8 +63,13 @@ func (s TrustCenterAccessService) ensureAccessInTx(
return nil, nil, fmt.Errorf("cannot load trust center: %w", err) return nil, nil, fmt.Errorf("cannot load trust center: %w", err)
} }
identity := &coredata.Identity{}
if err := identity.LoadByID(ctx, tx, identityID); err != nil {
return nil, nil, fmt.Errorf("cannot load identity: %w", err)
}
existingAccess := &coredata.TrustCenterAccess{} existingAccess := &coredata.TrustCenterAccess{}
err := existingAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, email) err := existingAccess.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, trustCenterID, identityID)
if err == nil { if err == nil {
return existingAccess, trustCenter, nil return existingAccess, trustCenter, nil
} }
@@ -89,10 +83,6 @@ func (s TrustCenterAccessService) ensureAccessInTx(
OrganizationID: trustCenter.OrganizationID, OrganizationID: trustCenter.OrganizationID,
TenantID: s.svc.scope.GetTenantID(), TenantID: s.svc.scope.GetTenantID(),
TrustCenterID: trustCenterID, TrustCenterID: trustCenterID,
Email: email,
Name: fullName,
State: coredata.TrustCenterAccessStateActive,
HasAcceptedNonDisclosureAgreement: false,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
@@ -105,7 +95,7 @@ func (s TrustCenterAccessService) ensureAccessInTx(
OrganizationID: access.OrganizationID, OrganizationID: access.OrganizationID,
DocumentType: coredata.ElectronicSignatureDocumentTypeNDA, DocumentType: coredata.ElectronicSignatureDocumentTypeNDA,
FileID: *trustCenter.NonDisclosureAgreementFileID, FileID: *trustCenter.NonDisclosureAgreementFileID,
SignerEmail: access.Email, SignerEmail: identity.EmailAddress,
}, },
) )
if err != nil { if err != nil {
@@ -124,8 +114,7 @@ func (s TrustCenterAccessService) ensureAccessInTx(
func (s TrustCenterAccessService) EnsureAccess( func (s TrustCenterAccessService) EnsureAccess(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
fullName string,
) (*coredata.TrustCenterAccess, error) { ) (*coredata.TrustCenterAccess, error) {
var access *coredata.TrustCenterAccess var access *coredata.TrustCenterAccess
@@ -133,7 +122,7 @@ func (s TrustCenterAccessService) EnsureAccess(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
var err error var err error
access, _, err = s.ensureAccessInTx(ctx, tx, trustCenterID, email, fullName) access, _, err = s.ensureAccessInTx(ctx, tx, trustCenterID, identityID)
return err return err
}, },
) )
@@ -145,10 +134,6 @@ func (s TrustCenterAccessService) Request(
ctx context.Context, ctx context.Context,
req *TrustCenterAccessRequest, req *TrustCenterAccessRequest,
) (*coredata.TrustCenterAccess, error) { ) (*coredata.TrustCenterAccess, error) {
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("invalid request arguments: %w", err)
}
var ( var (
now = time.Now() now = time.Now()
access *coredata.TrustCenterAccess access *coredata.TrustCenterAccess
@@ -160,7 +145,7 @@ func (s TrustCenterAccessService) Request(
var trustCenter *coredata.TrustCenter var trustCenter *coredata.TrustCenter
var err error var err error
access, trustCenter, err = s.ensureAccessInTx(ctx, tx, req.TrustCenterID, req.Email, req.FullName) access, trustCenter, err = s.ensureAccessInTx(ctx, tx, req.TrustCenterID, req.IdentityID)
if err != nil { if err != nil {
return err return err
} }
@@ -272,7 +257,7 @@ func (s TrustCenterAccessService) Request(
return nil, err return nil, err
} }
if err := s.svc.SlackMessages.QueueSlackNotification(ctx, access.Email, req.TrustCenterID); err != nil { if err := s.svc.SlackMessages.QueueSlackNotification(ctx, req.IdentityID, req.TrustCenterID); err != nil {
s.logger.ErrorCtx(ctx, "cannot queue slack notification", log.Error(err)) s.logger.ErrorCtx(ctx, "cannot queue slack notification", log.Error(err))
} }
@@ -282,12 +267,12 @@ func (s TrustCenterAccessService) Request(
func (s TrustCenterAccessService) GetAccess( func (s TrustCenterAccessService) GetAccess(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
) (coredata.TrustCenterAccess, error) { ) (coredata.TrustCenterAccess, error) {
var access coredata.TrustCenterAccess var access coredata.TrustCenterAccess
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
return access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email) return access.LoadByTrustCenterIDAndIdentityID(ctx, conn, s.svc.scope, trustCenterID, identityID)
}) })
return access, err return access, err
@@ -296,14 +281,14 @@ func (s TrustCenterAccessService) GetAccess(
func (s TrustCenterAccessService) GetDocumentAccess( func (s TrustCenterAccessService) GetDocumentAccess(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
documentID gid.GID, documentID gid.GID,
) (*coredata.TrustCenterDocumentAccess, error) { ) (*coredata.TrustCenterDocumentAccess, error) {
var documentAccess *coredata.TrustCenterDocumentAccess var documentAccess *coredata.TrustCenterDocumentAccess
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
access := &coredata.TrustCenterAccess{} access := &coredata.TrustCenterAccess{}
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email) err := access.LoadByTrustCenterIDAndIdentityID(ctx, conn, s.svc.scope, trustCenterID, identityID)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrMembershipNotFound return ErrMembershipNotFound
@@ -312,8 +297,15 @@ func (s TrustCenterAccessService) GetDocumentAccess(
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
if access.State != coredata.TrustCenterAccessStateActive { profile := &coredata.MembershipProfile{}
return ErrMembershipInactive if err := profile.LoadByIdentityIDAndOrganizationID(ctx, conn, s.svc.scope, identityID, access.OrganizationID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrUserNotFound
}
}
if profile.State != coredata.ProfileStateActive {
return ErrUserInactive
} }
documentAccess = &coredata.TrustCenterDocumentAccess{} documentAccess = &coredata.TrustCenterDocumentAccess{}
@@ -339,14 +331,14 @@ func (s TrustCenterAccessService) GetDocumentAccess(
func (s TrustCenterAccessService) GetReportAccess( func (s TrustCenterAccessService) GetReportAccess(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
reportID gid.GID, reportID gid.GID,
) (*coredata.TrustCenterDocumentAccess, error) { ) (*coredata.TrustCenterDocumentAccess, error) {
var reportAccess *coredata.TrustCenterDocumentAccess var reportAccess *coredata.TrustCenterDocumentAccess
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
access := &coredata.TrustCenterAccess{} access := &coredata.TrustCenterAccess{}
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email) err := access.LoadByTrustCenterIDAndIdentityID(ctx, conn, s.svc.scope, trustCenterID, identityID)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrMembershipNotFound return ErrMembershipNotFound
@@ -355,8 +347,15 @@ func (s TrustCenterAccessService) GetReportAccess(
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
if access.State != coredata.TrustCenterAccessStateActive { profile := &coredata.MembershipProfile{}
return ErrMembershipInactive if err := profile.LoadByIdentityIDAndOrganizationID(ctx, conn, s.svc.scope, identityID, access.OrganizationID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrUserNotFound
}
}
if profile.State != coredata.ProfileStateActive {
return ErrUserInactive
} }
reportAccess = &coredata.TrustCenterDocumentAccess{} reportAccess = &coredata.TrustCenterDocumentAccess{}
@@ -382,14 +381,14 @@ func (s TrustCenterAccessService) GetReportAccess(
func (s TrustCenterAccessService) GetTrustCenterFileAccess( func (s TrustCenterAccessService) GetTrustCenterFileAccess(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,
email mail.Addr, identityID gid.GID,
trustCenterFileID gid.GID, trustCenterFileID gid.GID,
) (*coredata.TrustCenterDocumentAccess, error) { ) (*coredata.TrustCenterDocumentAccess, error) {
var fileAccess *coredata.TrustCenterDocumentAccess var fileAccess *coredata.TrustCenterDocumentAccess
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error { err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
access := &coredata.TrustCenterAccess{} access := &coredata.TrustCenterAccess{}
err := access.LoadByTrustCenterIDAndEmail(ctx, conn, s.svc.scope, trustCenterID, email) err := access.LoadByTrustCenterIDAndIdentityID(ctx, conn, s.svc.scope, trustCenterID, identityID)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) { if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrMembershipNotFound return ErrMembershipNotFound
@@ -398,8 +397,15 @@ func (s TrustCenterAccessService) GetTrustCenterFileAccess(
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
if access.State != coredata.TrustCenterAccessStateActive { profile := &coredata.MembershipProfile{}
return ErrMembershipInactive if err := profile.LoadByIdentityIDAndOrganizationID(ctx, conn, s.svc.scope, identityID, access.OrganizationID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrUserNotFound
}
}
if profile.State != coredata.ProfileStateActive {
return ErrUserInactive
} }
fileAccess = &coredata.TrustCenterDocumentAccess{} fileAccess = &coredata.TrustCenterDocumentAccess{}
@@ -436,12 +442,28 @@ func (s *TrustCenterAccessService) GrantByIDs(
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
} }
identity := &coredata.Identity{}
if err := identity.LoadByEmail(ctx, tx, email); err != nil {
return fmt.Errorf("cannot load identity: %w", err)
}
access := &coredata.TrustCenterAccess{} access := &coredata.TrustCenterAccess{}
if err := access.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenter.ID, email); err != nil { if err := access.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, trustCenter.ID, identity.ID); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
shouldSendEmail := access.State != coredata.TrustCenterAccessStateActive profile := &coredata.MembershipProfile{}
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, s.svc.scope, identity.ID, access.OrganizationID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrUserNotFound
}
}
if profile.State != coredata.ProfileStateActive {
return ErrUserInactive
}
shouldSendEmail := profile.State != coredata.ProfileStateActive
now := time.Now() now := time.Now()
if len(documentIDs) > 0 { if len(documentIDs) > 0 {
@@ -461,13 +483,13 @@ func (s *TrustCenterAccessService) GrantByIDs(
} }
if shouldSendEmail { if shouldSendEmail {
access.State = coredata.TrustCenterAccessStateActive profile.State = coredata.ProfileStateActive
access.UpdatedAt = now profile.UpdatedAt = now
if err := access.Update(ctx, tx, s.svc.scope); err != nil { if err := profile.Update(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot update trust center access: %w", err) return fmt.Errorf("cannot update profile: %w", err)
} }
if err := s.sendAccessEmail(ctx, tx, access); err != nil { if err := s.sendAccessEmail(ctx, tx, access, profile); err != nil {
return fmt.Errorf("cannot send access email: %w", err) return fmt.Errorf("cannot send access email: %w", err)
} }
} }
@@ -476,7 +498,7 @@ func (s *TrustCenterAccessService) GrantByIDs(
}) })
} }
func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Conn, access *coredata.TrustCenterAccess) error { func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Conn, access *coredata.TrustCenterAccess, profile *coredata.MembershipProfile) error {
organization := &coredata.Organization{} organization := &coredata.Organization{}
if err := organization.LoadByID(ctx, tx, s.svc.scope, access.OrganizationID); err != nil { if err := organization.LoadByID(ctx, tx, s.svc.scope, access.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err) return fmt.Errorf("cannot load organization: %w", err)
@@ -494,7 +516,7 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
return fmt.Errorf("cannot get compliance page email presenter config: %w", err) return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
} }
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, access.Name) emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name) subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name)
if err != nil { if err != nil {
@@ -502,8 +524,8 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
} }
accessEmail := coredata.NewEmail( accessEmail := coredata.NewEmail(
access.Name, profile.FullName,
access.Email, profile.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,
@@ -529,11 +551,21 @@ func (s *TrustCenterAccessService) RejectOrRevokeByIDs(
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
} }
identity := &coredata.Identity{}
if err := identity.LoadByEmail(ctx, tx, email); err != nil {
return fmt.Errorf("cannot load identity: %w", err)
}
access := &coredata.TrustCenterAccess{} access := &coredata.TrustCenterAccess{}
if err := access.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenter.ID, email); err != nil { if err := access.LoadByTrustCenterIDAndIdentityID(ctx, tx, s.svc.scope, trustCenter.ID, identity.ID); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
profile := &coredata.MembershipProfile{}
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, s.svc.scope, identity.ID, access.OrganizationID); err != nil {
return fmt.Errorf("cannot load profile: %w", err)
}
shouldSendEmail := false shouldSendEmail := false
now := time.Now() now := time.Now()
@@ -557,7 +589,7 @@ func (s *TrustCenterAccessService) RejectOrRevokeByIDs(
} }
if shouldSendEmail { if shouldSendEmail {
if err := s.sendDocumentAccessRejectedEmail(ctx, tx, access, documentIDs, reportIDs, fileIDs); err != nil { if err := s.sendDocumentAccessRejectedEmail(ctx, tx, access, profile, documentIDs, reportIDs, fileIDs); err != nil {
return fmt.Errorf("cannot send access email: %w", err) return fmt.Errorf("cannot send access email: %w", err)
} }
} }
@@ -570,6 +602,7 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
ctx context.Context, ctx context.Context,
tx pg.Conn, tx pg.Conn,
access *coredata.TrustCenterAccess, access *coredata.TrustCenterAccess,
profile *coredata.MembershipProfile,
documentIDs []gid.GID, documentIDs []gid.GID,
reportIDs []gid.GID, reportIDs []gid.GID,
fileIDs []gid.GID, fileIDs []gid.GID,
@@ -613,11 +646,7 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
return fmt.Errorf("cannot get compliance page email presenter config: %w", err) return fmt.Errorf("cannot get compliance page email presenter config: %w", err)
} }
fullName := access.Name emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, profile.FullName)
if fullName == "" {
fullName = access.Email.Username()
}
emailPresenter := emails.NewPresenterFromConfig(s.svc.fileManager, emailPresenterCfg, fullName)
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterDocumentAccessRejected( subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterDocumentAccessRejected(
ctx, ctx,
@@ -629,8 +658,8 @@ func (s *TrustCenterAccessService) sendDocumentAccessRejectedEmail(
} }
accessEmail := coredata.NewEmail( accessEmail := coredata.NewEmail(
access.Name, profile.FullName,
access.Email, profile.EmailAddress,
subject, subject,
textBody, textBody,
htmlBody, htmlBody,