forked from lix-project/hydra
176 lines
5.7 KiB
Perl
Executable file
176 lines
5.7 KiB
Perl
Executable file
#! /usr/bin/env perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Hydra::Schema;
|
|
use Hydra::Helper::Nix;
|
|
use Hydra::Model::DB;
|
|
use Term::ReadKey;
|
|
use Getopt::Long qw(:config gnu_getopt);
|
|
|
|
sub showHelp {
|
|
print q%
|
|
Usage: hydra-create-user NAME
|
|
[--rename-from NAME]
|
|
[--type hydra|google|github]
|
|
[--full-name FULLNAME]
|
|
[--email-address EMAIL-ADDRESS]
|
|
[--password-prompt]
|
|
[--password-hash HASH]
|
|
[--password PASSWORD (dangerous)]
|
|
[--wipe-roles]
|
|
[--role ROLE]...
|
|
|
|
Create a new Hydra user account, or update or an existing one. The
|
|
--role flag can be given multiple times. If the account already
|
|
exists, roles are added to the existing roles unless --wipe-roles is
|
|
specified. If --rename-from is given, the specified account is
|
|
renamed.
|
|
|
|
* Specifying Passwords
|
|
|
|
** Interactively
|
|
|
|
Pass `--password-prompt` to collect the password on stdin.
|
|
|
|
The password will be hashed with Argon2id when stored.
|
|
|
|
Example:
|
|
|
|
$ hydra-create-user alice --password-prompt --role admin
|
|
|
|
** Specifying a Hash
|
|
|
|
You can generate a password hash and provide the hash as well. This
|
|
is useful so a user can send the administrator their password pre-hashed,
|
|
allowing the user to get their preferred password without exposing it
|
|
to the administrator.
|
|
|
|
Hydra uses Argon2id hashes, which can be generated like so:
|
|
|
|
$ nix-shell -p libargon2
|
|
[nix-shell]$ tr -d \\\\n | argon2 "$(LC_ALL=C tr -dc '[:alnum:]' < /dev/urandom | head -c16)" -id -t 3 -k 262144 -p 1 -l 16 -e
|
|
foobar
|
|
Ctrl^D
|
|
$argon2id$v=19$m=262144,t=3,p=1$NFU1QXJRNnc4V1BhQ0NJQg$6GHqjqv5cNDDwZqrqUD0zQ
|
|
|
|
Example:
|
|
|
|
$ hydra-create-user alice --password-hash '$argon2id$v=19$m=262144,t=3,p=1$NFU1QXJRNnc4V1BhQ0NJQg$6GHqjqv5cNDDwZqrqUD0zQ' --role admin
|
|
|
|
SHA1 is also accepted, but SHA1 support is deprecated and the user's
|
|
password will be upgraded to Argon2id on first login.
|
|
|
|
** Specifying a plain-text password as an argument (dangerous)
|
|
|
|
This option is dangerous and should not be used: it exposes passwords to
|
|
other users on the system. This option only exists for backwards
|
|
compatibility.
|
|
|
|
Example:
|
|
|
|
$ hydra-create-user alice --password foobar --role admin
|
|
|
|
%;
|
|
exit 0;
|
|
}
|
|
|
|
my ($renameFrom, $type, $fullName, $emailAddress, $password, $passwordHash, $passwordPrompt);
|
|
my $wipeRoles = 0;
|
|
my @roles;
|
|
|
|
GetOptions("rename-from=s" => \$renameFrom,
|
|
"type=s" => \$type,
|
|
"full-name=s" => \$fullName,
|
|
"email-address=s" => \$emailAddress,
|
|
"password=s" => \$password,
|
|
"password-prompt" => \$passwordPrompt,
|
|
"password-hash=s" => \$passwordHash,
|
|
"wipe-roles" => \$wipeRoles,
|
|
"role=s" => \@roles,
|
|
"help" => sub { showHelp() }
|
|
) or exit 1;
|
|
|
|
die "$0: one user name required\n" if scalar @ARGV != 1;
|
|
my $userName = $ARGV[0];
|
|
|
|
my $chosenPasswordOptions = grep { defined($_) } ($passwordPrompt, $passwordHash, $password);
|
|
if ($chosenPasswordOptions > 1) {
|
|
die "$0: please specify only one of --password-prompt or --password-hash. See --help for more information.\n";
|
|
}
|
|
|
|
die "$0: type must be `hydra', `google' or `github'\n"
|
|
if defined $type && $type ne "hydra" && $type ne "google" && $type ne "github";
|
|
|
|
my $db = Hydra::Model::DB->new();
|
|
|
|
$db->txn_do(sub {
|
|
my $user = $db->resultset('Users')->find({ username => $renameFrom // $userName });
|
|
if ($renameFrom) {
|
|
die "$0: user `$renameFrom' does not exist\n" unless $user;
|
|
$user->update({ username => $userName });
|
|
} elsif ($user) {
|
|
print STDERR "updating existing user `$userName'\n";
|
|
} else {
|
|
print STDERR "creating new user `$userName'\n";
|
|
$user = $db->resultset('Users')->create(
|
|
{ username => $userName, type => "hydra", emailaddress => "", password => "!" });
|
|
}
|
|
|
|
die "$0: Google or GitHub user names must be email addresses\n"
|
|
if ($user->type eq "google" || $user->type eq "github") && $userName !~ /\@/;
|
|
|
|
$user->update({ type => $type }) if defined $type;
|
|
|
|
$user->update({ fullname => $fullName eq "" ? undef : $fullName }) if defined $fullName;
|
|
|
|
if ($user->type eq "google" || $user->type eq "github") {
|
|
die "$0: Google and GitHub accounts do not have an explicitly set email address.\n"
|
|
if defined $emailAddress;
|
|
die "$0: Google and GitHub accounts do not have a password.\n"
|
|
if defined $password;
|
|
die "$0: Google and GitHub accounts do not have a password.\n"
|
|
if defined $passwordHash;
|
|
die "$0: Google and GitHub accounts do not have a password.\n"
|
|
if defined $passwordPrompt;
|
|
$user->update({ emailaddress => $userName, password => "!" });
|
|
} else {
|
|
$user->update({ emailaddress => $emailAddress }) if defined $emailAddress;
|
|
|
|
if (defined $password) {
|
|
# !!! TODO: Remove support for plaintext passwords in 2023.
|
|
print STDERR "Submitting plaintext passwords as arguments is deprecated and will be removed. See --help for alternatives.\n";
|
|
$user->setPassword($password);
|
|
}
|
|
|
|
if (defined $passwordHash) {
|
|
$user->setPasswordHash($passwordHash);
|
|
}
|
|
|
|
if (defined $passwordPrompt) {
|
|
ReadMode 2;
|
|
print STDERR "Password: ";
|
|
my $password = <STDIN> // "";
|
|
chomp $password;
|
|
|
|
print STDERR "\nPassword Confirmation: ";
|
|
my $passwordConfirm = <STDIN> // "";
|
|
chomp $passwordConfirm;
|
|
ReadMode 0;
|
|
|
|
print STDERR "\n";
|
|
|
|
if ($password ne $passwordConfirm) {
|
|
die "Passwords don't match."
|
|
} elsif ($password eq "") {
|
|
die "Password cannot be empty."
|
|
}
|
|
|
|
$user->setPassword($password);
|
|
}
|
|
}
|
|
|
|
$user->userroles->delete if $wipeRoles;
|
|
$user->userroles->update_or_create({ role => $_ }) foreach @roles;
|
|
});
|