fed-sx-m2: Step 4b — token -> ActorId map + 8 new tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
POST /activity now routes through nx_kernel:publish_to/2 when the
bearer token resolves to an explicit ActorId via Cfg's :tokens
proplist:
Cfg = [{tokens, [{<<"alice-token">>, alice},
{<<"bob-token">>, bob}]}]
resolve_token/2 returns {ok, ActorId} on a :tokens hit. On a miss
it falls back to the M1 :publish_token single-token field — match
returns {ok, legacy}, routing through nx_kernel:publish/1 (which
fans out to bucket 0) so every M1 test continues to pass.
handle_post_activity threads the resolved ActorRef to
publish_if_kernel/3 which dispatches publish_to/2 for explicit
actor ids and publish/1 for the legacy atom. The no-kernel
auth-only path (which preserves the post_activity_response_for stub
for unit-style tests of http_server alone) is unchanged.
Dead expected_token/1 helper removed (was only called by the old
check_bearer arm that resolve_token replaces).
8 new cases in next/tests/http_multi_actor.sh (25/25 total):
- two-actor Cfg, Alice token -> 200 with cid:
- Alice token publishes to alice (log_tip alice=1, bob=0)
- Bob token publishes to bob (log_tip alice=0, bob=1)
- interleaved Alice + Bob + Alice -> {2, 1}
- unknown token + no :publish_token -> 401
- legacy :publish_token still works (M1 back-compat)
- tokens map AND legacy :publish_token coexist (each resolves to
its own actor; legacy lands on alice bucket via publish/1)
- no kernel + valid :tokens entry -> auth-only stub 200
Conformance 761/761. 116/116 across 10 Step-4-adjacent suites
(http_multi_actor, http_route, http_publish, http_post_format,
http_marshal, http_publish_fold, http_listen_bif, http_server_start,
nx_kernel_multi, bootstrap_start, actor_lifecycle).
This commit is contained in:
@@ -302,29 +302,38 @@ post_activity_response() ->
|
||||
|
||||
handle_post_activity(Req, Cfg) ->
|
||||
case check_bearer(Req, Cfg) of
|
||||
ok ->
|
||||
{ok, ActorRef} ->
|
||||
F = accept_format_from(Req),
|
||||
publish_if_kernel(Req, F);
|
||||
publish_if_kernel(Req, F, ActorRef);
|
||||
{error, _} ->
|
||||
unauthorized_response()
|
||||
end.
|
||||
|
||||
%% publish_if_kernel/2 — if the nx_kernel gen_server is registered,
|
||||
%% publish_if_kernel/3 — if the nx_kernel gen_server is registered,
|
||||
%% delegate the publish there and translate the result. Otherwise
|
||||
%% keep the stub response so the auth-only tests stay green without
|
||||
%% having to spin up a kernel process. Format threads through to
|
||||
%% both stub and CID responses so the Content-Type matches what
|
||||
%% the client asked for via Accept.
|
||||
publish_if_kernel(Req, F) ->
|
||||
%%
|
||||
%% ActorRef is either an explicit ActorId atom (Step 4b token map
|
||||
%% resolution: route through nx_kernel:publish_to/2) or the atom
|
||||
%% `legacy` from a single :publish_token Cfg back-compat (route
|
||||
%% through nx_kernel:publish/1, which fans out to bucket 0).
|
||||
publish_if_kernel(Req, F, ActorRef) ->
|
||||
case erlang:whereis(nx_kernel) of
|
||||
undefined ->
|
||||
post_activity_response_for(F);
|
||||
_Pid ->
|
||||
Body = field(body, Req),
|
||||
Request = [{type, create}, {object, Body}],
|
||||
case nx_kernel:publish(Request) of
|
||||
{ok, Result} ->
|
||||
case envelope:get_field(cid, Result) of
|
||||
Result = case ActorRef of
|
||||
legacy -> nx_kernel:publish(Request);
|
||||
_ -> nx_kernel:publish_to(ActorRef, Request)
|
||||
end,
|
||||
case Result of
|
||||
{ok, R} ->
|
||||
case envelope:get_field(cid, R) of
|
||||
{ok, Cid} -> cid_response_for(Cid, F);
|
||||
_ -> post_activity_response_for(F)
|
||||
end;
|
||||
@@ -348,14 +357,36 @@ validation_failed_response() ->
|
||||
|
||||
check_bearer(Req, Cfg) ->
|
||||
case bearer_token(Req) of
|
||||
{ok, Got} ->
|
||||
case expected_token(Cfg) of
|
||||
{ok, Want} when Got =:= Want -> ok;
|
||||
_ -> {error, bad_token}
|
||||
end;
|
||||
{ok, Got} -> resolve_token(Got, Cfg);
|
||||
not_found -> {error, no_auth}
|
||||
end.
|
||||
|
||||
%% resolve_token/2 — map a bearer token to either an explicit
|
||||
%% ActorId (via Cfg's :tokens proplist) or the back-compat `legacy`
|
||||
%% atom (via the M1 single-actor :publish_token). The :tokens map
|
||||
%% takes precedence; if both are configured, :publish_token is only
|
||||
%% consulted when the token isn't present in :tokens.
|
||||
resolve_token(Got, Cfg) ->
|
||||
case field(tokens, Cfg) of
|
||||
nil -> resolve_legacy_token(Got, Cfg);
|
||||
Tokens ->
|
||||
case lookup_token(Got, Tokens) of
|
||||
{ok, ActorId} -> {ok, ActorId};
|
||||
not_found -> resolve_legacy_token(Got, Cfg)
|
||||
end
|
||||
end.
|
||||
|
||||
resolve_legacy_token(Got, Cfg) ->
|
||||
case field(publish_token, Cfg) of
|
||||
nil -> {error, no_token_match};
|
||||
Want when Got =:= Want -> {ok, legacy};
|
||||
_ -> {error, bad_token}
|
||||
end.
|
||||
|
||||
lookup_token(_, []) -> not_found;
|
||||
lookup_token(K, [{K, V} | _]) -> {ok, V};
|
||||
lookup_token(K, [_ | Rest]) -> lookup_token(K, Rest).
|
||||
|
||||
%% Look up the Authorization header, strip "Bearer ", return token.
|
||||
bearer_token(Req) ->
|
||||
case field(headers, Req) of
|
||||
@@ -383,11 +414,6 @@ strip_bearer(V) ->
|
||||
_ -> not_found
|
||||
end.
|
||||
|
||||
expected_token(Cfg) ->
|
||||
case field(publish_token, Cfg) of
|
||||
nil -> not_found;
|
||||
T -> {ok, T}
|
||||
end.
|
||||
|
||||
%% ── Step 8d: Accept-header parsing ──────────────────────────────
|
||||
%%
|
||||
|
||||
Reference in New Issue
Block a user