Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .changeset/compose-loopback-and-pinned-supply-chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'seamless-auth-api': patch
---

The bundled Docker Compose stack serves on loopback only and no longer grants admin to a fixed address (#473).

- `docker-compose.yml` publishes the API on `127.0.0.1:${API_PORT:-5312}` and no longer publishes Postgres. Docker's port publishing bypasses host firewalls such as ufw, so the old bare port exposed the development defaults to the network. `POSTGRES_PORT` no longer applies to this file; use `docker compose exec postgres psql -U seamless seamless_auth` for a shell.
- `docker-compose.dev.yml` publishes the API and Postgres on `127.0.0.1` only.
- Neither file defaults `OWNER_EMAIL` to `owner@example.com` any more. Pass your own, for example `OWNER_EMAIL=you@example.com docker compose up`; without it nobody is granted admin. An existing stack whose owner account was already created keeps its role.

The release image now builds the admin dashboard from a pinned commit (`SEAMLESS_ADMIN_DASHBOARD_REF`, dashboard v0.10.1) and refuses a build arg that is not a full commit SHA (#474). Overriding it with a tag or branch name fails the build.
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
pull_request:
branches: ['**']

permissions:
contents: read

jobs:
# A pull request only ever runs ci.yml and codeql.yml, so a mistake in
# release.yml or docker-publish.yml reaches main unchallenged and is not
Expand All @@ -22,7 +25,7 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install actionlint
run: |
Expand Down Expand Up @@ -51,10 +54,10 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
Expand Down Expand Up @@ -87,7 +90,7 @@ jobs:

- name: Upload coverage reports to Codecov
if: matrix.node-version == 24
uses: codecov/codecov-action@v7
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
languages: javascript-typescript
# security-extended adds rules that are lower signal on their own but
Expand All @@ -36,10 +36,10 @@ jobs:
queries: security-extended

- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
category: /language:javascript-typescript

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ on:

jobs:
verify:
uses: fells-code/seamless-cli/.github/workflows/verify-conformance.yml@main
uses: fells-code/seamless-cli/.github/workflows/verify-conformance.yml@66cb013722ccfb747eb29ba3ac0bfc50a8d78b39 # v0.25.1
with:
api-ref: ${{ github.event.pull_request.head.sha }}
22 changes: 11 additions & 11 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,23 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.image_tag || github.ref_name }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: |
ghcr.io/${{ env.IMAGE_NAME }}
Expand All @@ -64,7 +64,7 @@ jobs:
# the local daemon, which is why this is separate from the push below.
# Buildx caches the layers, so the second build is cheap.
- name: Build image for scanning
uses: docker/build-push-action@v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: false
Expand All @@ -73,7 +73,7 @@ jobs:
platforms: linux/amd64

- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@v0.36.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.IMAGE_NAME }}:scan
format: table
Expand All @@ -96,7 +96,7 @@ jobs:
# Runs even when the gate above failed, so the findings that blocked the
# release are visible rather than only in the job log.
if: always()
uses: aquasecurity/trivy-action@v0.36.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.IMAGE_NAME }}:scan
format: sarif
Expand All @@ -107,14 +107,14 @@ jobs:

- name: Upload scan findings
if: always()
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: trivy-results.sarif
category: trivy-image

- name: Build and push Docker image
id: push
uses: docker/build-push-action@v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: true
Expand All @@ -129,7 +129,7 @@ jobs:
provenance: mode=max

- name: Install cosign
uses: sigstore/cosign-installer@v3
uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.1

- name: Sign the published image
env:
Expand All @@ -152,7 +152,7 @@ jobs:
echo
echo '```bash'
echo "cosign verify ghcr.io/${IMAGE_NAME}@${DIGEST} \\"
echo " --certificate-identity-regexp '^https://github.com/${GITHUB_REPOSITORY}/' \\"
echo " --certificate-identity-regexp '^https://github\\.com/${GITHUB_REPOSITORY}/\\.github/workflows/docker-publish\\.yml@refs/(heads/main|tags/v.*)\$' \\"
echo " --certificate-oidc-issuer https://token.actions.githubusercontent.com"
echo '```'
echo
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down Expand Up @@ -111,12 +111,12 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: '.nvmrc'
cache: 'npm'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ jobs:

