diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 60b385403..3471f4f88 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -10,7 +10,13 @@
"ghcr.io/devcontainers/features/node:1": {}
},
"onCreateCommand": "./.devcontainer/on-create.sh",
- "forwardPorts": [3000, 54320, 9000, 2500, 1100],
+ "forwardPorts": [
+ 3000,
+ 54320,
+ 9000,
+ 2500,
+ 1100
+ ],
"customizations": {
"vscode": {
"extensions": [
@@ -25,8 +31,8 @@
"GitHub.copilot",
"GitHub.vscode-pull-request-github",
"Prisma.prisma",
- "VisualStudioExptTeam.vscodeintellicode",
+ "VisualStudioExptTeam.vscodeintellicode"
]
}
}
-}
+}
\ No newline at end of file
diff --git a/.env.example b/.env.example
index 9250ab9cf..c482c128e 100644
--- a/.env.example
+++ b/.env.example
@@ -25,7 +25,7 @@ NEXT_PRIVATE_DIRECT_DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/
# [[E2E Tests]]
E2E_TEST_AUTHENTICATE_USERNAME="Test User"
E2E_TEST_AUTHENTICATE_USER_EMAIL="testuser@mail.com"
-E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_password"
+E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_Password123"
# [[STORAGE]]
# OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3
@@ -74,6 +74,8 @@ NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN=
NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR=
# OPTIONAL: The private key to use for DKIM signing.
NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY=
+# OPTIONAL: Displays the maximum document upload limit to the user in MBs
+NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=5
# [[STRIPE]]
NEXT_PRIVATE_STRIPE_API_KEY=
diff --git a/.github/actions/cache-build/action.yml b/.github/actions/cache-build/action.yml
new file mode 100644
index 000000000..e1eb4da22
--- /dev/null
+++ b/.github/actions/cache-build/action.yml
@@ -0,0 +1,24 @@
+name: Cache production build binaries
+description: 'Cache or restore if necessary'
+inputs:
+ node_version:
+ required: false
+ default: v18.x
+runs:
+ using: 'composite'
+ steps:
+ - name: Cache production build
+ uses: actions/cache@v3
+ id: production-build-cache
+ with:
+ path: |
+ ${{ github.workspace }}/apps/web/.next
+ ${{ github.workspace }}/apps/marketing/.next
+ **/.turbo/**
+ **/dist/**
+
+ key: prod-build-${{ github.run_id }}
+ restore-keys: prod-build-
+
+ - run: npm run build
+ shell: bash
diff --git a/.github/actions/node-install/action.yml b/.github/actions/node-install/action.yml
new file mode 100644
index 000000000..77483a9a4
--- /dev/null
+++ b/.github/actions/node-install/action.yml
@@ -0,0 +1,39 @@
+name: 'Setup node and cache node_modules'
+inputs:
+ node_version:
+ required: false
+ default: v18.x
+
+runs:
+ using: 'composite'
+ steps:
+ - name: Set up Node ${{ inputs.node_version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ inputs.node_version }}
+
+ - name: Cache npm
+ uses: actions/cache@v3
+ with:
+ path: ~/.npm
+ key: npm-${{ hashFiles('package-lock.json') }}
+ restore-keys: npm-
+
+ - name: Cache node_modules
+ uses: actions/cache@v3
+ id: cache-node-modules
+ with:
+ path: |
+ node_modules
+ packages/*/node_modules
+ apps/*/node_modules
+ key: modules-${{ hashFiles('package-lock.json') }}
+
+ - name: Install dependencies
+ if: steps.cache-node-modules.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ npm ci --no-audit
+ npm run prisma:generate
+ env:
+ HUSKY: '0'
diff --git a/.github/actions/playwright-install/action.yml b/.github/actions/playwright-install/action.yml
new file mode 100644
index 000000000..27d0e66b4
--- /dev/null
+++ b/.github/actions/playwright-install/action.yml
@@ -0,0 +1,19 @@
+name: Install playwright binaries
+description: 'Install playwright, cache and restore if necessary'
+runs:
+ using: 'composite'
+ steps:
+ - name: Cache playwright
+ id: cache-playwright
+ uses: actions/cache@v3
+ with:
+ path: |
+ ~/.cache/ms-playwright
+ ${{ github.workspace }}/node_modules/playwright
+ key: playwright-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: playwright-
+
+ - name: Install playwright
+ if: steps.cache-playwright.outputs.cache-hit != 'true'
+ run: npx playwright install --with-deps
+ shell: bash
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index deda53ff0..bebca8e85 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,6 +1,7 @@
name: 'Continuous Integration'
on:
+ workflow_call:
push:
branches: ['main']
pull_request:
@@ -10,9 +11,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
-env:
- HUSKY: 0
-
jobs:
build_app:
name: Build App
@@ -23,20 +21,12 @@ jobs:
with:
fetch-depth: 2
- - name: Install Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 18
- cache: npm
-
- - name: Install dependencies
- run: npm ci
+ - uses: ./.github/actions/node-install
- name: Copy env
run: cp .env.example .env
- - name: Build
- run: npm run build
+ - uses: ./.github/actions/cache-build
build_docker:
name: Build Docker Image
@@ -47,5 +37,31 @@ jobs:
with:
fetch-depth: 2
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Cache Docker layers
+ uses: actions/cache@v3
+ with:
+ path: /tmp/.buildx-cache
+ key: ${{ runner.os }}-buildx-${{ github.sha }}
+ restore-keys: |
+ ${{ runner.os }}-buildx-
+
- name: Build Docker Image
- run: ./docker/build.sh
+ uses: docker/build-push-action@v5
+ with:
+ push: false
+ context: .
+ file: ./docker/Dockerfile
+ tags: documenso-${{ github.sha }}
+ cache-from: type=local,src=/tmp/.buildx-cache
+ cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
+
+ - # Temp fix
+ # https://github.com/docker/build-push-action/issues/252
+ # https://github.com/moby/buildkit/issues/1896
+ name: Move cache
+ run: |
+ rm -rf /tmp/.buildx-cache
+ mv /tmp/.buildx-cache-new /tmp/.buildx-cache
diff --git a/.github/workflows/clean-cache.yml b/.github/workflows/clean-cache.yml
new file mode 100644
index 000000000..2cb13f661
--- /dev/null
+++ b/.github/workflows/clean-cache.yml
@@ -0,0 +1,29 @@
+name: cleanup caches by a branch
+on:
+ pull_request:
+ types:
+ - closed
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cleanup
+ run: |
+ gh extension install actions/gh-actions-cache
+
+ echo "Fetching list of cache key"
+ cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
+
+ ## Setting this to not fail the workflow while deleting cache keys.
+ set +e
+ echo "Deleting caches..."
+ for cacheKey in $cacheKeysForPR
+ do
+ gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
+ done
+ echo "Done"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 465041c0a..314dc7b7b 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -25,19 +25,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 18
- cache: npm
-
- - name: Install Dependencies
- run: npm ci
-
- name: Copy env
run: cp .env.example .env
- - name: Build Documenso
- run: npm run build
+ - uses: ./.github/actions/node-install
+
+ - uses: ./.github/actions/cache-build
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index 7b05458d9..12a7d9521 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -6,29 +6,21 @@ on:
branches: ['main']
jobs:
e2e_tests:
- name: "E2E Tests"
+ name: 'E2E Tests'
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 18
- cache: npm
- - name: Install dependencies
- run: npm ci
- name: Copy env
run: cp .env.example .env
+ - uses: ./.github/actions/node-install
+
- name: Start Services
run: npm run dx:up
- - name: Install Playwright Browsers
- run: npx playwright install --with-deps
-
- - name: Generate Prisma Client
- run: npm run prisma:generate -w @documenso/prisma
+ - uses: ./.github/actions/playwright-install
- name: Create the database
run: npm run prisma:migrate-dev
@@ -36,6 +28,8 @@ jobs:
- name: Seed the database
run: npm run prisma:seed
+ - uses: ./.github/actions/cache-build
+
- name: Run Playwright tests
run: npm run ci
@@ -43,7 +37,7 @@ jobs:
if: always()
with:
name: test-results
- path: "packages/app-tests/**/test-results/*"
+ path: 'packages/app-tests/**/test-results/*'
retention-days: 30
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 000000000..f69ddb57b
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,133 @@
+name: Publish Docker
+
+on:
+ push:
+ branches: ['release']
+
+jobs:
+ build_and_publish_platform_containers:
+ name: Build and publish platform containers
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - warp-ubuntu-latest-x64-4x
+ - warp-ubuntu-latest-arm64-4x
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-tags: true
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GH_TOKEN }}
+
+ - name: Build the docker image
+ env:
+ BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
+ run: |
+ APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
+ GIT_SHA="$(git rev-parse HEAD)"
+
+ docker build \
+ -f ./docker/Dockerfile \
+ --progress=plain \
+ -t "documenso/documenso-$BUILD_PLATFORM:latest" \
+ -t "documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \
+ -t "documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \
+ -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:latest" \
+ -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$GIT_SHA" \
+ -t "ghcr.io/documenso/documenso-$BUILD_PLATFORM:$APP_VERSION" \
+ .
+
+ - name: Push the docker image to DockerHub
+ run: docker push --all-tags "documenso/documenso-$BUILD_PLATFORM"
+ env:
+ BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
+
+ - name: Push the docker image to GitHub Container Registry
+ run: docker push --all-tags "ghcr.io/documenso/documenso-$BUILD_PLATFORM"
+ env:
+ BUILD_PLATFORM: ${{ matrix.os == 'warp-ubuntu-latest-arm64-4x' && 'arm64' || 'amd64' }}
+
+ create_and_publish_manifest:
+ name: Create and publish manifest
+ runs-on: ubuntu-latest
+ needs: build_and_publish_platform_containers
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-tags: true
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GH_TOKEN }}
+
+ - name: Create and push DockerHub manifest
+ run: |
+ APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
+ GIT_SHA="$(git rev-parse HEAD)"
+
+ docker manifest create \
+ documenso/documenso:latest \
+ --amend documenso/documenso-amd64:latest \
+ --amend documenso/documenso-arm64:latest \
+
+ docker manifest create \
+ documenso/documenso:$GIT_SHA \
+ --amend documenso/documenso-amd64:$GIT_SHA \
+ --amend documenso/documenso-arm64:$GIT_SHA \
+
+ docker manifest create \
+ documenso/documenso:$APP_VERSION \
+ --amend documenso/documenso-amd64:$APP_VERSION \
+ --amend documenso/documenso-arm64:$APP_VERSION \
+
+ docker manifest push documenso/documenso:latest
+ docker manifest push documenso/documenso:$GIT_SHA
+ docker manifest push documenso/documenso:$APP_VERSION
+
+ - name: Create and push Github Container Registry manifest
+ run: |
+ APP_VERSION="$(git name-rev --tags --name-only $(git rev-parse HEAD) | head -n 1 | sed 's/\^0//')"
+ GIT_SHA="$(git rev-parse HEAD)"
+
+ docker manifest create \
+ ghcr.io/documenso/documenso:latest \
+ --amend ghcr.io/documenso/documenso-amd64:latest \
+ --amend ghcr.io/documenso/documenso-arm64:latest \
+
+ docker manifest create \
+ ghcr.io/documenso/documenso:$GIT_SHA \
+ --amend ghcr.io/documenso/documenso-amd64:$GIT_SHA \
+ --amend ghcr.io/documenso/documenso-arm64:$GIT_SHA \
+
+ docker manifest create \
+ ghcr.io/documenso/documenso:$APP_VERSION \
+ --amend ghcr.io/documenso/documenso-amd64:$APP_VERSION \
+ --amend ghcr.io/documenso/documenso-arm64:$APP_VERSION \
+
+ docker manifest push ghcr.io/documenso/documenso:latest
+ docker manifest push ghcr.io/documenso/documenso:$GIT_SHA
+ docker manifest push ghcr.io/documenso/documenso:$APP_VERSION
diff --git a/.husky/pre-commit b/.husky/pre-commit
index d24fdfc60..3d805e3cf 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,16 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
+SCRIPT_DIR="$(readlink -f "$(dirname "$0")")"
+MONOREPO_ROOT="$(readlink -f "$SCRIPT_DIR/../")"
+
+echo "Copying pdf.js"
+npm run copy:pdfjs --workspace apps/**
+
+echo "Copying .well-known/ contents"
+node "$MONOREPO_ROOT/scripts/copy-wellknown.cjs"
+
+git add "$MONOREPO_ROOT/apps/web/public/"
+git add "$MONOREPO_ROOT/apps/marketing/public/"
+
npx lint-staged
diff --git a/.well-known/security.txt b/.well-known/security.txt
new file mode 100644
index 000000000..1a3f685e5
--- /dev/null
+++ b/.well-known/security.txt
@@ -0,0 +1,7 @@
+# General Issues
+Contact: https://github.com/documenso/documenso/issues/new?assignees=&labels=bug&projects=&template=bug-report.yml
+
+# Report critical issues privately to let us take appropriate action before publishing.
+Contact: mailto:security@documenso.com
+Preferred-Languages: en
+Canonical: https://documenso.com/.well-known/security.txt
\ No newline at end of file
diff --git a/README.md b/README.md
index 6d2fab334..93c6d9f95 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
->We are nominated for a Product Hunt Gold Kitty ๐บโจ and appreciate any support: https://documen.so/kitty
-
{Object.defineProperty(t,"__esModule",{value:!0});t.Parser=t.Linearization=t.Lexer=void 0;var r=a(2),i=a(4),n=a(3),s=a(17),o=a(19),c=a(20),l=a(22),h=a(23),u=a(26),d=a(29),f=a(31),g=a(8),p=a(32),m=a(33);class Parser{constructor({lexer:e,xref:t,allowStreams:a=!1,recoveryMode:r=!1}){this.lexer=e;this.xref=t;this.allowStreams=a;this.recoveryMode=r;this.imageCache=Object.create(null);this._imageId=0;this.refill()}refill(){this.buf1=this.lexer.getObj();this.buf2=this.lexer.getObj()}shift(){if(this.buf2 instanceof i.Cmd&&"ID"===this.buf2.cmd){this.buf1=this.buf2;this.buf2=null}else{this.buf1=this.buf2;this.buf2=this.lexer.getObj()}}tryShift(){try{this.shift();return!0}catch(e){if(e instanceof n.MissingDataException)throw e;return!1}}getObj(e=null){const t=this.buf1;this.shift();if(t instanceof i.Cmd)switch(t.cmd){case"BI":return this.makeInlineImage(e);case"[":const a=[];for(;!(0,i.isCmd)(this.buf1,"]")&&this.buf1!==i.EOF;)a.push(this.getObj(e));if(this.buf1===i.EOF){if(this.recoveryMode)return a;throw new n.ParserEOFException("End of file inside array.")}this.shift();return a;case"<<":const s=new i.Dict(this.xref);for(;!(0,i.isCmd)(this.buf1,">>")&&this.buf1!==i.EOF;){if(!(this.buf1 instanceof i.Name)){(0,r.info)("Malformed dictionary: key must be a name object");this.shift();continue}const t=this.buf1.name;this.shift();if(this.buf1===i.EOF)break;s.set(t,this.getObj(e))}if(this.buf1===i.EOF){if(this.recoveryMode)return s;throw new n.ParserEOFException("End of file inside dictionary.")}if((0,i.isCmd)(this.buf2,"stream"))return this.allowStreams?this.makeStream(s,e):s;this.shift();return s;default:return t}if(Number.isInteger(t)){if(Number.isInteger(this.buf1)&&(0,i.isCmd)(this.buf2,"R")){const e=i.Ref.get(t,this.buf1);this.shift();this.shift();return e}return t}return"string"==typeof t&&e?e.decryptString(t):t}findDefaultInlineStreamEnd(e){const t=this.lexer,a=e.pos;let s,o,c=0;for(;-1!==(s=e.getByte());)if(0===c)c=69===s?1:0;else if(1===c)c=73===s?2:0;else if(32===s||10===s||13===s){o=e.pos;const a=e.peekBytes(10);for(let e=0,t=a.length;e>15&1;this.clow=this.clow<<1&65535;this.ct--}while(0==(32768&c));this.a=c;e[t]=r<<1|i;return o}}},(e,t,a)=>{Object.defineProperty(t,"__esModule",{value:!0});t.JpegStream=void 0;var r=a(18),i=a(4),n=a(27),s=a(2);class JpegStream extends r.DecodeStream{constructor(e,t,a){let r;for(;-1!==(r=e.getByte());)if(255===r){e.skip(-1);break}super(t);this.stream=e;this.dict=e.dict;this.maybeLength=t;this.params=a}get bytes(){return(0,s.shadow)(this,"bytes",this.stream.getBytes(this.maybeLength))}ensureBuffer(e){}readBlock(){if(this.eof)return;const e={decodeTransform:void 0,colorTransform:void 0},t=this.dict.getArray("D","Decode");if((this.forceRGBA||this.forceRGB)&&Array.isArray(t)){const a=this.dict.get("BPC","BitsPerComponent")||8,r=t.length,i=new Int32Array(r);let n=!1;const s=(1<{Object.defineProperty(t,"__esModule",{value:!0});t.JpegImage=void 0;var r=a(2),i=a(28),n=a(3);class JpegError extends r.BaseException{constructor(e){super(`JPEG error: ${e}`,"JpegError")}}class DNLMarkerError extends r.BaseException{constructor(e,t){super(e,"DNLMarkerError");this.scanLines=t}}class EOIMarkerError extends r.BaseException{constructor(e){super(e,"EOIMarkerError")}}const s=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),o=4017,c=799,l=3406,h=2276,u=1567,d=3784,f=5793,g=2896;function buildHuffmanTable(e,t){let a,r,i=0,n=16;for(;n>0&&!e[n-1];)n--;const s=[{children:[],index:0}];let o,c=s[0];for(a=0;a>4==0)for(m=0;m<64;m++){x=s[m];a[x]=e[o++]}else{if(t>>4!=1)throw new JpegError("DQT - invalid table spec");for(m=0;m<64;m++){x=s[m];a[x]=(0,n.readUint16)(e,o);o+=2}}u[15&t]=a}break;case 65472:case 65473:case 65474:if(a)throw new JpegError("Only single frame JPEGs supported");o+=2;a={};a.extended=65473===g;a.progressive=65474===g;a.precision=e[o++];const C=(0,n.readUint16)(e,o);o+=2;a.scanLines=t||C;a.samplesPerLine=(0,n.readUint16)(e,o);o+=2;a.components=[];a.componentIds={};const k=e[o++];let v=0,F=0;for(p=0;p0?Math.min(r.xcb,i.PPx-1):Math.min(r.xcb,i.PPx);i.ycb_=a>0?Math.min(r.ycb,i.PPy-1):Math.min(r.ycb,i.PPy);return i}function buildPrecincts(e,t,a){const r=1<0,o=t+10){c=a-i;s&&(r[c-1]+=16);o&&(r[c+1]+=16);r[c]+=4}if(e+1=0;t--){d[t]=o[a];a=l[a]}}else d[f++]=d[0]}if(i){l[s]=u;c[s]=c[u]+1;o[s]=d[0];s++;h=s+n&s+n-1?h:0|Math.min(Math.log(s+n)/.6931471805599453+1,12)}u=e;g+=f;if(r