Electronic Signature System (eSign)
FaceKom’s electronic signature subsystem. Two services work in tandem: esign_css (customer-facing frontend) and esign_oss (backend with business logic, PDF signing, and admin UI). Enables legally binding document signing with 2FA verification.
Architecture
graph TB
Customer[Customer Browser] -->|HTTPS :30180| esign_css
Operator[Operator Browser] -->|HTTPS :20180| esign_oss
subgraph esign_css ["esign_css (Frontend)"]
WebCSS[Express :10183]
SocketCSS[Socket.IO :10184]
end
subgraph esign_oss ["esign_oss (Backend)"]
WebOSS[Admin UI :10181]
ExtAPI[External API :10182]
SocketOSS[Socket.IO :10180]
end
esign_css <-->|RabbitMQ RPC<br/>8 queues| esign_oss
esign_oss -->|RabbitMQ RPC| pdfservice[["[[pdfservice]]<br/>PDF Signing"]]
esign_oss --> PostgreSQL[(PostgreSQL)]
esign_oss --> Redis[(Redis)]
esign_oss -->|SMTP| Email[Email Service]
esign_oss -->|SmsApi.com| SMS[SMS Service]
esign_oss -->|FCM| Push[Push Notifications]
Service Overview
Property esign_css esign_oss Role Customer-facing signing UI Backend, admin, business logic Version 1.3.0 1.3.0 Node.js >= 20.8.1 >= 22.13.0 Ports :10183 (web), :10184 (socket) :10181 (admin), :10182 (ext API), :10180 (socket) Database None (all via RPC) PostgreSQL (27 models) Build Browserify, Stylus, Dust.js + Twig Browserify, Stylus, Dust.js + Twig
esign_css — Frontend
Key Files
File Role server.jsEntry point: queue, services, web (:10183), socket (:10184) config.jsConfig loader (getconfig + Spring Cloud Config) server/web/routes.jsHTTP routes: customer APIs, signature flow, offer signing UI server/web/web-server.jsExpress setup with Redis sessions, Helmet, CSP server/service/SessionService.jsJWT-based session management server/service/SignatureService.jsSignature operations proxied to esign_oss via RPC server/service/CustomerService.jsCustomer auth/login proxied to esign_oss via RPC server/socket/socket-server.jsSocket.IO real-time updates client/features/signature/App.jsClient-side signature flow (2FA auth, refuse) client/features/signature/BrowserSignature.jsBrowser-based signature implementation
RPC Clients (talks to esign_oss)
Client Purpose App App registration/management JwtAuth JWT token validation Customer Customer auth, login, 2FA Timestamp Timestamp operations Document Document CRUD Contract Contract management Offer Offer operations Signature Signature creation/verification SignatureCheck Signature check service
RPC Servers (receives from esign_oss)
Ping — Health check
SystemOss — System configuration push
Queue Servers
ServiceBus — General service bus messages
Customer — Customer updates
Contract — Contract updates
esign_oss — Backend
Key Files
File Role server.jsEntry point: DB, queue, web (:10181), external API (:10182), socket (:10180) server/db/models.jsSequelize models: 27 entities with relationships server/service/SignatureService.jsPDF signing via Java binary, signature verification server/service/CryptoService.jsAES-256-GCM encryption for cert passwords server/gcm.jsFull AES-256-GCM implementation (encrypt/decrypt/streams) server/service/TrustedTimestampService.jsRFC 3161 trusted timestamps via Hungarian e-Szigno server/external/routes.jsExternal webhook API server/web/routes.jsAdmin UI routes with ACL-based access control cron.jsCron jobs (offer timeout, document deletion, URL expiry)
Three Concurrent Servers
Port Service Purpose :10181 Admin UI Operator web interface with ACL protection :10182 External API Webhook endpoints for external system integration :10180 Socket.IO Real-time updates to operator UI
RPC Servers (handles from esign_css)
Queue Purpose esign:rpc-appApp registration/management esign:rpc-customerCustomer auth, login, 2FA esign:rpc-timestampTimestamp operations esign:rpc-documentDocument CRUD esign:rpc-contractContract management esign:rpc-offerOffer operations esign:rpc-signatureSignature creation/verification esign:rpc-signature-checkSignature check service
RPC Clients (calls esign_css)
esign:css-ping — Health check
esign:cron-ping — Cron health check
esign:rpc-system-oss — System configuration push
Database Schema (27 Models)
erDiagram
Customer ||--o{ Contract : has
Customer ||--o{ Offer : has
Customer ||--o{ Activity : has
Customer ||--o{ CustomerData : has
Customer ||--o{ VerificationToken : has
Customer ||--o{ App : has
Contract ||--o{ Document : has
Offer ||--o{ Signature : has
Signature ||--o{ SignatureData : has
Signature ||--o| SignatureValidation : has
App ||--o| Device : has
App ||--o| FcmInfo : has
Supporting models: User, AuditLog, EmailLog, SmsLog, Setting, Shortener, ClientErrorLog, Cert, BackgroundProcess, BackgroundProcessLog, TimestampToken, Attachment, Session.
External Webhook API (port 10182)
Each endpoint can be independently enabled/disabled and protected with HTTP Basic Auth.
Endpoint Purpose POST /offer/uploadUpload new offer POST /offer/revokeRevoke offer POST /contract/uploadUpload signed contract POST /signature/verifyVerify signature POST /customer/bootstrapBootstrap customer POST /customer/send-association-tokenSend device association token POST /customer/approveApprove customer POST /app/notificationSend push notification
PDF Signing Flow
The SignatureService.signPdf() method:
Takes a customer-signed PDF, certificate (.p12), and password
Creates temp files for the PDF and certificate
Writes a JSON config file with signature parameters and TSA credentials
Executes either:
Pre-4.0 : java -jar /opt/facekom/pdf/FaceKomPDFSigner.jar <config>
4.0+ : /opt/facekom/pdf/bin/facekom_pdf_signer <config>
TSA credentials passed via environment variables
Reads the signed PDF from disk, cleans up temp files
Also supports PAdES hash-based signing via signPdfPadesHash() using FaceKomPDFClientSigner.jar.
Crypto Implementation (AES-256-GCM)
Property Value Algorithm AES-256-GCM Key derivation PBKDF2 (5000 iterations, SHA-256) Key encoding Base62 (32 bytes) IV 12 bytes random MAC 16 bytes Operations Buffer, streaming, and string encrypt/decrypt
5000 PBKDF2 iterations is well below the modern recommendation of 600,000+ for SHA-256.
Cron Jobs
Job Schedule Purpose OfferTimeoutCronJob Weekly (Monday 8am) Revokes expired offers DeleteDocumentsCronJob Daily (6am) Deletes old documents DeleteExpiredURLCronJob Daily (3am) Cleans up expired short URLs
End-to-End Signing Flow
sequenceDiagram
participant Op as Operator
participant EOSS as esign_oss
participant ECSS as esign_css
participant Cust as Customer
participant PDF as pdfservice
Op->>EOSS: Upload offer (admin UI or webhook)
EOSS->>Cust: Notification (SMS/email/push)
Cust->>ECSS: Open esign_css, authenticate
ECSS->>EOSS: RPC: fetch documents
EOSS-->>ECSS: Return documents
ECSS-->>Cust: Display documents for review
Cust->>ECSS: Initiate signing (M1: send public key)
ECSS->>EOSS: RPC: create Signature + send 2FA SMS
EOSS-->>Cust: SMS with verification token
Cust->>ECSS: Provide username + password + SMS token
ECSS->>EOSS: RPC: 2FA auth + M2 (signed data)
EOSS->>EOSS: Validate signature cryptographically
EOSS->>PDF: RPC: sign PDF with bank certificate
PDF->>PDF: Add PAdES signature + trusted timestamp
PDF-->>EOSS: Return signed PDF
EOSS->>EOSS: Store signed contract
EOSS-->>Op: Notify operator (Socket.IO)
Op->>EOSS: Verify signature (optional)
EOSS->>PDF: RPC: validate signature
Customization System
customization/customizations.js — Hook-based system using event emitter
customization/branding-options.json — Visual branding
customization/translations/offer.trans.js — Translation overrides
Dependencies
Package Version Purpose @techteamer/mq^7.0.1 RabbitMQ client Express 5.2.1 HTTP framework Socket.IO 4.6.1 Real-time communication node-jose- JWE/JWS crypto csurf- CSRF protection connect-redis- Session storage jQuery 4.0 Client-side (esign_css)
TODOs
Location Issue esign_css/server/service/AppService.js:5”oss sends notification” — not implemented esign_css/server/web/web-server.js:112”expires false?” on cookie config esign_css/server/web/routes.js:69”provide estimate location” (always ‘N/A’) esign_css/server/web/api/customer/login.js:11Handle duplicate login sessions esign_css/server/web/api/pre-check.js:3”implement user-agent check login” (always returns compatible)
Mobile App REST API (esign_oss)
App Lifecycle
Endpoint Method Auth Purpose /api/app/registerPOST None Register device (IMEI, OS, FCM token) → returns sessionToken /api/app/associatePOST Bearer Associate device with SMS token /api/app/remove-accountPOST Bearer Remove account from device /api/app/update-refresh-tokenPOST Bearer Update FCM push token
Customer
Endpoint Method Purpose /api/customer/check-usernamePOST Validate username (too_short/too_long/unavailable) /api/customer/check-passwordPOST Validate password (too_short/too_long/pattern_mismatch) /api/customer/createPOST Create customer account /api/customer/loginPOST Login with username+password /api/customer/2f-authPOST 2FA auth (signatureId + username + password + smsToken)
Signature
Endpoint Method Purpose /api/signature/m1POST M1 message: publicKey + signedData + ts1 + offerId /api/signature/m2POST M2 message: ts3Data + signedTs3 + ts3
Documents & Contracts
Endpoint Method Purpose /api/document/{id}GET Download document (octet-stream) /api/document/{id}/mark-as-readPOST Mark document as read /api/contract/{id}GET Get contract with documents list /api/contract/{id}/acknowledgeGET Acknowledge contract /api/contractsGET List all contracts /api/offer/{id}GET Get offer with documents list /api/offer/{id}/refusePOST Refuse offer /api/offersGET List all offers /api/timestamp/POST Request trusted timestamp (digest + hashAlgorithm + dataSize)
Cryptographic Signing Protocol (M1/M2)
Customer Onboarding Flow
App registers device → receives session token
Customer enters SMS verification token to associate device
Customer creates username/password
Operator approves registration → push notification sent
Customer gains access to offers/contracts
M1 Message (Key Registration)
rawData = ByteArray([publicKey, userName])
signedData = sign(rawData, privateKey) // RSA-2048
ts1 = getTimestamp(ByteArray([signedData, rawData]))
→ Send: { publicKey, signedData, ts1, offerId }
Server stores M1 values, generates 2FA SMS token.
M2 Message (Signature with 2FA)
offerHashes = offers.map(pdf → hash(pdf))
ts2Data = [publicKey, offerHashes, username, smsToken, deviceInfo, photo]
ts2 = getTimestamp(ts2Data)
customerSignature = [publicKey, offerHashes, username, smsToken, deviceInfo, photo, ts2]
customerSignatureHash = hash(customerSignature)
signedTs3 = sign(ByteArray([customerSignatureHash, customerSignature]), privateKey)
ts3 = getTimestamp(ByteArray([customerSignatureHash, customerSignature, signedTs3]))
→ Send: { version, offers, publicKey, userName, smsToken, deviceInfo, photo,
customerSignatureHash, ts3Data, signedTs3Data, ts2, ts3 }
Server Verification (Phase 4)
Verify SIGNATURE1 (M1) using stored public key
Verify SIGNATURE2 (M2) using stored public key
Validate offer hashes match server-stored offers
Validate publicKey, username, smsToken match stored values
Re-calculate customerSignatureHash
Verify timestamp ordering: TS1 < TS2 < TS3
Verify TS3 - TS1 < 10 minutes
Post-Signing (Phase 5)
Server generates signed PDF with customer signature hash
Bank signs the PDF (SIGNED_CONTRACT)
Server timestamps the contract
Customer receives push notification, downloads signed contract
App acknowledges download to server