Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
745f0e4
feat: add Modgud product landing page
windischb Jul 23, 2026
9172276
feat(landing): sharpen product positioning
windischb Jul 23, 2026
ee7242e
feat: enforce realm-owned jobs and sessions
windischb Jul 23, 2026
375f744
feat: redesign resource server integration
windischb Jul 23, 2026
ae63db0
feat: isolate realm and platform audit data
windischb Jul 23, 2026
bbb520d
feat: classify audit event durability
windischb Jul 23, 2026
41c04e4
fix: make realm admin roles explicitly unscoped
windischb Jul 23, 2026
f17c70d
docs: clarify resource access audience contract
windischb Jul 23, 2026
b1c08cd
fix: align SAML federation and logout behavior
windischb Jul 23, 2026
74a0b4e
feat(frontend): replace CoarNote blocks with compact AppNote
windischb Jul 24, 2026
d319ff4
refactor(frontend): move field help text into CoarFormField hint
windischb Jul 24, 2026
9853db8
fix(frontend): surface hidden realm-settings help + wrap overflowing …
windischb Jul 24, 2026
4d1db9f
fix(frontend): shrink app modal from full to large frame
windischb Jul 24, 2026
4f99d8a
fix(frontend): fall back to username for blank user modal title
windischb Jul 24, 2026
75df8bc
refactor(frontend): use CoarTag for the 2FA status badge
windischb Jul 24, 2026
81a78a6
refactor(frontend): unify grid status columns on tag vs icon
windischb Jul 24, 2026
06f1c5f
refactor(frontend): stage user group membership into Save
windischb Jul 24, 2026
e3f6a44
refactor(frontend): show inherited values in app override settings
windischb Jul 24, 2026
d839449
refactor(frontend): put profile modals on the standard modal plumbing
windischb Jul 24, 2026
b161f22
fix(frontend): repair the service-account credential modal
windischb Jul 24, 2026
e7529b8
feat(users): create a user complete in one step
windischb Jul 24, 2026
dfe062d
feat(frontend): add AppBanner for scope-level statements
windischb Jul 28, 2026
905b347
fix(frontend): use the subtle border and a tinted foreground on AppBa…
windischb Jul 28, 2026
09f66b8
fix(frontend): cut the system-entity banners down to the operative se…
windischb Jul 28, 2026
79a2863
fix(frontend): one appearance for system-managed entities
windischb Jul 28, 2026
f1525ac
refactor(frontend): merge AppNote and AppBanner into one Notice
windischb Jul 28, 2026
0be7c52
fix(frontend): stop the chroma arithmetic from draining the quiet var…
windischb Jul 28, 2026
a95c5d1
docs: link the local-development guide from the README
windischb Jul 29, 2026
1085812
refactor(frontend): adopt cocoar vue 2.18 notices
windischb Jul 30, 2026
a212a6c
feat(admin): redesign OAuth client editor
windischb Jul 30, 2026
8cb8dad
feat(admin): redesign login provider editor
windischb Jul 30, 2026
be3c9f0
feat: overhaul first installation and admin editors
windischb Jul 31, 2026
bfda3f9
fix(ci): refresh pnpm locks and patch postcss
windischb Jul 31, 2026
c898cec
fix(ci): provision Wolverine storage for realms
windischb Aug 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
32 changes: 16 additions & 16 deletions .github/workflows/cd-publish-nuget-prerelease.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: CD - Publish NuGet Prerelease

# Builds and publishes a prerelease of Modgud.Client.AspNetCore to
# Builds and publishes a prerelease of Modgud.AspNetCore.ResourceServer to
# nuget.org. Mirrors the cocoar.configuration workflow pattern so the
# release plumbing is consistent across the org.
#
Expand All @@ -23,7 +23,7 @@ name: CD - Publish NuGet Prerelease
# Trigger this when:
# - You need a specific prerelease build to test in a downstream
# consumer app
# - An external user asked for "the latest" of the client library
# - An external user asked for "the latest" resource-server package
# - Pre-stable release-candidate testing
#
# Otherwise leave it alone. The version still ships via the
Expand All @@ -50,7 +50,7 @@ concurrency:

