-module(mongo@scram). -compile(no_auto_import). -export([first_payload/1, first_message/1, parse_first_reply/1, second_message/5, parse_second_reply/2, hi/3]). -spec first_payload(binary()) -> binary(). first_payload(Username) -> Nonce = begin _pipe = gleam@crypto:strong_random_bytes(24), gleam@base:encode64(_pipe, true) end, _pipe@2 = [<<"n="/utf8>>, begin _pipe@1 = Username, clean_username(_pipe@1) end, <<",r="/utf8>>, Nonce], gleam@string:concat(_pipe@2). -spec first_message(binary()) -> bson@types:value(). first_message(Payload) -> Payload@1 = begin _pipe = [<<"n,,"/utf8>>, Payload], _pipe@1 = gleam@string:concat(_pipe), bson@generic:from_string(_pipe@1) end, {document, [{<<"saslStart"/utf8>>, {boolean, true}}, {<<"mechanism"/utf8>>, {str, <<"SCRAM-SHA-256"/utf8>>}}, {<<"payload"/utf8>>, {binary, {generic, Payload@1}}}, {<<"autoAuthorize"/utf8>>, {boolean, true}}, {<<"options"/utf8>>, {document, [{<<"skipEmptyExchange"/utf8>>, {boolean, true}}]}}]}. -spec parse_first_reply(list({binary(), bson@types:value()})) -> {ok, {{binary(), binary(), integer()}, binary(), integer()}} | {error, nil}. parse_first_reply(Reply) -> case Reply of [{<<"ok"/utf8>>, {double, 0.0}} | _@1] -> {error, nil}; [{<<"conversationId"/utf8>>, {integer, Cid}}, {<<"done"/utf8>>, {boolean, false}}, {<<"payload"/utf8>>, {binary, {generic, Data}}}, {<<"ok"/utf8>>, {double, 1.0}}] -> case begin _pipe = Data, bson@generic:to_string(_pipe) end of {error, _try} -> {error, _try}; {ok, Data@1} -> case begin _pipe@1 = Data@1, parse_payload(_pipe@1) end of {error, _try@1} -> {error, _try@1}; {ok, [{<<"r"/utf8>>, Rnonce}, {<<"s"/utf8>>, Salt}, {<<"i"/utf8>>, I}]} -> case begin _pipe@2 = I, gleam@int:parse(_pipe@2) end of {ok, Iterations} -> case Iterations >= 4096 of true -> {ok, {{Rnonce, Salt, Iterations}, Data@1, Cid}}; false -> {error, nil} end; {error, nil} -> {error, nil} end end end end. -spec second_message( {binary(), binary(), integer()}, binary(), binary(), integer(), binary() ) -> {ok, {bson@types:value(), bitstring()}} | {error, nil}. second_message(Server_params, First_payload, Server_payload, Cid, Password) -> {Rnonce, Salt, Iterations} = Server_params, case begin _pipe = Salt, gleam@base:decode64(_pipe) end of {error, _try} -> {error, _try}; {ok, Salt@1} -> Salted_password = hi(Password, Salt@1, Iterations), Client_key = gleam@crypto:hmac( begin _pipe@1 = <<"Client Key"/utf8>>, gleam@bit_string:from_string(_pipe@1) end, sha256, Salted_password ), Server_key = gleam@crypto:hmac( begin _pipe@2 = <<"Server Key"/utf8>>, gleam@bit_string:from_string(_pipe@2) end, sha256, Salted_password ), Stored_key = gleam@crypto:hash(sha256, Client_key), Auth_message = begin _pipe@3 = [First_payload, <<","/utf8>>, Server_payload, <<",c=biws,r="/utf8>>, Rnonce], _pipe@4 = gleam@string:concat(_pipe@3), bson@generic:from_string(_pipe@4) end, Client_signature = gleam@crypto:hmac( begin _pipe@5 = Auth_message, bson@generic:to_bit_string(_pipe@5) end, sha256, Stored_key ), Second_payload = begin _pipe@7 = [<<"c=biws,r="/utf8>>, Rnonce, <<",p="/utf8>>, begin _pipe@6 = 'xor'( Client_key, Client_signature, <<>> ), gleam@base:encode64(_pipe@6, true) end], _pipe@8 = gleam@string:concat(_pipe@7), bson@generic:from_string(_pipe@8) end, Server_signature = gleam@crypto:hmac( begin _pipe@9 = Auth_message, bson@generic:to_bit_string(_pipe@9) end, sha256, Server_key ), {ok, {{document, [{<<"saslContinue"/utf8>>, {boolean, true}}, {<<"conversationId"/utf8>>, {integer, Cid}}, {<<"payload"/utf8>>, {binary, {generic, Second_payload}}}]}, Server_signature}} end. -spec parse_second_reply(list({binary(), bson@types:value()}), bitstring()) -> {ok, nil} | {error, nil}. parse_second_reply(Reply, Server_signature) -> case Reply of [{<<"ok"/utf8>>, {double, 0.0}} | _@1] -> {error, nil}; [{<<"conversationId"/utf8>>, _@2}, {<<"done"/utf8>>, {boolean, true}}, {<<"payload"/utf8>>, {binary, {generic, Data}}}, {<<"ok"/utf8>>, {double, 1.0}}] -> case begin _pipe = Data, bson@generic:to_string(_pipe) end of {error, _try} -> {error, _try}; {ok, Data@1} -> case parse_payload(Data@1) of {error, _try@1} -> {error, _try@1}; {ok, [{<<"v"/utf8>>, Data@2}]} -> case gleam@base:decode64(Data@2) of {error, _try@2} -> {error, _try@2}; {ok, Received_signature} -> case (gleam@bit_string:byte_size( Server_signature ) =:= gleam@bit_string:byte_size( Received_signature )) andalso gleam@crypto:secure_compare( Server_signature, Received_signature ) of true -> {ok, nil}; false -> {error, nil} end end end end end. -spec parse_payload(binary()) -> {ok, list({binary(), binary()})} | {error, nil}. parse_payload(Payload) -> _pipe = Payload, _pipe@1 = gleam@string:split(_pipe, <<","/utf8>>), gleam@list:try_map( _pipe@1, fun(Item) -> _pipe@2 = Item, gleam@string:split_once(_pipe@2, <<"="/utf8>>) end ). -spec clean_username(binary()) -> binary(). clean_username(Username) -> _pipe = Username, _pipe@1 = gleam@string:replace(_pipe, <<"="/utf8>>, <<"=3D"/utf8>>), gleam@string:replace(_pipe@1, <<","/utf8>>, <<"=2C"/utf8>>). -spec hi(binary(), bitstring(), integer()) -> bitstring(). hi(Password, Salt, Iterations) -> crypto:pbkdf2_hmac(sha256, Password, Salt, Iterations, 32). -spec 'xor'(bitstring(), bitstring(), bitstring()) -> bitstring(). 'xor'(A, B, Storage) -> <> = A, <> = B, New_storage = begin _pipe = [Storage, <<(gleam@bitwise:exclusive_or(Fa, Fb))>>], gleam@bit_string:concat(_pipe) end, case [Ra, Rb] of [<<>>, <<>>] -> New_storage; _@1 -> 'xor'(Ra, Rb, New_storage) end.