diff --git a/.env.example b/.env.example index 9d7a990a15..ae7c5f0f4a 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,40 @@ REACT_APP_APPID=opensign # Google tag manager container id for configuring GTM on all pages of the app REACT_APP_GTM=GTM-123N7LD +# OAuth2/OIDC SSO - set both to enable "Sign in with SSO" on login +REACT_APP_OAUTH_ISSUER=https://auth.yourdomain.com/application/o +REACT_APP_OAUTH_CLIENT_ID=your-client-id + +# Backend SSO — BFF token exchange (Confidential + PKCE) +SSO_API_URL=https://auth.yourdomain.com/application/o +SSO_USERINFO_PATH=/userinfo/ +# Must match REACT_APP_OAUTH_CLIENT_ID +SSO_OAUTH_CLIENT_ID= +SSO_OAUTH_CLIENT_SECRET= + +# Restrict SSO to internal users only. If set, user must be in one of these groups. +# 1. In oauth2 provider: Customization → Property Mappings → Create → Scope Mapping +# 2. Scope: "profile" (must match the scope we request) +# 3. Expression: return {"groups": [str(g.name) for g in request.user.ak_groups.all()]} +# 4. Assign this mapping to your OAuth2 Provider for OpenSign +SSO_ALLOWED_GROUPS=opensign-internal,employees +# userinfo claim name (default: groups) +SSO_GROUPS_CLAIM=groups +# userinfo claim for first-time SSO role mapping (Editor, OrgAdmin, default User) +SSO_ROLE_CLAIM=opensign_role + +# SDK API (service-to-service) auth via oauth2 provider client_credentials +OAUTH_INTROSPECTION_URL=https://auth.yourdomain.com/application/o/introspect/ +OAUTH_CLIENT_ID=your-api-client-id +OAUTH_CLIENT_SECRET=your-api-client-secret +# Admin user OpenSign will act as when creating sign requests. +# Must exist in OpenSign and belong to the tenant that will own these documents. +SDK_ACT_AS_ADMIN_EMAIL=admin@example.com +# SDK-created sign request email display name and Reply-To header. +# The actual From address still comes from SMTP_USER_EMAIL or MAILGUN_SENDER. +SDK_SIGN_REQUEST_FROM_NAME="Cargofort Sign" +SDK_SIGN_REQUEST_REPLY_TO=no-reply@example.com + # Backend ExpressJS config **************************************************************************************************************************************************************************************** # A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App. APP_ID=opensign @@ -24,6 +58,8 @@ MONGODB_URI=mongodb://username:password@localhost:27017 PARSE_MOUNT=/app # Set it to the URL from where APIs will be accessible to the NodeJS functions, for local development it should be localhost:3000/api/app (use your local port number instead) SERVER_URL=https://app.opensignlabs.com/api/app +# Optional: internal Parse base URL for server-to-server calls (defaults to http://localhost:8080/app) +# INTERNAL_SERVER_URL=http://localhost:8080/app # Storage config ************************************************************************************************************************************************************************************************ # Digital ocean space name or AWS S3 bucket name for uploading documents @@ -45,14 +81,26 @@ DO_REGION=us-west MAILGUN_API_KEY=XXXXX MAILGUN_DOMAIN=mail.yourdomain.com MAILGUN_SENDER=postmaster@mail.yourdomain.com + SMTP_ENABLE= SMTP_HOST= SMTP_PORT= +# SMTP auth username (for AWS SES this can be IAM SMTP credentials and not an email) +SMTP_USERNAME= +# Verified sender address shown in the From header SMTP_USER_EMAIL= SMTP_PASS= +# Optional SMTP envelope MAIL FROM override (defaults to SMTP_USER_EMAIL) +SMTP_MAIL_FROM= # Base64 encoded PFX or p12 document signing certificate file ********************************************************************************************************************* PFX_BASE64= # Provide Pass pharse of above PFX or p12 document -PASS_PHRASE= +PASS_PHRASE= + +# Webhook notifications **************************************************************************************************************************** +# URL to POST webhook events to. Both WEBHOOK_URL and WEBHOOK_SECRET must be set to enable webhooks. +WEBHOOK_URL= +# Shared secret for HMAC-SHA256 payload signing. The receiver verifies the x-webhook-signature header. +WEBHOOK_SECRET= diff --git a/.env.local_dev b/.env.local_dev index dc3d04fd21..3fe0b261a9 100644 --- a/.env.local_dev +++ b/.env.local_dev @@ -44,11 +44,17 @@ USE_LOCAL=true MAILGUN_API_KEY= MAILGUN_DOMAIN=mail.yourdomain.com MAILGUN_SENDER=postmaster@mail.yourdomain.com + SMTP_ENABLE=true SMTP_HOST=smtp.yourhost.com -SMTP_PORT=443 +SMTP_PORT=587 +# SMTP auth username (for AWS SES this can be IAM SMTP credentials and not an email) +SMTP_USERNAME=AKIA... +# Verified sender address shown in the From header SMTP_USER_EMAIL=mailer@yourdomain.com -SMTP_PASS=password # if your password includes spaces then write password in single quotes ('asdf pasd asdf bgds'). +SMTP_PASS=password +# Optional SMTP envelope MAIL FROM override (defaults to SMTP_USER_EMAIL) +SMTP_MAIL_FROM= # Base64 encoded PFX or p12 document signing certificate file ********************************************************************************************************************* @@ -110,5 +116,9 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl # Provide Pass pharse of above PFX or p12 document PASS_PHRASE=opensign +# BFF token exchange — Confidential + PKCE (must match REACT_APP_OAUTH_CLIENT_ID) +SSO_OAUTH_CLIENT_ID= +SSO_OAUTH_CLIENT_SECRET= + # (DEPRECATED) This should not be changed if provided; it should be 'opensign'. APP_ID=opensign diff --git a/.gitignore b/.gitignore index b6dcebdf3e..bb74011b71 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,9 @@ yarn-debug.log* yarn-error.log* apps/OpenSign/public/mfbuild/* microfrontends/SignDocuments/build/* -apps/OpenSignServer/files/files/* \ No newline at end of file +apps/OpenSignServer/files/files/* + + +docker-compose.swarm*.yml +.env.empty +.env.swarm.worktrees diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..2233d04481 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,157 @@ +# CLAUDE.md + +Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +--- + +**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. + +## Project Overview + +OpenSign is an open-source e-signature platform (DocuSign alternative). This is a **Cargofort fork** that adds SSO/OAuth2 support, global email branding, SDK sign requests API, and batch document processing. + +## Architecture + +**Monorepo with two independent apps** (no monorepo tooling — each app has its own `package.json`): + +- **`apps/OpenSign/`** — React 19 frontend (Vite, Redux Toolkit, Tailwind CSS, react-pdf, pdf-lib) +- **`apps/OpenSignServer/`** — Node.js backend (Express 5 + Parse Server 8.x, MongoDB) + +**Parse Server** is the core backend framework. All business logic lives in **cloud functions** registered in `apps/OpenSignServer/cloud/main.js` (73+ functions). The database schema uses Parse classes prefixed with `contracts_` (documents, users, signatures, templates) and `partners_` (tenants, global settings). + +**Docker Compose** orchestrates: server (port 8080), client (port 3000), MongoDB (port 27018), Caddy reverse proxy (ports 3001/80/443), and mailcatcher (ports 1025/1080). Caddy routes `/api/*` to the server and everything else to the client. + +**File storage**: Local filesystem by default (`USE_LOCAL=true`), or S3/DigitalOcean Spaces in production. Configured via `DO_*` env vars. + +## Development Commands + +### Container-Only Development + +Do **not** run project code, package scripts, installs, tests, linters, builds, database tools, or app servers directly on the host machine. All execution must happen inside Docker containers. + +- Use host commands only to manage Docker itself, inspect files, or edit files. +- For container execution, use `docker compose exec ...` in already-running containers. +- For one-off runs, use `docker compose run --rm ...` when a container is not running. +- Do not run local `npm`, `node`, `jasmine`, `vite`, `mongodb-runner`, or similar project tooling on the host. + +### Docker (full stack) +```bash +make build # Build and start all containers (requires HOST_URL env var) +make up # Start containers without rebuilding +make down # Stop all containers +make ssh # Shell into the server container +``` + +### Frontend (`apps/OpenSign/`) +```bash +docker compose exec client npm run dev +docker compose exec client npm run build +docker compose exec client npm test +docker compose exec client npm run test:watch +``` + +### Backend (`apps/OpenSignServer/`) +```bash +docker compose exec server npm start +docker compose exec server npm run watch +docker compose exec server npm test +docker compose exec server npm run coverage +docker compose exec server npm run lint +docker compose exec server npm run lint-fix +``` + +### Root +```bash +# Pre-commit hook runs prettier on staged .js files via lint-staged +``` + +## Environment Setup + +Copy `.env.local_dev` to `.env` for local development. Key variables: +- `REACT_APP_SERVERURL` — backend API URL (frontend) +- `REACT_APP_APPID` / `APP_ID` — must be `opensign` +- `MONGODB_URI` — MongoDB connection string +- `MASTER_KEY` — Parse Server master key +- `USE_LOCAL=true` — use local file storage instead of S3 +- `PFX_BASE64` / `PASS_PHRASE` — PDF signing certificate + +Frontend env vars must be prefixed with `REACT_APP_` (Vite config maps them from `process.env` to work with the CRA-era codebase). + +## Fork-Specific Features (SSO, Email Branding, SDK API) + +**SSO**: `apps/OpenSignServer/auth/authadapter.js` + `cloud/parsefunction/ssoLogin.js`. Configured via `REACT_APP_OAUTH_ISSUER`, `REACT_APP_OAUTH_CLIENT_ID`, `SSO_API_URL`, `SSO_USERINFO_PATH`, `SSO_ALLOWED_GROUPS`, `SSO_GROUPS_CLAIM`, `SSO_ROLE_CLAIM`. + +**Global Email Branding**: `cloud/parsefunction/getGlobalEmailBranding.js` and `updateGlobalEmailBranding.js`. Stored in `partners_GlobalSettings` Parse class. + +**SDK Sign Requests**: `cloud/parsefunction/sdkSignRequests.js`. OAuth2 client credentials flow for service-to-service document signing. + +**Batch Documents**: `cloud/parsefunction/createBatchDocs.js`. Bulk document creation. + +**Webhooks**: `utils/webhook.js` dispatches `document.signed` and `document.completed` events to `WEBHOOK_URL` with HMAC-SHA256 signing (`WEBHOOK_SECRET`). Integration points are in `cloud/parsefunction/pdf/PDF.js`. Fire-and-forget, no retries. + +## Key Patterns + +- Cloud functions are in `apps/OpenSignServer/cloud/parsefunction/` and registered in `cloud/main.js` +- Custom HTTP routes are in `apps/OpenSignServer/cloud/customRoute/` +- Database migrations are in `apps/OpenSignServer/databases/migrations/` +- Frontend pages/routes are defined in `apps/OpenSign/src/App.jsx` +- Frontend env var access uses `process.env.REACT_APP_*` (Vite translates this at build time) +- Node version requirement: 18, 20, or 22 diff --git a/Caddyfile b/Caddyfile index 1fcaba49ad..57afd392e2 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,7 +1,8 @@ {$HOST_URL} { - reverse_proxy client:3000 handle_path /api/* { reverse_proxy server:8080 - rewrite * {uri} + } + handle { + reverse_proxy client:3000 } } diff --git a/Makefile b/Makefile index 3741a2bc0b..fc768e990d 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ build: @echo "Building with HOST_URL=${HOST_URL}" - cp .env.local_dev .env - cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build - HOST_URL=${HOST_URL} docker compose up --build --force-recreate + HOST_URL=${HOST_URL} docker compose up -d --build --force-recreate -run: +up: @echo "Building with HOST_URL=${HOST_URL}" - cp .env.local_dev .env docker compose up -d + +down: + docker compose down + +ssh: + docker compose exec server bash diff --git a/README.md b/README.md index 83e6675f43..d43750bd94 100644 --- a/README.md +++ b/README.md @@ -1,179 +1,299 @@ -

