Current section
Files
Jump to
Current section
Files
src/glupbit@auth.erl
-module(glupbit@auth).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glupbit/auth.gleam").
-export([access_key/1, secret_key/1, credentials/2, credentials_from_env/0, sha512_hex/1, generate_token/1, generate_token_with_query/2, generate_token_with_body/2]).
-export_type([access_key/0, secret_key/0, credentials/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(
" JWT HS512 authentication for Upbit Exchange API.\n"
"\n"
" Generates signed tokens with `access_key`, `nonce`, and optional\n"
" `query_hash` / `query_hash_alg` claims using the **gose** JOSE library.\n"
).
-opaque access_key() :: {access_key, binary()}.
-opaque secret_key() :: {secret_key, binary()}.
-opaque credentials() :: {credentials, access_key(), secret_key()}.
-file("src/glupbit/auth.gleam", 41).
?DOC(" Wrap a raw string as an AccessKey.\n").
-spec access_key(binary()) -> access_key().
access_key(Key) ->
{access_key, Key}.
-file("src/glupbit/auth.gleam", 46).
?DOC(" Wrap a raw string as a SecretKey.\n").
-spec secret_key(binary()) -> secret_key().
secret_key(Key) ->
{secret_key, Key}.
-file("src/glupbit/auth.gleam", 51).
?DOC(" Bundle an access key and secret key into Credentials.\n").
-spec credentials(access_key(), secret_key()) -> credentials().
credentials(Ak, Sk) ->
{credentials, Ak, Sk}.
-file("src/glupbit/auth.gleam", 57).
?DOC(
" Load credentials from `UPBIT_OPEN_API_ACCESS_KEY` and\n"
" `UPBIT_OPEN_API_SECRET_KEY` environment variables.\n"
).
-spec credentials_from_env() -> {ok, credentials()} | {error, binary()}.
credentials_from_env() ->
gleam@result:'try'(
begin
_pipe = envoy_ffi:get(<<"UPBIT_OPEN_API_ACCESS_KEY"/utf8>>),
gleam@result:replace_error(
_pipe,
<<"UPBIT_OPEN_API_ACCESS_KEY not set"/utf8>>
)
end,
fun(Ak) ->
gleam@result:'try'(
begin
_pipe@1 = envoy_ffi:get(
<<"UPBIT_OPEN_API_SECRET_KEY"/utf8>>
),
gleam@result:replace_error(
_pipe@1,
<<"UPBIT_OPEN_API_SECRET_KEY not set"/utf8>>
)
end,
fun(Sk) ->
{ok, credentials({access_key, Ak}, {secret_key, Sk})}
end
)
end
).
-file("src/glupbit/auth.gleam", 101).
?DOC(" Compute the lowercase-hex SHA-512 digest of a string.\n").
-spec sha512_hex(binary()) -> binary().
sha512_hex(Input) ->
_pipe = Input,
_pipe@1 = gleam_stdlib:identity(_pipe),
_pipe@2 = gleam@crypto:hash(sha512, _pipe@1),
_pipe@3 = gleam_stdlib:base16_encode(_pipe@2),
string:lowercase(_pipe@3).
-file("src/glupbit/auth.gleam", 109).
-spec query_hash_claims(binary()) -> list({binary(), gleam@json:json()}).
query_hash_claims(Query_string) ->
[{<<"query_hash"/utf8>>, gleam@json:string(sha512_hex(Query_string))},
{<<"query_hash_alg"/utf8>>, gleam@json:string(<<"SHA512"/utf8>>)}].
-file("src/glupbit/auth.gleam", 154).
-spec jwt_error_to_api_error(gose@jwt:jwt_error()) -> glupbit@types:api_error().
jwt_error_to_api_error(Err) ->
{auth_error, <<"JWT: "/utf8, (gleam@string:inspect(Err))/binary>>}.
-file("src/glupbit/auth.gleam", 161).
?DOC(
" Zero-pad a BitArray to at least `min` bytes.\n"
" HMAC internally pads keys to block size, so this produces identical\n"
" signatures — it only satisfies gose's HS512 minimum key length check.\n"
).
-spec pad_to_min_bytes(bitstring(), integer()) -> bitstring().
pad_to_min_bytes(Bytes, Min) ->
Size = erlang:byte_size(Bytes),
case Size >= Min of
true ->
Bytes;
false ->
Padding_bits = (Min - Size) * 8,
gleam@bit_array:append(
Bytes,
<<0:(lists:max([(Padding_bits), 0]))>>
)
end.
-file("src/glupbit/auth.gleam", 117).
?DOC(" Build, sign and serialize a JWT with HS512.\n").
-spec sign_jwt(credentials(), list({binary(), gleam@json:json()})) -> {ok,
binary()} |
{error, glupbit@types:api_error()}.
sign_jwt(Creds, Extra_claims) ->
{credentials, {access_key, Ak}, {secret_key, Sk}} = Creds,
Key_bytes = gleam_stdlib:identity(Sk),
Key_bytes@1 = pad_to_min_bytes(Key_bytes, 64),
gleam@result:'try'(
begin
_pipe = Key_bytes@1,
_pipe@1 = gose@jwk:from_octet_bits(_pipe),
gleam@result:map_error(
_pipe@1,
fun(E) ->
{auth_error,
<<"JWK: "/utf8, (gose:error_message(E))/binary>>}
end
)
end,
fun(Key) ->
Base = [{<<"access_key"/utf8>>, gleam@json:string(Ak)},
{<<"nonce"/utf8>>, gleam@json:string(youid@uuid:v4_string())}],
gleam@result:'try'(
begin
_pipe@2 = lists:append(Base, Extra_claims),
gleam@list:try_fold(
_pipe@2,
gose@jwt:claims(),
fun(Claims, Pair) ->
_pipe@3 = gose@jwt:with_claim(
Claims,
erlang:element(1, Pair),
erlang:element(2, Pair)
),
gleam@result:map_error(
_pipe@3,
fun jwt_error_to_api_error/1
)
end
)
end,
fun(Claims@1) ->
gleam@result:'try'(
begin
_pipe@4 = gose@jwt:sign(
{jws_hmac, hmac_sha512},
Claims@1,
Key
),
gleam@result:map_error(
_pipe@4,
fun jwt_error_to_api_error/1
)
end,
fun(Signed) -> {ok, gose@jwt:serialize(Signed)} end
)
end
)
end
).
-file("src/glupbit/auth.gleam", 72).
?DOC(" Generate a JWT for requests **without** parameters.\n").
-spec generate_token(credentials()) -> {ok, binary()} |
{error, glupbit@types:api_error()}.
generate_token(Creds) ->
sign_jwt(Creds, []).
-file("src/glupbit/auth.gleam", 78).
?DOC(
" Generate a JWT for GET/DELETE requests **with** query parameters.\n"
" The raw `query_string` is SHA-512 hashed and included as `query_hash`.\n"
).
-spec generate_token_with_query(credentials(), binary()) -> {ok, binary()} |
{error, glupbit@types:api_error()}.
generate_token_with_query(Creds, Query_string) ->
sign_jwt(Creds, query_hash_claims(Query_string)).
-file("src/glupbit/auth.gleam", 87).
?DOC(
" Generate a JWT for POST requests with a JSON body.\n"
" Body params are converted to `key=value&...` format before hashing.\n"
).
-spec generate_token_with_body(credentials(), list({binary(), binary()})) -> {ok,
binary()} |
{error, glupbit@types:api_error()}.
generate_token_with_body(Creds, Params) ->
Query_string = begin
_pipe = Params,
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
<<<<(erlang:element(1, Pair))/binary, "="/utf8>>/binary,
(erlang:element(2, Pair))/binary>>
end
),
gleam@string:join(_pipe@1, <<"&"/utf8>>)
end,
sign_jwt(Creds, query_hash_claims(Query_string)).