1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
|
defmodule Mix.Tasks.Hex.User do
use Mix.Task
@shortdoc "Manages your Hex user account"
@moduledoc """
Hex user tasks.
## Register a new user
$ mix hex.user register
## Print the current user
$ mix hex.user whoami
## Authorize a new user
Authorizes a new user on the local machine by generating a new API key and
storing it in the Hex config.
$ mix hex.user auth [--key-name KEY_NAME]
### Command line options
* `--key-name KEY_NAME` - By default Hex will base the key name on your machine's
hostname, use this option to give your own name.
## Deauthorize the user
Deauthorizes the user from the local machine by removing the API key from the Hex config.
$ mix hex.user deauth
## Generate user key
Generates an unencrypted API key for your account. Keys generated by this command will be owned
by you and will give access to your private resources, do not share this key with anyone. For
keys that will be shared by organization members use `mix hex.organization key` instead. By
default this command sets the `api:write` permission which allows write access to the API,
it can be overridden with the `--permission` flag.
$ mix hex.user key generate
### Command line options
* `--key-name KEY_NAME` - By default Hex will base the key name on your machine's
hostname, use this option to give your own name.
* `--permission PERMISSION` - Sets the permissions on the key, this option can be given
multiple times, possible values are:
* `api:read` - API read access.
* `api:write` - API write access.
* `repository:ORGANIZATION_NAME` - Access to given organization repository.
* `repositories` - Access to repositories for all organizations you are member of.
## Revoke key
Removes given key from account.
The key can no longer be used to authenticate API requests.
$ mix hex.user key revoke KEY_NAME
## Revoke all keys
Revoke all keys from your account.
$ mix hex.user key revoke --all
## List keys
Lists all keys associated with your account.
$ mix hex.user key list
## Reset user account password
Starts the process for resetting account password.
$ mix hex.user reset_password account
## Reset local password
Updates the local password for your local authentication credentials.
$ mix hex.user reset_password local
"""
@behaviour Hex.Mix.TaskDescription
@switches [
all: :boolean,
key_name: :string,
permission: [:string, :keep]
]
@impl true
def run(args) do
Hex.start()
{opts, args} = OptionParser.parse!(args, strict: @switches)
case args do
["register"] ->
register()
["whoami"] ->
whoami()
["auth"] ->
auth(opts)
["deauth"] ->
deauth()
["key", "generate"] ->
key_generate(opts)
["key", "revoke", key_name] ->
key_revoke(key_name)
["key", "revoke"] ->
if opts[:all], do: key_revoke_all(), else: invalid_args()
["key", "list"] ->
key_list()
["reset_password", "account"] ->
reset_account_password()
["reset_password", "local"] ->
reset_local_password()
_ ->
invalid_args()
end
end
@impl true
def tasks() do
[
{"register", "Register a new user"},
{"whoami", "Prints the current user"},
{"auth", "Authorize a new user"},
{"deauth", "Deauthorize the user"},
{"key generate", "Generate user key"},
{"key revoke KEY_NAME", "Removes given key from account"},
{"key revoke --all", "Revoke all keys"},
{"key list", "Lists all keys associated with your account"},
{"reset_password account", "Reset user account password"},
{"reset_password local", "Reset local password"}
]
end
defp invalid_args() do
Mix.raise("""
Invalid arguments, expected one of:
mix hex.user register
mix hex.user whoami
mix hex.user auth
mix hex.user deauth
mix hex.user key generate
mix hex.user key revoke KEY_NAME
mix hex.user key revoke --all
mix hex.user key list
mix hex.user reset_password account
mix hex.user reset_password local
""")
end
defp whoami() do
auth = Mix.Tasks.Hex.auth_info(:read)
case Hex.API.User.me(auth) do
{:ok, {code, body, _}} when code in 200..299 ->
Hex.Shell.info(body["username"])
other ->
Hex.Shell.error("Failed to auth")
Hex.Utils.print_error_result(other)
end
end
defp reset_account_password() do
name = Hex.Shell.prompt("Username or Email:") |> String.trim()
case Hex.API.User.password_reset(name) do
{:ok, {code, _, _}} when code in 200..299 ->
Hex.Shell.info(
"We’ve sent you an email containing a link that will allow you to reset " <>
"your account password for the next 24 hours. Please check your spam folder if the " <>
"email doesn’t appear within a few minutes."
)
other ->
Hex.Shell.error("Initiating password reset for #{name} failed")
Hex.Utils.print_error_result(other)
end
end
defp reset_local_password() do
encrypted_key = Hex.State.fetch!(:api_key_write)
read_key = Hex.State.fetch!(:api_key_read)
unless encrypted_key do
Mix.raise("No authorized user found. Run `mix hex.user auth`")
end
decrypted_key = Mix.Tasks.Hex.prompt_decrypt_key(encrypted_key, "Current local password")
Mix.Tasks.Hex.prompt_encrypt_key(decrypted_key, read_key, "New local password")
Hex.Shell.info("Password changed")
end
defp deauth() do
Mix.Tasks.Hex.update_keys(nil, nil)
deauth_organizations()
Hex.Shell.info(
"Authentication credentials removed from the local machine. " <>
"To authenticate again, run `mix hex.user auth` " <>
"or create a new user with `mix hex.user register`"
)
end
defp deauth_organizations() do
Hex.State.fetch!(:repos)
|> Enum.reject(fn {name, _config} -> String.starts_with?(name, "hexpm:") end)
|> Map.new()
|> Hex.Config.update_repos()
end
defp register() do
Hex.Shell.info("""
By registering an account on Hex.pm you accept all our \
policies and terms of service found at:
https://hex.pm/policies/codeofconduct
https://hex.pm/policies/termsofservice
https://hex.pm/policies/privacy
""")
username = Hex.Shell.prompt("Username:") |> String.trim()
email = Hex.Shell.prompt("Email:") |> String.trim()
password = Mix.Tasks.Hex.password_get("Account password:") |> String.trim()
confirm = Mix.Tasks.Hex.password_get("Account password (confirm):") |> String.trim()
if password != confirm do
Mix.raise("Entered passwords do not match")
end
Hex.Shell.info("Registering...")
create_user(username, email, password)
end
defp create_user(username, email, password) do
case Hex.API.User.new(username, email, password) do
{:ok, {code, _, _}} when code in 200..299 ->
Mix.Tasks.Hex.generate_all_user_keys(username, password)
Hex.Shell.info(
"You are required to confirm your email to access your account, " <>
"a confirmation email has been sent to #{email}"
)
other ->
Hex.Shell.error("Registration of user #{username} failed")
Hex.Utils.print_error_result(other)
end
end
defp auth(opts) do
Mix.Tasks.Hex.auth(opts)
end
defp key_revoke_all() do
auth = Mix.Tasks.Hex.auth_info(:write)
Hex.Shell.info("Revoking all keys...")
case Hex.API.Key.delete_all(auth) do
{:ok, {code, %{"name" => _, "authing_key" => true}, _headers}} when code in 200..299 ->
Mix.Tasks.Hex.User.run(["deauth"])
other ->
Hex.Shell.error("Key revocation failed")
Hex.Utils.print_error_result(other)
end
end
defp key_revoke(key) do
auth = Mix.Tasks.Hex.auth_info(:write)
Hex.Shell.info("Revoking key #{key}...")
case Hex.API.Key.delete(key, auth) do
{:ok, {200, %{"name" => ^key, "authing_key" => true}, _headers}} ->
Mix.Tasks.Hex.User.run(["deauth"])
:ok
{:ok, {code, _body, _headers}} when code in 200..299 ->
:ok
other ->
Hex.Shell.error("Key revocation failed")
Hex.Utils.print_error_result(other)
end
end
# TODO: print permissions
defp key_list() do
auth = Mix.Tasks.Hex.auth_info(:read)
case Hex.API.Key.get(auth) do
{:ok, {code, body, _headers}} when code in 200..299 ->
values =
Enum.map(body, fn %{"name" => name, "inserted_at" => time} ->
[name, time]
end)
Mix.Tasks.Hex.print_table(["Name", "Created at"], values)
other ->
Hex.Shell.error("Key fetching failed")
Hex.Utils.print_error_result(other)
end
end
defp key_generate(opts) do
username = Hex.Shell.prompt("Username:") |> String.trim()
password = Mix.Tasks.Hex.password_get("Account password:") |> String.trim()
key_name = Mix.Tasks.Hex.general_key_name(opts[:key_name])
permissions = Keyword.get_values(opts, :permission)
permissions = Mix.Tasks.Hex.convert_permissions(permissions) || [%{"domain" => "api"}]
Hex.Shell.info("Generating key...")
result =
Mix.Tasks.Hex.generate_user_key(
key_name,
permissions,
user: username,
pass: password
)
case result do
{:ok, secret} -> Hex.Shell.info(secret)
:error -> :ok
end
end
end
|