Files
moonwell-core/create-account.sh
T

235 lines
5.9 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
USERNAME=""
PASSWORD=""
EMAIL=""
if [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
fi
EMAIL="${ACORE_ACCOUNT_DEFAULT_EMAIL:-}"
GMLEVEL="${ACORE_ACCOUNT_DEFAULT_GMLEVEL:-3}"
REALM_ID="${ACORE_ACCOUNT_DEFAULT_REALM_ID:--1}"
EXPANSION="${ACORE_ACCOUNT_DEFAULT_EXPANSION:-2}"
usage() {
cat <<EOF
Usage: $(basename "$0") <username> <password> [options]
Creates or updates an AzerothCore account in the dockerized auth database.
Options:
--email <email> Account email
--gmlevel <0-3> GM level, default: 3
--realm <id> Realm ID for account_access, default: -1
--expansion <0-2> Expansion flag, default: 2
-h, --help Show this help
Examples:
./create-account.sh admin strongpass
./create-account.sh player1 secret --gmlevel 0
EOF
}
log() {
printf '[create-account] %s\n' "$*"
}
require_value() {
local flag="$1"
local value="${2:-}"
if [[ -z "$value" ]]; then
printf 'Option %s requires a value\n' "$flag" >&2
exit 1
fi
}
if (($# == 1)) && [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
if (($# < 2)); then
usage >&2
exit 1
fi
USERNAME="$1"
PASSWORD="$2"
shift 2
while (($#)); do
case "$1" in
--email)
require_value "$1" "${2:-}"
EMAIL="$2"
shift 2
;;
--gmlevel)
require_value "$1" "${2:-}"
GMLEVEL="$2"
shift 2
;;
--realm)
require_value "$1" "${2:-}"
REALM_ID="$2"
shift 2
;;
--expansion)
require_value "$1" "${2:-}"
EXPANSION="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
printf 'Unknown option: %s\n\n' "$1" >&2
usage >&2
exit 1
;;
esac
done
if ! [[ "$GMLEVEL" =~ ^[0-3]$ ]]; then
printf 'gmlevel must be 0, 1, 2, or 3\n' >&2
exit 1
fi
if ! [[ "$REALM_ID" =~ ^-?[0-9]+$ ]]; then
printf 'realm must be an integer\n' >&2
exit 1
fi
if ! [[ "$EXPANSION" =~ ^[0-2]$ ]]; then
printf 'expansion must be 0, 1, or 2\n' >&2
exit 1
fi
cd "$ROOT_DIR"
if ! docker compose ps --status running --services | grep -qx 'ac-database'; then
printf 'ac-database is not running. Start the server first with ./start-server.sh\n' >&2
exit 1
fi
export ACORE_USERNAME="$USERNAME"
export ACORE_PASSWORD="$PASSWORD"
export ACORE_EMAIL="$EMAIL"
export ACORE_GMLEVEL="$GMLEVEL"
export ACORE_REALM_ID="$REALM_ID"
export ACORE_EXPANSION="$EXPANSION"
SQL="$(python3 <<'PY'
import hashlib
import os
import secrets
N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
G = 7
username = os.environ["ACORE_USERNAME"]
password = os.environ["ACORE_PASSWORD"]
email = os.environ.get("ACORE_EMAIL", "")
gmlevel = int(os.environ["ACORE_GMLEVEL"])
realm_id = int(os.environ["ACORE_REALM_ID"])
expansion = int(os.environ["ACORE_EXPANSION"])
def upper_only_latin(value: str) -> str:
chars = []
for ch in value:
code = ord(ch)
if 97 <= code <= 122:
chars.append(chr(code - 32))
else:
chars.append(ch)
return "".join(chars)
def sql_quote(value: str) -> str:
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"
username = upper_only_latin(username)
password = upper_only_latin(password)
email = upper_only_latin(email)
if not username:
raise SystemExit("username must not be empty")
if not password:
raise SystemExit("password must not be empty")
if len(username) > 32:
raise SystemExit("username is too long (max 32 characters)")
if len(password) > 32:
raise SystemExit("password is too long (max 32 characters)")
if len(email) > 255:
raise SystemExit("email is too long (max 255 characters)")
salt = secrets.token_bytes(32)
inner = hashlib.sha1(f"{username}:{password}".encode("utf-8")).digest()
x = hashlib.sha1(salt + inner).digest()
# AzerothCore SRP6 treats the SHA1 digest as a little-endian bignum.
verifier_int = pow(G, int.from_bytes(x, "little"), N)
verifier = verifier_int.to_bytes(32, "little")
email_insert = sql_quote(email)
if email:
email_update = "reg_mail = VALUES(reg_mail), email = VALUES(email)"
else:
email_update = "reg_mail = reg_mail, email = email"
print("START TRANSACTION;")
print("SET @account_id := (SELECT id FROM account WHERE username = %s LIMIT 1);" % sql_quote(username))
print(
"INSERT INTO account (username, salt, verifier, expansion, reg_mail, email, joindate) "
"VALUES (%s, UNHEX('%s'), UNHEX('%s'), %d, %s, %s, NOW()) "
"ON DUPLICATE KEY UPDATE salt = VALUES(salt), verifier = VALUES(verifier), expansion = VALUES(expansion), "
"%s;"
% (
sql_quote(username),
salt.hex().upper(),
verifier.hex().upper(),
expansion,
email_insert,
email_insert,
email_update,
)
)
print("SET @account_id := COALESCE(@account_id, LAST_INSERT_ID());")
print(
"INSERT IGNORE INTO realmcharacters (realmid, acctid, numchars) "
"SELECT realmlist.id, @account_id, 0 FROM realmlist;"
)
print(
"INSERT INTO account_access (id, gmlevel, RealmID, comment) "
"VALUES (@account_id, %d, %d, 'managed by create-account.sh') "
"ON DUPLICATE KEY UPDATE gmlevel = VALUES(gmlevel), comment = VALUES(comment);"
% (gmlevel, realm_id)
)
print(
"SELECT id, username, expansion FROM account WHERE id = @account_id;"
)
print(
"SELECT id, gmlevel, RealmID FROM account_access WHERE id = @account_id ORDER BY RealmID;"
)
print("COMMIT;")
PY
)"
log "writing account ${USERNAME} (gmlevel=${GMLEVEL}, realm=${REALM_ID}, expansion=${EXPANSION})"
docker compose exec -T ac-database bash -lc 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" acore_auth' <<<"$SQL"
log "account ready"