AccountController.Signin (and ExternalController / ConsentController)
return this.LoadingPage("Redirect", model.ReturnUrl) when the OIDC
client is a native one (e.g. PostIt, with a custom-scheme redirect
URI). The LoadingPage extension in Yavsc.Extensions renders
controller.View("Redirect", …), so a /Views/Shared/Redirect.cshtml
must exist.
The file was missing, and the absence surfaced as a 500 on
POST /signin once the login itself succeeded — the user authenticated
fine, Identity.Application signed in, but the response body never
rendered and the POST returned InvalidOperationException
('The view Redirect was not found'). This is what broke the PostIt
flow after the seed/IdentityResource fixes landed.
The view is the standard IdentityServer quickstart loading page: a
meta-refresh that redirects the embedded browser to the OIDC
client's callback URI (postit://callback). Localizer strings are
used so the page is translatable like the rest of the auth UI.
The previous Details view was a sketch: a handful of fields, a
half-broken <dt>/<dd> pairing around FrontChannelLogoutUri, and
nothing about token lifetimes, security flags, or collection sizes.
For an admin trying to understand what a given OIDC client actually
does (and why a login flow fails), that meant bouncing between the
list page and the edit page to read off half a dozen scalars.
The new view surfaces the same property surface as Edit.cshtml, but
read-only:
- Two-column layout: Identity + Security on the left, Tokens + Logout
on the right. Security flags render as a Bootstrap 3 label
(green/grey) so an admin can spot at a glance whether PKCE, consent,
offline access, etc. are on or off.
- Lifetimes are formatted in human units (5 min, 2 h, 30 d) instead of
raw seconds. Zero / unset is rendered as 'default' or '—' to avoid
the silent-zero footgun.
- Enum-valued columns (AccessTokenType, RefreshTokenUsage,
RefreshTokenExpiration) are rendered as their integer value since
that's the on-disk representation in IdentityServer8.
- The Collections list is mirrored from Edit.cshtml so every nested
editor (scopes, grant types, redirect URIs, CORS origins, IdP
restrictions, claims, properties, secrets) is one click away.
- Secrets get a structured table: type, description, created/expiration
timestamps, and a status badge (active / expires soon / expired /
no expiry). Secret values are never displayed — only the freshly
generated one, via the existing RegenerateSecret flow — and the
note is repeated here so the table can't be misread.
- Footer promoted from inline links to a button bar (Edit, Regenerate
secret, Back to List) for clearer call-to-action.
The ClientSecret property surface was confirmed by decompiling
IdentityServer8.EntityFramework.Storage 8.0.5: Expiration is
DateTime? (null = no expiry), Created is DateTime (default UtcNow).
No MinValue sentinel — previous draft's handling was wrong and has
been replaced by a single DateOrDash(DateTime?) helper.
The OAuth2 client editor at /Client/Edit/{id} previously exposed 8
fields out of ~30 scalars and 10 collections on the IdentityServer8
Client entity. Editing the collections (RedirectUris, Scopes, Grant
Types, Cors Origins, IdP Restrictions, Claims, Properties, Secrets)
was either impossible or jammed into a single broken text input that
bound against an IEnumerable<string> property.
Restructure into per-collection subpages, each with its own
list/add/remove flow:
- RedirectUris /Client/EditRedirectUris/{id}
- PostLogoutRedirectUris /Client/EditPostLogoutRedirectUris/{id}
- Scopes /Client/EditScopes/{id}
- GrantTypes /Client/EditGrantTypes/{id}
- CorsOrigins /Client/EditCorsOrigins/{id}
- IdPRestrictions /Client/EditIdPRestrictions/{id}
- Claims /Client/EditClaims/{id}
- Properties /Client/EditProperties/{id}
- Secrets /Client/EditSecrets/{id}
Implementation:
- New partial class ClientController.Collections.cs with one
GET/Add/Remove trio per collection. Add/Remove dispatch through
generic helpers that handle the EF row + ClientId check.
- Shared _EditableStringList.cshtml partial consumed by the six
single-string-field collection pages. Uses reflection to pull
the value field and the row Id off the entity — avoids six
nearly-identical table+form copies.
- Claims / Properties / Secrets each have their own view because
they carry 2+ fields (Type+Value, Key+Value, or
Type+Value+Description+Expiration).
- Main Edit.cshtml enriched: ClientId/Id hidden, all scalar
fields split into fieldsets (Core, Security, Logout, Tokens,
Device flow, Tokens extra), nav links to the 9 subpages with
current row counts as badges.
- ClientController.Edit(int) GET now loads the client with all
navigations via LoadClientAsync so the Edit.cshtml nav badges
render real counts.
Field-correctness notes (verified by disassembling HigginsSoft
IdentityServer8.EntityFramework.Entities.Client 8.0.5-preview-net9):
- The property is PairWiseSubjectSalt, not PairwiseSubjectSalt
(capital W on 'Wise').
- CibaLifetime and PollingInterval do NOT exist on Client in this
IdentityServer8 version — those properties were a guess. The
Device flow fieldset contains DeviceCodeLifetime + UserCodeType
instead.
- AllowedIdentityTokenSigningAlgorithms and AllowAccessTokensViaBrowser
were missing from the original form and are now exposed.
- ConsentLifetime and UserSsoLifetime are int? (nullable); the form
binds them as plain int fields which accept empty strings.
Security:
- All new actions stay under [Authorize('AdministratorOnly')].
- Each Add/Remove takes an explicit id (Client.Id) and the row's
ClientId is checked on the server before any delete; a rowId
from another client returns NotFound.
Docs:
- doc/dev-tracking/client-editor-overhaul.md — inventory, status,
follow-up ideas (confirmation prompts, validation, MVC tests).
Add ActivePageAny(ViewContext, IEnumerable<string>) so a dropdown
toggle gets the active class + aria-current="page" whenever any of
its children is the current route. Apply it to the Plateforme,
Administration, and account menu toggles.
Apply PageHelpers.ActivePage to all 13 <a class="dropdown-item">
entries across the Plateforme, Administration, and account
dropdown menus so users see which section they're in. The
Logout entry matches Account/Logout specifically to avoid
colliding with the Register/Signin entries on the same Account
controller.
Add PageHelpers.ActivePage extension and apply it to top-level nav
items and the Account/Register + Account/Signin items in _LoginPartial,
so the current route gets the active class and aria-current="page"
for accessibility.
JQuery, jQuery UI, Bootstrap, jquery-validation and
jquery-validation-unobtrusive are now loaded as separate
<script> tags by _Layout.cshtml, BEFORE the core bundle.
Why: esbuild IIFE bundles do not expose jQuery ($ and jQuery)
on window — UMD-style modules bundled in IIFE format are wrapped
in a closure. The application code (site.js, md-helpers.js,
yavsc-remote-fs.js, etc.) consumes window.$ / window.jQuery, so
it broke at runtime. Loading these scripts as global <script> tags
restores the expected global exposure.
This commit only touches the layout. Future commits will remove
the corresponding imports from each bundle's entry (chat, dropzone,
datetime, timepicker) and let them rely on window.$ being already
defined by the layout.
Tested: dotnet test 11/11 green. The new global scripts are
served by ASP.NET static files (HTTP 200 verified). Server
restart by developer required to pick up the new layout.
Sort ~50 Mo de libs tierces hors du repo et regroupe le code
front en 6 bundles thématiques + 2 assets statiques.
Bundles produits par esbuild :
- core.bundle.min.js : jQuery + jQuery UI + Bootstrap 4 + validation
+ tout le code applicatif 'core'
(site, signout, signin, input-lib, md-helpers,
audiovideoinput, parallax, google.geocode,
google-geoloc). Chargé par _Layout.cshtml.
- chat.bundle.min.js : core subset (sans Bootstrap) + chat.js + comment.js
(SignalR client chargé séparément, voir plus bas)
- dropzone.bundle.min.js : jQuery + dropzone + yavsc-remote-fs
- datetime.bundle.min.js : jQuery + jQuery UI + eonasdan datetimepicker
+ jquery-timepicker
- timepicker.bundle.min.js : jQuery + jQuery UI + jquery-timepicker
- quill.bundle.min.js : Quill rich text editor
Assets statiques (non bundlés, copiés depuis node_modules) :
- signalr.min.js : client SignalR 2.x (legacy server)
- moment-with-locales.min.js : moment + 137 locales (require dynamique
non résolvable statiquement par esbuild)
CSS vendor copiées comme assets statiques vers wwwroot/css/ :
bootstrap.min.css, jquery-ui.min.css, dropzone.min.css,
dropzone-basic.min.css, bootstrap-datetimepicker.min.css,
jquery.timepicker.css
Exception gitignore : jonthornton-Datepair/jquery.datepair.min.js
n'est pas sur npm, conservé comme asset statique sous
wwwroot/lib/jonthornton-Datepair/ (téléchargé depuis upstream).
Vues Razor mises à jour (15 fichiers) pour pointer vers les bundles
et les CSS vendor. Suppression de la majorité de wwwroot/lib/ (1300+
fichiers). Suppression des jquery*.js, quill.js, showdown.js,
to-markdown.js, jquery.signalR-2.2.1.js, dropzone.js et de leurs
.min.js associés.
'jquery-datepair' et 'jquery-ui-map' non publiés sur npm :
- jquery-ui-map n'est utilisé nulle part dans le repo (vérifié),
la dépendance est purement historique.
- datepair reste en static sous wwwroot/lib/jonthornton-Datepair/.