-

+# OpenSign — Cargofort Fork -[The free and open source alternative to DocuSign](https://www.opensignlabs.com) +A self-hosted e-signature platform based on [OpenSign](https://www.opensignlabs.com) (open-source DocuSign alternative). This fork adds SSO/OIDC authentication, global email branding, a service-to-service SDK API, batch document processing, and webhook notifications. -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/opensignlabs/opensign.svg)](http://isitmaintained.com/project/opensignlabs/opensign "Average time to resolve an issue") -[![All Contributors](https://img.shields.io/github/all-contributors/opensignlabs/opensign?color=ee8449&style=flat-square)](#contributors) -![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/w/opensignlabs/opensign) -![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/opensignlabs/opensign) +--- +## Table of Contents -Website -   •   - Help Docs -   •   - API Docs -   •   - Blog -   •   - Discord -   •   - Twitter -   •   - LinkedIn +1. [Architecture](#architecture) +2. [Quick Start](#quick-start) +3. [Environment Variables](#environment-variables) +4. [SSO / OIDC Setup](#sso--oidc-setup) +5. [SDK Sign Requests API](#sdk-sign-requests-api) +6. [Webhooks](#webhooks) +7. [Global Email Branding](#global-email-branding) +8. [Batch Documents](#batch-documents) +9. [Development](#development) +--- -## The premier open source document signing solution(DocuSign alternative) +## Architecture ---- -
+Monorepo with two independent apps, each with its own `package.json`: + +| Service | Description | Port | +|---|---|---| +| `apps/OpenSign/` | React 19 frontend (Vite, Redux, Tailwind, react-pdf) | 3000 | +| `apps/OpenSignServer/` | Node.js backend (Express 5, Parse Server 8.x, MongoDB) | 8080 | + +**Docker Compose** orchestrates five services: -### Table of Contents +- `server` — Parse Server backend +- `client` — React frontend +- `mongo` — MongoDB (exposed on host port `27018`) +- `caddy` — Reverse proxy; routes `/api/*` → server, everything else → client (ports 3001 / 80 / 443) +- `mailcatcher` — Local SMTP trap for development (SMTP `1025`, Web UI `1080`) -1. [Introduction](#introduction) -2. [Features](#features) -3. [Installation](#installation) -4. [Usage](#usage) -5. [Contribution Guidelines](#contribution-guidelines) -6. [License](#license) -7. [Acknowledgments](#acknowledgments) +**Parse Server** is the core backend framework. All business logic lives in cloud functions registered in `apps/OpenSignServer/cloud/main.js`. The database uses Parse classes prefixed with `contracts_` (documents, users, signatures, templates) and `partners_` (tenants, global settings). + +**File storage**: Local filesystem by default (`USE_LOCAL=true`), or S3/DigitalOcean Spaces via `DO_*` env vars. --- -Please star ⭐ the repo to support us! 😀 -### Introduction +## Quick Start + +### Prerequisites + +- Docker and Docker Compose +- A `.env` file (copy from `.env.local_dev` for local dev, or `.env.example` for production reference) + +### Run + +```bash +# First time / after dependency changes +HOST_URL=https://opensign.yourdomain.com make build -Welcome to OpenSign, the premier open source docusign alternative - document e-signing solution designed to provide a secure, reliable and free alternative to commercial esign platforms like DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, HelloSign & Zoho sign. Our mission is to democratize the document signing process, making it accessible and straightforward for everyone. +# Start without rebuilding +make up + +# Stop +make down + +# Shell into server container +make ssh +``` + +`HOST_URL` is required for production — it sets `SERVER_URL` and `PUBLIC_URL` inside the containers. Omit it for local development (defaults to `https://localhost:3001`). --- -### Features - -- **Secure PDF E-Signing:** With the help of robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility. Now sign unlimited documents even on the [cloud hosted free version of OpenSign](https://www.opensignlabs.com/). -- **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that allows hand drawn signatures, uploaded images, typed signatures & saved signatures for the simplest open source document signing experience ever. -- **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "Templates", "One click signatures" and "OpenSign™ Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders. OpenSign intends to provide the best document signing experience in the open source ecosystem. -- **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite by sharing signing links & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players in e-signature space. -- **Email Unique Code(OTP) verification support for guest signers:** With OpenSign™, your documents are fully secure even when being signed by guest users. Guest signers can only sign the document after entering a unique code sent to their email address.  -- **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign. Not just this, OpenSign™ also allows signers to reject signing a document with a reason that will be promptly shared with the sender. -- **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates. Not just this, you are even allowed to customise the email templates making your free document signing invitations look the way you always wanted them to be. -- **PDF Template Creation:** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time & collect e-signatures seamlessly. -- **OpenSign™ Drive:** It is a centralised secure vault for your digital documents that makes storing, signing, organizing, sharing & archieving your docs a breeze. -- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety. -- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/v1.1) to start integrating it in your existing applications. -- **Integrations:** The open source document signing experience becomes even more seamless because of integrations with various Cloud storage systems, CRMs & enterprise platforms. We also have a Zapier integration that allows you to integrate it with virtually any application. -Login page -Dash_board -Widgets -Request Signature -Create template -Manage_Template -Folders -OpenSign_Confetti +## Environment Variables + +Copy `.env.local_dev` to `.env` to get started. Key variables: + +### Core + +| Variable | Description | +|---|---| +| `HOST_URL` | Public URL of the app (e.g. `https://opensign.yourdomain.com`). Set as a shell env var before running `make build`. | +| `APP_ID` / `REACT_APP_APPID` | Must both be `opensign` | +| `MASTER_KEY` | Parse Server master key — keep secret | +| `MONGODB_URI` | MongoDB connection string | +| `REACT_APP_SERVERURL` | Backend API URL seen by the browser (e.g. `https://opensign.yourdomain.com/api/app`) | +| `SERVER_URL` | Backend API URL used by server-side cloud functions | +| `USE_LOCAL=true` | Use local filesystem instead of S3 | +| `PFX_BASE64` / `PASS_PHRASE` | PDF signing certificate (base64-encoded PFX/P12) | + +### Email (one of the following) + +| Variable | Description | +|---|---| +| `MAILGUN_API_KEY` + `MAILGUN_DOMAIN` + `MAILGUN_SENDER` | Mailgun sending | +| `SMTP_ENABLE=true` + `SMTP_HOST` + `SMTP_PORT` + `SMTP_USERNAME` + `SMTP_USER_EMAIL` + `SMTP_PASS` | SMTP sending | +| `SMTP_MAIL_FROM` | Optional SMTP envelope override (defaults to `SMTP_USER_EMAIL`) | + +### Storage (S3 / DigitalOcean Spaces) + +| Variable | Description | +|---|---| +| `DO_SPACE` | Bucket/space name | +| `DO_ENDPOINT` | S3-compatible endpoint | +| `DO_BASEURL` | Public base URL for stored files | +| `DO_ACCESS_KEY_ID` / `DO_SECRET_ACCESS_KEY` | Credentials | +| `DO_REGION` | Region | --- -### Deploy +## SSO / OIDC Setup + +### How it works -Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL. +Users authenticate via an OAuth2/OIDC provider (e.g. Authentik, Keycloak). The frontend redirects to the provider using PKCE; the backend validates the access token against the userinfo endpoint. -#### DigitalOcean -[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0) +### Required env vars -#### Docker -The simplest way to install OpenSign on your own server is using official docker images by running the following command - +```bash +# Frontend — enables the "Sign in with SSO" button +REACT_APP_OAUTH_ISSUER=https://auth.yourdomain.com/application/o +REACT_APP_OAUTH_CLIENT_ID=your-client-id -**Command for linux/MacOS** -``` -export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate +# Backend — token validation +SSO_API_URL=https://auth.yourdomain.com/application/o +SSO_USERINFO_PATH=/userinfo/ +SSO_OAUTH_CLIENT_ID=your-client-id +SSO_OAUTH_CLIENT_SECRET=your-client-secret ``` -**Command for Windows (Powershell)** + +### Restricting access to internal users (optional) + +Set `SSO_ALLOWED_GROUPS` to a comma-separated list of group names. Users not in any of these groups will be rejected at login. + +```bash +SSO_ALLOWED_GROUPS=opensign-internal,employees +SSO_GROUPS_CLAIM=groups # userinfo claim name that contains groups (default: groups) ``` -$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate + +To populate the `groups` claim in Authentik: go to **Customization → Property Mappings → Create → Scope Mapping**, set scope to `profile`, and use the expression: + +```python +return {"groups": [str(g.name) for g in request.user.ak_groups.all()]} ``` -**Command for Windows (CMD/Terminal)** + +Assign this mapping to your OAuth2 Provider for OpenSign. + +### Role mapping (new users only) + +On first SSO signup, the user's role is read from the userinfo claim defined by `SSO_ROLE_CLAIM` (default: `opensign_role`). + +```bash +SSO_ROLE_CLAIM=opensign_role # default, can be omitted ``` -set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate + +In your OIDC provider, add a custom claim to the userinfo response: + +```json +{ + "opensign_role": "Admin" +} ``` -Make sure that you have `Docker` and `git` installed before you run this command - -Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system. +| Claim value | OpenSign role | +|---|---| +| `Admin` | `contracts_Admin` (super-admin, one per system) | +| `OrgAdmin`, `Org_Admin` | `contracts_OrgAdmin` (recommended for additional admins) | +| `Editor` | `contracts_Editor` | +| anything else / missing | `contracts_User` | + +> **Important:** Role mapping only runs at signup (first login). For existing users, set `UserRole` directly in the `contracts_Users` MongoDB collection. --- -### Usage +## SDK Sign Requests API + +Service-to-service API for programmatically creating sign requests. Uses OAuth2 client credentials (machine-to-machine) — no user session required. + +### Required env vars + +```bash +OAUTH_INTROSPECTION_URL=https://auth.yourdomain.com/application/o/introspect/ +OAUTH_CLIENT_ID=your-api-client-id +OAUTH_CLIENT_SECRET=your-api-client-secret -For comprehensive guidelines on how to use OpenSign™, please consult our [User Manual](USAGE.md). +# OpenSign admin user the SDK will act as (must exist in the DB) +SDK_ACT_AS_ADMIN_EMAIL=admin@example.com + +# Optional email display overrides +SDK_SIGN_REQUEST_FROM_NAME="Cargofort Sign" +SDK_SIGN_REQUEST_REPLY_TO=no-reply@example.com +``` + +### Authentication + +Obtain a token from your OAuth2 provider using client credentials, then pass it as a Bearer token: + +``` +Authorization: Bearer +``` + +The backend introspects the token against `OAUTH_INTROSPECTION_URL` using `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET`. + +### Cloud function + +``` +POST /1/functions/sdkSignRequests +``` + +The function accepts a document (as base64-encoded PDF), signer list with signature positions, and creates the document + sign request in OpenSign acting as `SDK_ACT_AS_ADMIN_EMAIL`. --- -### Contribution Guidelines +## Webhooks -We welcome contributions from the open-source community. For more information on how to contribute, please read our [Contribution Guidelines](CONTRIBUTING.md). +Outbound HTTP notifications for document events. Both variables must be set to enable webhooks. + +```bash +WEBHOOK_URL=https://your-service.example.com/webhooks/opensign +WEBHOOK_SECRET=your-hmac-secret +``` + +### Events + +| Event | Trigger | +|---|---| +| `document.signed` | A signer completes signing | +| `document.completed` | All signers have signed | + +### Payload + +```json +{ + "event": "document.completed", + "timestamp": "2026-05-27T16:00:00.000Z", + ...document fields +} +``` + +### Signature verification + +Every request includes an `x-webhook-signature` header — an HMAC-SHA256 of the raw JSON body signed with `WEBHOOK_SECRET`. Verify it on the receiving end to confirm the request is authentic. + +> Webhooks are fire-and-forget with no retries. Delivery failures are logged server-side. + +### Per-document callbacks + +Sign requests can include a `callbackUrl` field. If set, webhook events for that document are also sent to that URL (signed with `WEBHOOK_SECRET` if set, unsigned otherwise). --- -### License +## Global Email Branding + +Admins can configure a global email logo and colors that are applied to all outgoing emails. Settings are stored in the `partners_GlobalSettings` Parse class. -OpenSign is licensed under the AGPL-3 License. For more details, see the [LICENSE](LICENSE) file. +Cloud functions: +- `getGlobalEmailBranding` — returns current branding settings +- `updateGlobalEmailBranding` — updates settings (requires `contracts_Admin` or `contracts_OrgAdmin` role) --- -### Acknowledgments +## Batch Documents -We would like to thank all our contributors and users for their support and feedback. Special thanks to [OpenSignLabs](https://www.opensignlabs.com) for spearheading this initiative. +`createBatchDocs` cloud function — creates multiple documents in a single request. Useful for bulk onboarding or bulk signing workflows. --- -## Contributors - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Aleksandar Jakovljevic
Aleksandar Jakovljevic

