Compare commits
5 Commits
bb6021adf7
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1abaa22a97 | |||
| 0919adf4ef | |||
| f5f4455b81 | |||
| 3f30f889c3 | |||
| ae1479f55b |
@@ -7,7 +7,7 @@ jobs:
|
|||||||
full-gate:
|
full-gate:
|
||||||
runs-on: docker-host
|
runs-on: docker-host
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # ci.sh's docs-only check needs history; checkout defaults to depth 1
|
fetch-depth: 0 # ci.sh's docs-only check needs history; checkout defaults to depth 1
|
||||||
- run: bash ci.sh
|
- run: bash ci.sh
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
github-mirror:
|
github-mirror:
|
||||||
runs-on: docker-host
|
runs-on: docker-host
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: |
|
- run: |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ jobs:
|
|||||||
prune-stale-images:
|
prune-stale-images:
|
||||||
runs-on: docker-host
|
runs-on: docker-host
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.4.0
|
||||||
- name: Delete hash images that are neither release-tagged nor a branch head
|
- name: Delete hash images that are neither release-tagged nor a branch head
|
||||||
env:
|
env:
|
||||||
REGISTRY_TOKEN: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
|
REGISTRY_TOKEN: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ jobs:
|
|||||||
retag-image:
|
retag-image:
|
||||||
runs-on: docker-host
|
runs-on: docker-host
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.4.0
|
||||||
- name: Promote the commit-hash image to semver + latest
|
- name: Promote the commit-hash image to semver + latest
|
||||||
env:
|
env:
|
||||||
GIT_TAG: ${{ github.ref_name }}
|
GIT_TAG: ${{ github.ref_name }}
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Run Renovate against this repo
|
- name: Run Renovate against this repo
|
||||||
env:
|
env:
|
||||||
|
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }}
|
||||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
|
-e GITHUB_COM_TOKEN \
|
||||||
-e LOG_LEVEL=info \
|
-e LOG_LEVEL=info \
|
||||||
-e RENOVATE_ENDPOINT=https://gitea.larvit.se/api/v1 \
|
-e RENOVATE_ENDPOINT=https://gitea.larvit.se/api/v1 \
|
||||||
-e RENOVATE_GIT_AUTHOR="Renovate Bot <renovate@larvit.se>" \
|
-e RENOVATE_GIT_AUTHOR="Renovate Bot <renovate@larvit.se>" \
|
||||||
@@ -32,7 +34,7 @@ jobs:
|
|||||||
runs-on: docker-host
|
runs-on: docker-host
|
||||||
needs: renovate
|
needs: renovate
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.4.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Tag a release for what Renovate merged
|
- name: Tag a release for what Renovate merged
|
||||||
|
|||||||
@@ -1118,8 +1118,9 @@ docker compose -f compose.yml -f e2e-tests/compose.oauth.yml down -v
|
|||||||
|
|
||||||
**Full browser flow** (`full-flow.spec.ts`) — the real Playwright UI against the live stack:
|
**Full browser flow** (`full-flow.spec.ts`) — the real Playwright UI against the live stack:
|
||||||
the themed **password login** and a **mocked-SSO** login (an in-network mock OIDC provider,
|
the themed **password login** and a **mocked-SSO** login (an in-network mock OIDC provider,
|
||||||
`e2e-tests/mock-oidc.ts`), **menu filtering by role**, the **users/groups/roles** admin CRUD, a
|
`e2e-tests/mock-oidc.ts`), **menu filtering by role**, the **users/groups/roles** admin CRUD, the
|
||||||
permission-gated **plugin page**, and **logout**. Because the themed form posts straight to
|
**OAuth2-clients** admin screen (register → one-time secret → delete; Hydra is part of this stack
|
||||||
|
for it), a permission-gated **plugin page**, and **logout**. Because the themed form posts straight to
|
||||||
Kratos and cookies are host-scoped, a tiny same-origin gateway (`e2e-tests/proxy.ts`) fronts web +
|
Kratos and cookies are host-scoped, a tiny same-origin gateway (`e2e-tests/proxy.ts`) fronts web +
|
||||||
Kratos on one host (`ory/kratos/e2e-proxy.yml` points Kratos at it) — exactly as a production
|
Kratos on one host (`ory/kratos/e2e-proxy.yml` points Kratos at it) — exactly as a production
|
||||||
reverse proxy would.
|
reverse proxy would.
|
||||||
@@ -1249,8 +1250,11 @@ Renovate merges it once `CI / full-gate (push)` is green (rebasing stale branche
|
|||||||
fast-forward-only merge still holds) — routine bumps land untouched; only a red gate needs a
|
fast-forward-only merge still holds) — routine bumps land untouched; only a red gate needs a
|
||||||
human. One-time setup: reuse the shared `renovate@larvit.se` bot — give it write access to
|
human. One-time setup: reuse the shared `renovate@larvit.se` bot — give it write access to
|
||||||
this repo and store its Gitea PAT as the Actions **secret** `RENOVATE_TOKEN`. Until it
|
this repo and store its Gitea PAT as the Actions **secret** `RENOVATE_TOKEN`. Until it
|
||||||
exists, the nightly job fails loud (and, like the other secrets, a `GITEA_` prefix is
|
exists, the nightly job fails loud (and, like the other secrets, a `GITEA_`/`GITHUB_`
|
||||||
rejected).
|
prefix is rejected). Also store a **scopeless** (read-only) github.com PAT as the secret
|
||||||
|
`RENOVATE_GITHUB_TOKEN` — the workflow hands it to Renovate as `GITHUB_COM_TOKEN`, so
|
||||||
|
lookups of github.com-hosted deps (actions, Playwright, changelogs) run authenticated
|
||||||
|
instead of tripping the anonymous 60-requests/hour limit.
|
||||||
|
|
||||||
**Auto-release on dependency updates** — a second job in `renovate.yml` (`auto-release`, `needs:
|
**Auto-release on dependency updates** — a second job in `renovate.yml` (`auto-release`, `needs:
|
||||||
renovate`) cuts **one** `vX.Y.Z` tag per run covering the renovate-bot commits merged to `main`
|
renovate`) cuts **one** `vX.Y.Z` tag per run covering the renovate-bot commits merged to `main`
|
||||||
|
|||||||
+12
-13
@@ -1,21 +1,16 @@
|
|||||||
# Full browser E2E — the real Playwright UI flow against the live stack: password +
|
# Full browser E2E — the real Playwright UI flow against the live stack: password + mocked-SSO
|
||||||
# mocked-SSO login, menu filtering by role, users/groups/roles CRUD, a plugin page, logout. A tiny
|
# login, menu filtering by role, users/groups/roles/OAuth2-clients CRUD, a plugin page, logout. A
|
||||||
# same-origin gateway (proxy, e2e-tests/proxy.ts) fronts web + Kratos on one host so the browser's cookies
|
# tiny same-origin gateway (proxy, e2e-tests/proxy.ts) fronts web + Kratos on one host so the browser's cookies
|
||||||
# round-trip (ory/kratos/e2e-proxy.yml points Kratos at it); a mock OIDC provider backs the SSO test.
|
# round-trip (ory/kratos/e2e-proxy.yml points Kratos at it); a mock OIDC provider backs the SSO test.
|
||||||
# docker compose -f compose.yml -f e2e-tests/compose.full.yml run --build --rm e2e
|
# docker compose -f compose.yml -f e2e-tests/compose.full.yml run --build --rm e2e
|
||||||
# docker compose -f compose.yml -f e2e-tests/compose.full.yml down -v # tear down after
|
# docker compose -f compose.yml -f e2e-tests/compose.full.yml down -v # tear down after
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
# First-party + SSO flows need Kratos + Keto + bootstrap, not Hydra — drop it so the stack is
|
# The base's full depends_on applies (Hydra included — the admin plugin's OAuth2-clients
|
||||||
# leaner. SSO is enabled here only (clean clone stays password-only): the mock provider's whole
|
# screen needs it); only the reference plugin's upstream is added. SSO is enabled here only
|
||||||
# array is the env-settable form Kratos offers, mapped through the committed claims jsonnet.
|
# (clean clone stays password-only): the mock provider's whole array is the env-settable form
|
||||||
depends_on: !override
|
# Kratos offers, mapped through the committed claims jsonnet.
|
||||||
bootstrap:
|
depends_on:
|
||||||
condition: service_completed_successfully
|
|
||||||
kratos:
|
|
||||||
condition: service_healthy
|
|
||||||
keto:
|
|
||||||
condition: service_healthy
|
|
||||||
shifts-upstream:
|
shifts-upstream:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
@@ -52,6 +47,10 @@ services:
|
|||||||
SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS: >-
|
SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS: >-
|
||||||
[{"id":"mock","provider":"generic","label":"Mock SSO","client_id":"plainpages-e2e","client_secret":"e2e-secret","issuer_url":"http://mock-oidc:9000","scope":["openid","email"],"mapper_url":"file:///etc/config/kratos/oidc/claims.jsonnet"}]
|
[{"id":"mock","provider":"generic","label":"Mock SSO","client_id":"plainpages-e2e","client_secret":"e2e-secret","issuer_url":"http://mock-oidc:9000","scope":["openid","email"],"mapper_url":"file:///etc/config/kratos/oidc/claims.jsonnet"}]
|
||||||
|
|
||||||
|
# --dev permits the http issuer (the base file drops it for an https prod issuer).
|
||||||
|
hydra:
|
||||||
|
command: serve all --dev -c /etc/config/hydra/hydra.yml
|
||||||
|
|
||||||
# The reference plugin's upstream (examples/shifts-upstream) so /scheduling/shifts shows real rows.
|
# The reference plugin's upstream (examples/shifts-upstream) so /scheduling/shifts shows real rows.
|
||||||
shifts-upstream:
|
shifts-upstream:
|
||||||
image: node:24.18.1-alpine3.24
|
image: node:24.18.1-alpine3.24
|
||||||
|
|||||||
@@ -85,6 +85,35 @@ test.describe.serial("authenticated admin journey", () => {
|
|||||||
await expect(page.locator("main")).toContainText(role);
|
await expect(page.locator("main")).toContainText(role);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("OAuth2 clients CRUD: register a client (writes go to Hydra), see the one-time secret once, then delete it via the confirm step", async () => {
|
||||||
|
const name = `e2e-client-${suffix}`;
|
||||||
|
await page.goto("/admin/clients");
|
||||||
|
await page.getByRole("link", { name: "Register client" }).click();
|
||||||
|
await page.fill('input[name="name"]', name);
|
||||||
|
await page.fill('textarea[name="redirectUris"]', "https://app.example.com/callback");
|
||||||
|
await page.locator('.form-card button[type="submit"]').click();
|
||||||
|
|
||||||
|
// Hydra returns the secret exactly once, so the POST renders the detail directly (no PRG).
|
||||||
|
await expect(page.locator("h1")).toHaveText("Client registered");
|
||||||
|
const clientId = await page.locator("#cid").inputValue();
|
||||||
|
expect(clientId).toBeTruthy();
|
||||||
|
await expect(page.locator("#csecret")).toHaveValue(/.+/);
|
||||||
|
|
||||||
|
// Listed; the row header links to the plain detail, which never shows the secret again.
|
||||||
|
await page.goto("/admin/clients");
|
||||||
|
const row = page.locator("tr", { hasText: name });
|
||||||
|
await expect(row).toBeVisible();
|
||||||
|
await row.getByRole("link", { name }).click();
|
||||||
|
await expect(page).toHaveURL(new RegExp(`/admin/clients/${clientId}`));
|
||||||
|
await expect(page.locator("#csecret")).toHaveCount(0);
|
||||||
|
|
||||||
|
// Delete through the confirm interstitial (danger link on the detail → confirm form's button).
|
||||||
|
await page.getByRole("link", { name: "Delete client" }).click();
|
||||||
|
await page.getByRole("button", { name: "Delete client" }).click();
|
||||||
|
await expect(page).toHaveURL(/\/admin\/clients(\?|$)/);
|
||||||
|
await expect(page.locator("tr", { hasText: name })).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
test("plugin page: the reference plugin renders its upstream shifts inside the native shell", async () => {
|
test("plugin page: the reference plugin renders its upstream shifts inside the native shell", async () => {
|
||||||
await page.goto("/scheduling/shifts");
|
await page.goto("/scheduling/shifts");
|
||||||
await expect(page.locator("h1")).toHaveText("Shifts");
|
await expect(page.locator("h1")).toHaveText("Shifts");
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
- [x] CI/CD - Sync docker images to docker hub after each re-tag to git tags. (`release.yml` pushes the same `X.Y.Z`/`X.Y`/`X`/`latest` tags to `docker.io/larvit/plainpages` after the Gitea re-tag — releases only, no hash tags; auth via the `DOCKERHUB_USER` variable + `DOCKERHUB_TOKEN` secret; documented in README → CI/CD.)
|
- [x] CI/CD - Sync docker images to docker hub after each re-tag to git tags. (`release.yml` pushes the same `X.Y.Z`/`X.Y`/`X`/`latest` tags to `docker.io/larvit/plainpages` after the Gitea re-tag — releases only, no hash tags; auth via the `DOCKERHUB_USER` variable + `DOCKERHUB_TOKEN` secret; documented in README → CI/CD.)
|
||||||
- [x] Write a short text on how to use this docker image to publish on docker hub and save it to README-dockerhub.md (tagline, tags, clone-free quick start — the image ships the Ory config, extracted via `docker run … tar` + a self-contained compose.yml — env table, first plugin; pasted into the Docker Hub overview by hand — noted in README → CI/CD.)
|
- [x] Write a short text on how to use this docker image to publish on docker hub and save it to README-dockerhub.md (tagline, tags, clone-free quick start — the image ships the Ory config, extracted via `docker run … tar` + a self-contained compose.yml — env table, first plugin; pasted into the Docker Hub overview by hand — noted in README → CI/CD.)
|
||||||
- [x] CI/CD - Setup renovate bot. Check how other repos on this Gitea is setup you can get access to, there should be a number of renovate bot activated ones. (`renovate.yml` runs the self-hosted `renovate/renovate` image nightly against `renovate.json` — this repo only, via the shared `renovate@larvit.se` bot + `RENOVATE_TOKEN` secret, mirroring the `pwrpln/core` pattern; standard managers cover npm/Dockerfiles/compose/gitea-action pins, two custom regex managers cover the image tags embedded in workflow `run:` steps, the Ory + Playwright lockstep sets are grouped, every bump stays an exact pin, and each PR automerges once the gate is green; documented in README → CI/CD.)
|
- [x] CI/CD - Setup renovate bot. Check how other repos on this Gitea is setup you can get access to, there should be a number of renovate bot activated ones. (`renovate.yml` runs the self-hosted `renovate/renovate` image nightly against `renovate.json` — this repo only, via the shared `renovate@larvit.se` bot + `RENOVATE_TOKEN` secret, mirroring the `pwrpln/core` pattern; standard managers cover npm/Dockerfiles/compose/gitea-action pins, two custom regex managers cover the image tags embedded in workflow `run:` steps, the Ory + Playwright lockstep sets are grouped, every bump stays an exact pin, and each PR automerges once the gate is green; documented in README → CI/CD.)
|
||||||
- [ ] CI/CD - Renovate: set a read-only `GITHUB_COM_TOKEN` env in `renovate.yml` so Renovate stops hitting github.com rate limits when resolving github-hosted deps (Playwright, lucide, `actions/checkout`) and can fetch changelogs. Non-blocking refinement; needs a read-only GitHub PAT stored as an Actions secret.
|
- [x] CI/CD - Renovate: set a read-only `GITHUB_COM_TOKEN` env in `renovate.yml` so Renovate stops hitting github.com rate limits when resolving github-hosted deps (Playwright, lucide, `actions/checkout`) and can fetch changelogs. Non-blocking refinement; needs a read-only GitHub PAT stored as an Actions secret. (The renovate job forwards the `RENOVATE_GITHUB_TOKEN` secret — a scopeless read-only github.com PAT; Gitea rejects `GITHUB_`-prefixed secret names — into the container as `GITHUB_COM_TOKEN`; documented in README → CI/CD.)
|
||||||
- [x] CI/CD - When renovate updates a dependency - also release a new version of plainpages based on what got updated with Renovate. Major typescript? New apiVersion + new major. A tiny patch to ejs? Only patch release etc. Before implementing, explain in detail how you will solve this. (`renovate.yml` gains an `auto-release` job (`needs: renovate`) that cuts one `vX.Y.Z` tag per run for what Renovate merged; level = highest `Release-Bump:` trailer Renovate stamps via `commitBody`, any dep's major/minor/patch mapped straight through (default patch). Decoupled from `apiVersion` (tag-only, `HOST_API_VERSION` untouched — a "major" is just a bigger image tag, never a plugin break); pre-1.0 shifts down so nothing auto-crosses into 1.0.0. Pure `auto-release/next-version.ts` + unit tests; tag pushed with renovate-bot's PAT so `release.yml` fires; documented in README → CI/CD.)
|
- [x] CI/CD - When renovate updates a dependency - also release a new version of plainpages based on what got updated with Renovate. Major typescript? New apiVersion + new major. A tiny patch to ejs? Only patch release etc. Before implementing, explain in detail how you will solve this. (`renovate.yml` gains an `auto-release` job (`needs: renovate`) that cuts one `vX.Y.Z` tag per run for what Renovate merged; level = highest `Release-Bump:` trailer Renovate stamps via `commitBody`, any dep's major/minor/patch mapped straight through (default patch). Decoupled from `apiVersion` (tag-only, `HOST_API_VERSION` untouched — a "major" is just a bigger image tag, never a plugin break); pre-1.0 shifts down so nothing auto-crosses into 1.0.0. Pure `auto-release/next-version.ts` + unit tests; tag pushed with renovate-bot's PAT so `release.yml` fires; documented in README → CI/CD.)
|
||||||
- [ ] Add an e2e test for the admin plugin's OAuth2-clients (Hydra) screen. The full-flow e2e suite runs without Hydra (compose.full.yml), so /admin/clients register/detail/delete is only unit-covered (src/http/app.test.ts); wire Hydra into an e2e stack and drive the screen in the browser.
|
- [x] Add an e2e test for the admin plugin's OAuth2-clients (Hydra) screen. The full-flow e2e suite runs without Hydra (compose.full.yml), so /admin/clients register/detail/delete is only unit-covered (src/http/app.test.ts); wire Hydra into an e2e stack and drive the screen in the browser. (compose.full.yml now includes Hydra (`serve all --dev`) and full-flow.spec.ts drives /admin/clients register → one-time secret → list → detail → delete in the browser; documented in README → Testing.)
|
||||||
- [ ] Build and publish docker image as CI/CD.
|
- [ ] Build and publish docker image as CI/CD.
|
||||||
- [ ] The human developer understands the security model in the auth in this project.
|
- [ ] The human developer understands the security model in the auth in this project.
|
||||||
- [ ] Add i18n support.
|
- [ ] Add i18n support.
|
||||||
|
|||||||
Reference in New Issue
Block a user