Add electronic signature
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/esign"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/authz"
|
||||
@@ -25,12 +26,13 @@ import (
|
||||
"go.probo.inc/probo/pkg/server/gqlutils"
|
||||
)
|
||||
|
||||
func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, customDomainCname string, logger *log.Logger) http.Handler {
|
||||
func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, esignSvc *esign.Service, customDomainCname string, logger *log.Logger) http.Handler {
|
||||
config := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
authorize: authz.NewAuthorizeFunc(iamSvc, logger),
|
||||
probo: proboSvc,
|
||||
iam: iamSvc,
|
||||
esign: esignSvc,
|
||||
customDomainCname: customDomainCname,
|
||||
logger: logger,
|
||||
},
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/baseurl"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/esign"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
@@ -47,6 +48,7 @@ type (
|
||||
authorize authz.AuthorizeFunc
|
||||
probo *probo.Service
|
||||
iam *iam.Service
|
||||
esign *esign.Service
|
||||
logger *log.Logger
|
||||
customDomainCname string
|
||||
}
|
||||
@@ -56,6 +58,7 @@ func NewMux(
|
||||
logger *log.Logger,
|
||||
proboSvc *probo.Service,
|
||||
iamSvc *iam.Service,
|
||||
esignSvc *esign.Service,
|
||||
cookieConfig securecookie.Config,
|
||||
tokenSecret string,
|
||||
connectorRegistry *connector.ConnectorRegistry,
|
||||
@@ -66,7 +69,7 @@ func NewMux(
|
||||
|
||||
safeRedirect := &saferedirect.SafeRedirect{AllowedHost: baseURL.Host()}
|
||||
|
||||
graphqlHandler := NewGraphQLHandler(iamSvc, proboSvc, customDomainCname, logger)
|
||||
graphqlHandler := NewGraphQLHandler(iamSvc, proboSvc, esignSvc, customDomainCname, logger)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig))
|
||||
|
||||
@@ -2714,6 +2714,7 @@ type TrustCenterAccess implements Node {
|
||||
name: String!
|
||||
state: TrustCenterAccessState!
|
||||
hasAcceptedNonDisclosureAgreement: Boolean!
|
||||
ndaSignature: ElectronicSignature @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
lastTokenExpiresAt: Datetime
|
||||
@@ -5387,3 +5388,149 @@ type CreateCustomDomainPayload {
|
||||
type DeleteCustomDomainPayload {
|
||||
deletedCustomDomainId: ID!
|
||||
}
|
||||
|
||||
# Electronic Signature
|
||||
|
||||
enum ElectronicSignatureStatus
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatus"
|
||||
) {
|
||||
PENDING
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusPending"
|
||||
)
|
||||
ACCEPTED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusAccepted"
|
||||
)
|
||||
PROCESSING
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusProcessing"
|
||||
)
|
||||
COMPLETED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusCompleted"
|
||||
)
|
||||
FAILED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureStatusFailed"
|
||||
)
|
||||
}
|
||||
|
||||
enum ElectronicSignatureDocumentType
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentType"
|
||||
) {
|
||||
NDA
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeNDA"
|
||||
)
|
||||
DPA
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeDPA"
|
||||
)
|
||||
MSA
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeMSA"
|
||||
)
|
||||
SOW
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSOW"
|
||||
)
|
||||
SLA
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeSLA"
|
||||
)
|
||||
TOS
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeTOS"
|
||||
)
|
||||
PRIVACY_POLICY
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypePrivacyPolicy"
|
||||
)
|
||||
OTHER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureDocumentTypeOther"
|
||||
)
|
||||
}
|
||||
|
||||
enum ElectronicSignatureEventType
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventType"
|
||||
) {
|
||||
DOCUMENT_VIEWED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeDocumentViewed"
|
||||
)
|
||||
CONSENT_GIVEN
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeConsentGiven"
|
||||
)
|
||||
FULL_NAME_TYPED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeFullNameTyped"
|
||||
)
|
||||
SIGNATURE_ACCEPTED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureAccepted"
|
||||
)
|
||||
SIGNATURE_COMPLETED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSignatureCompleted"
|
||||
)
|
||||
SEAL_COMPUTED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeSealComputed"
|
||||
)
|
||||
TIMESTAMP_REQUESTED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeTimestampRequested"
|
||||
)
|
||||
CERTIFICATE_GENERATED
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeCertificateGenerated"
|
||||
)
|
||||
PROCESSING_ERROR
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventTypeProcessingError"
|
||||
)
|
||||
}
|
||||
|
||||
enum ElectronicSignatureEventSource
|
||||
@goModel(
|
||||
model: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSource"
|
||||
) {
|
||||
CLIENT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceClient"
|
||||
)
|
||||
SERVER
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.ElectronicSignatureEventSourceServer"
|
||||
)
|
||||
}
|
||||
|
||||
type ElectronicSignature implements Node {
|
||||
id: ID!
|
||||
status: ElectronicSignatureStatus!
|
||||
documentType: ElectronicSignatureDocumentType!
|
||||
consentText: String!
|
||||
lastError: String
|
||||
signedAt: Datetime
|
||||
certificateFileUrl: String @goField(forceResolver: true)
|
||||
events: [ElectronicSignatureEvent!]! @goField(forceResolver: true)
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type ElectronicSignatureEvent {
|
||||
id: ID!
|
||||
eventType: ElectronicSignatureEventType!
|
||||
eventSource: ElectronicSignatureEventSource!
|
||||
actorEmail: String!
|
||||
actorIpAddress: String!
|
||||
actorUserAgent: String!
|
||||
occurredAt: Datetime!
|
||||
createdAt: Datetime!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
45
pkg/server/api/console/v1/types/electronic_signature.go
Normal file
45
pkg/server/api/console/v1/types/electronic_signature.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewElectronicSignature(es *coredata.ElectronicSignature) *ElectronicSignature {
|
||||
return &ElectronicSignature{
|
||||
ID: es.ID,
|
||||
Status: es.Status,
|
||||
DocumentType: es.DocumentType,
|
||||
ConsentText: es.ConsentText,
|
||||
LastError: es.LastError,
|
||||
SignedAt: es.SignedAt,
|
||||
CreatedAt: es.CreatedAt,
|
||||
UpdatedAt: es.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewElectronicSignatureEvent(ev *coredata.ElectronicSignatureEvent) *ElectronicSignatureEvent {
|
||||
return &ElectronicSignatureEvent{
|
||||
ID: ev.ID,
|
||||
EventType: ev.EventType,
|
||||
EventSource: ev.EventSource,
|
||||
ActorEmail: ev.ActorEmail,
|
||||
ActorIPAddress: ev.ActorIPAddress,
|
||||
ActorUserAgent: ev.ActorUserAgent,
|
||||
OccurredAt: ev.OccurredAt,
|
||||
CreatedAt: ev.CreatedAt,
|
||||
}
|
||||
}
|
||||
@@ -1206,6 +1206,33 @@ type DocumentVersionSignatureOrder struct {
|
||||
Direction page.OrderDirection `json:"direction"`
|
||||
}
|
||||
|
||||
type ElectronicSignature struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Status coredata.ElectronicSignatureStatus `json:"status"`
|
||||
DocumentType coredata.ElectronicSignatureDocumentType `json:"documentType"`
|
||||
ConsentText string `json:"consentText"`
|
||||
LastError *string `json:"lastError,omitempty"`
|
||||
SignedAt *time.Time `json:"signedAt,omitempty"`
|
||||
CertificateFileURL *string `json:"certificateFileUrl,omitempty"`
|
||||
Events []*ElectronicSignatureEvent `json:"events"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (ElectronicSignature) IsNode() {}
|
||||
func (this ElectronicSignature) GetID() gid.GID { return this.ID }
|
||||
|
||||
type ElectronicSignatureEvent struct {
|
||||
ID gid.GID `json:"id"`
|
||||
EventType coredata.ElectronicSignatureEventType `json:"eventType"`
|
||||
EventSource coredata.ElectronicSignatureEventSource `json:"eventSource"`
|
||||
ActorEmail string `json:"actorEmail"`
|
||||
ActorIPAddress string `json:"actorIpAddress"`
|
||||
ActorUserAgent string `json:"actorUserAgent"`
|
||||
OccurredAt time.Time `json:"occurredAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type Evidence struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Size int `json:"size"`
|
||||
@@ -1847,6 +1874,7 @@ type TrustCenterAccess struct {
|
||||
Name string `json:"name"`
|
||||
State coredata.TrustCenterAccessState `json:"state"`
|
||||
HasAcceptedNonDisclosureAgreement bool `json:"hasAcceptedNonDisclosureAgreement"`
|
||||
NdaSignature *ElectronicSignature `json:"ndaSignature,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
LastTokenExpiresAt *time.Time `json:"lastTokenExpiresAt,omitempty"`
|
||||
|
||||
@@ -1241,6 +1241,40 @@ func (r *documentVersionSignatureConnectionResolver) TotalCount(ctx context.Cont
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// CertificateFileURL is the resolver for the certificateFileUrl field.
|
||||
func (r *electronicSignatureResolver) CertificateFileURL(ctx context.Context, obj *types.ElectronicSignature) (*string, error) {
|
||||
sig, err := r.esign.LoadSignatureByID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load signature: %w", err)
|
||||
}
|
||||
|
||||
if sig.CertificateFileID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
url, err := r.esign.GenerateCertificateFileURL(ctx, *sig.CertificateFileID, 1*time.Hour)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate certificate file URL: %w", err)
|
||||
}
|
||||
|
||||
return &url, nil
|
||||
}
|
||||
|
||||
// Events is the resolver for the events field.
|
||||
func (r *electronicSignatureResolver) Events(ctx context.Context, obj *types.ElectronicSignature) ([]*types.ElectronicSignatureEvent, error) {
|
||||
events, err := r.esign.LoadEventsBySignatureID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load signature events: %w", err)
|
||||
}
|
||||
|
||||
result := make([]*types.ElectronicSignatureEvent, len(events))
|
||||
for i := range events {
|
||||
result[i] = types.NewElectronicSignatureEvent(events[i])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// File is the resolver for the file field.
|
||||
func (r *evidenceResolver) File(ctx context.Context, obj *types.Evidence) (*types.File, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionFileGet); err != nil {
|
||||
@@ -7785,6 +7819,42 @@ func (r *trustCenterResolver) Permission(ctx context.Context, obj *types.TrustCe
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// NdaSignature is the resolver for the ndaSignature field.
|
||||
func (r *trustCenterAccessResolver) NdaSignature(ctx context.Context, obj *types.TrustCenterAccess) (*types.ElectronicSignature, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
access, err := prb.TrustCenterAccesses.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load trust center access: %w", err)
|
||||
}
|
||||
|
||||
trustCenter, _, err := prb.TrustCenters.Get(ctx, access.TrustCenterID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load trust center: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.NonDisclosureAgreementFileID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sig, err := r.esign.LoadSignatureByOrgEmailAndDocType(
|
||||
ctx,
|
||||
access.OrganizationID,
|
||||
string(access.Email),
|
||||
coredata.ElectronicSignatureDocumentTypeNDA,
|
||||
*trustCenter.NonDisclosureAgreementFileID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil // No signature row — pre-existing access
|
||||
}
|
||||
|
||||
return types.NewElectronicSignature(sig), nil
|
||||
}
|
||||
|
||||
// PendingRequestCount is the resolver for the pendingRequestCount field.
|
||||
func (r *trustCenterAccessResolver) PendingRequestCount(ctx context.Context, obj *types.TrustCenterAccess) (int, error) {
|
||||
if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterAccessGet); err != nil {
|
||||
@@ -8784,6 +8854,11 @@ func (r *Resolver) DocumentVersionSignatureConnection() schema.DocumentVersionSi
|
||||
return &documentVersionSignatureConnectionResolver{r}
|
||||
}
|
||||
|
||||
// ElectronicSignature returns schema.ElectronicSignatureResolver implementation.
|
||||
func (r *Resolver) ElectronicSignature() schema.ElectronicSignatureResolver {
|
||||
return &electronicSignatureResolver{r}
|
||||
}
|
||||
|
||||
// Evidence returns schema.EvidenceResolver implementation.
|
||||
func (r *Resolver) Evidence() schema.EvidenceResolver { return &evidenceResolver{r} }
|
||||
|
||||
@@ -9029,6 +9104,7 @@ type documentVersionResolver struct{ *Resolver }
|
||||
type documentVersionConnectionResolver struct{ *Resolver }
|
||||
type documentVersionSignatureResolver struct{ *Resolver }
|
||||
type documentVersionSignatureConnectionResolver struct{ *Resolver }
|
||||
type electronicSignatureResolver struct{ *Resolver }
|
||||
type evidenceResolver struct{ *Resolver }
|
||||
type evidenceConnectionResolver struct{ *Resolver }
|
||||
type fileResolver struct{ *Resolver }
|
||||
|
||||
Reference in New Issue
Block a user