💻
Priyanshu Dwivedi
Priyanshu Dwivedi

💻
Akriti Sengar
Akriti Sengar

💻
Parth Chawande
Parth Chawande

💻
Rishabh Dewangan
Rishabh Dewangan

💻
Nitin Mishra
Nitin Mishra

💻
Jobin Selvanose
Jobin Selvanose

📖
Hans Fraiponts
Hans Fraiponts

📖
Monil Prajapati
Monil Prajapati

💻
Edogbanya Emmanuel
Edogbanya Emmanuel

🐛
pranav514
pranav514

💻
Aria
Aria

💻
Soumyadipto Pal
Soumyadipto Pal

💻
Andrey Didenko
Andrey Didenko

💻
VishakhaSainani
VishakhaSainani

💻
Andrew
Andrew

💻
Rishab
Rishab

💻
Maurizio Pillitu
Maurizio Pillitu

🐛
Luis Parra
Luis Parra

️️️️♿️
Govinda Kocharekar
Govinda Kocharekar

💻
Bilal Ahmad Bhat
Bilal Ahmad Bhat

💻
Vikram
Vikram

💻
ugoconsonni
ugoconsonni

💻
Daniel Mutwiri
Daniel Mutwiri

💻
Zathiel
Zathiel

💻
1024mb
1024mb

