Packages

A Gleam library for sending emails via SMTP, inspired by the Rust mail-send crate

Current section

Files

Jump to
lumenmail src lumenmail@auth.erl
Raw

src/lumenmail@auth.erl

-module(lumenmail@auth).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lumenmail/auth.gleam").
-export([encode_plain/1, encode_login_username/1, encode_login_password/1, encode_cram_md5/2, encode_xoauth2/1, select_best_mechanism/2, mechanism_to_string/1, parse_mechanism/1]).
-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(
" Authentication module for SMTP.\n"
" Implements various SMTP authentication mechanisms.\n"
).
-file("src/lumenmail/auth.gleam", 15).
?DOC(
" Encodes credentials for PLAIN authentication.\n"
" Format: \\0username\\0password (base64 encoded)\n"
).
-spec encode_plain(lumenmail@types:credentials()) -> binary().
encode_plain(Credentials) ->
case Credentials of
{plain, Username, Password} ->
Auth_string = <<<<<<"\x{0000}"/utf8, Username/binary>>/binary,
"\x{0000}"/utf8>>/binary,
Password/binary>>,
gleam_stdlib:base64_encode(<<Auth_string/binary>>, true);
{o_auth2, Username@1, _} ->
Auth_string@1 = <<<<"\x{0000}"/utf8, Username@1/binary>>/binary,
"\x{0000}"/utf8>>,
gleam_stdlib:base64_encode(<<Auth_string@1/binary>>, true)
end.
-file("src/lumenmail/auth.gleam", 30).
?DOC(" Encodes the username for LOGIN authentication (first step).\n").
-spec encode_login_username(lumenmail@types:credentials()) -> binary().
encode_login_username(Credentials) ->
case Credentials of
{plain, Username, _} ->
gleam_stdlib:base64_encode(<<Username/binary>>, true);
{o_auth2, Username@1, _} ->
gleam_stdlib:base64_encode(<<Username@1/binary>>, true)
end.
-file("src/lumenmail/auth.gleam", 38).
?DOC(" Encodes the password for LOGIN authentication (second step).\n").
-spec encode_login_password(lumenmail@types:credentials()) -> binary().
encode_login_password(Credentials) ->
case Credentials of
{plain, _, Password} ->
gleam_stdlib:base64_encode(<<Password/binary>>, true);
{o_auth2, _, Token} ->
gleam_stdlib:base64_encode(<<Token/binary>>, true)
end.
-file("src/lumenmail/auth.gleam", 48).
?DOC(
" Generates CRAM-MD5 response from challenge.\n"
" Challenge is base64 encoded from server.\n"
" Response format: username HMAC-MD5-digest (base64 encoded)\n"
).
-spec encode_cram_md5(lumenmail@types:credentials(), binary()) -> binary().
encode_cram_md5(Credentials, Challenge) ->
case Credentials of
{plain, Username, Password} ->
case gleam@bit_array:base64_decode(Challenge) of
{error, _} ->
<<""/utf8>>;
{ok, Decoded} ->
case gleam@bit_array:to_string(Decoded) of
{error, _} ->
<<""/utf8>>;
{ok, Challenge_decoded} ->
Digest = gleam_crypto_ffi:hmac(
<<Challenge_decoded/binary>>,
md5,
<<Password/binary>>
),
Hex_digest = begin
_pipe = Digest,
_pipe@1 = gleam_stdlib:base16_encode(_pipe),
string:lowercase(_pipe@1)
end,
Response = <<<<Username/binary, " "/utf8>>/binary,
Hex_digest/binary>>,
gleam_stdlib:base64_encode(
<<Response/binary>>,
true
)
end
end;
{o_auth2, _, _} ->
<<""/utf8>>
end.
-file("src/lumenmail/auth.gleam", 87).
?DOC(
" Generates XOAUTH2 authentication string.\n"
" Format: user=<email>\\x01auth=Bearer <token>\\x01\\x01\n"
).
-spec encode_xoauth2(lumenmail@types:credentials()) -> binary().
encode_xoauth2(Credentials) ->
case Credentials of
{o_auth2, Username, Token} ->
Auth_string = <<<<<<<<"user="/utf8, Username/binary>>/binary,
"\x{0001}auth=Bearer "/utf8>>/binary,
Token/binary>>/binary,
"\x{0001}\x{0001}"/utf8>>,
gleam_stdlib:base64_encode(<<Auth_string/binary>>, true);
{plain, Username@1, Password} ->
Auth_string@1 = <<<<<<<<"user="/utf8, Username@1/binary>>/binary,
"\x{0001}auth=Bearer "/utf8>>/binary,
Password/binary>>/binary,
"\x{0001}\x{0001}"/utf8>>,
gleam_stdlib:base64_encode(<<Auth_string@1/binary>>, true)
end.
-file("src/lumenmail/auth.gleam", 113).
?DOC(
" Selects the best authentication mechanism from available options.\n"
" Prefers more secure mechanisms.\n"
).
-spec select_best_mechanism(
list(lumenmail@types:auth_mechanism()),
lumenmail@types:credentials()
) -> {ok, lumenmail@types:auth_mechanism()} | {error, binary()}.
select_best_mechanism(Available, Credentials) ->
case Credentials of
{o_auth2, _, _} ->
case gleam@list:contains(Available, auth_x_o_auth2) of
true ->
{ok, auth_x_o_auth2};
false ->
case gleam@list:contains(Available, auth_plain) of
true ->
{ok, auth_plain};
false ->
case gleam@list:contains(Available, auth_login) of
true ->
{ok, auth_login};
false ->
{error,
<<"No compatible authentication mechanism found"/utf8>>}
end
end
end;
{plain, _, _} ->
case gleam@list:contains(Available, auth_cram_md5) of
true ->
{ok, auth_cram_md5};
false ->
case gleam@list:contains(Available, auth_plain) of
true ->
{ok, auth_plain};
false ->
case gleam@list:contains(Available, auth_login) of
true ->
{ok, auth_login};
false ->
{error,
<<"No compatible authentication mechanism found"/utf8>>}
end
end
end
end.
-file("src/lumenmail/auth.gleam", 154).
?DOC(" Returns the SMTP command name for an auth mechanism.\n").
-spec mechanism_to_string(lumenmail@types:auth_mechanism()) -> binary().
mechanism_to_string(Mechanism) ->
case Mechanism of
auth_plain ->
<<"PLAIN"/utf8>>;
auth_login ->
<<"LOGIN"/utf8>>;
auth_cram_md5 ->
<<"CRAM-MD5"/utf8>>;
auth_x_o_auth2 ->
<<"XOAUTH2"/utf8>>
end.
-file("src/lumenmail/auth.gleam", 164).
?DOC(" Parses an auth mechanism string from server.\n").
-spec parse_mechanism(binary()) -> {ok, lumenmail@types:auth_mechanism()} |
{error, nil}.
parse_mechanism(S) ->
case string:uppercase(S) of
<<"PLAIN"/utf8>> ->
{ok, auth_plain};
<<"LOGIN"/utf8>> ->
{ok, auth_login};
<<"CRAM-MD5"/utf8>> ->
{ok, auth_cram_md5};
<<"XOAUTH2"/utf8>> ->
{ok, auth_x_o_auth2};
_ ->
{error, nil}
end.