-module(wisp_basic_auth). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/wisp_basic_auth.gleam"). -export([parse_credentials/1, validate_basic_auth/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/wisp_basic_auth.gleam", 63). ?DOC( " Parse a list of credentials in the format `client:password`\n" " separated by semi-colons.\n" " \n" " ```\n" " parse_credentials(\"a:A;b:B\")\n" " // -> [#(\"a\", \"A\"), #(\"b\", \"B\")]\n" " ```\n" ). -spec parse_credentials(binary()) -> list({binary(), binary()}). parse_credentials(Credentials) -> Split_credential = fun(Credential) -> case gleam@string:split_once(Credential, <<":"/utf8>>) of {ok, Credential@1} -> [Credential@1]; {error, _} -> [] end end, _pipe = Credentials, _pipe@1 = gleam@string:split(_pipe, <<";"/utf8>>), gleam@list:flat_map(_pipe@1, Split_credential). -file("src/wisp_basic_auth.gleam", 82). -spec encode_creds(binary(), binary()) -> binary(). encode_creds(Id, Secret) -> Expected_creds = <<<>/binary, Secret/binary>>, Encoded_creds = gleam_stdlib:base64_encode( gleam_stdlib:identity(Expected_creds), true ), <<"Basic "/utf8, Encoded_creds/binary>>. -file("src/wisp_basic_auth.gleam", 76). -spec precalculate_credentials(list({binary(), binary()})) -> list(binary()). precalculate_credentials(Known_clients) -> gleam@list:map( Known_clients, fun(Id_secret) -> encode_creds( erlang:element(1, Id_secret), erlang:element(2, Id_secret) ) end ). -file("src/wisp_basic_auth.gleam", 89). -spec unauthorized_response(binary()) -> gleam@http@response:response(wisp:body()). unauthorized_response(Realm) -> Realm@1 = <<<<"Basic realm=\""/utf8, Realm/binary>>/binary, "\""/utf8>>, _pipe = 401, _pipe@1 = wisp:response(_pipe), _pipe@2 = wisp:string_body(_pipe@1, <<"Unauthorized"/utf8>>), fun gleam@http@response:set_header/3( _pipe@2, <<"WWW-Authenticate"/utf8>>, Realm@1 ). -file("src/wisp_basic_auth.gleam", 97). -spec forbidden_response() -> gleam@http@response:response(wisp:body()). forbidden_response() -> _pipe = 403, _pipe@1 = wisp:response(_pipe), wisp:string_body(_pipe@1, <<"Forbidden"/utf8>>). -file("src/wisp_basic_auth.gleam", 37). ?DOC( " Middleware that validates an `Authorization: Basic` header\n" " against a known list of client ids and passwords within a realm.\n" " \n" " The basic authentication scheme is based on the model that the \n" " user agent must authenticate itself with a user-ID and a password \n" " for each realm.\n" " \n" " The realm value should be considered an opaque string which can \n" " only be compared for equality with other realms on that server.\n" " \n" " Example header: `Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==`\n" " \n" " Using `curl` to set the header:\n" " \n" " ```bash\n" " curl -X POST -u \"client:password\" ... https://example.com\n" " ```\n" " \n" " Set the middleware in your router:\n" " \n" " ```gleam\n" " let validate_basic_auth = wisp_basic_auth.validate_basic_auth(realm, known_clients)\n" " use _ <- validate_basic_auth(request)\n" " ```\n" ). -spec validate_basic_auth(binary(), list({binary(), binary()})) -> fun((gleam@http@request:request(wisp@internal:connection()), fun((gleam@http@request:request(wisp@internal:connection())) -> gleam@http@response:response(wisp:body()))) -> gleam@http@response:response(wisp:body())). validate_basic_auth(Realm, Known_clients) -> Authorized_clients = precalculate_credentials(Known_clients), fun(Request, Handler) -> case gleam@http@request:get_header(Request, <<"Authorization"/utf8>>) of {error, _} -> unauthorized_response(Realm); {ok, Auth_header} -> case gleam@list:contains(Authorized_clients, Auth_header) of true -> Handler(Request); false -> forbidden_response() end end end.