🌍
Eugene Howe
Eugene Howe

💻
- - - - - - -This project is tested with BrowserStack. +## Development + +> All project commands must run inside Docker containers. Do not run `npm`, `node`, or other project tooling directly on the host. + +### Frontend + +```bash +docker compose exec client npm run dev +docker compose exec client npm run build +docker compose exec client npm test +``` + +### Backend + +```bash +docker compose exec server npm start # production +docker compose exec server npm run watch # dev with hot reload +docker compose exec server npm test +docker compose exec server npm run lint-fix +``` + +### Useful paths + +| Path | Description | +|---|---| +| `apps/OpenSignServer/cloud/main.js` | Cloud function registry (73+ functions) | +| `apps/OpenSignServer/cloud/parsefunction/` | Individual cloud function implementations | +| `apps/OpenSignServer/cloud/customRoute/` | Custom HTTP routes | +| `apps/OpenSignServer/databases/migrations/` | DB migrations | +| `apps/OpenSign/src/App.jsx` | Frontend routes | +| `apps/OpenSignServer/auth/authadapter.js` | SSO Parse auth adapter | + +--- +Based on [OpenSign](https://www.opensignlabs.com) by OpenSignLabs, licensed under AGPL-3. diff --git a/apps/OpenSign/entrypoint.sh b/apps/OpenSign/entrypoint.sh index 91553527a1..0059dce361 100644 --- a/apps/OpenSign/entrypoint.sh +++ b/apps/OpenSign/entrypoint.sh @@ -8,7 +8,7 @@ echo "Generating runtime env file at $ENV_FILE..." echo "window.RUNTIME_ENV = {" > $ENV_FILE # List of keys to include -RUNTIME_KEYS="REACT_APP_SERVERURL" +RUNTIME_KEYS="REACT_APP_SERVERURL REACT_APP_OAUTH_ISSUER REACT_APP_OAUTH_CLIENT_ID" for key in $RUNTIME_KEYS; do # First check docker env (-e), fallback to .env file diff --git a/apps/OpenSign/src/App.jsx b/apps/OpenSign/src/App.jsx index e3a18583bd..4064089036 100644 --- a/apps/OpenSign/src/App.jsx +++ b/apps/OpenSign/src/App.jsx @@ -34,6 +34,7 @@ const UpdateExistUserAdmin = lazyWithRetry( ); const Preferences = lazyWithRetry(() => import("./pages/Preferences")); const Login = lazyWithRetry(() => import("./pages/Login")); +const AuthCallback = lazyWithRetry(() => import("./pages/AuthCallback")); const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument")); const EmailBuilder = lazyWithRetry(() => import("./pages/EmailBuilder")); @@ -67,6 +68,7 @@ function App() { }> } /> + } /> } /> { } setresponse(arr.length); }); + } else if (props.Data.Redirect_id === "1MwEuxLEkF") { + // Out for signatures: route through getReport so OrgAdmin gets org-wide count + const params = { + reportId: props.Data.Redirect_id, + skip: 0, + limit: 200 + }; + const reportUrl = `${parseBaseUrl}functions/getReport`; + await axios + .post(reportUrl, params, { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": parseAppId, + sessiontoken: localStorage.getItem("accesstoken") + } + }) + .then((res) => { + setresponse(res.data?.result?.length ?? 0); + }); } else { await axios.get(url, { headers: headers }).then((res) => { if (res?.data?.[props.Data.key]) { diff --git a/apps/OpenSign/src/components/preferences/MailTemplateEditor.jsx b/apps/OpenSign/src/components/preferences/MailTemplateEditor.jsx index fd8ea5a6e1..a6510013e4 100644 --- a/apps/OpenSign/src/components/preferences/MailTemplateEditor.jsx +++ b/apps/OpenSign/src/components/preferences/MailTemplateEditor.jsx @@ -33,6 +33,13 @@ const MailTemplateEditor = ({ request: false, completion: false }); + const [brandingConfig, setBrandingConfig] = useState({ + logoUrl: "", + primaryColor: "#47a3ad", + footerText: "", + wrapperHtml: "" + }); + const [isBrandingLoader, setIsBrandingLoader] = useState(false); const [isalert, setIsAlert] = useState({ type: "success", msg: "" }); const [editorType, setEditorType] = useState({ request: "basic", @@ -47,6 +54,7 @@ const MailTemplateEditor = ({ useEffect(() => { fetchSubscription(); + fetchGlobalBrandingConfig(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ info @@ -61,6 +69,28 @@ const MailTemplateEditor = ({ await tenantEmailTemplate(info); }; + const fetchGlobalBrandingConfig = withSessionValidation(async () => { + setIsBrandingLoader(true); + try { + const brandingRes = await Parse.Cloud.run("getglobalemailbranding"); + if (brandingRes) { + const parsed = JSON.parse(JSON.stringify(brandingRes)); + setBrandingConfig({ + logoUrl: parsed?.logoUrl || "", + primaryColor: parsed?.primaryColor || "#47a3ad", + footerText: parsed?.footerText || "", + wrapperHtml: parsed?.wrapperHtml || "" + }); + } + } catch (err) { + console.error("Error while fetching global email branding: ", err); + setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); + setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500); + } finally { + setIsBrandingLoader(false); + } + }); + const tenantEmailTemplate = async (tenantRes) => { if (tenantRes === "user does not exist!") { alert(t("user-not-exist")); @@ -309,6 +339,54 @@ const MailTemplateEditor = ({ setEditorType((p) => ({ ...p, [flow]: editor })); }; + const handleBrandingInput = (field, value) => { + setBrandingConfig((prev) => ({ ...prev, [field]: value })); + }; + + const applyBrandingPreview = () => { + const wrapper = brandingConfig?.wrapperHtml || ""; + if (!wrapper) { + return ""; + } + return wrapper + .replaceAll("__APP_NAME__", appName) + .replaceAll("__LOGO_URL__", brandingConfig?.logoUrl || "") + .replaceAll("__PRIMARY_COLOR__", brandingConfig?.primaryColor || "#47a3ad") + .replaceAll("__HEADER_TEXT__", brandingConfig?.headerText || "Digital Signature Request") + .replaceAll( + "__FOOTER_TEXT__", + brandingConfig?.footerText || `This is an automated email from ${appName}.` + ) + .replaceAll( + "__EMAIL_BODY__", + "

