+ map_validate/2: a simple way to validate maps + gitversion.mk removed
163 lines
5.5 KiB
Erlang
163 lines
5.5 KiB
Erlang
-module(utools).
|
|
|
|
-export([b32_decode/1]).
|
|
-export([b32_encode/1]).
|
|
-export([hex_encode/1]).
|
|
-export([hotp/2]).
|
|
-export([map_validate/2]).
|
|
-export([rand_bytes/0, rand_bytes/1]).
|
|
-export([rand_chars/1]).
|
|
-export([rand_hash/0]).
|
|
-export([sha256/1]).
|
|
-export([totp/1, totp/2]).
|
|
-export([totp_check/2]).
|
|
-export([totp_generate/0]).
|
|
|
|
b32_decode({<<V, "======">>, Bits}) ->
|
|
<<Bits/bits, (b32_std_dec(V) bsr 2):3>>;
|
|
b32_decode({<<V, "====">>, Bits}) ->
|
|
<<Bits/bits, (b32_std_dec(V) bsr 4):1>>;
|
|
b32_decode({<<V, "===">>, Bits}) ->
|
|
<<Bits/bits, (b32_std_dec(V) bsr 1):4>>;
|
|
b32_decode({<<V, "=">>, Bits}) ->
|
|
<<Bits/bits, (b32_std_dec(V) bsr 3):2>>;
|
|
b32_decode({<<V, R/binary>>, Bits}) ->
|
|
b32_decode({R, <<Bits/bits, (b32_std_dec(V)):5>>});
|
|
b32_decode({<<>>, Bits}) ->
|
|
Bits;
|
|
b32_decode(V) when is_list(V) ->
|
|
b32_decode(list_to_binary(V));
|
|
b32_decode(V) when is_binary(V) ->
|
|
b32_decode({V, <<>>}).
|
|
|
|
b32_encode({body, V}) ->
|
|
O = 5 * (byte_size(V) div 5),
|
|
<<Body:O/binary, R/binary>> = V,
|
|
{<< <<(b32_std_enc(X))>> || <<X:5>> <= Body>>, R};
|
|
b32_encode({rest, V}) ->
|
|
O = 5 * (bit_size(V) div 5),
|
|
<<Body:O/bits, Rest/bits>> = V,
|
|
B0 = << <<(b32_std_enc(X))>> || <<X:5>> <= Body>>,
|
|
{B1, Pad} = case Rest of
|
|
<<I:3>> -> {<<(b32_std_enc(I bsl 2))>>, 6};
|
|
<<I:1>> -> {<<(b32_std_enc(I bsl 4))>>, 4};
|
|
<<I:4>> -> {<<(b32_std_enc(I bsl 1))>>, 3};
|
|
<<I:2>> -> {<<(b32_std_enc(I bsl 3))>>, 1};
|
|
<<>> -> {<<>>, 0}
|
|
end,
|
|
{<<B0/binary, B1/binary>>, Pad};
|
|
b32_encode(V) when is_list(V) ->
|
|
b32_encode(list_to_binary(V));
|
|
b32_encode(V) when is_binary(V) ->
|
|
{EB, _R} = b32_encode({body, V}),
|
|
{ER, P} = b32_encode({rest, V}),
|
|
Padding = list_to_binary(lists:duplicate(P, $=)),
|
|
<<EB/binary, ER/binary, Padding/binary>>.
|
|
|
|
b32_std_dec(I) when I >= $2 andalso I =< $7 ->
|
|
I - 24;
|
|
b32_std_dec(I) when I >= $a andalso I =< $z ->
|
|
I - $a;
|
|
b32_std_dec(I) when I >= $A andalso I =< $Z ->
|
|
I - $A.
|
|
|
|
b32_std_enc(I) when is_integer(I) andalso I >= 26 andalso I =< 31 ->
|
|
I + 24;
|
|
b32_std_enc(I) when is_integer(I) andalso I >= 0 andalso I =< 25 ->
|
|
I + $a.
|
|
|
|
hex_encode(<<X:128/big-unsigned-integer>>) ->
|
|
binary:list_to_bin(lists:flatten(io_lib:format("~32.16.0b", [X])));
|
|
hex_encode(<<X:160/big-unsigned-integer>>) ->
|
|
binary:list_to_bin(lists:flatten(io_lib:format("~40.16.0b", [X])));
|
|
hex_encode(<<X:256/big-unsigned-integer>>) ->
|
|
binary:list_to_bin(lists:flatten(io_lib:format("~64.16.0b", [X]))).
|
|
|
|
hotp(Token, Time) ->
|
|
K = b32_decode(Token),
|
|
M = <<Time:8/big-unsigned-integer-unit:8>>,
|
|
D = crypto:mac(hmac, sha, K, M),
|
|
<<_:19/binary, _O:8>> = D,
|
|
O = _O band 15,
|
|
<<_TB:4/integer-unit:8>> = binary:part(D, O, 4),
|
|
TB = _TB band 16#7fffffff,
|
|
_T = TB rem trunc(math:pow(10, 6)),
|
|
T = integer_to_binary(_T),
|
|
P = << <<48:8>> || _ <- lists:seq(1, 6 - byte_size(T)) >>,
|
|
{ok, <<P/binary, T/binary>>}.
|
|
|
|
map_validate({_, _, {error, _} = Error}) ->
|
|
Error;
|
|
map_validate({Map, #{with := W} = Doc, R}) when is_list(W) ->
|
|
map_validate({maps:with(W, Map), maps:remove(with, Doc), R});
|
|
map_validate({Map, #{rules := []} = Doc, R}) ->
|
|
map_validate({Map, maps:remove(rules, Doc), R});
|
|
map_validate({Map, #{rules := [#{key := K, required := true} = Rule | T]} = Doc, R}) ->
|
|
map_validate({Map, Doc#{rules => T}, map_validate_rule(maps:get(K, Map, required), maps:remove(required, Rule), R)});
|
|
map_validate({Map, #{rules := [#{key := K, default := nil} = Rule | T]} = Doc, R}) ->
|
|
map_validate({Map, Doc#{rules => T}, map_validate_rule(maps:get(K, Map, nil), maps:remove(required, Rule), R)});
|
|
map_validate({Map, #{rules := [#{key := K, default := V} = Rule | T]} = Doc, R}) ->
|
|
map_validate({Map, Doc#{rules => T}, map_validate_rule(maps:get(K, Map, V), maps:remove(required, Rule), R)});
|
|
map_validate({Map, #{rules := [_ | T]} = Doc, R}) ->
|
|
map_validate({Map, Doc#{rules => T}, R});
|
|
map_validate({Map, #{merge := true} = Doc, R}) ->
|
|
map_validate({nil, maps:remove(merge, Doc), maps:merge(Map, R)});
|
|
map_validate({_, #{without := W}, R}) when is_list(W) ->
|
|
map_validate({nil, #{}, maps:without(W, R)});
|
|
map_validate({_, #{}, R}) ->
|
|
R.
|
|
|
|
map_validate(#{} = Map, #{} = Doc) ->
|
|
map_validate({Map, Doc, #{}});
|
|
map_validate(_, _) ->
|
|
{error, 'no data'}.
|
|
|
|
map_validate_rule(required, #{key := K}, _) ->
|
|
{error, K};
|
|
map_validate_rule(nil, _, M) ->
|
|
M;
|
|
map_validate_rule(V, #{prefn := FN} = Rule, M) when is_function(FN) ->
|
|
map_validate_rule(FN(V), maps:remove(prefn, Rule), M);
|
|
map_validate_rule(V, #{posfn := FN} = Rule, M) when is_function(FN) ->
|
|
map_validate_rule(FN(V), maps:remove(posfn, Rule), M);
|
|
map_validate_rule(V, #{key := K}, M) ->
|
|
M#{K => V};
|
|
map_validate_rule(_, _, _) ->
|
|
{error, invalid}.
|
|
|
|
rand_bytes() ->
|
|
rand_bytes(8).
|
|
|
|
rand_bytes(N) when is_integer(N) ->
|
|
{ok, crypto:strong_rand_bytes(N)}.
|
|
|
|
rand_chars({0, Swap}) ->
|
|
{ok, Swap};
|
|
rand_chars({Size, <<Swap/binary>>}) when Size > 0 ->
|
|
Char = rand:uniform(26) + 96,
|
|
rand_chars({Size - 1, <<Swap/binary, Char>>});
|
|
rand_chars(Size) when Size > 0 ->
|
|
rand_chars({Size, <<"">>}).
|
|
|
|
rand_hash() ->
|
|
{ok, R} = rand_chars(64),
|
|
{ok, sha256(<<"utools:rand_hash(", R/binary, ")">>)}.
|
|
|
|
sha256(<<Data/binary>>) ->
|
|
hex_encode(crypto:hash(sha256, Data)).
|
|
|
|
totp(<<Secret/binary>>) ->
|
|
totp(Secret, erlang:timestamp()).
|
|
|
|
totp(<<Secret/binary>>, {M, S, _}) ->
|
|
T = (M * 1000000 + S) / 30,
|
|
Time = trunc(T),
|
|
hotp(Secret, Time).
|
|
|
|
totp_check(<<Token/binary>>, <<Secret/binary>>) ->
|
|
{M, S, _} = erlang:timestamp(),
|
|
TL = [totp(Secret, {M, S - 30, 0}), totp(Secret, {M, S, 0}), totp(Secret, {M, S + 30, 0})],
|
|
{ok, lists:member({ok, Token}, TL)}.
|
|
|
|
totp_generate() ->
|
|
rand_chars(16).
|