steps:
- name: Checkout repo
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: '.nvmrc'
cache: npm
Expand All @@ -57,7 +57,7 @@ jobs:

- name: Create or update version and changelog PR
id: changesets
uses: changesets/action@v2
uses: changesets/action@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
with:
# v2 renamed every one of these. Unknown inputs are ignored rather than
# rejected, so the old names would have left the version PR titled
Expand Down
17 changes: 10 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,26 @@ RUN npm prune --omit=dev
# (`build:console` sets VITE_BASE_PATH=/console/ and VITE_SAME_ORIGIN=true, so assets, the
# React Router basename, and the origin-derived API base all agree). This API then serves the
# result at /console. The dashboard repo is public, but its npm package is not published
# (package.json is marked private), so it is fetched from git. Bump
# SEAMLESS_ADMIN_DASHBOARD_REF to a dashboard release tag to ship a new dashboard on the next
# auth-image release. Never point a release build at a floating branch.
# (package.json is marked private), so it is fetched from git. To ship a new dashboard on the
# next auth-image release, set SEAMLESS_ADMIN_DASHBOARD_REF to the full commit SHA of a
# dashboard release tag and update the tag in the comment above it. A tag can be moved, so the
# build refuses anything but a full SHA and checks HEAD against it after checkout.
FROM node:24-slim AS admin-dashboard
WORKDIR /dashboard

ARG SEAMLESS_ADMIN_DASHBOARD_REPO=https://github.com/fells-code/seamless-auth-admin-dashboard.git
# Pinned dashboard release tag. The same-origin /console build has shipped since v0.2.0.
ARG SEAMLESS_ADMIN_DASHBOARD_REF=v0.10.1
# Dashboard release v0.10.1. The same-origin /console build has shipped since v0.2.0.
ARG SEAMLESS_ADMIN_DASHBOARD_REF=e16c1e84e6a105fb7cadc328570a2fbc469f463d

