AI Implementation feature(881): Authenticated Shell, Quick Tabs and Change Password 1.05 (#20)

This commit was merged in pull request #20.
This commit is contained in:
2026-07-22 10:35:27 +00:00
parent 90e570c43c
commit de527ec6d6
15 changed files with 688 additions and 56 deletions
+30 -8
View File
@@ -3,7 +3,7 @@ type: architecture
title: Frontend Structure
description: Angular routes, components, services, guards, interceptors, and authenticated SSE transport.
tags: [architecture, frontend, angular, shell, sse]
timestamp: 2026-07-22T09:35:00Z
timestamp: 2026-07-22T10:32:00Z
---
# Routes
@@ -24,13 +24,14 @@ Routes live in `frontend/src/app/app.routes.ts`:
| Symbol | Path | Responsibility |
|---|---|---|
| `HomeComponent` | `frontend/src/app/features/home/home.component.ts` | Smart shell; owns navigation signals, user hydration, change-password flow, and event-stream lifecycle. |
| `HomeComponent` | `frontend/src/app/features/home/home.component.ts` | Smart shell; owns navigation signals, user hydration, change-password flow, event-stream lifecycle, and peer-invalidation subscription. |
| `ShellHeaderComponent` | `frontend/src/app/features/shell/header/shell-header.component.ts` | Renders title, active section, event LED/countdown, and user menu; contains component-scoped LED styling. |
| `QuickTabsComponent` | `frontend/src/app/features/shell/tabs/quick-tabs.component.ts` | Emits navigation events for Challenges, Scoreboard, and Blog. |
| `EventStatusStore` | `frontend/src/app/core/services/event-status.store.ts` | Applies event state frames and computes countdown text; re-exports the pure LED color mapping. |
| `AuthenticatedEventSourceService` | `frontend/src/app/core/services/authenticated-event-source.service.ts` | Opens authenticated SSE over `fetch`, parsing streamed frames into `EventSourceLike` events. |
| `AuthService` | `frontend/src/app/core/services/auth.service.ts` | Stores the access token used by the authenticated SSE transport. |
| `EventStatusStore` | `frontend/src/app/core/services/event-status.store.ts` | Applies event state frames and computes countdown text; re-exports the pure LED color mapping; wires an optional `onUnauthorized` callback for the new SSE `401`/`403` event. |
| `AuthenticatedEventSourceService` | `frontend/src/app/core/services/authenticated-event-source.service.ts` | Opens authenticated SSE over `fetch`, parsing streamed frames into `EventSourceLike` events; dispatches a separate `'unauthorized'` event for `401`/`403`. |
| `AuthService` | `frontend/src/app/core/services/auth.service.ts` | Stores the access token used by the authenticated SSE transport; owns the cross-tab invalidation `BroadcastChannel` + `storage`-event fallback and exposes `clearAndBroadcast` / `onPeerInvalidation`. |
| `UserStore` | `frontend/src/app/core/services/user.store.ts` | Hydrates the signed-in user and rank data. |
| `auth-session-events.pure.ts` | `frontend/src/app/core/services/auth-session-events.pure.ts` | Pure cross-tab message encoding/validation and the namespaced channel / storage-key constants. |
# Authenticated SSE wiring
@@ -44,10 +45,17 @@ Routes live in `frontend/src/app/app.routes.ts`:
4. Splits complete SSE records on blank lines, joins repeated `data:` lines, and
dispatches `MessageEvent` objects.
5. Buffers records received before the message listener is attached.
6. Aborts and clears listeners when `close()` is called.
6. Inspects the response status *before* the `ok`/`body` check; on `401` or
`403` dispatches a separate `'unauthorized'` event and returns without
reading the body. Generic transport failures still dispatch `'error'`.
7. Aborts and clears listeners when `close()` is called.
`EventStatusStore` parses each message as `EventStatePayload`, updates its signals,
and runs a one-second local timer. `HomeComponent` stops the store on destruction.
`EventStatusStore.start(createSource, onUnauthorized?)` parses each message as
`EventStatePayload`, updates its signals, runs a one-second local timer, and —
when `onUnauthorized` is provided — invokes it on `'unauthorized'` events.
`HomeComponent` passes an `onUnauthorized` callback that delegates to its
shared `handleSessionInvalidated('sse-unauthorized')` flow and stops the store
on destruction.
# Key integration files
@@ -84,6 +92,20 @@ color always comes from `LED_COLOR_BY_STATE` and is guaranteed to be a
non-transparent theme token. The four `led-<state>` classes remain on the
element as selectors/test hooks but no longer own the color.
# Cross-tab session invalidation
`AuthService` (constructed once at root) installs a single
`BroadcastChannel('hipctf.auth.v1')` and a `localStorage` `storage`-event
fallback. The shape of the broadcast payload is defined in
`auth-session-events.pure.ts` and contains only `{ type, origin, ts }` — no
tokens or user data. `clearAndBroadcast()` is invoked from `logout()` and
from the SSE unauthorized callback; peer listeners clear their local state
without rebroadcasting. `HomeComponent` subscribes via
`auth.onPeerInvalidation(...)`, resets `UserStore`, closes any open shell
overlays, and navigates to `/login`. See
[Cross-Tab Authenticated Shell Invalidation](/guides/cross-tab-invalidation.md)
for the full contract and tester matrix.
# See also
- [Authenticated Shell](/guides/authenticated-shell.md)