feat(v1.1.8): F3 realtime auth_mode = 'session' (migration 0033)

Migration 0033 widens topics.auth_mode CHECK to include 'session'
alongside 'public' and 'token'.

TopicAuthMode enum gains a Session variant (as_str + from_db
extended uniformly).

RealtimeAuthorityImpl now takes Arc<dyn UsersService> as a third
constructor arg. The Session branch of authorize_subscribe
delegates to UsersService::verify_session_for_realtime(app_id,
token):
  * Returns Some(user) → allow. The service bumps the sliding TTL
    on success.
  * Returns None → Unauthorized.
  * Defense-in-depth: even though verify_session_for_realtime
    already enforces cross-app isolation, the branch re-checks
    user.app_id == app_id.

Tests added (4 new cases): valid session token allows; missing
token is Unauthorized; wrong token is Unauthorized; cross-app
session token is Unauthorized. All 12 realtime_authority tests
pass.

Dashboard: TopicAuthMode TypeScript union widened to include
'session'; the topic create + edit forms gain a third radio option
labeled "session — requires a per-app user session minted by
users::login (v1.1.8)".

picloud binary: construction order reshuffled so users is built
before realtime_authority. app_secrets_repo is now .clone()'d into
the pubsub realtime wiring so the original Arc can be re-used by
realtime_authority.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 15:00:47 +02:00
parent 3c2c4a3767
commit ff4f443531
6 changed files with 374 additions and 12 deletions

View File

@@ -201,11 +201,6 @@ pub async fn build_app(
// column. Operators upgrading from v1.1.6 or earlier MUST apply
// v1.1.7 first; the 0032 migration's guard refuses to run
// otherwise.
let realtime_authority: Arc<dyn RealtimeAuthority> = Arc::new(RealtimeAuthorityImpl::new(
topic_repo.clone(),
app_secrets_repo.clone(),
));
// v1.1.5 durable pub/sub, extended in v1.1.6 with the realtime
// broadcast + subscriber-token mint. Publishes fan out to matching
// pubsub triggers at publish time (one outbox row each, delivered by
@@ -216,7 +211,7 @@ pub async fn build_app(
PubsubServiceImpl::new(pubsub_repo, authz.clone()).with_realtime(
broadcaster.clone(),
topic_repo.clone(),
app_secrets_repo,
app_secrets_repo.clone(),
SubscriberTokenConfig::from_env(),
),
);
@@ -261,6 +256,14 @@ pub async fn build_app(
events.clone(),
UsersServiceConfig::from_env(),
));
// v1.1.8 F3: build the realtime authority now that UsersService
// is constructed; the Session arm of authorize_subscribe calls
// users.verify_session_for_realtime.
let realtime_authority: Arc<dyn RealtimeAuthority> = Arc::new(RealtimeAuthorityImpl::new(
topic_repo.clone(),
app_secrets_repo.clone(),
users.clone(),
));
let services = Services::new(
kv,
docs,