158 lines
3.9 KiB
Go
158 lines
3.9 KiB
Go
package users
|
|
|
|
import (
|
|
"clortho/db"
|
|
"clortho/utils"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"math/big"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
var secretKey = []byte("your-secret-key")
|
|
|
|
// GetPepper retrieves the pepper from the environment variable.
|
|
func getPepper() string {
|
|
return os.Getenv("CLORTHO_PEPPER") // Example: Set PASSWORD_PEPPER in your .env or system environment
|
|
}
|
|
|
|
// HashPassword hashes a password with bcrypt and an optional pepper.
|
|
func HashPassword(password string) (string, error) {
|
|
pepper := getPepper()
|
|
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password+pepper), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(hashedBytes), nil
|
|
}
|
|
|
|
// CheckPasswordHash verifies if a plaintext password + pepper matches a stored hash.
|
|
func CheckPasswordHash(password, hash string) bool {
|
|
pepper := getPepper()
|
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+pepper))
|
|
return err == nil
|
|
}
|
|
|
|
func GetUser(username string) *db.User {
|
|
var user db.User
|
|
result := db.Connection.Where("username = ?", username).First(&user)
|
|
if result.RowsAffected != 0 {
|
|
return &user
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func CreateUser(username string) (*db.User, error) {
|
|
user := db.User{Username: username}
|
|
result := db.Connection.Create(&user)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func GetUsers() []db.User {
|
|
var users []db.User
|
|
db.Connection.Find(&users)
|
|
return users
|
|
}
|
|
|
|
func GetSession(id uint) *db.UserSession {
|
|
var session db.UserSession
|
|
result := db.Connection.First(&session, id)
|
|
if result.RowsAffected != 0 {
|
|
return &session
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func InitAdminUser() (*string, error) {
|
|
username := "admin"
|
|
user := GetUser(username)
|
|
if user != nil {
|
|
return nil, errors.New("Admin user already exists")
|
|
}
|
|
|
|
var pass string
|
|
if utils.IsDev() {
|
|
pass = "admin"
|
|
} else {
|
|
pass, _ = GenerateSecurePassword(32)
|
|
}
|
|
hash, _ := HashPassword(pass)
|
|
admin := db.User{Username: username, DisplayName: &username, Admin: true, PasswordHash: &hash}
|
|
result := db.Connection.Create(&admin)
|
|
if result.RowsAffected == 0 || result.Error != nil {
|
|
return nil, errors.New(fmt.Sprintf("Unable to create admin user: %s", result.Error))
|
|
}
|
|
|
|
return &pass, nil
|
|
}
|
|
|
|
const (
|
|
lowercase = "abcdefghijklmnopqrstuvwxyz"
|
|
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
digits = "0123456789"
|
|
specialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?/~"
|
|
allChars = lowercase + uppercase + digits + specialChars
|
|
)
|
|
|
|
// GenerateSecurePassword generates a cryptographically secure password
|
|
func GenerateSecurePassword(length int) (string, error) {
|
|
if length < 1 {
|
|
return "", fmt.Errorf("password length must be at least 1")
|
|
}
|
|
|
|
password := make([]byte, length)
|
|
for i := range password {
|
|
index, _ := rand.Int(rand.Reader, big.NewInt(int64(len(allChars))))
|
|
password[i] = allChars[index.Int64()]
|
|
}
|
|
return string(password), nil
|
|
}
|
|
|
|
func GetSessionFromCookie(authCookie string) (*db.UserSession, error) {
|
|
token, err := jwt.Parse(authCookie, func(token *jwt.Token) (interface{}, error) {
|
|
return secretKey, nil
|
|
})
|
|
|
|
if err != nil || !token.Valid {
|
|
return nil, errors.New("invalid or expired token")
|
|
}
|
|
|
|
subject, err := token.Claims.GetSubject()
|
|
if err != nil {
|
|
return nil, errors.New("invalid or expired token")
|
|
}
|
|
|
|
sessionId, err := strconv.ParseInt(subject, 10, 64)
|
|
if err != nil {
|
|
return nil, errors.New("invalid or expired token")
|
|
}
|
|
|
|
session := GetSession(uint(sessionId))
|
|
if session == nil {
|
|
return nil, errors.New("invalid or expired token")
|
|
}
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func GenerateJwt(sessionId uint) (string, error) {
|
|
claims := jwt.RegisteredClaims{Subject: fmt.Sprint(sessionId)}
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return token.SignedString(secretKey)
|
|
}
|
|
|
|
func NewSession(user db.User) *db.UserSession {
|
|
session := db.UserSession{User: user}
|
|
db.Connection.Create(&session)
|
|
return &session
|
|
}
|