Packages

Auth layer for the Glimr web framework

Current section

Files

Jump to
glimr_auth src glimr_auth@hash.erl
Raw

src/glimr_auth@hash.erl

-module(glimr_auth@hash).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/glimr_auth/hash.gleam").
-export([make/1, verify/2, dummy_verify/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(
" Password Hashing\n"
"\n"
" Controllers shouldn't configure hashing parameters or\n"
" manage salts — those are security-critical details that\n"
" should be set once correctly. This module wraps the argus\n"
" library behind a two-function API (make/verify) so the\n"
" only decision callers make is what to hash or check.\n"
" Argon2id with OWASP-recommended defaults is used because\n"
" it resists both GPU and side-channel attacks better than\n"
" bcrypt or scrypt.\n"
"\n"
).
-file("src/glimr_auth/hash.gleam", 24).
?DOC(
" Generates a fresh random salt per call so identical\n"
" passwords produce different hashes — without this, an\n"
" attacker who compromised the database could spot users\n"
" sharing the same password. The assert on hash success is\n"
" safe because argus only fails on invalid parameters, which\n"
" can't happen with the default hasher config.\n"
).
-spec make(binary()) -> binary().
make(Password) ->
Salt = argus_nif:gen_salt(),
Hashes@1 = case begin
_pipe = argus:hasher(),
argus:hash(_pipe, Password, Salt)
end of
{ok, Hashes} -> Hashes;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"glimr_auth/hash"/utf8>>,
function => <<"make"/utf8>>,
line => 27,
value => _assert_fail,
start => 1007,
'end' => 1083,
pattern_start => 1018,
pattern_end => 1028})
end,
erlang:element(3, Hashes@1).
-file("src/glimr_auth/hash.gleam", 40).
?DOC(
" Collapsing both Ok(False) and Error(_) into False simplifies\n"
" the call site — callers only care whether the password\n"
" matched, not why verification failed. Argus handles constant-\n"
" time comparison internally so this function doesn't leak\n"
" timing information about partial matches.\n"
).
-spec verify(binary(), binary()) -> boolean().
verify(Password, Encoded_hash) ->
case argus:verify(Encoded_hash, Password) of
{ok, true} ->
true;
_ ->
false
end.
-file("src/glimr_auth/hash.gleam", 54).
?DOC(
" Without this, login attempts for nonexistent users return\n"
" instantly while real users take ~100ms for the hash — an\n"
" attacker can enumerate valid usernames by measuring response\n"
" times. Hashing a dummy value burns the same CPU time as a\n"
" real verify, making the timing indistinguishable regardless\n"
" of whether the account exists.\n"
).
-spec dummy_verify(binary()) -> boolean().
dummy_verify(Password) ->
Dummy_hash = make(<<"__glimr_dummy__"/utf8>>),
verify(Password, Dummy_hash).