jobs:
test:
name: Test client lib
name: Test resource-server package
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
Expand All @@ -67,24 +67,24 @@ jobs:
- name: Setup .NET
uses: ./.github/actions/setup-dotnet

- name: Restore client lib + unit tests
- name: Restore resource-server package + unit tests
run: |
dotnet restore Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj
dotnet restore Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj
dotnet restore Modgud.Tests.Unit/Modgud.Tests.Unit.csproj
working-directory: ./src/dotnet

- name: Build client lib + unit tests
- name: Build resource-server package + unit tests
run: |
dotnet build Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj -c Release --no-restore
dotnet build Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj -c Release --no-restore
dotnet build Modgud.Tests.Unit/Modgud.Tests.Unit.csproj -c Release --no-restore
working-directory: ./src/dotnet

# Filter to the Client.AspNetCore tests so we don't pull in
# Filter to the ResourceServer tests so we don't pull in
# integration tests that need Docker/Postgres (those live in
# Modgud.Api.Tests and aren't relevant for a client-lib
# Modgud.Api.Tests and aren't relevant for this package
# publish gate).
- name: Test client lib
run: dotnet test Modgud.Tests.Unit/Modgud.Tests.Unit.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName~Client"
- name: Test resource-server package
run: dotnet test Modgud.Tests.Unit/Modgud.Tests.Unit.csproj -c Release --no-build --verbosity normal --filter "FullyQualifiedName~ResourceServer"
working-directory: ./src/dotnet

publish-prerelease:
Expand Down Expand Up @@ -134,12 +134,12 @@ jobs:
echo "Calculated prerelease version: $VERSION"

- name: Restore
run: dotnet restore Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj
run: dotnet restore Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj
working-directory: ./src/dotnet

- name: Build
run: |
dotnet build Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj \
dotnet build Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj \
-c Release --no-restore \
-p:Version=$PACKAGE_VERSION \
-p:AssemblyVersion=${{ steps.dotnet.outputs.assembly-semver }} \
Expand All @@ -149,7 +149,7 @@ jobs:

