Working in memory database for users and cookie authentication
parent
328271edc3
commit
d8f1a9b4d2
@ -0,0 +1,136 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicUser is the "public" version of the "User" struct, this excludes private information
|
||||||
|
type PublicUser struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User represents a user in the database
|
||||||
|
type User struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
PasswordBCrypt []byte `json:"passwordBCrypt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u User) PublicUser() PublicUser {
|
||||||
|
return PublicUser{
|
||||||
|
Username: u.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Database interface {
|
||||||
|
GetUsers() ([]User, error)
|
||||||
|
GetUser(username string) (User, error)
|
||||||
|
CreateUser(user User) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type memDB struct {
|
||||||
|
Users map[string]User `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryDB() (Database, error) {
|
||||||
|
var db memDB
|
||||||
|
|
||||||
|
exampleFile, err := os.Open("example-db.local.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(exampleFile).Decode(&db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *memDB) GetUsers() ([]User, error) {
|
||||||
|
users := make([]User, 0, len(db.Users))
|
||||||
|
for _, u := range db.Users {
|
||||||
|
users = append(users, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *memDB) GetUser(username string) (User, error) {
|
||||||
|
user, ok := db.Users[username]
|
||||||
|
if !ok {
|
||||||
|
return User{}, fmt.Errorf(`no user with username %q`, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *memDB) CreateUser(user User) error {
|
||||||
|
if _, ok := db.Users[user.Username]; ok {
|
||||||
|
return fmt.Errorf(`user with username %q already exists`, user.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Users[user.Username] = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Auth interface {
|
||||||
|
Register(username, password string) error
|
||||||
|
Login(username, password string) (string, error)
|
||||||
|
UserForSession(token string) (string, error)
|
||||||
|
GetUser(username string) (User, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type memAuth struct {
|
||||||
|
db Database
|
||||||
|
|
||||||
|
sessions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInMemoryAuth(db Database) Auth {
|
||||||
|
return &memAuth{db, map[string]string{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *memAuth) Register(username, password string) error {
|
||||||
|
passwordBCrypt, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth.db.CreateUser(User{
|
||||||
|
Username: username,
|
||||||
|
PasswordBCrypt: passwordBCrypt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *memAuth) Login(username, password string) (string, error) {
|
||||||
|
user, err := auth.db.GetUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword(user.PasswordBCrypt, []byte(password)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := GenerateRandomString(16)
|
||||||
|
auth.sessions[token] = username
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *memAuth) UserForSession(token string) (string, error) {
|
||||||
|
username, ok := auth.sessions[token]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf(`invalid session token`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *memAuth) GetUser(username string) (User, error) {
|
||||||
|
return auth.db.GetUser(username)
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login • Lupus Lite</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Crea Partita | Lupus Lite</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,9 +1,106 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/gofiber/fiber/v2"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const UserKey = "github.com/aziis98/lupus-lite/user"
|
||||||
|
|
||||||
|
func requestUser(c *fiber.Ctx) *User {
|
||||||
|
return c.Locals(UserKey).(*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireLoggedMiddleware(auth Auth) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
token := c.Cookies("sid")
|
||||||
|
username, err := auth.UserForSession(token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := auth.GetUser(username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Locals(UserKey, &user)
|
||||||
|
|
||||||
|
return c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mountApiRoutes(api fiber.Router) {
|
func mountApiRoutes(api fiber.Router) {
|
||||||
|
db, err := NewInMemoryDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auth := NewInMemoryAuth(db)
|
||||||
|
requireLogged := RequireLoggedMiddleware(auth)
|
||||||
|
|
||||||
api.Get("/status", func(c *fiber.Ctx) error {
|
api.Get("/status", func(c *fiber.Ctx) error {
|
||||||
|
s, err := json.MarshalIndent(db, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(string(s))
|
||||||
|
|
||||||
return c.SendString("ok")
|
return c.SendString("ok")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.Post("/login", func(c *fiber.Ctx) error {
|
||||||
|
var loginForm struct {
|
||||||
|
Username string `form:"username"`
|
||||||
|
Password string `form:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(&loginForm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := auth.Login(loginForm.Username, loginForm.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Cookie(&fiber.Cookie{
|
||||||
|
Name: "sid",
|
||||||
|
Value: token,
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Now().Add(3 * 24 * time.Hour),
|
||||||
|
})
|
||||||
|
|
||||||
|
return c.Redirect("/")
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Post("/logout", func(c *fiber.Ctx) error {
|
||||||
|
panic("TODO: not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Post("/register", func(c *fiber.Ctx) error {
|
||||||
|
var loginForm struct {
|
||||||
|
Username string `form:"username"`
|
||||||
|
Password string `form:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BodyParser(&loginForm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth.Register(loginForm.Username, loginForm.Password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect("/login")
|
||||||
|
})
|
||||||
|
|
||||||
|
api.Get("/user", requireLogged, func(c *fiber.Ctx) error {
|
||||||
|
return c.JSON(requestUser(c).PublicUser())
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const alphabet = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateRandomString(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
|
||||||
|
for i := range b {
|
||||||
|
b[i] = alphabet[rand.Intn(len(alphabet))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b)
|
||||||
|
}
|
Loading…
Reference in New Issue