RUN apt-get update && \
apt-get install -y git python3 make g++ && \
rm -rf /var/lib/apt/lists/*

# Full clone + checkout so the ref can be a tag, branch, or commit SHA.
RUN echo "${SEAMLESS_ADMIN_DASHBOARD_REF}" | grep -Eqx '[0-9a-f]{40}' || \
{ echo "SEAMLESS_ADMIN_DASHBOARD_REF must be a full commit SHA" >&2; exit 1; }
RUN git clone "${SEAMLESS_ADMIN_DASHBOARD_REPO}" . && \
git checkout "${SEAMLESS_ADMIN_DASHBOARD_REF}"
git -c advice.detachedHead=false checkout --detach "${SEAMLESS_ADMIN_DASHBOARD_REF}" && \
test "$(git rev-parse HEAD)" = "${SEAMLESS_ADMIN_DASHBOARD_REF}"
RUN npm ci
RUN npm run build:console

Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,29 +335,32 @@ development defaults, so there is nothing to configure first: no `.env` file, no
no separate Postgres.

```bash
docker compose up
OWNER_EMAIL=you@example.com docker compose up
```

That starts the API on `http://localhost:5312`, runs migrations on first boot, generates a
development signing keypair (kept on the `dev-keys` volume, so it survives a recreate), and serves
the admin console at `http://localhost:5312/console`.

The API is published on `127.0.0.1` only, so it is not reachable from other machines on the
network, and Postgres is not published at all: only the API container reaches it. Docker's port
publishing bypasses host firewalls such as ufw, which is why the bind address is set in the compose
file rather than left to the host. For a database shell, use
`docker compose exec postgres psql -U seamless seamless_auth`.

Verify it:

```bash
curl http://localhost:5312/health/status
```

To get an admin account, sign up at `/console` with `owner@example.com`. That address is the compose
file's `OWNER_EMAIL`, so the account is granted `admin:write` on creation (or `admin` on an instance
whose `AVAILABLE_ROLES` lists only that).
Override it before first boot to use your own:

```bash
OWNER_EMAIL=you@example.com docker compose up
```
To get an admin account, sign up at `/console` with the address you passed as `OWNER_EMAIL`. That
account is granted `admin:write` on creation (or `admin` on an instance whose `AVAILABLE_ROLES`
lists only that). `OWNER_EMAIL` has no default: without it the stack still starts, but nobody is
granted admin. The grant happens only when the account is created, so set it before that account
signs up.

Other values worth overriding are `API_PORT`, `POSTGRES_PORT`, `APP_ORIGINS`, `ORIGINS`, and
Other values worth overriding are `API_PORT`, `APP_ORIGINS`, `ORIGINS`, and
`SEAMLESS_AUTH_IMAGE` (to pin a specific published tag). Stop the stack with `docker compose down`,
or `docker compose down -v` to also discard the database.

Expand Down
8 changes: 5 additions & 3 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ services:
build:
context: .
dockerfile: Dockerfile.dev
# Loopback only, as in docker-compose.yml: Docker publishes ports ahead of host firewalls.
ports:
- '${API_PORT:-5312}:5312'
- '127.0.0.1:${API_PORT:-5312}:5312'
volumes:
- .:/app
# node_modules stays out of the bind mount: the host tree is built for the host's
Expand Down Expand Up @@ -67,7 +68,7 @@ services:

SEAMLESS_SERVICE_TOKEN: ${SEAMLESS_SERVICE_TOKEN:-${API_SERVICE_TOKEN:-local-development-service-token-not-for-deployment}}
ALLOW_UNCREDENTIALED_DELIVERY_SECRETS: 'true'
OWNER_EMAIL: ${OWNER_EMAIL:-owner@example.com}
OWNER_EMAIL: ${OWNER_EMAIL:-}
SERVE_ADMIN_DASHBOARD: 'false'
env_file:
- path: .env
Expand All @@ -76,8 +77,9 @@ services:

postgres:
image: postgres:18-alpine
# Published on loopback so the suite can run against it with TEST_DB=postgres.
ports:
- '${POSTGRES_PORT:-5432}:5432'
- '127.0.0.1:${POSTGRES_PORT:-5432}:5432'
environment:
POSTGRES_USER: seamless
POSTGRES_PASSWORD: seamless
Expand Down
15 changes: 10 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Brings up Postgres and the published Seamless Auth API image with development
# defaults, so no .env file and no generated secrets are needed to try the project.
# The admin console is served by the same container at http://localhost:5312/console.
# The API listens on 127.0.0.1 only and Postgres is not published.
#
# Every value below is a LOCAL DEVELOPMENT default and is not safe for a deployed
# instance. See docs/configuration.md for the real thing, and
Expand All @@ -16,8 +17,10 @@ name: seamless-auth
services:
api:
image: ${SEAMLESS_AUTH_IMAGE:-ghcr.io/fells-code/seamless-auth-api:latest}
# Loopback only. Docker publishes ports ahead of host firewalls such as ufw, so a bare
# port here would expose these development defaults to the network.
ports:
- '${API_PORT:-5312}:5312'
- '127.0.0.1:${API_PORT:-5312}:5312'
depends_on:
postgres:
condition: service_healthy
Expand Down Expand Up @@ -67,8 +70,10 @@ services:
ALLOW_UNCREDENTIALED_DELIVERY_SECRETS: 'true'

# The first account created with this email is granted the admin role, so the
# console at /console has a working admin as soon as you sign up.
OWNER_EMAIL: ${OWNER_EMAIL:-owner@example.com}
# console at /console has a working admin as soon as you sign up. No default: unset,
# nobody is granted admin. Set it before that account signs up, for example
# `OWNER_EMAIL=you@example.com docker compose up`.
OWNER_EMAIL: ${OWNER_EMAIL:-}

SERVE_ADMIN_DASHBOARD: 'true'

Expand All @@ -91,8 +96,8 @@ services:

postgres:
image: postgres:18-alpine
ports:
- '${POSTGRES_PORT:-5432}:5432'
# Not published: only the api service reaches it, over the compose network. Use
# `docker compose exec postgres psql -U seamless seamless_auth` for a shell.
environment:
POSTGRES_USER: seamless
POSTGRES_PASSWORD: seamless
Expand Down
Loading
Loading