235 lines
5.9 KiB
Bash
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"
|