mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-12-23 09:28:00 +01:00
86 lines
2.3 KiB
Elixir
86 lines
2.3 KiB
Elixir
|
defmodule PhilomenaWeb.Fingerprint do
|
||
|
import Plug.Conn
|
||
|
|
||
|
@type t :: String.t()
|
||
|
@name "_ses"
|
||
|
|
||
|
@doc """
|
||
|
Assign the current fingerprint to the conn.
|
||
|
"""
|
||
|
@spec fetch_fingerprint(Plug.Conn.t(), any()) :: Plug.Conn.t()
|
||
|
def fetch_fingerprint(conn, _opts) do
|
||
|
conn =
|
||
|
conn
|
||
|
|> fetch_session()
|
||
|
|> fetch_cookies()
|
||
|
|
||
|
# Try to get the fingerprint from the session, then from the cookie.
|
||
|
fingerprint = upgrade(get_session(conn, @name), conn.cookies[@name])
|
||
|
|
||
|
# If the fingerprint is valid, persist to session.
|
||
|
case valid_format?(fingerprint) do
|
||
|
true ->
|
||
|
conn
|
||
|
|> put_session(@name, fingerprint)
|
||
|
|> assign(:fingerprint, fingerprint)
|
||
|
|
||
|
false ->
|
||
|
assign(conn, :fingerprint, nil)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp upgrade(<<"c", _::binary>> = session_value, <<"d", _::binary>> = cookie_value) do
|
||
|
if valid_format?(cookie_value) do
|
||
|
# When both fingerprint values are valid and the session value
|
||
|
# is an old version, use the cookie value.
|
||
|
cookie_value
|
||
|
else
|
||
|
# Use the session value.
|
||
|
session_value
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defp upgrade(session_value, cookie_value) do
|
||
|
# Prefer the session value, using the cookie value if it is unavailable.
|
||
|
session_value || cookie_value
|
||
|
end
|
||
|
|
||
|
@doc """
|
||
|
Determine whether the fingerprint corresponds to a valid format.
|
||
|
|
||
|
Valid formats start with `c` or `d` (for the version). The `c` format is a legacy format
|
||
|
corresponding to an integer-valued hash from the frontend. The `d` format is the current
|
||
|
format corresponding to a hex-valued hash from the frontend. By design, it is not
|
||
|
possible to infer anything else about these values from the server.
|
||
|
|
||
|
See assets/js/fp.ts for additional information on the generation of the `d` format.
|
||
|
|
||
|
## Examples
|
||
|
|
||
|
iex> valid_format?("b2502085657")
|
||
|
false
|
||
|
|
||
|
iex> valid_format?("c637334158")
|
||
|
true
|
||
|
|
||
|
iex> valid_format?("d63c4581f8cf58d")
|
||
|
true
|
||
|
|
||
|
iex> valid_format?("5162549b16e8448")
|
||
|
false
|
||
|
|
||
|
"""
|
||
|
@spec valid_format?(any()) :: boolean()
|
||
|
def valid_format?(fingerprint)
|
||
|
|
||
|
def valid_format?(<<"c", rest::binary>>) when byte_size(rest) <= 12 do
|
||
|
match?({_result, ""}, Integer.parse(rest))
|
||
|
end
|
||
|
|
||
|
def valid_format?(<<"d", rest::binary>>) when byte_size(rest) == 14 do
|
||
|
match?({:ok, _result}, Base.decode16(rest, case: :lower))
|
||
|
end
|
||
|
|
||
|
def valid_format?(_fingerprint), do: false
|
||
|
end
|