Ecto Configuration - Utilizing Input Values From A User

# config.exs

import Config
 @db_password Credentials.get_database_password()

config :arbit, Databse.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "database",
  username: "user",
  password: @db_password,
  hostname: "localhost",
  log: false,
  migration_timestamps: [type: :utc_datetime_usec]
#Credentials.ex

defmodule Credentials do
  def validate_entry(password_length, password) when password_length == 15, do: String.trim(password)

  def validate_entry(_, _) do
    Print.error("Incorrect password. Try again.")
    get_database_password()
  end

  def process_password_string_input() do
    password = request_password()

    String.length(password)
    |> validate_entry(password)
  end

  def request_password() do
    IO.gets("Enter database password: ")
  end

  def get_database_password(), do: process_password_string_input()
end

** (ArgumentError) cannot invoke @/1 outside module
    (elixir 1.11.2) lib/kernel.ex:5625: Kernel.assert_module_scope/3
    (elixir 1.11.2) expanding macro: Kernel.@/1
    config/config.exs:4: (file)

Goal:
Obtain the database password from the user in order to avoid hardcoding it in the config file. What’s the safest, most efficient way of accomplishing this?

are you trying to take db password as user input?

what is your use case here my friend for this weird execution?

Why are you not using environment variables?

The error is plain and simple, saying you can’t invoke @ outside of a module, which you are in the config.

Yup

How do you mean, precisely? :slight_smile:

# config/dev.exs
config :myapp, MyApp.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("DB_USER"),
password: System.get_env("DB_PASSWORD"),
database: System.get_env("DB_NAME"),
hostname: System.get_env("DB_HOST"),
pool_size: 10
#config.exs

config :my_app, My_AppDB.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "my_appdb",
  username: "user",
  password: System.get_env("db_password"),
  hostname: "localhost",
  log: false,
  migration_timestamps: [type: :utc_datetime_usec],
  pool_size: 10
#My_appDB_Credentials

defmodule My_appDB_Credentials do
  # def validate_entry(password_length, password) when password_length == 15, do: String.trim(password)
  
  def validate_entry(password_length, password) when password_length == 15 do
  String.trim(password)
  |> System.put_env("db_password", password)
  end

  def validate_entry(_, _) do
    Print.error("Incorrect password. Try again.")
    get_database_password()
  end

  def process_password_string_input() do
    password = request_password()

    String.length(password)
    |> validate_entry(password)
  end

  def request_password() do
    IO.gets("Enter database password: ")
  end

  def get_database_password(), do: process_passwordstring_input()
end
12:20:04.563 [error] GenServer #PID<0.668.0> terminating
** (RuntimeError) connect raised KeyError exception: key :password not found. The exception details are hidden, as they may contain sensitive data such as database credentials. You may set :show_sensitive_data_on_connection_error to true when starting your connection if you wish to see all of the details
    (elixir 1.11.2) lib/keyword.ex:420: Keyword.fetch!/2
    (postgrex 0.15.7) lib/postgrex/protocol.ex:722: Postgrex.Protocol.auth_md5/4
    (postgrex 0.15.7) lib/postgrex/protocol.ex:579: Postgrex.Protocol.handshake/2
    (db_connection 2.3.1) lib/db_connection/connection.ex:82: DBConnection.Connection.connect/2
    (connection 1.1.0) lib/connection.ex:622: Connection.enter_connect/5
    (stdlib 3.14) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: nil
State: Postgrex.Protocol

you do not need this file. Your config will read the env variables you have mentioned.

you should set the environment variable yourself from bash/terminal/shell/cmd or your OS env variables

And staying within Elixir? How would the db password be set as an environment variable from user input?

why would you need the user input?

You are not explaining yourself really.

$ export DB_PASSWORD='test'
$ mix run my app
1 Like

To avoid hardcoding sensitive information.

You pass those sensitive data as system env, not as user input.

For even considering this, your application should be running… but cannot because You did not provide those sensitive data.

You should look how it is done in config/prod.secret.exs.

2 Likes

Gotcha, thanks so much for the detailed explanation and reference :slight_smile: