neiam
/feedpug
feedpub
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Add set_password release command
608eff1 · gmorell · 2026-06-23 19:48
Message
{commit_body(@commit)}
Files changed
modified
lib/feed_pug/release.ex
+59
−0
@@ -73,6 +73,65 @@ defmodule FeedPug.Release do
| 73 | 73 | end |
| 74 | 74 | end |
| 75 | 75 | |
| 76 | + @doc """ | |
| 77 | + Sets (creates/resets) the password for an existing user, found by email. Useful when email | |
| 78 | + delivery is down and the normal "forgot password" flow can't reach the user. | |
| 79 | + | |
| 80 | + Pass a password, or omit it to have a strong one generated and printed. All of the user's | |
| 81 | + existing sessions/tokens are invalidated on change (standard phx.gen.auth behaviour). | |
| 82 | + | |
| 83 | + bin/feed_pug eval 'FeedPug.Release.set_password("user@example.com", "a-long-passphrase")' | |
| 84 | + bin/feed_pug eval 'FeedPug.Release.set_password("user@example.com")' # generates one | |
| 85 | + # or: bin/set-password user@example.com [password] | |
| 86 | + """ | |
| 87 | + def set_password(email, password \\ nil) when is_binary(email) do | |
| 88 | + load_app() | |
| 89 | + | |
| 90 | + {password, generated?} = | |
| 91 | + case password do | |
| 92 | + nil -> {random_password(), true} | |
| 93 | + p when is_binary(p) -> {p, false} | |
| 94 | + end | |
| 95 | + | |
| 96 | + {:ok, result, _} = | |
| 97 | + Ecto.Migrator.with_repo(FeedPug.Repo, fn _repo -> | |
| 98 | + case FeedPug.Accounts.get_user_by_email(email) do | |
| 99 | + nil -> {:error, :not_found} | |
| 100 | + user -> FeedPug.Accounts.update_user_password(user, %{password: password}) | |
| 101 | + end | |
| 102 | + end) | |
| 103 | + | |
| 104 | + case result do | |
| 105 | + {:ok, {_user, _expired_tokens}} -> | |
| 106 | + IO.puts("✓ password set for #{email}") | |
| 107 | + if generated?, do: IO.puts("\n generated password: #{password}\n") | |
| 108 | + :ok | |
| 109 | + | |
| 110 | + {:error, :not_found} -> | |
| 111 | + IO.puts("No user found with email #{inspect(email)}") | |
| 112 | + System.halt(1) | |
| 113 | + | |
| 114 | + {:error, %Ecto.Changeset{} = changeset} -> | |
| 115 | + IO.puts("Failed to set password:\n#{format_errors(changeset)}") | |
| 116 | + System.halt(1) | |
| 117 | + end | |
| 118 | + end | |
| 119 | + | |
| 120 | + # A strong, URL-safe random password comfortably above the 12-char minimum. | |
| 121 | + defp random_password do | |
| 122 | + :crypto.strong_rand_bytes(18) |> Base.url_encode64(padding: false) | |
| 123 | + end | |
| 124 | + | |
| 125 | + defp format_errors(changeset) do | |
| 126 | + changeset | |
| 127 | + |> Ecto.Changeset.traverse_errors(fn {msg, opts} -> | |
| 128 | + Enum.reduce(opts, msg, fn {key, value}, acc -> | |
| 129 | + String.replace(acc, "%{#{key}}", to_string(value)) | |
| 130 | + end) | |
| 131 | + end) | |
| 132 | + |> Enum.map_join("\n", fn {field, msgs} -> " #{field}: #{Enum.join(msgs, ", ")}" end) | |
| 133 | + end | |
| 134 | + | |
| 76 | 135 | defp repos do |
| 77 | 136 | Application.fetch_env!(@app, :ecto_repos) |
| 78 | 137 | end |
added
rel/overlays/bin/set-password
+17
−0
@@ -0,0 +1,17 @@
| 1 | +#!/bin/sh | |
| 2 | +set -eu | |
| 3 | + | |
| 4 | +cd -P -- "$(dirname -- "$0")" | |
| 5 | + | |
| 6 | +# Usage: bin/set-password <email> [password] | |
| 7 | +# Generates a strong password when none is given (and prints it). | |
| 8 | +if [ "$#" -lt 1 ]; then | |
| 9 | + echo "usage: bin/set-password <email> [password]" >&2 | |
| 10 | + exit 2 | |
| 11 | +fi | |
| 12 | + | |
| 13 | +if [ "$#" -ge 2 ]; then | |
| 14 | + exec ./feed_pug eval "FeedPug.Release.set_password(\"$1\", \"$2\")" | |
| 15 | +else | |
| 16 | + exec ./feed_pug eval "FeedPug.Release.set_password(\"$1\")" | |
| 17 | +fi |
Parents: f68263a