Conversation
❌ Deploy Preview for gr-sentinel failed. Why did it fail? →
|
❌ Deploy Preview for gr-sentinel failed. Why did it fail? →
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onCode model and service
…verify endpoints in core
…n refresh exchange
Rincon normalizes display names to slugs on registration, so passing the display form here keeps the source config readable without affecting runtime behavior.
The previous implementation passed the zero-value ID from a shadowed variable into CreateEntity, which made every restart generate a fresh ULID and leak orphan entity rows. Pass the SentinelCoreEntityID constant so the default entity is idempotent across restarts.
Move POST /applications/verify under the /core namespace since it is an internal service-to-service endpoint, not part of the public applications CRUD surface. Add GET /core/entity/:entityID and GET /core/entity/:entityID/groups so oauth (and other internal callers) can resolve entity type, linked user/service-account ids, and group memberships without hitting core's database directly.
Resolve entity type, linked user_id or service_account_id, and group membership via core's new /core/entity/:id endpoints before each token is signed, and pass them through as JWT claims. Downstream services can now gate on group membership locally without a round-trip per request. Claim building lives in oauth/service/token_claims.go as a seam for future per-app and per-entity overrides. Make ACCESS_TOKEN_TTL and REFRESH_TOKEN_TTL env-configurable (defaults 30m / 7d), shortening the access-token lifetime reduces how long stale group membership can linger in a JWT. Invalid values log at warn; unset values log at info and use the default.
Bootstrap the v5 web client with Vite 8, React 19, TypeScript 6, Tailwind v4 (via @tailwindcss/vite), shadcn/ui (Nova/radix preset), react-router-dom 7, and axios. Includes the @/ path alias, configured axios instances for core and oauth with env-driven base URLs, and placeholder pages for home, login, the OAuth consent screen, and applications. No business logic yet — this lands the scaffold only.
Runs node:22-alpine with npm install + npm run dev --host 0.0.0.0 on port 5173. Source is bind-mounted for HMR, node_modules lives in a named volume to keep restarts fast. VITE_CORE_API_URL and VITE_OAUTH_API_URL are set to localhost since the SPA is evaluated in the browser, not in the container.
Add kerbecs as a single-port edge proxy on :10310 routing to internal services by path: /api/core/*, /api/users/*, /api/applications/*, /api/groups/* -> core /api/oauth/* -> oauth /api/discord/* -> discord /* -> web All backend routes use rewrite.strip_prefix: /api so services keep their existing route registrations. Envelope is passthrough on every route since OAuth response shapes are spec-defined and the SPA needs to stream HMR. CORS is enabled with a permissive dev allowlist. Drops host port mappings from core, oauth, and discord — they're now reachable only through the gateway. db, web, and rincon stay exposed for direct dev access (psql, npm run dev fallback, registry inspection). Frontend collapses VITE_CORE_API_URL + VITE_OAUTH_API_URL to a single VITE_API_URL pointing at the gateway, and web/src/lib/api.ts exports one axios instance with baseURL ending in /api.
Scaffold the v5 web app's design surface end-to-end with mock data so the layout and pages can be iterated on before any business logic lands. Layout - AppShell wraps a sidebar + scrollable inset; sidebar is static (collapsible none) since collapse adds drag-handle UX we don't want. - AppHeader is a slim translucent bar inside the inset with just the user dropdown — page chrome lives in the sidebar instead. - AppSidebar shows a gradient brand mark, Dashboard / Applications / Groups / Analytics / Settings nav, and a footer caption. - AppFooter ports the v4 design (GR logo, gradient hairline divider, copyright, social links) with inline brand SVGs in components/icons/socials.tsx so we don't pull a brand-icon dep. - PageContainer + PageHeader primitives in components/PageContainer.tsx remove the duplicated max-w-6xl / title-and-description pattern across every page. Pages - HomePage: greeting, "Recently accessed" apps grid (sorted by lastAccessedAt), and a recent-activity card. Mock data only. - Applications, Groups, Analytics, Settings: stubs using PageHeader plus a "Coming soon" card. - Login, Authorize, NotFound stay full-bleed and don't use AppShell. Brand + theme - Geist Variable from shadcn's Nova preset, dark mode forced via <html class="dark">. - gr-pink, gr-purple, discord-blurple live in src/styles/brand.css as Tailwind v4 @theme tokens, imported separately from index.css so a future shadcn re-init can rewrite the theme file without touching brand. Mock data in src/lib/mock.ts is the single seam to replace once API calls are wired up.
Header - HeaderSearch: input-shaped trigger that opens a shadcn CommandDialog (cmd+k or click) with grouped results across applications, groups, and members. Patches a bug in shadcn's generated CommandDialog where it forgot to wrap children in <Command>, which crashed cmdk on open. - HeaderNotifications: bell icon with an unread dot, dropdown listing notifications with type icons, body text, relative timestamps, and a "Mark all read" action. - HeaderUserMenu extracted in-file for symmetry; sideOffset bumped on both header dropdowns so they don't sit on top of their triggers. Footer - Version pulled live from /api/core/ping and parsed out of the message string. Single source of truth — bumping Version in core/config/config.go now flows through to the UI. - Wordmark uses a new font-brand token (Inter Variable) to match the Gaucho Racing logo guidelines while body text stays Geist. Layout / theming - AppHeader: shrink-0 so flex-col children don't collapse it below h-14. - brand.css: re-stating --font-sans inside its @theme to keep Geist pinned (re-importing tailwindcss in a second CSS file resets the default tokens). Mock data - mockMembers and mockNotifications added in src/lib/mock.ts as the search and notifications data sources until the API lands. Assets - Full set of Gaucho Racing logo PNGs copied into web/public/logo (car, mechanic variants, wordmark variants).
Login page (web/src/pages/auth/LoginPage.tsx)
- Email + password placeholders (sr-only labels), Google + Discord
social buttons, OR divider, footer link to the Discord invite for
account requests.
- Per-button loading state ("email" | "google" | "discord" | null) —
only the clicked button spins, the others disable while busy.
- Success transition: login widget converges (scale + fade), an
animated checkmark draws in (circle stroke then check stroke) using
the gr-pink -> gr-purple gradient via SVG linearGradient, brief hold,
then document.startViewTransition cross-fades to the dashboard.
Components
- OutlineButton: gradient-bordered button matching v4 with a loading
prop that swaps children for a spinner (web/src/components/OutlineButton.tsx).
- SuccessCheck: animated SVG check used by the login transition; uses
useId so the gradient def has a unique, css-safe id
(web/src/components/SuccessCheck.tsx).
- Google + Discord brand SVG icons added to socials.tsx.
- Label primitive added via shadcn for accessible sr-only labels.
Debug index
- New /debug page lists every route in the app grouped as Dashboard /
Auth flow / Edge cases, with sample query params on the OAuth
authorize link (web/src/pages/debug/DebugPage.tsx).
- Added Bug nav item to the sidebar.
Misc
- Discord invite URL surfaced as DISCORD_INVITE_URL in lib/links.ts.
- @Keyframes draw-stroke + a global cursor-pointer rule for buttons
in styles/brand.css.
Replace the AuthorizePage stub with a real consent screen styled to match the login page (centered standalone content, no card chrome so the OutlineButton's bg-background sits flush with the page). - App heading: gradient icon tile + name + "wants to access your Sentinel account". Falls back to an "Unknown application" state when the client_id doesn't resolve. - "Signed in as" block reuses mockUser. - "This will let X:" lists each requested scope with a friendly label, description, and lucide icon. Unknown scopes degrade gracefully and show the raw key in mono. Scope metadata lives in src/lib/scopes.ts so labels stay consistent if the consent ever needs to render outside this page. - Redirect note shows where the user will land. - Cancel + Authorize buttons share h-10 / rounded-xl so the shadcn outline button visually matches the OutlineButton next to it; each has its own loading state. - Approve triggers the same SuccessCheck transition as login (converge + animated gradient checkmark + hold) before redirecting to redirect_uri?code=mock_authorization_code. Cancel goes to redirect_uri?error=access_denied.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sentinel v5 rewrite, see design doc here for more info.