Trust center access management - get rid of access token

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-15 16:16:06 +04:00
committed by Bryan Frimin
parent 8b3bda56e6
commit 44d85a2b12
39 changed files with 582 additions and 1800 deletions

View File

@@ -308,6 +308,10 @@ LIMIT 1
customDomain, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CustomDomain])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect custom domain: %w", err)
}

View File

@@ -102,6 +102,10 @@ LIMIT 1;
trustCenter, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenter])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect trust center: %w", err)
}
@@ -146,6 +150,10 @@ LIMIT 1;
trustCenter, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenter])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect trust center: %w", err)
}
@@ -186,6 +194,10 @@ LIMIT 1;
trustCenter, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[TrustCenter])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect trust center: %w", err)
}

View File

@@ -290,6 +290,7 @@ func (t *TrustCenterFiles) LoadByOrganizationID(
scope Scoper,
organizationID gid.GID,
cursor *page.Cursor[TrustCenterFileOrderField],
filter *TrustCenterFileFilter,
) error {
q := `
SELECT
@@ -307,12 +308,14 @@ WHERE
%s
AND organization_id = @organization_id
AND %s
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, filter.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)

View File

@@ -24,9 +24,9 @@ type (
}
)
type TrustCenterFileOption func(f *TrustCenterFileFilter)
type TrustCenterFileFilterOption func(f *TrustCenterFileFilter)
func NewTrustCenterFileFilter(opts ...TrustCenterFileOption) *TrustCenterFileFilter {
func NewTrustCenterFileFilter(opts ...TrustCenterFileFilterOption) *TrustCenterFileFilter {
f := &TrustCenterFileFilter{}
for _, opt := range opts {
@@ -36,7 +36,7 @@ func NewTrustCenterFileFilter(opts ...TrustCenterFileOption) *TrustCenterFileFil
return f
}
func WithTrustCenterFileVisibilities(visibilities ...TrustCenterVisibility) TrustCenterFileOption {
func WithTrustCenterFileVisibilities(visibilities ...TrustCenterVisibility) TrustCenterFileFilterOption {
return func(f *TrustCenterFileFilter) {
f.trustCenterVisibilities = visibilities
}

View File

@@ -24,7 +24,7 @@ import (
type ErrInvalidToken struct{ message string }
func NewInvalidTokenError() error {
return &ErrInvalidToken{"invalid invitation token"}
return &ErrInvalidToken{"invalid token"}
}
func (e ErrInvalidToken) Error() string {

View File

@@ -28,7 +28,6 @@ import (
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/slack"
"go.probo.inc/probo/pkg/statelesstoken"
"go.probo.inc/probo/pkg/validator"
)
@@ -402,28 +401,14 @@ func (s TrustCenterAccessService) Delete(
}
func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Conn, access *coredata.TrustCenterAccess) error {
accessToken, err := statelesstoken.NewToken(
s.svc.trustConfig.TokenSecret,
s.svc.trustConfig.TokenType,
s.svc.trustConfig.TokenDuration,
TrustCenterAccessData{
TrustCenterID: access.TrustCenterID,
Email: access.Email,
},
)
if err != nil {
return fmt.Errorf("cannot generate access token: %w", err)
}
trustCenter := &coredata.TrustCenter{}
err = trustCenter.LoadByID(ctx, tx, s.svc.scope, access.TrustCenterID)
if err != nil {
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, access.TrustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
organization := &coredata.Organization{}
err = organization.LoadByID(ctx, tx, s.svc.scope, trustCenter.OrganizationID)
if err != nil {
if err := organization.LoadByID(ctx, tx, s.svc.scope, trustCenter.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
@@ -434,7 +419,7 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Con
hostname := baseURLParsed.Host
scheme := baseURLParsed.Scheme
path := "/trust/" + trustCenter.Slug + "/access"
path := "/trust/" + trustCenter.Slug
if organization.CustomDomainID != nil {
customDomain, err := s.svc.CustomDomains.GetOrganizationCustomDomain(ctx, organization.ID)
@@ -448,16 +433,13 @@ func (s TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Con
hostname = customDomain.Domain
scheme = "https"
path = "/access"
path = ""
}
accessURL := url.URL{
Scheme: scheme,
Host: hostname,
Path: path,
RawQuery: url.Values{
"token": []string{accessToken},
}.Encode(),
}
now := time.Now()

View File

@@ -83,13 +83,14 @@ func (s TrustCenterFileService) ListForOrganizationID(
ctx context.Context,
organizationID gid.GID,
cursor *page.Cursor[coredata.TrustCenterFileOrderField],
filter *coredata.TrustCenterFileFilter,
) (*page.Page[*coredata.TrustCenterFile, coredata.TrustCenterFileOrderField], error) {
var files coredata.TrustCenterFiles
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
if err := files.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor); err != nil {
if err := files.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter); err != nil {
return fmt.Errorf("cannot load trust center files: %w", err)
}

View File

@@ -23,10 +23,16 @@ import (
type ctxKey struct{ name string }
var (
compliancePageKey = &ctxKey{name: "compliance_page"}
compliancePageKey = &ctxKey{name: "compliance_page"}
complianceMembershipKey = &ctxKey{name: "compliance_membership"}
)
func CompliancePageFromContext(ctx context.Context) *coredata.TrustCenter {
trustCenter, _ := ctx.Value(compliancePageKey).(*coredata.TrustCenter)
return trustCenter
}
func ComplianceMembershipFromContext(ctx context.Context) *coredata.TrustCenterAccess {
membership, _ := ctx.Value(complianceMembershipKey).(*coredata.TrustCenterAccess)
return membership
}

View File

@@ -16,10 +16,15 @@ package compliancepage
import (
"context"
"errors"
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/go-chi/chi/v5"
"github.com/vektah/gqlparser/v2/gqlerror"
"go.gearno.de/kit/httpserver"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/trust"
)
@@ -33,7 +38,25 @@ func NewIDMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Handl
if id, err := gid.ParseGID(value); err == nil {
compliancePage, err := trustSvc.Get(ctx, id)
if err != nil || !compliancePage.Active {
if err != nil {
if errors.Is(err, trust.ErrPageNotFound) {
next.ServeHTTP(w, r)
return
}
httpserver.RenderJSON(
w,
http.StatusInternalServerError,
&graphql.Response{
Errors: gqlerror.List{
gqlutils.Internal(ctx),
},
},
)
return
}
if !compliancePage.Active {
next.ServeHTTP(w, r)
return
}
@@ -43,7 +66,26 @@ func NewIDMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Handl
return
}
if compliancePage, err := trustSvc.GetBySlug(ctx, value); err == nil && compliancePage.Active {
compliancePage, err := trustSvc.GetBySlug(ctx, value)
if err != nil {
if errors.Is(err, trust.ErrPageNotFound) {
next.ServeHTTP(w, r)
return
}
httpserver.RenderJSON(
w,
http.StatusInternalServerError,
&graphql.Response{
Errors: gqlerror.List{
gqlutils.Internal(ctx),
},
},
)
return
}
if compliancePage.Active {
ctx = context.WithValue(ctx, compliancePageKey, compliancePage)
next.ServeHTTP(w, r.WithContext(ctx))
return

View File

@@ -0,0 +1,74 @@
// 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/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.Active {
ctx = context.WithValue(ctx, complianceMembershipKey, membership)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
next.ServeHTTP(w, r)
},
)
}
}

View File

@@ -16,8 +16,13 @@ package compliancepage
import (
"context"
"errors"
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/v2/gqlerror"
"go.gearno.de/kit/httpserver"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/trust"
)
@@ -32,13 +37,31 @@ func NewSNIMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Hand
}
compliancePage, err := trustSvc.GetByDomainName(ctx, r.TLS.ServerName)
if err != nil || !compliancePage.Active {
next.ServeHTTP(w, r)
if err != nil {
if errors.Is(err, trust.ErrPageNotFound) {
next.ServeHTTP(w, r)
return
}
httpserver.RenderJSON(
w,
http.StatusInternalServerError,
&graphql.Response{
Errors: gqlerror.List{
gqlutils.Internal(ctx),
},
},
)
return
}
ctx = context.WithValue(ctx, compliancePageKey, compliancePage)
next.ServeHTTP(w, r.WithContext(ctx))
if compliancePage.Active {
ctx = context.WithValue(ctx, compliancePageKey, compliancePage)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
next.ServeHTTP(w, r)
})
}
}

View File

@@ -6169,7 +6169,7 @@ func (r *organizationResolver) TrustCenterFiles(ctx context.Context, obj *types.
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
pageResult, err := prb.TrustCenterFiles.ListForOrganizationID(ctx, obj.ID, cursor)
pageResult, err := prb.TrustCenterFiles.ListForOrganizationID(ctx, obj.ID, cursor, &coredata.TrustCenterFileFilter{})
if err != nil {
// TODO no panic use gqlutils.InternalError
panic(fmt.Errorf("cannot list organization trust center files: %w", err))

View File

@@ -0,0 +1,63 @@
// 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 trust_v1
import (
"context"
"net/http"
"github.com/99designs/gqlgen/graphql"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/securecookie"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/compliancepage"
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/server/gqlutils/directives/session"
"go.probo.inc/probo/pkg/trust"
)
func MembersOnlyDirective(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
membership := compliancepage.ComplianceMembershipFromContext(ctx)
if membership == nil {
return nil, gqlutils.Forbiddenf(ctx, "insufficient permission to access this resource")
}
return next(ctx)
}
func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, logger *log.Logger, baseURL *baseurl.BaseURL, cookieConfig securecookie.Config) http.Handler {
config := schema.Config{
Resolvers: &Resolver{
iam: iamSvc,
trust: trustSvc,
logger: logger,
baseURL: baseURL,
sessionCookie: authn.NewCookie(&cookieConfig),
},
Directives: schema.DirectiveRoot{
Session: session.Directive,
MembersOnly: MembersOnlyDirective,
},
}
es := schema.NewExecutableSchema(config)
gqlh := gqlutils.NewHandler(es, logger)
return gqlh
}

View File

@@ -28,9 +28,6 @@ import (
"go.probo.inc/probo/pkg/securecookie"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/compliancepage"
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/server/gqlutils/directives/session"
"go.probo.inc/probo/pkg/trust"
)
@@ -85,21 +82,9 @@ func NewMux(
r.Use(compliancepage.NewCompliancePagePresenceMiddleware())
r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig))
r.Use(compliancepage.NewMembershipMiddleware(trustSvc, logger))
config := schema.Config{
Resolvers: &Resolver{
iam: iamSvc,
trust: trustSvc,
logger: logger,
sessionCookie: authn.NewCookie(&cookieConfig),
baseURL: baseURL,
},
Directives: schema.DirectiveRoot{
Session: session.Directive,
},
}
es := schema.NewExecutableSchema(config)
graphqlHandler := gqlutils.NewHandler(es, logger)
graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, logger, baseURL, cookieConfig)
r.Handle("/graphql", graphqlHandler)

View File

@@ -12,6 +12,8 @@ directive @goModel(
directive @goEnum(value: String) on ENUM_VALUE
directive @membersOnly on FIELD_DEFINITION
enum Role {
NONE
USER
@@ -495,7 +497,7 @@ type TrustCenter implements Node {
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true)
isViewerMember: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents(
@@ -610,30 +612,37 @@ type Query {
type Mutation {
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
@session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: OPTIONAL)
requestAllAccesses: RequestAccessesPayload!
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT)
exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
@session(required: NONE)
@session(required: PRESENT)
@membersOnly
exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload!
@session(required: NONE)
@session(required: PRESENT)
@membersOnly
acceptNonDisclosureAgreement: AcceptNonDisclosureAgreementPayload!
@session(required: PRESENT)
@membersOnly
requestDocumentAccess(
input: RequestDocumentAccessInput!
): RequestAccessesPayload!
): RequestAccessesPayload! @session(required: PRESENT)
requestReportAccess(input: RequestReportAccessInput!): RequestAccessesPayload!
requestReportAccess(
input: RequestReportAccessInput!
): RequestAccessesPayload! @session(required: PRESENT)
requestTrustCenterFileAccess(
input: RequestTrustCenterFileAccessInput!
): RequestAccessesPayload!
): RequestAccessesPayload! @session(required: PRESENT)
exportTrustCenterFile(
input: ExportTrustCenterFileInput!
): ExportTrustCenterFilePayload!
): ExportTrustCenterFilePayload! @session(required: PRESENT) @membersOnly
}

View File

@@ -60,7 +60,8 @@ type ResolverRoot interface {
}
type DirectiveRoot struct {
Session func(ctx context.Context, obj any, next graphql.Resolver, required session.SessionRequirement) (res any, err error)
MembersOnly func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error)
Session func(ctx context.Context, obj any, next graphql.Resolver, required session.SessionRequirement) (res any, err error)
}
type ComplexityRoot struct {
@@ -187,7 +188,7 @@ type ComplexityRoot struct {
Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
HasAcceptedNonDisclosureAgreement func(childComplexity int) int
ID func(childComplexity int) int
IsUserAuthenticated func(childComplexity int) int
IsViewerMember func(childComplexity int) int
NdaFileName func(childComplexity int) int
NdaFileURL func(childComplexity int) int
Organization func(childComplexity int) int
@@ -304,7 +305,7 @@ type ReportResolver interface {
type TrustCenterResolver interface {
NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error)
Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error)
IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, error)
IsViewerMember(ctx context.Context, obj *types.TrustCenter) (bool, error)
HasAcceptedNonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (bool, error)
Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error)
Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error)
@@ -803,12 +804,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.complexity.TrustCenter.ID(childComplexity), true
case "TrustCenter.isUserAuthenticated":
if e.complexity.TrustCenter.IsUserAuthenticated == nil {
case "TrustCenter.isViewerMember":
if e.complexity.TrustCenter.IsViewerMember == nil {
break
}
return e.complexity.TrustCenter.IsUserAuthenticated(childComplexity), true
return e.complexity.TrustCenter.IsViewerMember(childComplexity), true
case "TrustCenter.ndaFileName":
if e.complexity.TrustCenter.NdaFileName == nil {
break
@@ -1209,6 +1210,8 @@ directive @goModel(
directive @goEnum(value: String) on ENUM_VALUE
directive @membersOnly on FIELD_DEFINITION
enum Role {
NONE
USER
@@ -1692,7 +1695,7 @@ type TrustCenter implements Node {
ndaFileName: String
ndaFileUrl: String @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
isUserAuthenticated: Boolean! @goField(forceResolver: true)
isViewerMember: Boolean! @goField(forceResolver: true)
hasAcceptedNonDisclosureAgreement: Boolean! @goField(forceResolver: true)
documents(
@@ -1807,32 +1810,39 @@ type Query {
type Mutation {
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
@session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: OPTIONAL)
requestAllAccesses: RequestAccessesPayload!
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT)
exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
@session(required: NONE)
@session(required: PRESENT)
@membersOnly
exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload!
@session(required: NONE)
@session(required: PRESENT)
@membersOnly
acceptNonDisclosureAgreement: AcceptNonDisclosureAgreementPayload!
@session(required: PRESENT)
@membersOnly
requestDocumentAccess(
input: RequestDocumentAccessInput!
): RequestAccessesPayload!
): RequestAccessesPayload! @session(required: PRESENT)
requestReportAccess(input: RequestReportAccessInput!): RequestAccessesPayload!
requestReportAccess(
input: RequestReportAccessInput!
): RequestAccessesPayload! @session(required: PRESENT)
requestTrustCenterFileAccess(
input: RequestTrustCenterFileAccessInput!
): RequestAccessesPayload!
): RequestAccessesPayload! @session(required: PRESENT)
exportTrustCenterFile(
input: ExportTrustCenterFileInput!
): ExportTrustCenterFilePayload!
): ExportTrustCenterFilePayload! @session(required: PRESENT) @membersOnly
}
`, BuiltIn: false},
{Name: "../../../../gqlutils/directives/session/schema.graphql", Input: `# Session directive for GraphQL APIs
@@ -3136,7 +3146,25 @@ func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field g
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().SendMagicLink(ctx, fc.Args["input"].(types.SendMagicLinkInput))
},
nil,
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, "OPTIONAL")
if err != nil {
var zeroVal *types.SendMagicLinkPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.SendMagicLinkPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalOSendMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSendMagicLinkPayload,
true,
false,
@@ -3181,7 +3209,25 @@ func (ec *executionContext) _Mutation_verifyMagicLink(ctx context.Context, field
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().VerifyMagicLink(ctx, fc.Args["input"].(types.VerifyMagicLinkInput))
},
nil,
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, "OPTIONAL")
if err != nil {
var zeroVal *types.VerifyMagicLinkPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.VerifyMagicLinkPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalOVerifyMagicLinkPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐVerifyMagicLinkPayload,
true,
false,
@@ -3225,7 +3271,25 @@ func (ec *executionContext) _Mutation_requestAllAccesses(ctx context.Context, fi
func(ctx context.Context) (any, error) {
return ec.resolvers.Mutation().RequestAllAccesses(ctx)
},
nil,
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.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3263,7 +3327,7 @@ func (ec *executionContext) _Mutation_exportDocumentPDF(ctx context.Context, fie
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT")
if err != nil {
var zeroVal *types.ExportDocumentPDFPayload
return zeroVal, err
@@ -3274,8 +3338,15 @@ func (ec *executionContext) _Mutation_exportDocumentPDF(ctx context.Context, fie
}
return ec.directives.Session(ctx, nil, directive0, required)
}
directive2 := func(ctx context.Context) (any, error) {
if ec.directives.MembersOnly == nil {
var zeroVal *types.ExportDocumentPDFPayload
return zeroVal, errors.New("directive membersOnly is not implemented")
}
return ec.directives.MembersOnly(ctx, nil, directive1)
}
next = directive1
next = directive2
return next
},
ec.marshalNExportDocumentPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportDocumentPDFPayload,
@@ -3326,7 +3397,7 @@ func (ec *executionContext) _Mutation_exportReportPDF(ctx context.Context, field
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE")
required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT")
if err != nil {
var zeroVal *types.ExportReportPDFPayload
return zeroVal, err
@@ -3337,8 +3408,15 @@ func (ec *executionContext) _Mutation_exportReportPDF(ctx context.Context, field
}
return ec.directives.Session(ctx, nil, directive0, required)
}
directive2 := func(ctx context.Context) (any, error) {
if ec.directives.MembersOnly == nil {
var zeroVal *types.ExportReportPDFPayload
return zeroVal, errors.New("directive membersOnly is not implemented")
}
return ec.directives.MembersOnly(ctx, nil, directive1)
}
next = directive1
next = directive2
return next
},
ec.marshalNExportReportPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFPayload,
@@ -3399,8 +3477,15 @@ func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.C
}
return ec.directives.Session(ctx, nil, directive0, required)
}
directive2 := func(ctx context.Context) (any, error) {
if ec.directives.MembersOnly == nil {
var zeroVal *types.AcceptNonDisclosureAgreementPayload
return zeroVal, errors.New("directive membersOnly is not implemented")
}
return ec.directives.MembersOnly(ctx, nil, directive1)
}
next = directive1
next = directive2
return next
},
ec.marshalNAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload,
@@ -3436,7 +3521,25 @@ func (ec *executionContext) _Mutation_requestDocumentAccess(ctx context.Context,
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestDocumentAccess(ctx, fc.Args["input"].(types.RequestDocumentAccessInput))
},
nil,
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.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3481,7 +3584,25 @@ func (ec *executionContext) _Mutation_requestReportAccess(ctx context.Context, f
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestReportAccess(ctx, fc.Args["input"].(types.RequestReportAccessInput))
},
nil,
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.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3526,7 +3647,25 @@ func (ec *executionContext) _Mutation_requestTrustCenterFileAccess(ctx context.C
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RequestTrustCenterFileAccess(ctx, fc.Args["input"].(types.RequestTrustCenterFileAccessInput))
},
nil,
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.RequestAccessesPayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.RequestAccessesPayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
next = directive1
return next
},
ec.marshalNRequestAccessesPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRequestAccessesPayload,
true,
true,
@@ -3571,7 +3710,32 @@ func (ec *executionContext) _Mutation_exportTrustCenterFile(ctx context.Context,
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().ExportTrustCenterFile(ctx, fc.Args["input"].(types.ExportTrustCenterFileInput))
},
nil,
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.ExportTrustCenterFilePayload
return zeroVal, err
}
if ec.directives.Session == nil {
var zeroVal *types.ExportTrustCenterFilePayload
return zeroVal, errors.New("directive session is not implemented")
}
return ec.directives.Session(ctx, nil, directive0, required)
}
directive2 := func(ctx context.Context) (any, error) {
if ec.directives.MembersOnly == nil {
var zeroVal *types.ExportTrustCenterFilePayload
return zeroVal, errors.New("directive membersOnly is not implemented")
}
return ec.directives.MembersOnly(ctx, nil, directive1)
}
next = directive2
return next
},
ec.marshalNExportTrustCenterFilePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportTrustCenterFilePayload,
true,
true,
@@ -4045,8 +4209,8 @@ func (ec *executionContext) fieldContext_Query_currentTrustCenter(_ context.Cont
return ec.fieldContext_TrustCenter_ndaFileUrl(ctx, field)
case "organization":
return ec.fieldContext_TrustCenter_organization(ctx, field)
case "isUserAuthenticated":
return ec.fieldContext_TrustCenter_isUserAuthenticated(ctx, field)
case "isViewerMember":
return ec.fieldContext_TrustCenter_isViewerMember(ctx, field)
case "hasAcceptedNonDisclosureAgreement":
return ec.fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field)
case "documents":
@@ -4550,14 +4714,14 @@ func (ec *executionContext) fieldContext_TrustCenter_organization(_ context.Cont
return fc, nil
}
func (ec *executionContext) _TrustCenter_isUserAuthenticated(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
func (ec *executionContext) _TrustCenter_isViewerMember(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_TrustCenter_isUserAuthenticated,
ec.fieldContext_TrustCenter_isViewerMember,
func(ctx context.Context) (any, error) {
return ec.resolvers.TrustCenter().IsUserAuthenticated(ctx, obj)
return ec.resolvers.TrustCenter().IsViewerMember(ctx, obj)
},
nil,
ec.marshalNBoolean2bool,
@@ -4566,7 +4730,7 @@ func (ec *executionContext) _TrustCenter_isUserAuthenticated(ctx context.Context
)
}
func (ec *executionContext) fieldContext_TrustCenter_isUserAuthenticated(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
func (ec *executionContext) fieldContext_TrustCenter_isViewerMember(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "TrustCenter",
Field: field,
@@ -9046,7 +9210,7 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
case "isUserAuthenticated":
case "isViewerMember":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
@@ -9055,7 +9219,7 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._TrustCenter_isUserAuthenticated(ctx, field, obj)
res = ec._TrustCenter_isViewerMember(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&fs.Invalids, 1)
}

View File

@@ -177,7 +177,7 @@ type TrustCenter struct {
NdaFileName *string `json:"ndaFileName,omitempty"`
NdaFileURL *string `json:"ndaFileUrl,omitempty"`
Organization *Organization `json:"organization"`
IsUserAuthenticated bool `json:"isUserAuthenticated"`
IsViewerMember bool `json:"isViewerMember"`
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`

View File

@@ -166,11 +166,12 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa
customDomain, err := r.trust.GetCustomDomainByOrganizationID(ctx, organization.ID)
if err != nil {
var errNotFound *iam.ErrOrganizationNotFound
if !errors.As(err, &errNotFound) {
r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err))
return nil, gqlutils.Internal(ctx)
if errors.Is(err, trust.ErrCustomDomainNotFound) {
return nil, gqlutils.NotFoundf(ctx, "custom domain not found")
}
r.logger.ErrorCtx(ctx, "cannot get custom domain", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
req := &iam.SendMagicLinkRequest{
@@ -874,11 +875,11 @@ func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.Trust
return obj.Organization, nil
}
// IsUserAuthenticated is the resolver for the isUserAuthenticated field.
func (r *trustCenterResolver) IsUserAuthenticated(ctx context.Context, obj *types.TrustCenter) (bool, error) {
identity := authn.IdentityFromContext(ctx)
// IsViewerMember is the resolver for the isViewerMember field.
func (r *trustCenterResolver) IsViewerMember(ctx context.Context, obj *types.TrustCenter) (bool, error) {
membership := compliancepage.ComplianceMembershipFromContext(ctx)
return identity != nil, nil
return membership != nil, nil
}
// HasAcceptedNonDisclosureAgreement is the resolver for the hasAcceptedNonDisclosureAgreement field.
@@ -985,7 +986,13 @@ func (r *trustCenterResolver) TrustCenterFiles(ctx context.Context, obj *types.T
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
trustCenterFilePage, err := trustService.TrustCenterFiles.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
filter := coredata.NewTrustCenterFileFilter(
coredata.WithTrustCenterFileVisibilities(
coredata.TrustCenterVisibilityPublic,
coredata.TrustCenterVisibilityPrivate,
),
)
trustCenterFilePage, err := trustService.TrustCenterFiles.ListForOrganizationId(ctx, obj.Organization.ID, cursor, filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot list public trust center files", log.Error(err))
return nil, gqlutils.Internal(ctx)

23
pkg/trust/errors.go Normal file
View File

@@ -0,0 +1,23 @@
// 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 trust
import "errors"
var (
ErrCustomDomainNotFound = errors.New("custom domain not found")
ErrPageNotFound = errors.New("page not found")
ErrMembershipNotFound = errors.New("membership not found")
)

View File

@@ -16,6 +16,7 @@ package trust
import (
"context"
"errors"
"fmt"
"time"
@@ -28,6 +29,7 @@ import (
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/slack"
)
@@ -159,6 +161,9 @@ func (s *Service) Get(
func(conn pg.Conn) error {
err := trustCenter.LoadByID(ctx, conn, coredata.NewNoScope(), id)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrPageNotFound
}
return fmt.Errorf("cannot load trust center: %w", err)
}
@@ -184,6 +189,9 @@ func (s *Service) GetBySlug(
func(conn pg.Conn) error {
err := trustCenter.LoadBySlug(ctx, conn, slug)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrPageNotFound
}
return fmt.Errorf("cannot load trust center: %w", err)
}
@@ -206,16 +214,28 @@ func (s *Service) GetByDomainName(ctx context.Context, domain string) (*coredata
func(conn pg.Conn) error {
var customDomain coredata.CustomDomain
if err := customDomain.LoadByDomain(ctx, conn, coredata.NewNoScope(), s.encryptionKey, domain); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrPageNotFound
}
return fmt.Errorf("cannot load custom domain: %w", err)
}
var org coredata.Organization
if err := org.LoadByCustomDomainID(ctx, conn, coredata.NewNoScope(), customDomain.ID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrPageNotFound
}
return fmt.Errorf("cannot load organization: %w", err)
}
trustCenter = &coredata.TrustCenter{}
if err := trustCenter.LoadByOrganizationID(ctx, conn, coredata.NewNoScope(), org.ID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return ErrPageNotFound
}
return fmt.Errorf("cannot load trust center: %w", err)
}
@@ -239,6 +259,39 @@ func (s *Service) GetCustomDomainByOrganizationID(ctx context.Context, organizat
return customDomain.LoadByOrganizationID(ctx, conn, coredata.NewNoScope(), s.encryptionKey, organizationID)
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, ErrCustomDomainNotFound
}
return nil, err
}
return customDomain, err
}
func (s *Service) GetMembershipByCompliancePageIDAndEmail(ctx context.Context, compliancePageID gid.GID, email mail.Addr) (*coredata.TrustCenterAccess, error) {
membership := &coredata.TrustCenterAccess{}
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return membership.LoadByTrustCenterIDAndEmail(
ctx,
conn,
coredata.NewScopeFromObjectID(compliancePageID),
compliancePageID,
email,
)
},
)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, ErrMembershipNotFound
}
return nil, err
}
return membership, nil
}

