User login and creation
This commit is contained in:
@@ -3,11 +3,13 @@ package darkstorm
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
@@ -28,9 +30,34 @@ type User struct {
|
||||
Password string `json:"password" bson:"password"`
|
||||
Salt string `json:"salt" bson:"salt"`
|
||||
Email string `json:"email" bson:"email"`
|
||||
Fails int `json:"fails" bson:"fails"`
|
||||
Timeout int64 `json:"timeout" bson:"timeout"`
|
||||
PasswordChange int64 `json:"passwordChange" bson:"passwordChange"`
|
||||
}
|
||||
|
||||
func NewUser(username, password, email string) (User, error) {
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
salt, err := generateSalt()
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
u := User{
|
||||
Perm: make(map[string]string),
|
||||
ID: id.String(),
|
||||
Username: username,
|
||||
Salt: salt,
|
||||
Email: email,
|
||||
}
|
||||
u.Password, err = u.HashPassword(password)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
type ReqUser struct {
|
||||
Perm map[string]string
|
||||
ID string
|
||||
@@ -82,12 +109,65 @@ type createUserRequest struct {
|
||||
}
|
||||
|
||||
type createUserReturn struct {
|
||||
Username string
|
||||
Token string
|
||||
Username string `json:"username"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
func (b *Backend) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||
//TODO
|
||||
hdr, err := b.ParseHeader(r)
|
||||
if hdr.k == nil || !hdr.k.Perm["user"] || errors.Is(err, ErrApiKeyUnauthorized) {
|
||||
ReturnError(w, http.StatusUnauthorized, "invalidKey", "Application not authorized")
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
var req createUserRequest
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil || req.Username == "" || req.Password == "" || req.Email == "" {
|
||||
ReturnError(w, http.StatusBadRequest, "invalidBody", "Bad request")
|
||||
return
|
||||
}
|
||||
if len(req.Password) < 12 || len(req.Password) > 128 {
|
||||
ReturnError(w, http.StatusUnauthorized, "password", "Invalid password.")
|
||||
return
|
||||
}
|
||||
// TODO: filter offensive words/phrases
|
||||
b.userMutex.Lock()
|
||||
defer b.userMutex.Unlock()
|
||||
matchUsername, err := b.userTable.Find(map[string]any{"username": req.Username})
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
} else if (err == nil || errors.Is(err, ErrNotFound)) && len(matchUsername) > 0 {
|
||||
ReturnError(w, http.StatusUnauthorized, "taken", "Username or email already used")
|
||||
return
|
||||
}
|
||||
matchEmail, err := b.userTable.Find(map[string]any{"email": req.Email})
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
} else if (err == nil || errors.Is(err, ErrNotFound)) && len(matchEmail) > 0 {
|
||||
ReturnError(w, http.StatusUnauthorized, "taken", "Username or email already used")
|
||||
return
|
||||
}
|
||||
u, err := NewUser(req.Username, req.Password, req.Email)
|
||||
if err != nil {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
err = b.userTable.Insert(u)
|
||||
if err != nil {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
var ret createUserReturn
|
||||
ret.Username = u.Username
|
||||
ret.Token, err = b.generateJWT(u.toReqUser())
|
||||
if err != nil {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
}
|
||||
|
||||
type loginRequest struct {
|
||||
@@ -96,8 +176,9 @@ type loginRequest struct {
|
||||
}
|
||||
|
||||
type loginReturn struct {
|
||||
Token string
|
||||
Timeout int
|
||||
Token string `json:"token"`
|
||||
Error string `json:"error"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
|
||||
func (b *Backend) Login(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -106,5 +187,54 @@ func (b *Backend) Login(w http.ResponseWriter, r *http.Request) {
|
||||
ReturnError(w, http.StatusUnauthorized, "invalidKey", "Application not authorized")
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
var req loginRequest
|
||||
err = json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil || req.Username == "" || req.Password == "" {
|
||||
ReturnError(w, http.StatusBadRequest, "invalidBody", "Bad request")
|
||||
return
|
||||
}
|
||||
b.userMutex.RLock()
|
||||
defer b.userMutex.RUnlock()
|
||||
var ret loginReturn
|
||||
users, err := b.userTable.Find(map[string]any{"username": req.Username})
|
||||
if errors.Is(err, ErrNotFound) || len(users) != 1 {
|
||||
ret.Error = "invalid"
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
return
|
||||
}
|
||||
u := users[0]
|
||||
if time.Unix(u.Timeout, 0).After(time.Now()) {
|
||||
ret.Error = "timeout"
|
||||
ret.Timeout = time.Now().Unix() - u.Timeout
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
return
|
||||
}
|
||||
hash, err := u.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
if u.Password == hash {
|
||||
ret.Token, err = b.generateJWT(u.toReqUser())
|
||||
if err != nil {
|
||||
ReturnError(w, http.StatusInternalServerError, "internal", "Server error")
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
} else {
|
||||
ret.Error = "invalid"
|
||||
upd := map[string]any{"fails": u.Fails + 1}
|
||||
if (u.Fails+1)%3 == 0 {
|
||||
minutes := 3 ^ ((u.Fails / 3) - 1)
|
||||
timeout := time.Now().Add(time.Duration(minutes) * time.Minute).Unix()
|
||||
upd["timeout"] = timeout
|
||||
ret.Timeout = timeout - time.Now().Unix()
|
||||
}
|
||||
b.userTable.PartUpdate(u.ID, upd)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ret)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user