Current section
Files
Jump to
Current section
Files
src/acumen@rotate_key.erl
-module(acumen@rotate_key).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/acumen/rotate_key.gleam").
-export([build/3, request/1, response/3]).
-export_type([request_builder/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Account key rotation for ACME.\n"
"\n"
" Key rotation allows you to replace your account's key pair while maintaining\n"
" account continuity.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import acumen\n"
" import acumen/rotate_key\n"
" import gose/jwk\n"
" import kryptos/ec\n"
"\n"
" // Generate a new key to rotate to\n"
" let new_key = jwk.generate_ec(ec.P256)\n"
"\n"
" // Build and execute the key change request\n"
" let change = rotate_key.request(new_key)\n"
"\n"
" let assert Ok(#(resp, ctx)) = acumen.execute(\n"
" ctx,\n"
" build: rotate_key.build(change, _, old_registered_key),\n"
" send: httpc.send,\n"
" )\n"
"\n"
" // Parse the response to get the new registered key\n"
" let assert Ok(new_registered_key) =\n"
" rotate_key.response(resp, new_key, old_registered_key)\n"
" ```\n"
).
-opaque request_builder() :: {request_builder, gose@jwk:jwk()}.
-file("src/acumen/rotate_key.gleam", 70).
-spec build_inner(gose@jwk:jwk(), acumen:registered_key(), acumen@url:url()) -> {ok,
gleam@json:json()} |
{error, binary()}.
build_inner(New_key, Old_key, Url) ->
gleam@result:'try'(
begin
_pipe = gose@jwk:public_key(erlang:element(2, Old_key)),
gleam@result:map_error(
_pipe,
fun acumen@internal@utils:gose_error_to_string/1
)
end,
fun(Old_public_key) ->
_pipe@1 = gleam@json:object(
[{<<"account"/utf8>>,
gleam@json:string(
acumen@url:to_string(erlang:element(3, Old_key))
)},
{<<"oldKey"/utf8>>, gose@jwk:to_json(Old_public_key)}]
),
_pipe@2 = gleam@json:to_string(_pipe@1),
acumen@internal@jws:sign_key_change_inner(New_key, _pipe@2, Url)
end
).
-file("src/acumen/rotate_key.gleam", 52).
?DOC(
" Builds the HTTP request for key rotation.\n"
"\n"
" Creates a nested JWS: inner payload `{account, oldKey}` signed with the\n"
" new key, wrapped in an outer JWS signed with the old key.\n"
).
-spec build(request_builder(), acumen:context(), acumen:registered_key()) -> {ok,
gleam@http@request:request(binary())} |
{error, acumen:acme_error()}.
build(Builder, Context, Key) ->
_pipe = build_inner(
erlang:element(2, Builder),
Key,
erlang:element(6, erlang:element(2, Context))
),
_pipe@1 = gleam@result:map(_pipe, fun gleam@json:to_string/1),
_pipe@2 = gleam@result:'try'(
_pipe@1,
fun(_capture) ->
acumen@internal@jws:sign_with_kid(
erlang:element(2, Key),
erlang:element(3, Key),
_capture,
erlang:element(3, Context),
erlang:element(6, erlang:element(2, Context))
)
end
),
_pipe@3 = gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {jws_error, Field@0} end
),
gleam@result:'try'(
_pipe@3,
fun(_capture@1) ->
acumen:build_post_request(
erlang:element(6, erlang:element(2, Context)),
_capture@1
)
end
).
-file("src/acumen/rotate_key.gleam", 89).
?DOC(" Creates a new key rotation request builder wrapping the replacement key.\n").
-spec request(gose@jwk:jwk()) -> request_builder().
request(New_key) ->
{request_builder, New_key}.
-file("src/acumen/rotate_key.gleam", 97).
?DOC(
" Parses the key rotation response.\n"
"\n"
" Returns a new `RegisteredKey` with the new key and the old key's\n"
" account URL.\n"
).
-spec response(
gleam@http@response:response(binary()),
gose@jwk:jwk(),
acumen:registered_key()
) -> {ok, acumen:registered_key()} | {error, acumen:acme_error()}.
response(Resp, New_key, Old_key) ->
case erlang:element(2, Resp) of
200 ->
{ok, {registered_key, New_key, erlang:element(3, Old_key)}};
_ ->
{error,
{invalid_response,
acumen@internal@utils:unexpected_status_message(
erlang:element(2, Resp)
)}}
end.