- name: Pack (incl. symbols)
run: |
dotnet pack Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj \
dotnet pack Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj \
-c Release --no-build \
-p:Version=$PACKAGE_VERSION \
-p:ContinuousIntegrationBuild=true \
Expand Down Expand Up @@ -189,10 +189,10 @@ jobs:
- name: Summary
run: |
{
echo "### Modgud.Client.AspNetCore — $PACKAGE_VERSION (prerelease)"
echo "### Modgud.AspNetCore.ResourceServer — $PACKAGE_VERSION (prerelease)"
echo ""
if [ "${{ github.ref }}" = "refs/heads/develop" ]; then
echo "**Pushed to:** https://www.nuget.org/packages/Modgud.Client.AspNetCore/$PACKAGE_VERSION"
echo "**Pushed to:** https://www.nuget.org/packages/Modgud.AspNetCore.ResourceServer/$PACKAGE_VERSION"
else
echo "**Artifact only** (feature branch — not pushed to nuget.org)."
echo "Download the \`prerelease-packages-$PACKAGE_VERSION\` artifact and add it as a local source to test."
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/cd-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ name: CD - Release
# Sibling workflows for staging / prerelease / editorial doc paths:
# - cd-publish-staging-image.yml — pushes `:beta` / `:<slug>` moving
# Docker tags (auto on develop push + manual)
# - cd-publish-nuget-prerelease.yml — prerelease NuGet for client-lib
# - cd-publish-nuget-prerelease.yml — prerelease resource-server NuGet
# (manual workflow_dispatch)
# - cd-deploy-docs.yml — editorial doc deploy to Shelf
# between releases (manual)
Expand Down Expand Up @@ -146,7 +146,7 @@ jobs:
working-directory: ./src/dotnet

pack-nuget:
name: Pack NuGet (Client.AspNetCore)
name: Pack NuGet (ResourceServer)
needs: validate-version
runs-on: ubuntu-latest
permissions:
Expand All @@ -165,20 +165,20 @@ jobs:
uses: ./.github/actions/setup-dotnet

- name: Restore
run: dotnet restore Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj
run: dotnet restore Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj
working-directory: ./src/dotnet

- name: Build (stable)
run: |
dotnet build Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj \
dotnet build Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj \
-c Release --no-restore \
-p:Version=${{ needs.validate-version.outputs.version }} \
-p:ContinuousIntegrationBuild=true
working-directory: ./src/dotnet

- name: Pack (incl. symbols)
run: |
dotnet pack Modgud.Client.AspNetCore/Modgud.Client.AspNetCore.csproj \
dotnet pack Modgud.AspNetCore.ResourceServer/Modgud.AspNetCore.ResourceServer.csproj \
-c Release --no-build \
-p:Version=${{ needs.validate-version.outputs.version }} \
-p:ContinuousIntegrationBuild=true \
Expand Down Expand Up @@ -442,9 +442,9 @@ jobs:
- name: Summary
run: |
{
echo "### NuGet — Modgud.Client.AspNetCore ${{ needs.validate-version.outputs.version }}"
echo "### NuGet — Modgud.AspNetCore.ResourceServer ${{ needs.validate-version.outputs.version }}"
echo ""
echo "**URL:** https://www.nuget.org/packages/Modgud.Client.AspNetCore/${{ needs.validate-version.outputs.version }}"
echo "**URL:** https://www.nuget.org/packages/Modgud.AspNetCore.ResourceServer/${{ needs.validate-version.outputs.version }}"
} >> "$GITHUB_STEP_SUMMARY"

publish-docker:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
.astro/

# Visual Studio 6 build log
*.plg
Expand Down
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ emission, full 2FA spectrum, GDPR self-service.
database separation prevents query-level tenant mixing.
- **Multi-app permission model** — Apps are first-class. Permissions
are 2-segment (`<resource>:<action>`) inside an app's catalog. Two
bypass tiers, no more. Roles bind to one App, groups carry a
`BoundTo` activation list.
- **Keycloak-style `resource_access` on UserInfo** — per-audience
blocks with bypass pre-expansion and per-RS subset narrowing. A
drop-in `IClaimsTransformation` library flattens the right block
into `ClaimTypes.Role` so `[Authorize(Roles = "...")]` works
without per-endpoint plumbing.
bypass tiers, no more. Application roles bind to one App; a pure
`realm:admin` role is the explicit realm-local exception. Groups
carry a `BoundTo` activation list.
- **Keycloak-shaped `resource_access` authorization claims** — when a
token targets a registered OAuth API and requests `roles` and/or
`permissions`, Modgud emits a block keyed by that API's exact
audience, with bypass pre-expansion and per-RS subset narrowing.
`Modgud.AspNetCore.ResourceServer` projects only its configured
audience block into native role and permission claims.
- **Full 2FA spectrum + WebAuthn** — TOTP, Email-OTP, FIDO2/Passkey,
Magic Link, recovery codes. 2FA enforcement middleware with grace
period and per-user override.
- **OIDC federation** — Microsoft Entra ID, Google, any OIDC IdP.
JIT user provisioning + JavaScript claim-mapping (`UserUpdateScript`).
- **OIDC and SAML 2.0 federation** — Microsoft Entra ID and
standards-compatible OIDC or SAML identity providers. Modgud consumes SAML
as an SP; it does not issue SAML assertions. JIT user provisioning +
JavaScript claim-mapping (`UserUpdateScript`).
- **Dynamic Client Registration (RFC 7591)** with triple opt-in
(realm master / per-API / per-scope), audience-target containment,
full audit-event trail.
Expand All @@ -51,6 +55,7 @@ emission, full 2FA spectrum, GDPR self-service.
|---|---|
| [📘 Get Started](./docs/getting-started/) | What this is, requirements, first-time setup |
| [⚡ Quickstart (Docker)](./docs/getting-started/quickstart.md) | From `docker compose up` to first login in 10 minutes |
| [🧑‍💻 Developing locally](./docs/contribute/developing-locally.md) | Running from source: dev loop, `*.localhost` realms, recovery CLI, tests |
| [🧠 Concepts](./docs/concepts/) | Realms, apps, permissions, OAuth, tokens — the mental model |
| [🛠️ Operate](./docs/operate/) | Deployment, observability, recovery CLI, feature flags |
| [👤 Administer](./docs/admin/) | Users, groups, roles, OAuth clients, login providers |
Expand Down Expand Up @@ -86,9 +91,16 @@ pnpm install
pnpm dev
```

First-time admin bootstrap via the recovery CLI — see
[First-time setup](./docs/getting-started/first-time-setup.md) for
the walkthrough.
That is the short version. [Developing locally](./docs/contribute/developing-locally.md)
is the full one and the page that is kept in sync with the code: the
Postgres container, what the first boot actually does, reaching tenant
realms at `*.localhost`, the recovery CLI, demo seed data, tests and
Playwright.

For the first admin you need the recovery CLI — that guide covers it, and
[First-time setup](./docs/getting-started/first-time-setup.md) has the
decision tree for the other bootstrap routes (invite mode, provisioning
further realms).

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ thing.
- The Modgud IdP itself — backend (`src/dotnet/Modgud.Api`,
`Modgud.Authentication`, `Modgud.Authorization`, `Modgud.Domain`,
`Modgud.Infrastructure`) and admin SPA (`src/frontend-vue/`).
- The `Modgud.Client.AspNetCore` NuGet package that downstream apps
- The `Modgud.AspNetCore.ResourceServer` NuGet package that downstream apps
use to validate Modgud-issued tokens.
- The official Docker image (`ghcr.io/cocoar-dev/modgud:*`).
- The default configuration shipped in
Expand Down
6 changes: 4 additions & 2 deletions docs/admin/applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ effectively **rename** one — is to clone it. In the list, right-click a row
- **Display name, description and the whole permission catalog** are copied. The
catalog entries are copied as *new* entries (fresh ids), so the source app's
role grants and resource-server subsets are left untouched.
- **Settings** are copied too — branding, registration, native-grant / DCR / CIMD
- **Settings** are copied too — branding, registration, client-session, native-grant / DCR / CIMD
overrides — **except the Origin subdomain**, which is globally unique and would
collide. Set a new subdomain on the copy if it needs one.

Expand All @@ -110,7 +110,8 @@ the app's slug and link it to the app. Its `PermissionIds` declare
which subset of the catalog this resource server gates on (full
catalog is the typical default; tighten for microservices that only
need a slice). This is the identity Modgud uses to compute the
per-Audience `resource_access` block in UserInfo.
audience-keyed `resource_access` block at the token boundary. The key
is the OAuth API Audience, not this App's slug.

## Extending or changing the catalog

Expand Down Expand Up @@ -140,6 +141,7 @@ re-inherits the realm.
| **Email branding** | The product name used in this App's outbound emails (OTP, magic link, ...) instead of the realm default. |
| **Self-registration** | Per-app override of the realm self-registration policy (allowed email domains, admin approval, default groups, ToS/privacy URLs) plus the **posture** (see below). Captcha stays realm-level. |
| **Registration fields** | Per-app override of which identity fields (username / first / last name) are required when an account is created — each one inheriting the realm by default. See [Registration fields](#registration-fields) below. |
| **Client sessions** | Idle and absolute lifetime defaults for refresh-token-backed native/OAuth sessions belonging to this App. Each field inherits the realm unless overridden; an individual OAuth client can override the App again. |
| **Native grants** | Per-app toggle + token lifetimes for the cookieless [native passwordless grants](../integrate/native-apps). |
| **DCR** | Per-app override of [Dynamic Client Registration](./dynamic-client-registration) (enable, token lifetimes, rate limits, reserved-name blocklist). |
| **CIMD** | Per-app override of [Client-ID Metadata Documents](./client-id-metadata-documents) (enable, token lifetimes). |
Expand Down
Loading
Loading