Hello, this is a preview of your branded email wrapper.

The request/completion content will be injected here.

" + ); + }; + + const handleSaveBrandingConfig = withSessionValidation(async (e) => { + e.preventDefault(); + setIsBrandingLoader(true); + try { + const updateRes = await Parse.Cloud.run("updateglobalemailbranding", { + details: { + logoUrl: brandingConfig?.logoUrl || "", + primaryColor: brandingConfig?.primaryColor || "#47a3ad", + footerText: brandingConfig?.footerText || "", + wrapperHtml: brandingConfig?.wrapperHtml || "" + } + }); + if (updateRes) { + setIsAlert({ type: "success", msg: t("saved-successfully") }); + setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500); + } + } catch (err) { + console.error("Error while saving global email branding: ", err); + setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); + setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500); + } finally { + setIsBrandingLoader(false); + } + }); return ( <> {isalert.msg && {isalert.msg}} @@ -488,6 +566,98 @@ const MailTemplateEditor = ({ +

+ Global Email Branding +

+
+ {isBrandingLoader && ( +
+ +
+ )} +
+

+ Applies globally to request and completion emails. +

+
+
+ + handleBrandingInput("logoUrl", e.target.value)} + className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" + placeholder="https://example.com/logo.png" + /> +
+
+ +
+ handleBrandingInput("primaryColor", e.target.value)} + className="op-input op-input-bordered op-input-sm h-[34px] w-[52px] p-1" + /> + handleBrandingInput("primaryColor", e.target.value)} + className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" + placeholder="#47a3ad" + /> +
+
+
+
+ +