Enha: input check; hash passwords
This commit is contained in:
@@ -2,8 +2,10 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gralias/models"
|
"gralias/models"
|
||||||
"gralias/utils"
|
"gralias/utils"
|
||||||
@@ -65,7 +67,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
username := r.PostFormValue("username")
|
username := r.PostFormValue("username")
|
||||||
if username == "" {
|
if username == "" || !utils.IsInputSane(username) {
|
||||||
msg := "username not provided"
|
msg := "username not provided"
|
||||||
log.Error(msg)
|
log.Error(msg)
|
||||||
abortWithError(w, msg)
|
abortWithError(w, msg)
|
||||||
@@ -77,6 +79,9 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
// make sure username does not exists
|
// make sure username does not exists
|
||||||
cleanName := utils.RemoveSpacesFromStr(username)
|
cleanName := utils.RemoveSpacesFromStr(username)
|
||||||
clearPass := utils.RemoveSpacesFromStr(password)
|
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
|
// check if that user was already in db
|
||||||
userstate, err := repo.PlayerGetByName(r.Context(), cleanName)
|
userstate, err := repo.PlayerGetByName(r.Context(), cleanName)
|
||||||
if err != nil || userstate == nil {
|
if err != nil || userstate == nil {
|
||||||
@@ -84,8 +89,8 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
userstate = models.InitPlayer(cleanName)
|
userstate = models.InitPlayer(cleanName)
|
||||||
makeplayer = true
|
makeplayer = true
|
||||||
} else {
|
} else {
|
||||||
if userstate.Password != clearPass {
|
if userstate.Password != hashedPass {
|
||||||
log.Error("wrong password", "username", cleanName, "password", clearPass)
|
log.Error("wrong password", "username", cleanName)
|
||||||
abortWithError(w, "wrong password")
|
abortWithError(w, "wrong password")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -126,7 +131,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
// save state to cache
|
// save state to cache
|
||||||
if makeplayer {
|
if makeplayer {
|
||||||
userstate.Password = clearPass
|
userstate.Password = hashedPass
|
||||||
if err := repo.PlayerAdd(r.Context(), userstate); err != nil {
|
if err := repo.PlayerAdd(r.Context(), userstate); err != nil {
|
||||||
log.Error("failed to save state", "error", err)
|
log.Error("failed to save state", "error", err)
|
||||||
abortWithError(w, err.Error())
|
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"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SaneLengthMax = 20
|
||||||
|
)
|
||||||
|
|
||||||
func RemoveSpacesFromStr(origin string) string {
|
func RemoveSpacesFromStr(origin string) string {
|
||||||
return strings.Map(func(r rune) rune {
|
return strings.Map(func(r rune) rune {
|
||||||
if unicode.IsSpace(r) {
|
if unicode.IsSpace(r) {
|
||||||
@@ -35,3 +39,13 @@ func RemoveFromSlice(key string, sl []string) []string {
|
|||||||
}
|
}
|
||||||
return resp
|
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