okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.pettest: build the oauth test app without updating a literal4006092

main
3.1 KiB69 linesraw
1%% The Google account refresh and Colab account choice against a one-shot local token endpoint.
2-module(albedo_google_oauth_test_support).
3-export([refresh/2, signed_in/1]).
4
5-define(APP, #{name => <<"Google Colab">>, client_id => <<"id">>, client_secret => <<"secret">>,
6               key => <<"google-colab">>, required => [<<"access">>, <<"refresh">>]}).
7
8%% Refreshes the first account of AccountsJson against a server answering Status
9%% and Body: {Result, creds.json after, the form the server received}.
10refresh(AccountsJson, {Status, Body}) ->
11    Home = home(AccountsJson),
12    {Url, Await} = serve(Status, Body),
13    Path = albedo_credentials:creds_path(Home),
14    {ok, #{<<"google-colab">> := [First | _]}} = albedo_credentials:accounts(Path),
15    Result = case albedo_google_oauth:ensure(maps:put(token_url, Url, ?APP), Path, First) of
16        {ok, #{<<"access">> := Access}} -> {ok, Access};
17        {error, invalid} -> {error, <<"invalid">>};
18        {error, _} = Error -> Error
19    end,
20    {Result, file_text(Path), Await()}.
21
22signed_in(AccountsJson) ->
23    albedo_colab_auth:signed_in(home(AccountsJson)).
24
25home(AccountsJson) ->
26    Home = filename:join(os:getenv("TMPDIR", "/tmp"), "albedo-google-" ++ integer_to_list(erlang:unique_integer([positive]))),
27    ok = filelib:ensure_path(Home),
28    ok = file:write_file(filename:join(Home, "creds.json"),
29                         json:encode(#{<<"accounts">> => #{<<"google-colab">> => json:decode(AccountsJson)}})),
30    Home.
31
32file_text(Path) ->
33    {ok, Text} = file:read_file(Path),
34    Text.
35
36%% A one-shot token endpoint. The request reaches the parent before the
37%% answer goes out, so by the time the client has its answer `Await` already
38%% holds it; closing the listener ends a server nobody called.
39serve(Status, Body) ->
40    {ok, Listen} = gen_tcp:listen(0, [binary, {active, false}, {packet, http_bin}, {ip, {127, 0, 0, 1}}]),
41    {ok, {_, Port}} = inet:sockname(Listen),
42    Parent = self(),
43    Ref = make_ref(),
44    spawn(fun() ->
45        case gen_tcp:accept(Listen) of
46            {ok, Socket} ->
47                {ok, {http_request, _, _, _}} = gen_tcp:recv(Socket, 0, 30000),
48                Length = headers(Socket, 0),
49                ok = inet:setopts(Socket, [{packet, raw}]),
50                {ok, Form} = gen_tcp:recv(Socket, Length, 30000),
51                Parent ! {Ref, Form},
52                ok = gen_tcp:send(Socket, ["HTTP/1.1 ", integer_to_list(Status), " X\r\nContent-Type: application/json\r\n",
53                    "Connection: close\r\nContent-Length: ", integer_to_list(byte_size(Body)), "\r\n\r\n", Body]),
54                gen_tcp:close(Socket);
55            {error, _} -> ok
56        end
57    end),
58    Url = "http://127.0.0.1:" ++ integer_to_list(Port) ++ "/token",
59    {Url, fun() ->
60        ok = gen_tcp:close(Listen),
61        receive {Ref, Form} -> Form after 0 -> <<"no request">> end
62    end}.
63
64headers(Socket, Length) ->
65    case gen_tcp:recv(Socket, 0, 30000) of
66        {ok, http_eoh} -> Length;
67        {ok, {http_header, _, 'Content-Length', _, Value}} -> headers(Socket, binary_to_integer(Value));
68        {ok, {http_header, _, _, _, _}} -> headers(Socket, Length)
69    end.