Enha: input check; hash passwords
This commit is contained in:
@@ -2,8 +2,10 @@ package handlers
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gralias/models"
|
||||
"gralias/utils"
|
||||
@@ -65,7 +67,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
username := r.PostFormValue("username")
|
||||
if username == "" {
|
||||
if username == "" || !utils.IsInputSane(username) {
|
||||
msg := "username not provided"
|
||||
log.Error(msg)
|
||||
abortWithError(w, msg)
|
||||
@@ -77,6 +79,9 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
||||
// make sure username does not exists
|
||||
cleanName := utils.RemoveSpacesFromStr(username)
|
||||
clearPass := utils.RemoveSpacesFromStr(password)
|
||||
// hash the password with md5
|
||||
hash := md5.Sum([]byte(clearPass))
|
||||
hashedPass := hex.EncodeToString(hash[:])
|
||||
// check if that user was already in db
|
||||
userstate, err := repo.PlayerGetByName(r.Context(), cleanName)
|
||||
if err != nil || userstate == nil {
|
||||
@@ -84,8 +89,8 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
||||
userstate = models.InitPlayer(cleanName)
|
||||
makeplayer = true
|
||||
} else {
|
||||
if userstate.Password != clearPass {
|
||||
log.Error("wrong password", "username", cleanName, "password", clearPass)
|
||||
if userstate.Password != hashedPass {
|
||||
log.Error("wrong password", "username", cleanName)
|
||||
abortWithError(w, "wrong password")
|
||||
return
|
||||
}
|
||||
@@ -126,7 +131,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// save state to cache
|
||||
if makeplayer {
|
||||
userstate.Password = clearPass
|
||||
userstate.Password = hashedPass
|
||||
if err := repo.PlayerAdd(r.Context(), userstate); err != nil {
|
||||
log.Error("failed to save state", "error", err)
|
||||
abortWithError(w, err.Error())
|
||||
|
||||
113
scripts/migrate_passwords.py
Executable file
113
scripts/migrate_passwords.py
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
One-time script to migrate player passwords from plaintext to MD5 hashes.
|
||||
|
||||
This script:
|
||||
1. Connects to the SQLite database
|
||||
2. Retrieves all players with their passwords
|
||||
3. Skips passwords that already look like MD5 hashes (32 hex chars)
|
||||
4. Hashes plaintext passwords with MD5
|
||||
5. Updates the database with hashed passwords
|
||||
|
||||
Usage:
|
||||
python scripts/migrate_passwords.py [path_to_db]
|
||||
|
||||
If no path is provided, defaults to 'gralias.db' in the current directory.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def is_md5_hash(password: str) -> bool:
|
||||
"""Check if password looks like an MD5 hash (32 hex characters)."""
|
||||
return bool(re.match(r"^[a-fA-F0-9]{32}$", password))
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""Hash password with MD5."""
|
||||
return hashlib.md5(password.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def migrate_passwords(db_path: str) -> None:
|
||||
"""Migrate all player passwords to MD5 hashes."""
|
||||
print(f"Connecting to database: {db_path}")
|
||||
|
||||
if not Path(db_path).exists():
|
||||
print(f"Error: Database file not found: {db_path}")
|
||||
sys.exit(1)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# Get all players
|
||||
cursor.execute("SELECT username, password FROM players;")
|
||||
players = cursor.fetchall()
|
||||
|
||||
if not players:
|
||||
print("No players found in database.")
|
||||
return
|
||||
|
||||
print(f"Found {len(players)} player(s)")
|
||||
print("-" * 60)
|
||||
|
||||
migrated = 0
|
||||
skipped = 0
|
||||
errors = 0
|
||||
|
||||
for username, password in players:
|
||||
if not password:
|
||||
print(f"[SKIP] {username}: empty password")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
if is_md5_hash(password):
|
||||
print(f"[SKIP] {username}: already MD5 hashed")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Hash the password
|
||||
hashed = hash_password(password)
|
||||
|
||||
try:
|
||||
cursor.execute(
|
||||
"UPDATE players SET password = ? WHERE username = ?;",
|
||||
(hashed, username),
|
||||
)
|
||||
print(f"[MIGRATED] {username}: '{password}' -> '{hashed}'")
|
||||
migrated += 1
|
||||
except sqlite3.Error as e:
|
||||
print(f"[ERROR] {username}: {e}")
|
||||
errors += 1
|
||||
|
||||
print("-" * 60)
|
||||
print(f"Summary: {migrated} migrated, {skipped} skipped, {errors} errors")
|
||||
|
||||
# Commit changes
|
||||
conn.commit()
|
||||
print("Changes committed to database.")
|
||||
|
||||
except sqlite3.Error as e:
|
||||
print(f"Database error: {e}")
|
||||
conn.rollback()
|
||||
sys.exit(1)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def main():
|
||||
# Get database path from command line or use default
|
||||
if len(sys.argv) > 1:
|
||||
db_path = sys.argv[1]
|
||||
else:
|
||||
db_path = "gralias.db"
|
||||
|
||||
migrate_passwords(db_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,6 +5,10 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
SaneLengthMax = 20
|
||||
)
|
||||
|
||||
func RemoveSpacesFromStr(origin string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
@@ -35,3 +39,13 @@ func RemoveFromSlice(key string, sl []string) []string {
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func IsInputSane(s string) bool {
|
||||
if len(s) > SaneLengthMax {
|
||||
return false
|
||||
}
|
||||
if strings.ContainsAny(s, "{}?!$&:/[]~") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user