package models
import (
"database/sql"
"encoding/base64"
"errors"
"github.com/Queueue0/qpass/internal/crypto"
"github.com/google/uuid"
_ "github.com/mattn/go-sqlite3"
)
type User struct {
encryptedUsername string
ID uuid.UUID
Username string
Key []byte
AuthToken []byte
}
func (u User) EUsername() string {
return u.encryptedUsername
}
type UserModel struct {
DB *sql.DB
}
func (m *UserModel) Insert(username, password, uuid string) (int, error) {
key := crypto.GetKey(password, username)
encryptedUsername, err := crypto.Encrypt(username, key)
if err != nil {
return 0, err
}
stmt := `INSERT INTO users (uuid, username) VALUES (?, ?)`
result, err := m.DB.Exec(stmt, uuid, encryptedUsername)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
func (m *UserModel) ServerInsert(u User) (int, error) {
token := base64.RawStdEncoding.EncodeToString(u.AuthToken)
stmt := `INSERT INTO users (uuid, auth_token) VALUES (?, ?)`
result, err := m.DB.Exec(stmt, u.ID.String(), token)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
return int(id), err
}
func (m *UserModel) GetByUUID(id string) (*User, error) {
row := m.DB.QueryRow("SELECT uuid, auth_token FROM users WHERE uuid = ?", id)
var uuidStr, tokenStr string
err := row.Scan(&uuidStr, &tokenStr)
if err != nil {
return nil, err
}
return parseFromStrings(uuidStr, tokenStr)
}
func (m *UserModel) ServerGetByAuthToken(at []byte) (*User, error) {
t := base64.RawStdEncoding.EncodeToString(at)
row := m.DB.QueryRow("SELECT uuid, auth_token FROM users WHERE auth_token = ?", t)
var uuidStr, tokenStr string
err := row.Scan(&uuidStr, &tokenStr)
if err != nil {
return nil, err
}
return parseFromStrings(uuidStr, tokenStr)
}
func parseFromStrings(uuidStr, tokenStr string) (*User, error) {
var u User
var err error
u.ID, err = uuid.Parse(uuidStr)
if err != nil {
return nil, err
}
u.AuthToken, err = base64.RawStdEncoding.DecodeString(tokenStr)
if err != nil {
return nil, err
}
return &u, nil
}
func (m *UserModel) IDtoUUID(id int) (string, error) {
stmt := `SELECT uuid FROM users WHERE id=?`
row := m.DB.QueryRow(stmt, id)
var UUID string
err := row.Scan(&UUID)
return UUID, err
}
var ErrIncorrectCreds = errors.New("Username or password is incorrect")
func (m *UserModel) Authenticate(username, password string) (User, error) {
stmt := `SELECT uuid, username FROM users`
rows, err := m.DB.Query(stmt)
if err != nil {
return User{}, err
}
defer rows.Close()
key := crypto.GetKey(password, username)
for rows.Next() {
var u User
var uuidString string
err := rows.Scan(&uuidString, &u.encryptedUsername)
if err != nil {
return User{}, err
}
u.Username, err = crypto.Decrypt(u.encryptedUsername, key)
if err != nil {
if err.Error() == "chacha20poly1305: message authentication failed" {
continue
}
return User{}, err
}
if u.Username == username {
u.Key = key
u.ID, err = uuid.Parse(uuidString)
if err != nil {
return User{}, err
}
u.AuthToken = crypto.ClientAuthToken(username, password)
return u, nil
}
}
return User{}, ErrIncorrectCreds
}
func (m *UserModel) Count() int {
row := m.DB.QueryRow("SELECT COUNT(id) FROM users")
var c int
row.Scan(&c)
return c
}