zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

basic_auth.ex (5283B)


      1 defmodule Plug.BasicAuth do
      2   @moduledoc """
      3   Functionality for providing Basic HTTP authentication.
      4 
      5   It is recommended to only use this module in production
      6   if SSL is enabled and enforced. See `Plug.SSL` for more
      7   information.
      8 
      9   ## Compile-time usage
     10 
     11   If you have a single username and password, you can use
     12   the `basic_auth/2` plug:
     13 
     14       import Plug.BasicAuth
     15       plug :basic_auth, username: "hello", password: "secret"
     16 
     17   Or if you would rather put those in a config file:
     18 
     19       # lib/your_app.ex
     20       import Plug.BasicAuth
     21       plug :basic_auth, Application.compile_env(:my_app, :basic_auth)
     22 
     23       # config/config.exs
     24       config :my_app, :basic_auth, username: "hello", password: "secret"
     25 
     26   Once the user first accesses the page, the request will be denied
     27   with reason 401 and the request is halted. The browser will then
     28   prompt the user for username and password. If they match, then the
     29   request succeeds.
     30 
     31   Both approaches shown above rely on static configuration. Let's see
     32   alternatives.
     33 
     34   ## Runtime-time usage
     35 
     36   As any other Plug, we can use the `basic_auth` at runtime by simply
     37   wrapping it in a function:
     38 
     39       plug :auth
     40 
     41       defp auth(conn, opts) do
     42         username = System.fetch_env!("AUTH_USERNAME")
     43         password = System.fetch_env!("AUTH_PASSWORD")
     44         Plug.BasicAuth.basic_auth(conn, username: username, password: password)
     45       end
     46 
     47   This approach is useful when both username and password are specified
     48   upfront and available at runtime. However, you may also want to compute
     49   a different password for each different user. In those cases, we can use
     50   the low-level API.
     51 
     52   ## Low-level usage
     53 
     54   If you want to provide your own authentication logic on top of Basic HTTP
     55   auth, you can use the low-level functions. As an example, we define `:auth`
     56   plug that extracts username and password from the request headers, compares
     57   them against the database, and either assigns a `:current_user` on success
     58   or responds with an error on failure.
     59 
     60       plug :auth
     61 
     62       defp auth(conn, _opts) do
     63         with {user, pass} <- Plug.BasicAuth.parse_basic_auth(conn),
     64              %User{} = user <- MyApp.Accounts.find_by_username_and_password(user, pass) do
     65           assign(conn, :current_user, user)
     66         else
     67           _ -> conn |> Plug.BasicAuth.request_basic_auth() |> halt()
     68         end
     69       end
     70 
     71   Keep in mind that:
     72 
     73     * The supplied `user` and `pass` may be empty strings;
     74 
     75     * If you are comparing the username and password with existing strings,
     76       do not use `==/2`. Use `Plug.Crypto.secure_compare/2` instead.
     77 
     78   """
     79   import Plug.Conn
     80 
     81   @doc """
     82   Higher level usage of Basic HTTP auth.
     83 
     84   See the module docs for examples.
     85 
     86   ## Options
     87 
     88     * `:username` - the expected username
     89     * `:password` - the expected password
     90     * `:realm` - the authentication realm. The value is not fully
     91       sanitized, so do not accept user input as the realm and use
     92       strings with only alphanumeric characters and space
     93 
     94   """
     95   def basic_auth(conn, options \\ []) do
     96     username = Keyword.fetch!(options, :username)
     97     password = Keyword.fetch!(options, :password)
     98 
     99     with {request_username, request_password} <- parse_basic_auth(conn),
    100          valid_username? = Plug.Crypto.secure_compare(username, request_username),
    101          valid_password? = Plug.Crypto.secure_compare(password, request_password),
    102          true <- valid_username? and valid_password? do
    103       conn
    104     else
    105       _ -> conn |> request_basic_auth(options) |> halt()
    106     end
    107   end
    108 
    109   @doc """
    110   Parses the request username and password from Basic HTTP auth.
    111 
    112   It returns either `{user, pass}` or `:error`. Note the username
    113   and password may be empty strings. When comparing the username
    114   and password with the expected values, be sure to use
    115   `Plug.Crypto.secure_compare/2`.
    116 
    117   See the module docs for examples.
    118   """
    119   def parse_basic_auth(conn) do
    120     with ["Basic " <> encoded_user_and_pass] <- get_req_header(conn, "authorization"),
    121          {:ok, decoded_user_and_pass} <- Base.decode64(encoded_user_and_pass),
    122          [user, pass] <- :binary.split(decoded_user_and_pass, ":") do
    123       {user, pass}
    124     else
    125       _ -> :error
    126     end
    127   end
    128 
    129   @doc """
    130   Encodes a basic authentication header.
    131 
    132   This can be used during tests:
    133 
    134       put_req_header(conn, "authorization", encode_basic_auth("hello", "world"))
    135 
    136   """
    137   def encode_basic_auth(user, pass) when is_binary(user) and is_binary(pass) do
    138     "Basic " <> Base.encode64("#{user}:#{pass}")
    139   end
    140 
    141   @doc """
    142   Requests basic authentication from the client.
    143 
    144   It sets the response to status 401 with "Unauthorized" as body.
    145   The response is not sent though (nor the connection is halted),
    146   allowing developers to further customize it.
    147 
    148   ## Options
    149 
    150     * `:realm` - the authentication realm. The value is not fully
    151       sanitized, so do not accept user input as the realm and use
    152       strings with only alphanumeric characters and space
    153   """
    154   def request_basic_auth(conn, options \\ []) when is_list(options) do
    155     realm = Keyword.get(options, :realm, "Application")
    156     escaped_realm = String.replace(realm, "\"", "")
    157 
    158     conn
    159     |> put_resp_header("www-authenticate", "Basic realm=\"#{escaped_realm}\"")
    160     |> resp(401, "Unauthorized")
    161   end
    162 end