xmr-remote-nodes/internal/repo/admin.go
2024-05-20 04:22:58 +07:00

194 lines
4 KiB
Go

package repo
import (
"errors"
"fmt"
"strings"
"time"
"xmr-remote-nodes/internal/database"
"github.com/alexedwards/argon2id"
)
type Admin struct {
Id int `db:"id"`
Username string `db:"username"`
Password string `db:"password"`
LastactiveTs int64 `db:"lastactive_ts"`
CreatedTs int64 `db:"created_ts"`
}
type AdminRepo struct {
db *database.DB
}
type AdminRepository interface {
CreateAdmin(*Admin) (*Admin, error)
Login(username string, password string) (*Admin, error)
}
func NewAdminRepo(db *database.DB) AdminRepository {
return &AdminRepo{db}
}
func (repo *AdminRepo) CreateAdmin(admin *Admin) (*Admin, error) {
if !validUsername(admin.Username) {
return nil, errors.New("username is not valid, must be at least 4 characters long and contain only lowercase letters and numbers")
}
if !strongPassword(admin.Password) {
return nil, errors.New("password is not strong enough, must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character")
}
hash, err := setPassword(admin.Password)
if err != nil {
return nil, err
}
admin.Password = hash
admin.CreatedTs = time.Now().Unix()
if repo.isUsernameExists(admin.Username) {
return nil, errors.New("username already exists")
}
_, err = repo.db.Exec(`
INSERT INTO tbl_admin (
username,
password,
created_ts
) VALUES (
?,
?,
?
)`,
admin.Username,
admin.Password,
admin.CreatedTs)
if err != nil {
return nil, err
}
return admin, nil
}
func (repo *AdminRepo) Login(username, password string) (*Admin, error) {
row, err := repo.db.Query(`
SELECT
id,
username,
password
FROM
tbl_admin
WHERE
username = ?
LIMIT 1`, username)
if err != nil {
fmt.Println(err)
return nil, err
}
defer row.Close()
admin := Admin{}
if row.Next() {
err = row.Scan(&admin.Id, &admin.Username, &admin.Password)
if err != nil {
fmt.Println(err)
return nil, err
}
} else {
return nil, errors.New("Invalid username or password")
}
match, err := checkPassword(admin.Password, password)
if err != nil {
fmt.Println(err)
return nil, err
}
if !match {
return nil, errors.New("Invalid username or password")
}
_, err = repo.db.Exec(`
UPDATE tbl_admin
SET lastactive_ts = ?
WHERE id = ?`, time.Now().Unix(), admin.Id)
if err != nil {
fmt.Println(err)
return nil, err
}
return &admin, nil
}
func (repo *AdminRepo) isUsernameExists(username string) bool {
row, err := repo.db.Query(`
SELECT
id
FROM
tbl_admin
WHERE
username = ?
LIMIT 1`, username)
if err != nil {
return false
}
defer row.Close()
return row.Next()
}
func setPassword(password string) (string, error) {
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return "", err
}
return hash, nil
}
func checkPassword(hash, password string) (bool, error) {
match, err := argon2id.ComparePasswordAndHash(password, hash)
if err != nil {
return false, err
}
return match, nil
}
func strongPassword(password string) bool {
if len(password) < 8 {
return false
}
if !strings.ContainsAny(password, "0123456789") {
return false
}
if !strings.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
return false
}
if !strings.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") {
return false
}
if !strings.ContainsAny(password, "!@#$%^&*()_+|~-=`{}[]:;<>?,./") {
return false
}
return true
}
// No special character and unicode for username
func validUsername(username string) bool {
if len(username) < 5 || len(username) > 20 {
return false
}
// reject witespace, tabs, newlines, and other special characters
if strings.ContainsAny(username, " \t\n") {
return false
}
// reject unicode
if strings.ContainsAny(username, "^\x00-\x7F") {
return false
}
// reject special characters
if strings.ContainsAny(username, "!@#$%^&*()_+|~-=`{}[]:;<>?,./ ") { // note last blank space
return false
}
if !strings.ContainsAny(username, "abcdefghijklmnopqrstuvwxyz0123456789") {
return false
}
return true
}