View File

@@ -29,8 +29,6 @@ import (
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/statelesstoken"
"go.probo.inc/probo/pkg/validator"
)
@@ -445,28 +443,14 @@ func (s *TrustCenterAccessService) GrantByIDs(
}
func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Conn, access *coredata.TrustCenterAccess) error {
accessToken, err := statelesstoken.NewToken(
s.svc.trustConfig.TokenSecret,
s.svc.trustConfig.TokenType,
s.svc.trustConfig.TokenDuration,
probo.TrustCenterAccessData{
TrustCenterID: access.TrustCenterID,
Email: access.Email,
},
)
if err != nil {
return fmt.Errorf("cannot generate access token: %w", err)
}
trustCenter := &coredata.TrustCenter{}
err = trustCenter.LoadByID(ctx, tx, s.svc.scope, access.TrustCenterID)
if err != nil {
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, access.TrustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
organization := &coredata.Organization{}
err = organization.LoadByID(ctx, tx, s.svc.scope, trustCenter.OrganizationID)
if err != nil {
if err := organization.LoadByID(ctx, tx, s.svc.scope, trustCenter.OrganizationID); err != nil {
return fmt.Errorf("cannot load organization: %w", err)
}
@@ -477,7 +461,7 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
hostname := baseURLParsed.Host
scheme := baseURLParsed.Scheme
path := "/trust/" + trustCenter.Slug + "/access"
path := "/trust/" + trustCenter.Slug
if organization.CustomDomainID != nil {
customDomain, err := s.svc.Organizations.GetOrganizationCustomDomain(ctx, organization.ID)
@@ -491,16 +475,13 @@ func (s *TrustCenterAccessService) sendAccessEmail(ctx context.Context, tx pg.Co
hostname = customDomain.Domain
scheme = "https"
path = "/access"
path = ""
}
accessURL := url.URL{
Scheme: scheme,
Host: hostname,
Path: path,
RawQuery: url.Values{
"token": []string{accessToken},
}.Encode(),
}
now := time.Now()

View File

@@ -62,13 +62,14 @@ func (s *TrustCenterFileService) ListForOrganizationId(
ctx context.Context,
organizationID gid.GID,
cursor *page.Cursor[coredata.TrustCenterFileOrderField],
filter *coredata.TrustCenterFileFilter,
) (*page.Page[*coredata.TrustCenterFile, coredata.TrustCenterFileOrderField], error) {
var trustCenterFiles coredata.TrustCenterFiles
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := trustCenterFiles.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor)
err := trustCenterFiles.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
if err != nil {
return fmt.Errorf("cannot load trust center files: %w", err)
}