fed-sx-m1: Step 8c-post-auth — POST /activity bearer-token gate + route/2 + 13 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s

This commit is contained in:
2026-05-28 10:38:36 +00:00
parent 212bf53a03
commit f2aa294f00
3 changed files with 225 additions and 4 deletions

View File

@@ -1,11 +1,13 @@
-module(http_server).
-export([route/1, ok_response/1, not_found_response/0,
-export([route/1, route/2, ok_response/1, not_found_response/0,
welcome_body/0, capabilities_body/0,
capabilities_path/0,
match_prefix/2, actors_prefix/0, actor_doc_response/1,
artifacts_prefix/0, artifact_response/1,
projections_list_path/0, projections_prefix/0,
projections_list_response/0, projection_response/1]).
projections_list_response/0, projection_response/1,
activity_path/0, unauthorized_response/0,
post_activity_response/0]).
%% HTTP request router per design §16.1.
%%
@@ -26,9 +28,21 @@
%% `<<"GET">>` truncates to a single byte in this port.
route(Req) ->
route(Req, []).
%% route/2 — Cfg proplist carries optional `:publish_token` (binary)
%% for POST /activity auth. Other state (logs, projections, etc.) is
%% not yet threaded through — POST /activity returns a stub 200
%% once auth succeeds; real outbox:publish glue lands separately.
route(Req, Cfg) ->
M = field(method, Req),
P = field(path, Req),
dispatch(M, P).
case {M, P} of
{<<80,79,83,84>>, <<47,97,99,116,105,118,105,116,121>>} ->
handle_post_activity(Req, Cfg);
_ ->
dispatch(M, P)
end.
%% 71 69 84 = "GET" | 47 = "/"
dispatch(<<71, 69, 84>>, <<47>>) ->
@@ -161,3 +175,74 @@ projection_response(Name) ->
Pre = <<112,114,111,106,101,99,116,105,111,110,58,32>>,
Body = <<Pre/binary, Name/binary, 10>>,
ok_response(Body).
%% "/activity" — 9 bytes
activity_path() ->
<<47,97,99,116,105,118,105,116,121>>.
%% 401 Unauthorized response. Body: "unauthorized\n" = 13 bytes.
unauthorized_response() ->
[{status, 401}, {headers, []},
{body, <<117,110,97,117,116,104,111,114,105,122,101,100,10>>}].
%% Stub success body for POST /activity. Real impl will return
%% the published activity's CID once outbox:publish is wired
%% through a server-state context (Step 8c-post-publish).
post_activity_response() ->
%% "published (stub)\n" — hand-spelled
Body = <<112,117,98,108,105,115,104,101,100,32,
40,115,116,117,98,41,10>>,
ok_response(Body).
%% Auth helpers.
handle_post_activity(Req, Cfg) ->
case check_bearer(Req, Cfg) of
ok ->
post_activity_response();
{error, _} ->
unauthorized_response()
end.
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;
not_found -> {error, no_auth}
end.
%% Look up the Authorization header, strip "Bearer ", return token.
bearer_token(Req) ->
case field(headers, Req) of
nil -> not_found;
Hs ->
%% "authorization" — 13 bytes, lowercase as the BIF wrapper
%% normalises headers to lowercase keys.
AuthKey = <<97,117,116,104,111,114,105,122,97,116,105,111,110>>,
case find_header(AuthKey, Hs) of
not_found -> not_found;
{ok, V} -> strip_bearer(V)
end
end.
find_header(_, []) -> not_found;
find_header(K, [{K, V} | _]) -> {ok, V};
find_header(K, [_ | Rest]) -> find_header(K, Rest).
%% "Bearer " — 7 bytes — strip and return the rest as the token.
%% Anything else returns not_found (treated as missing auth).
strip_bearer(V) ->
Prefix = <<66,101,97,114,101,114,32>>,
case match_prefix(Prefix, V) of
{ok, Token} when byte_size(Token) > 0 -> {ok, Token};
_ -> not_found
end.
expected_token(Cfg) ->
case field(publish_token, Cfg) of
nil -> not_found;
T -> {ok, T}
end.