-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({<>, Bits}) -> <>; b32_decode({<>, Bits}) -> <>; b32_decode({<>, Bits}) -> <>; b32_decode({<>) -> binary:list_to_bin(lists:flatten(io_lib:format("~40.16.0b", [X]))); hex_encode(<>) -> binary:list_to_bin(lists:flatten(io_lib:format("~64.16.0b", [X]))). hotp(Token, Time) -> K = b32_decode(Token), M = <>, 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, <

>}. 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, <>}) when Size > 0 -> Char = rand:uniform(26) + 96, rand_chars({Size - 1, <>}); rand_chars(Size) when Size > 0 -> rand_chars({Size, <<"">>}). rand_hash() -> {ok, R} = rand_chars(64), {ok, sha256(<<"utools:rand_hash(", R/binary, ")">>)}. sha256(<>) -> hex_encode(crypto:hash(sha256, Data)). totp(<>) -> totp(Secret, erlang:timestamp()). totp(<>, {M, S, _}) -> T = (M * 1000000 + S) / 30, Time = trunc(T), hotp(Secret, Time). totp_check(<>, <>) -> {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).