Harden compliance portal auth and TLS

Align console references and OAuth branding with the
compliance-page model, and fix certificate cache eviction,
portal OAuth handlers, and magic-link edge cases left after
the trust-center rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-07-20 09:59:25 +02:00
parent b03acbd029
commit 43ce3a7c53
51 changed files with 626 additions and 458 deletions

View File

@@ -16,6 +16,7 @@ import (
"go.probo.inc/probo/pkg/server/api/complianceportal"
"go.probo.inc/probo/pkg/server/api/complianceportal/v1/types"
"go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/validator"
)
// UpdateFullName is the resolver for the updateFullName field.
@@ -25,43 +26,66 @@ func (r *mutationResolver) UpdateFullName(ctx context.Context, input types.Updat
return nil, gqlutils.Unauthenticatedf(ctx, "authentication is required to request access")
}
identity, err := r.iam.AccountService.UpdateIdentity(
ctx,
identity.ID,
&iam.UpdateIdentityRequest{
FullName: input.FullName,
},
)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
compliancePage := complianceportal.CompliancePageFromContext(ctx)
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, compliancePage.OrganizationID)
if err != nil {
// External trust-center visitors have no organization profile; updating
// the identity's full name above is all that is needed for them.
if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); ok {
return &types.UpdateFullNamePayload{Success: true}, nil
// External trust-center visitors have no organization profile; only
// their identity needs updating.
if _, ok := errors.AsType[*iam.ErrProfileNotFound](err); !ok {
r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
profile = nil
}
if profile.Source == coredata.ProfileSourceManual {
if _, err := r.iam.OrganizationService.UpdateUser(ctx, &iam.UpdateUserRequest{
// The identity and profile full names are validated by different rules.
// Validate the profile update up front so it cannot fail after the
// identity has already been mutated, keeping the two in sync.
var updateUserRequest *iam.UpdateUserRequest
if profile != nil && profile.Source == coredata.ProfileSourceManual {
updateUserRequest = &iam.UpdateUserRequest{
ID: profile.ID,
FullName: identity.FullName,
FullName: input.FullName,
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
Kind: profile.Kind,
Position: profile.Position,
ContractStartDate: &profile.ContractStartDate,
ContractEndDate: &profile.ContractEndDate,
}); err != nil {
}
if err := updateUserRequest.Validate(); err != nil {
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}
r.logger.ErrorCtx(ctx, "cannot validate profile update", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
}
if _, err := r.iam.AccountService.UpdateIdentity(
ctx,
identity.ID,
&iam.UpdateIdentityRequest{
FullName: input.FullName,
},
); err != nil {
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}
r.logger.ErrorCtx(ctx, "cannot update identity", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if updateUserRequest != nil {
if _, err := r.iam.OrganizationService.UpdateUser(ctx, updateUserRequest); err != nil {
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
return nil, gqlutils.Internal(ctx)
}