Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3bc372c
♻️(collaboration) switch collaboration server from hocuspocus to yhub
dmonad Aug 3, 2026
c6a0695
📄(collaboration) add license notice for yhub-server directory
dmonad Aug 4, 2026
ffc6627
✨(backend) add a service generating cached RS256 JWT tokens
lunika Aug 4, 2026
bcad65a
✨(backend) publish the JWT public key on a JWKS endpoint
lunika Aug 4, 2026
80cf998
✨(backend) add a method to create a dedicated admin token
lunika Aug 4, 2026
d2b4c00
🔧(dev) generate the JWT signing key when bootstrapping the dev stack
lunika Aug 4, 2026
1b49a4f
🔥(ci) remove checking print statement in lint-git
lunika Aug 4, 2026
9030c3f
🔥(backend) remove `CollaborationService` and `can-edit` endpoint
lunika Aug 4, 2026
0414067
✨(collaboration) add admin reset-connections endpoint on yhub 0.4.0
dmonad Aug 5, 2026
a24ca5a
🛂(django) use jwt token for converter services
AntoLC Aug 4, 2026
4632ba8
🛂(y-provider) verify jwt token instead of the shared api key
AntoLC Aug 4, 2026
0fb5d69
🔥(helm) remove occurences of Y_PROVIDER_API_KEY
AntoLC Aug 4, 2026
7ca5ffa
🛂(backend) add audience to jwt
AntoLC Aug 5, 2026
64b46de
✨(collaboration) add create-ydoc endpoint on yhub
dmonad Aug 5, 2026
123e8de
🔒️(collaboration) harden the create-ydoc endpoint
dmonad Aug 5, 2026
10eb434
🔥(frontend) remove "can-edit" mechanism
AntoLC Aug 5, 2026
ebacc97
🔥(project) remove occurences of COLLABORATION_SERVER_SECRET
AntoLC Aug 5, 2026
f575b42
🔥(frontend) remove content GET PATCH
AntoLC Aug 5, 2026
c57d5a1
🙈(dev) ignore playwright-mcp browser artifacts
dmonad Aug 5, 2026
2426375
✨(collaboration) soft-migrate legacy S3 documents into yhub
dmonad Aug 5, 2026
c0151a3
📝(changelog) note that get-connections is dropped, not deferred
dmonad Aug 6, 2026
a21b8fa
🔒️(collaboration) reject admin jwts not issued for the yhub audience
dmonad Aug 6, 2026
a9c4f6c
✨(collaboration) replay legacy s3 version history into yhub
dmonad Aug 7, 2026
38013cb
✅(collaboration) test the legacy migrations against a real yhub
dmonad Aug 10, 2026
d423fe2
⬆️(yhub) upgrade yhub to version 0.6.0
lunika Aug 12, 2026
bde1225
♻️(yhub) maintain database schema using npm run init-db
lunika Aug 12, 2026
ecfdd14
🔧(collaboration) adapt docker stack for development purpose
lunika Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/impress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ jobs:
fetch-depth: 0
- name: show
run: git log
- name: Enforce absence of print statements in code
if: always()
run: |
! git diff origin/${{ github.event.pull_request.base.ref }}..HEAD -- src/backend ':(exclude)**/impress.yml' | grep "print("
- name: Check absence of fixup commits
if: always()
run: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ env.d/terraform
compose.override.yml
docker/auth/*.local

# yhub server local install
src/yhub-server/node_modules/

# npm
node_modules

Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to

### Added

- ✨(backend) add a service generating cached RS256 JWT tokens
- ✨(backend) publish the JWT public key on a JWKS endpoint
- 🔧(dev) generate the JWT signing key when bootstrapping the dev stack
- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486

Expand All @@ -17,7 +20,17 @@ and this project adheres to

### Changed

- 💥(backend) move the resource server JWKS from `/api/{version}/jwks` to
`/external_api/{version}/jwks`
- ♿️(frontend) use semantic `<dl>` structure in document info card #2379
- ♻️(collaboration) migrate the collaboration server from hocuspocus to yhub:
the dev stack gains dedicated valkey and postgres services for yhub, and
the kick (reset-connections) and get-connections APIs have no yhub
equivalent yet — they are deferred with TODO(yhub) stubs
- 💥(y-provider) the published `lasuite/impress-y-provider` image becomes
converter-only and no longer serves `/collaboration/ws/`; deployments using
the existing helm values lose collaboration until the helm chart routes
collaboration to yhub (follow-up)

## [v5.4.1] - 2026-07-09

Expand Down
24 changes: 18 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ data/media:
data/static:
@mkdir -p data/static

# RSA key signing the JWT tokens the backend issues. Generated locally, never
# committed: "data/" is gitignored. Regenerate it by deleting the file.
data/jwt/private.pem:
@bin/generate-jwt-private-key.sh

# -- Project

create-env-local-files: ## create env.local files in env.d/development
Expand All @@ -81,7 +86,8 @@ create-env-local-files:
.PHONY: create-env-local-files

generate-secret-keys:
generate-secret-keys: ## generate secret keys to be stored in common.local
generate-secret-keys: ## generate the secret keys needed by the dev stack
generate-secret-keys: data/jwt/private.pem
@bin/generate-oidc-store-refresh-token-key.sh
.PHONY: generate-secret-keys

Expand Down Expand Up @@ -190,6 +196,7 @@ bootstrap-e2e: \
build: cache ?=
build: ## build the project containers
@$(MAKE) build-backend cache=$(cache)
@$(MAKE) build-yhub cache=$(cache)
@$(MAKE) build-yjs-provider cache=$(cache)
@$(MAKE) build-frontend cache=$(cache)
.PHONY: build
Expand All @@ -199,9 +206,14 @@ build-backend: ## build the app-dev container
@$(COMPOSE) build app-dev $(cache)
.PHONY: build-backend

build-yhub: cache ?=
build-yhub: ## build the yhub collaboration server container
@$(COMPOSE) build yhub $(cache)
.PHONY: build-yhub

build-yjs-provider: cache ?=
build-yjs-provider: ## build the y-provider container
@$(COMPOSE) build y-provider-development $(cache)
@$(COMPOSE) build y-provider-development-converter $(cache)
.PHONY: build-yjs-provider

build-frontend: cache ?=
Expand All @@ -212,8 +224,9 @@ build-frontend: ## build the frontend container
build-e2e: cache ?=
build-e2e: ## build the e2e container
@$(MAKE) build-backend cache=$(cache)
@$(MAKE) build-yhub cache=$(cache)
@$(COMPOSE_E2E) build frontend $(cache)
@$(COMPOSE_E2E) build y-provider $(cache)
@$(COMPOSE_E2E) build y-provider-converter $(cache)
.PHONY: build-e2e

nginx-frontend: ## build the nginx-frontend container
Expand All @@ -230,10 +243,11 @@ logs: ## display app-dev logs (follow mode)

run-backend: ## Start only the backend application and all needed services
@$(MAKE) create-docker-network
@$(MAKE) data/jwt/private.pem
@$(COMPOSE) up --force-recreate -d docspec
@$(COMPOSE) up --force-recreate -d celery-dev
@$(COMPOSE) up --force-recreate -d y-provider-development
@$(COMPOSE) up --force-recreate -d y-provider-development-converter
@$(COMPOSE) up --force-recreate -d yhub
@$(COMPOSE) up --force-recreate -d nginx
.PHONY: run-backend

Expand All @@ -246,9 +260,7 @@ run:
run-e2e: ## start the e2e server
run-e2e:
@$(MAKE) run-backend
@$(COMPOSE_E2E) stop y-provider-development
@$(COMPOSE_E2E) up --force-recreate -d frontend
@$(COMPOSE_E2E) up --force-recreate -d y-provider
@$(COMPOSE_E2E) up --force-recreate -d y-provider-converter
.PHONY: run-e2e

Expand Down
7 changes: 7 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ the following command inside your docker container:

## [Unreleased]

- The JWKS of the resource server moved from `/api/{version}/jwks` to
`/external_api/{version}/jwks`, alongside the rest of the resource server
endpoints. `/api/{version}/jwks` now publishes the public key validating the
tokens Docs issues to call external services. If you enabled the resource
server (`OIDC_RESOURCE_SERVER_ENABLED`), update the JWKS URI declared to your
OIDC provider accordingly.

### [5.0.0] - 2026-04-30

We made several changes around document content management leading to several breaking changes in the API.
Expand Down
4 changes: 4 additions & 0 deletions bin/_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ function _set_user() {
# options: docker compose command options
# ARGS : docker compose command arguments
function _docker_compose() {
# The backend settings point at this key and the containers mount it, so it
# has to exist before any of them starts.
"${REPO_DIR}/bin/generate-jwt-private-key.sh"

# Set DOCKER_USER for Windows compatibility with MinIO
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || -n "${WSL_DISTRO_NAME:-}" ]]; then
export DOCKER_USER="0:0"
Expand Down
23 changes: 23 additions & 0 deletions bin/generate-jwt-private-key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash

# Generate the RSA private key signing the JWT tokens issued by the backend.
#
# Development only. The key is generated locally and never committed: it lands
# in "data/", which is gitignored. The dev stack mounts it in the backend
# containers, where JWT_PRIVATE_KEY_FILE points at it.
#
# Idempotent: an existing key is kept. Delete the file to roll the key.

set -eo pipefail

REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
KEY_PATH="${REPO_DIR}/data/jwt/private.pem"

if [ -f "${KEY_PATH}" ]; then
exit 0
fi

mkdir -p "$(dirname "${KEY_PATH}")"
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "${KEY_PATH}" 2>/dev/null
chmod 600 "${KEY_PATH}"
Comment on lines +11 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Sensitive Data Exposure (CWE-732): Incorrect Permission Assignment for Critical Resource

Reachability: Internal

Create the private key with restrictive permissions.

If a shared development host uses a permissive umask and another user can traverse data/jwt, line 21 creates the private key before line 22 restricts it. A local attacker can read the key during that interval and mint JWTs accepted by yhub. Set umask 077 before creating the directory and key.

Proposed fix
 set -eo pipefail
+umask 077
 
 REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
#!/usr/bin/env bash
set -euo pipefail

tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT

(
  umask 0022
  openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
    -out "${tmp_dir}/private.pem" 2>/dev/null
  stat -c 'created mode: %a' "${tmp_dir}/private.pem"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/generate-jwt-private-key.sh` around lines 11 - 22, Set a restrictive
umask before the mkdir and openssl commands in the key-generation flow, ensuring
the directory and private key are created inaccessible to other users; retain
the existing chmod 600 safeguard and idempotent KEY_PATH check.

echo "✓ JWT private key generated in ${KEY_PATH}"
15 changes: 1 addition & 14 deletions compose-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
ports:
- "3000:3000"

y-provider:
y-provider-converter:
user: ${DOCKER_USER:-1000}
build:
context: .
Expand All @@ -24,16 +24,3 @@ services:
env_file:
- env.d/development/common
- env.d/development/common.local
ports:
- "4444:4444"

y-provider-converter:
user: ${DOCKER_USER:-1000}
image: impress:y-provider-production
restart: unless-stopped
env_file:
- env.d/development/common
- env.d/development/common.local
depends_on:
y-provider:
condition: service_started
69 changes: 57 additions & 12 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- ./data/jwt:/data/jwt:ro
- /app/.venv
depends_on:
postgresql:
Expand Down Expand Up @@ -111,6 +112,7 @@ services:
volumes:
- ./src/backend:/app
- ./data/static:/data/static
- ./data/jwt:/data/jwt:ro
- /app/.venv
depends_on:
- app-dev
Expand Down Expand Up @@ -181,7 +183,7 @@ services:
volumes:
- ".:/app"

y-provider-development:
y-provider-development-converter:
user: ${DOCKER_USER:-1000}
build:
context: .
Expand All @@ -192,27 +194,66 @@ services:
env_file:
- env.d/development/common
- env.d/development/common.local
ports:
- "4444:4444"
volumes:
- ./src/frontend/:/home/frontend
- /home/frontend/node_modules
- /home/frontend/servers/y-provider/node_modules

y-provider-development-converter:
yhub-valkey:
image: valkey/valkey:alpine
# volatile-lru per yhub DEPLOYMENT.md; AOF because valkey is the authoritative store
# for updates the worker hasn't persisted yet (up to taskDebounce+minMessageLifetime)
command: ["valkey-server", "--maxmemory-policy", "volatile-lru",
"--appendonly", "yes", "--appendfsync", "everysec"]
volumes:
- yhub-valkey-data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 1s
timeout: 2s
retries: 60

yhub-postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: yhub
POSTGRES_PASSWORD: yhub
POSTGRES_DB: yhub
volumes:
- yhub-pgdata:/var/lib/postgresql/data
# NOTE: initdb.d only runs on a FRESH volume; schema changes need `podman volume rm`
- ./docker/files/yhub/initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U yhub"]
interval: 1s
timeout: 2s
retries: 60
# no published port (Django's postgres already publishes)

yhub:
user: ${DOCKER_USER:-1000}
image: impress:y-provider-development
restart: unless-stopped
build:
context: ./src/yhub-server
dockerfile: Dockerfile
target: yhub
image: impress:yhub
environment:
HOME: /tmp # same reason as node-based services above (unmapped uid)
PORT: 3002
REDIS: redis://yhub-valkey:6379
POSTGRES: postgres://yhub:yhub@yhub-postgres:5432/yhub
REDIS_PREFIX: yhub
env_file:
- env.d/development/common
- env.d/development/common.local
volumes:
- ./src/frontend/:/home/frontend
- /home/frontend/node_modules
- /home/frontend/servers/y-provider/node_modules
restart: unless-stopped
ports:
- "3002:3002"
depends_on:
y-provider-development:
condition: service_started
yhub-valkey:
condition: service_healthy
yhub-postgres:
condition: service_healthy

kc_postgresql:
image: postgres:14.3
Expand Down Expand Up @@ -268,3 +309,7 @@ networks:
name: lasuite-network
driver: bridge
external: true

volumes:
yhub-pgdata: {}
yhub-valkey-data: {}
14 changes: 14 additions & 0 deletions docker/files/yhub/initdb/01-yhub.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Column-for-column from yhub bin/init-db.js (unquoted identifiers so
-- case-folding matches yhub's persistence.js queries).
CREATE TABLE IF NOT EXISTS yhub_ydoc_v1 (
org text,
docid text,
branch text,
t text,
created INT8,
gcDoc bytea,
nongcDoc bytea,
contentmap bytea,
contentids bytea,
PRIMARY KEY (org,docid,branch,t)
);
2 changes: 2 additions & 0 deletions documentation/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ These are the environment variables you can set for the `impress-backend` contai
| FRONTEND_JS_URL | To add a external js file to the app | |
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
| FRONTEND_THEME | Frontend theme to use | |
| JWT_PRIVATE_KEY | PEM encoded RSA private key used to sign the JWT tokens (RS256). Can be read from a file with JWT_PRIVATE_KEY_FILE | |
| JWT_TOKEN_LIFETIME | Lifetime in seconds of the generated JWT tokens. Also used as the cache timeout of these tokens | 3600 |
| LANGUAGE_CODE | Default language | en-us |
| LANGFUSE_SECRET_KEY | The Langfuse secret key used by the sdk | None |
| LANGFUSE_PUBLIC_KEY | The Langfuse public key used by the sdk | None |
Expand Down
5 changes: 5 additions & 0 deletions documentation/resource_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ OIDC_RS_ALLOWED_AUDIENCES=

It implements the resource server using `django-lasuite`, see the [documentation](https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-oidc-resource-server-backend.md)

When `OIDC_RS_PRIVATE_KEY_STR` is set, the resource server publishes its public
key on `/external_api/{version}/jwks`. This is the URI to declare to your OIDC
provider. Do not confuse it with `/api/{version}/jwks`, which publishes the key
validating the tokens Docs itself issues to call external services.

## Customise allowed routes

Configure the `EXTERNAL_API` setting to control which routes and actions are available in the external API. Set it via the `EXTERNAL_API` environment variable (as JSON) or in Django settings.
Expand Down
2 changes: 1 addition & 1 deletion documentation/system-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Production deployments differ significantly from development environments. The t
| --------- | --------------------- |
| 3000 | Next.js |
| 8071 | Django |
| 4444 | Y-Provider |
| 3002 | yhub (collaboration WS) |
| 8080 | Keycloak |
| 8083 | Nginx proxy |
| 9000/9001 | MinIO |
Expand Down
9 changes: 7 additions & 2 deletions env.d/development/common
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ DJANGO_EMAIL_LOGO_IMG="http://localhost:3000/assets/logo-suite-numerique.png"
DJANGO_EMAIL_PORT=1025
DJANGO_EMAIL_URL_APP="http://localhost:3000"

# JWT
# The key itself is generated locally by "make generate-secret-keys", it is
# never committed. A PEM does not fit in an env var, hence the _FILE variant.
JWT_PRIVATE_KEY_FILE=/data/jwt/private.pem

# Backend url
IMPRESS_BASE_URL="http://localhost:8072"

Expand Down Expand Up @@ -72,12 +77,12 @@ OIDC_RS_ALLOWED_AUDIENCES=""
USER_RECONCILIATION_FORM_URL=http://localhost:3000

# Collaboration
COLLABORATION_API_URL=http://y-provider-development:4444/collaboration/api/
# TODO(yhub): no management API yet
COLLABORATION_BACKEND_BASE_URL=http://app-dev:8000
COLLABORATION_SERVER_ORIGIN=http://localhost:3000
COLLABORATION_SERVER_SECRET=my-secret
COLLABORATION_WS_NOT_CONNECTED_READ_ONLY=true
COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
COLLABORATION_WS_URL=ws://localhost:3002/ws/docs
COLLABORATION_WS_INACTIVITY_TIMEOUT=15 # Seconds

DJANGO_SERVER_TO_SERVER_API_TOKENS=server-api-token
Expand Down
1 change: 0 additions & 1 deletion env.d/development/common.e2e
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# For the CI job test-e2e
BURST_THROTTLE_RATES="1000/minute"
COLLABORATION_API_URL=http://y-provider:4444/collaboration/api/
SUSTAINED_THROTTLE_RATES="1000/minute"
Y_PROVIDER_API_BASE_URL=http://y-provider-converter:4444/api/

Expand Down
Loading
Loading