Skip to content

Instantly share code, notes, and snippets.

@foxnewsnetwork
Created September 5, 2015 23:54
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save foxnewsnetwork/bbf373486f557f39902f to your computer and use it in GitHub Desktop.
Save foxnewsnetwork/bbf373486f557f39902f to your computer and use it in GitHub Desktop.
Elixir application module attributes production loading

The setup

In my Elixir phoenix web app, I have a plug which handles requests made to an internal endpoint which forbids access to all users without a proper authorization in the request header.

Sounds simple right? Well it is, the below is my plug. It checks if the incoming request has a "simwms-master-key" field, and passes all users who have that key.

defmodule Apiv3.Plugs.MasterKey do
import Plug.Conn
import Phoenix.Controller, only: [render: 4]
@master_key Application.get_env(:simwms, :master_key)
def init(o), do: o
def call(conn, _o) do
case conn |> has_master_key? do
{:error, msg} ->
conn
|> put_status(:forbidden)
|> render(Apiv3.ErrorView, "forbidden.json", msg: msg)
|> halt
{:ok, _} -> conn
end
end
defp has_master_key?(conn) do
conn
|> get_req_header("simwms-master-key")
|> List.first
|> match_master_key?
end
defp match_master_key?(request_key) do
case {request_key, @master_key} do
{nil, nil} -> {:error, "missing both server and request master key"}
{nil, _} -> {:error, "missing request master key"}
{_, nil} -> {:error, "missing server master key"}
{key, key} -> {:ok, "match"}
{key, _} -> {:error, "request key doesn't match server key"}
end
end
end

The Problem

After writing tests and running them agianst the rest of my application, great, I thought, this code works time to deploy in production... and that's when everything went to shit:

Every request was being hit with a 403 because the server had a nil master_key despite the fact the header key was definite present on the production machine. Furthermore, other requests to the production machine were working, so this meant it wasn't some PaaS provider configuration problem.

This sort of bug is every programmer's greatest fear: a production-only bug that is my fault. To make things worse, I had only been working with Elixir for a few months, and so didn't have the wealth of experience to draw upon as I could've had with Ruby or Javascript... and although Elixir is built by a guy from Rails (Jose Valim), no amount of clicking my heels and chanting "there's no place like home" was going to make a compiled functional language on the EVM suddenly become MRI-ruby on rails. What do I do?

The solution

After exhausting the first and most obvious solution of "just ask online", I had no choice but to fall back on the tried-and-true method of spending my weekend bashing my head against various desparate hypothesises in hopes one of them turns out to be correct (some people call this science, I call this spray-and-pray).

So here's the fix:

instead of:

@master_key Application.get_env(:simwms, :master_key)

It should've have been:

def master_key do
  Application.get_env(:simwms, :master_key)
end

Why

The exact reason for why master_key had to be a function def instead of a module attribute is because, on compilation of the code, @master_key would be set before Application even had a chance to load all the configuration files. Elixir doesn't throw an error on this, and instead just carries on with @master_key being nil the whole time. Meanwhile, the function definition version reads from the Application only when the function is called; the on-demand nature of this thus ensures that the proper value is delivered in the plug.

Conclusion

Don't use module attributes for anything other than literals.

@mraaroncruz
Copy link

🍻

@atlas-comstock
Copy link

atlas-comstock commented Aug 16, 2018

on compilation of the code, @master_key would be set before Application even had a chance to load all the configuration files

I don't think this is a correct explanation.

Did you recompile the app after config's env is set?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment