From 4b53126d6a63f2a40218c1e3b49ba9dedcf4db0e Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Wed, 22 Jun 2022 02:25:32 +0200 Subject: [PATCH] Slight refactor --- README.md | 4 +- {frontend => _frontend}/crea-partita.html | 0 {frontend => _frontend}/game.html | 0 {frontend => _frontend}/index.html | 0 {frontend => _frontend}/login.html | 0 {frontend => _frontend}/package-lock.json | 0 {frontend => _frontend}/package.json | 0 {frontend => _frontend}/profile.html | 0 {frontend => _frontend}/register.html | 0 _frontend/src/crea-partita/main.jsx | 144 ++++++++++++++++++++++ {frontend => _frontend}/src/home/index.js | 0 {frontend => _frontend}/src/main.scss | 37 +++++- {frontend => _frontend}/vite.config.js | 0 auth/auth.go | 17 +++ auth/inmemory.go | 57 +++++++++ database.go | 135 -------------------- database/database.go | 89 +++++++++++++ frontend/src/crea-partita/main.jsx | 100 --------------- lupus/lupus.go | 103 ++++++++++++++++ main.go | 7 +- model/model.go | 33 +++++ routes.go | 125 ------------------- routes/api.go | 100 +++++++++++++++ routes/auth.go | 38 ++++++ util/util.go | 33 +++++ validate.go => util/validate.go | 10 +- utils.go | 32 ----- 27 files changed, 658 insertions(+), 406 deletions(-) rename {frontend => _frontend}/crea-partita.html (100%) rename {frontend => _frontend}/game.html (100%) rename {frontend => _frontend}/index.html (100%) rename {frontend => _frontend}/login.html (100%) rename {frontend => _frontend}/package-lock.json (100%) rename {frontend => _frontend}/package.json (100%) rename {frontend => _frontend}/profile.html (100%) rename {frontend => _frontend}/register.html (100%) create mode 100644 _frontend/src/crea-partita/main.jsx rename {frontend => _frontend}/src/home/index.js (100%) rename {frontend => _frontend}/src/main.scss (89%) rename {frontend => _frontend}/vite.config.js (100%) create mode 100644 auth/auth.go create mode 100644 auth/inmemory.go create mode 100644 database/database.go delete mode 100644 frontend/src/crea-partita/main.jsx create mode 100644 lupus/lupus.go create mode 100644 model/model.go create mode 100644 routes/api.go create mode 100644 routes/auth.go create mode 100644 util/util.go rename validate.go => util/validate.go (75%) diff --git a/README.md b/README.md index 0557cc9..9edfecc 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ ## Usage ```bash -# Development Mode: also starts "npm run dev" inside "frontend/" +# Development Mode: also starts "npm run dev" inside "_frontend/" $ MODE=dev go run . # Production Mode -$ cd frontend +$ cd _frontend $ npm run build $ go build ``` diff --git a/frontend/crea-partita.html b/_frontend/crea-partita.html similarity index 100% rename from frontend/crea-partita.html rename to _frontend/crea-partita.html diff --git a/frontend/game.html b/_frontend/game.html similarity index 100% rename from frontend/game.html rename to _frontend/game.html diff --git a/frontend/index.html b/_frontend/index.html similarity index 100% rename from frontend/index.html rename to _frontend/index.html diff --git a/frontend/login.html b/_frontend/login.html similarity index 100% rename from frontend/login.html rename to _frontend/login.html diff --git a/frontend/package-lock.json b/_frontend/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to _frontend/package-lock.json diff --git a/frontend/package.json b/_frontend/package.json similarity index 100% rename from frontend/package.json rename to _frontend/package.json diff --git a/frontend/profile.html b/_frontend/profile.html similarity index 100% rename from frontend/profile.html rename to _frontend/profile.html diff --git a/frontend/register.html b/_frontend/register.html similarity index 100% rename from frontend/register.html rename to _frontend/register.html diff --git a/_frontend/src/crea-partita/main.jsx b/_frontend/src/crea-partita/main.jsx new file mode 100644 index 0000000..e5ccf8d --- /dev/null +++ b/_frontend/src/crea-partita/main.jsx @@ -0,0 +1,144 @@ +import { render } from 'preact' +import { useState, useEffect } from 'preact/hooks' + +const InputNumero = ({ name, id, value, setValue }) => ( +
+ setValue(e.target.value)} + /> + + +
+) + +const App = () => { + const [user, setUser] = useState(null) + + useEffect(() => { + fetch('/api/user') + .then(res => { + if (res.ok) { + return res.json() + } + }) + .then(user => setUser(user)) + }, []) + + const [numeroGiocatori, setNumeroGiocatori] = useState(10) + + const [numLupi, setNumLupi] = useState(2) + const [numFattucchiere, setNumFattucchiere] = useState(1) + const [numGuardie, setNumGuardie] = useState(1) + const [numCacciatori, setNumCacciatori] = useState(1) + const [numMedium, setNumMedium] = useState(1) + const [numVeggenti, setNumVeggenti] = useState(1) + + const [tooManyRuoli, setTooManyRuoli] = useState(false) + + useEffect(() => { + const totalNumGiocatoriConRuolo = + numLupi + numFattucchiere + numGuardie + numCacciatori + numMedium + numVeggenti + + console.log(totalNumGiocatoriConRuolo) + + setTooManyRuoli(numeroGiocatori < totalNumGiocatoriConRuolo) + }, [numLupi, numFattucchiere, numGuardie, numCacciatori, numMedium, numVeggenti]) + + return ( + <> +
+

+ Lupus Lite +

+

Crea Partita

+ {user && ( +
+ (Accesso eseguito come {user.username}) +
+ )} +
+
+
+

Nuova Partita

+

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Esse ipsam quisquam + laborum nemo at dignissimos excepturi sapiente incidunt enim fuga? +

+
+ + + + + + + + + + + + + + +
+ {tooManyRuoli && ( +
+
+ error +
+
+ Attenzione il numero di giocatori con ruolo è più alto del numero di + giocatori totali. +
+
+ )} + +
+ + ) +} + +render(, document.querySelector('main')) diff --git a/frontend/src/home/index.js b/_frontend/src/home/index.js similarity index 100% rename from frontend/src/home/index.js rename to _frontend/src/home/index.js diff --git a/frontend/src/main.scss b/_frontend/src/main.scss similarity index 89% rename from frontend/src/main.scss rename to _frontend/src/main.scss index ee1fd47..f6a9ad0 100644 --- a/frontend/src/main.scss +++ b/_frontend/src/main.scss @@ -126,6 +126,14 @@ form { grid-template-columns: auto 1fr; gap: 1rem 0.5rem; + .compact { + grid-column: span 2; + + display: grid; + grid-template-columns: auto 1fr; + gap: 0.5rem; + } + max-width: 35rem; width: 100%; @@ -137,7 +145,9 @@ form { } label { + align-self: center; justify-self: end; + font-weight: var(--ft-sans-wt-bold); } @@ -145,13 +155,32 @@ form { justify-self: end; } - & > :last-child { - margin-top: 1rem; + .info { + grid-column: span 2; + display: flex; + + align-items: start; + + gap: 0.25rem; + + .description { + width: 100%; + } } @media screen and (max-width: 512px) { grid-template-columns: 1fr; - gap: 0.25rem; + + .compact { + grid-column: span 1; + grid-template-columns: 1fr; + + gap: 0.25rem; + } + + .info { + grid-column: span 1; + } .fill-row { grid-column: span 1; @@ -161,8 +190,6 @@ form { label { justify-self: start; align-self: end; - - padding-top: 0.5rem; } button[type='submit'] { diff --git a/frontend/vite.config.js b/_frontend/vite.config.js similarity index 100% rename from frontend/vite.config.js rename to _frontend/vite.config.js diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..03aa0ce --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,17 @@ +package auth + +import ( + "github.com/aziis98/lupus-lite/database" + "github.com/aziis98/lupus-lite/model" +) + +type AuthService interface { + Register(username, password string) error + Login(username, password string) (string, error) + UserForSession(token string) (string, error) + GetUser(username string) (model.User, error) +} + +func NewInMemoryAuthService(db database.Database) AuthService { + return &memAuth{db, map[string]string{}} +} diff --git a/auth/inmemory.go b/auth/inmemory.go new file mode 100644 index 0000000..7f733d6 --- /dev/null +++ b/auth/inmemory.go @@ -0,0 +1,57 @@ +package auth + +import ( + "fmt" + + "github.com/aziis98/lupus-lite/database" + "github.com/aziis98/lupus-lite/model" + "github.com/aziis98/lupus-lite/util" + "golang.org/x/crypto/bcrypt" +) + +type memAuth struct { + db database.Database + + sessions 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(model.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 := util.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) (model.User, error) { + return auth.db.GetUser(username) +} diff --git a/database.go b/database.go index 0e0aff6..06ab7d0 100644 --- a/database.go +++ b/database.go @@ -1,136 +1 @@ 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) -} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..b375617 --- /dev/null +++ b/database/database.go @@ -0,0 +1,89 @@ +package database + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/aziis98/lupus-lite/model" + "github.com/aziis98/lupus-lite/util" +) + +type Database interface { + CreateUser(user model.User) error + GetUsers() ([]model.User, error) + GetUser(username string) (model.User, error) + + CreatePartita(partitaConfig model.PartitaConfig) (model.Partita, error) + GetPartita(uid string) (model.Partita, error) +} + +type memDB struct { + Users map[string]model.User `json:"users"` + Partite map[string]model.Partita `json:"partite"` +} + +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() ([]model.User, error) { + users := make([]model.User, 0, len(db.Users)) + for _, u := range db.Users { + users = append(users, u) + } + + return users, nil +} + +func (db *memDB) GetUser(username string) (model.User, error) { + user, ok := db.Users[username] + if !ok { + return model.User{}, fmt.Errorf(`no user with username %q`, username) + } + + return user, nil +} + +func (db *memDB) CreateUser(user model.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 +} + +func (db *memDB) CreatePartita(partitaConfig model.PartitaConfig) (model.Partita, error) { + uid := util.GenerateRandomString(12) + partita := model.Partita{ + Uid: uid, + Config: partitaConfig, + Players: []string{}, + Iniziata: false, + } + + db.Partite[uid] = partita + + return partita, nil +} + +func (db *memDB) GetPartita(uid string) (model.Partita, error) { + partita, ok := db.Partite[uid] + if !ok { + return model.Partita{}, fmt.Errorf(`nessuna partita con uid %q`, uid) + } + + return partita, nil +} diff --git a/frontend/src/crea-partita/main.jsx b/frontend/src/crea-partita/main.jsx deleted file mode 100644 index 273f204..0000000 --- a/frontend/src/crea-partita/main.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { render } from 'preact' -import { useState, useEffect } from 'preact/hooks' - -const InputNumero = ({ name, id, value, setValue }) => ( -
- setValue(e.target.value)} - /> - - -
-) - -const App = () => { - const [user, setUser] = useState(null) - useEffect(() => { - fetch('/api/user') - .then(res => { - if (res.ok) { - return res.json() - } - }) - .then(user => setUser(user)) - }) - - const [numeroGiocatori, setNumeroGiocatori] = useState(10) - const [numLupi, setNumLupi] = useState(2) - const [numFattucchiere, setNumFattucchiere] = useState(1) - - return ( - <> -
-

- Lupus Lite -

-

Crea Partita

- {user && ( -
- (Accesso eseguito come {user.username}) -
- )} -
-
-
-

Nuova Partita

-

- Lorem ipsum, dolor sit amet consectetur adipisicing elit. Esse ipsam quisquam - laborum nemo at dignissimos excepturi sapiente incidunt enim fuga? -

- - - - - - - - - - - - - - ) -} - -render(, document.querySelector('main')) diff --git a/lupus/lupus.go b/lupus/lupus.go new file mode 100644 index 0000000..6034008 --- /dev/null +++ b/lupus/lupus.go @@ -0,0 +1,103 @@ +package lupus + +// StatoPartita rappresenta lo stato della partita +type StatoPartita struct { + Uid string `json:"uid"` // Uid corrispondente come in "Partita" + + Players []Player `json:"players"` // Players è una mappa da username a giocatore + Time uint `json:"time"` // Time indica la fase corrente del gioco (la parità indica notte/giorno e si inizia da "Notte 0") + Actions []Action `json:"actions"` // PhaseActions indica quali azioni sono state fatte in una certa fase +} + +type Player struct { + Username string `json:"username"` + Ruolo Ruolo `json:"ruolo"` + Vivo bool `json:"vivo"` +} + +type Ruolo struct { + Uid string `json:"uid"` + Nome string `json:"nome"` + Fazione string `json:"fazione"` + Aura string `json:"aura"` +} + +type Action struct { + Uid string `json:"uid"` + Time uint `json:"time"` // Time indica in quale fase è stata compiuta l'azione + Player string `json:"player"` + TargetPlayer string `json:"targetPlayer"` +} + +var ( + AuraBianca = "bianca" + AuraNera = "nera" +) + +var ( + FazioneBuoni = "buoni" + FazioneCattivi = "cattivi" +) + +var ( + Contadino = Ruolo{ + Uid: "contadino", + Nome: "Contadino", + Fazione: FazioneBuoni, + Aura: AuraBianca, + } + Lupo = Ruolo{ + Uid: "lupo", + Nome: "Lupo", + Fazione: FazioneCattivi, + Aura: AuraNera, + } + Fattucchiera = Ruolo{ + Uid: "fattucchiera", + Nome: "Fattucchiera", + Fazione: FazioneCattivi, + Aura: AuraNera, + } + Indemoniato = Ruolo{ + Uid: "indemoniato", + Nome: "Indemoniato", + Fazione: FazioneCattivi, + Aura: AuraBianca, + } + Guardia = Ruolo{ + Uid: "guardia", + Nome: "Guardia", + Fazione: FazioneBuoni, + Aura: AuraBianca, + } + Cacciatore = Ruolo{ + Uid: "cacciatore", + Nome: "Cacciatore", + Fazione: FazioneBuoni, + Aura: AuraBianca, + } + Medium = Ruolo{ + Uid: "medium", + Nome: "Medium", + Fazione: FazioneBuoni, + Aura: AuraBianca, + } + Veggente = Ruolo{ + Uid: "veggente", + Nome: "Veggente", + Fazione: FazioneBuoni, + Aura: AuraBianca, + } +) + +// Ruoli è una lista di ruoli comuni, l'ordine è puramente casuale +var Ruoli = []Ruolo{ + Contadino, + Lupo, + Fattucchiera, + Indemoniato, + Guardia, + Cacciatore, + Medium, + Veggente, +} diff --git a/main.go b/main.go index 4652bd6..92511e6 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "os/exec" "strings" + "github.com/aziis98/lupus-lite/routes" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" @@ -19,15 +20,15 @@ func main() { app.Use(recover.New()) // Static files - app.Static("/", "./frontend/dist") + app.Static("/", "./_frontend/dist") // Api routes - app.Route("/api", mountApiRoutes) + app.Route("/api", routes.Api) if strings.HasPrefix(mode, "dev") { log.Printf(`Running dev server for frontend: "npm run dev"`) - err := (exec.Command("sh", "-c", "cd frontend/ && npm run dev").Start()) + err := (exec.Command("sh", "-c", "cd _frontend/ && npm run dev").Start()) if err != nil { log.Fatal(err) } diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..ff86512 --- /dev/null +++ b/model/model.go @@ -0,0 +1,33 @@ +package model + +// 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 PartitaConfig struct { + // NumeroGiocatori indica il numero totale di giocatori (per capire quanto tutti sono entrati nella partita) + NumeroGiocatori int + + // NumeroPerRuolo indica quanti giocatori di un certo ruolo ci dovrebbero essere + NumeroPerRuolo map[string]int +} + +type Partita struct { + Uid string // Uid è il codice identificativo della partita. + Config PartitaConfig // Config è la configurazione iniziale della partita. + Players []string // Players è la lista di giocatori che sono entrati in questa partita. + Iniziata bool // Iniziata indica se la partita è iniziata, quando lo è c'è una tabella corrispondente StatoPartita con le informazioni sui vari giocatori e sullo stato attuale del gioco. +} diff --git a/routes.go b/routes.go index 667123b..06ab7d0 100644 --- a/routes.go +++ b/routes.go @@ -1,126 +1 @@ package main - -import ( - "encoding/json" - "fmt" - "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") - if token == "" { - return fmt.Errorf(`request has no session token`) - } - - 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) { - db, err := NewInMemoryDB() - if err != nil { - log.Fatal(err) - } - - auth := NewInMemoryAuth(db) - requireLogged := RequireLoggedMiddleware(auth) - - 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") - }) - - 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 { - c.Cookie(&fiber.Cookie{ - Name: "sid", - Value: "", - Path: "/", - Expires: time.Now(), - }) - - return c.SendString("ok") - }) - - api.Post("/register", func(c *fiber.Ctx) error { - var loginForm struct { - Username string `form:"username"` - Password string `form:"password"` - Password2 string `form:"password2"` - } - - if err := c.BodyParser(&loginForm); err != nil { - return err - } - - if err := validateUsername(loginForm.Username); err != nil { - return err - } - if err := validatePasswords(loginForm.Password, loginForm.Password2); 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()) - }) - -} diff --git a/routes/api.go b/routes/api.go new file mode 100644 index 0000000..2a1df8f --- /dev/null +++ b/routes/api.go @@ -0,0 +1,100 @@ +package routes + +import ( + "encoding/json" + "log" + "time" + + "github.com/aziis98/lupus-lite/auth" + "github.com/aziis98/lupus-lite/database" + "github.com/aziis98/lupus-lite/util" + "github.com/gofiber/fiber/v2" +) + +func Api(api fiber.Router) { + db, err := database.NewInMemoryDB() + if err != nil { + log.Fatal(err) + } + + auth := auth.NewInMemoryAuthService(db) + requireLogged := RequireLoggedMiddleware(auth) + + 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") + }) + + 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 { + c.Cookie(&fiber.Cookie{ + Name: "sid", + Value: "", + Path: "/", + Expires: time.Now(), + }) + + return c.SendString("ok") + }) + + api.Post("/register", func(c *fiber.Ctx) error { + var loginForm struct { + Username string `form:"username"` + Password string `form:"password"` + Password2 string `form:"password2"` + } + + if err := c.BodyParser(&loginForm); err != nil { + return err + } + + if err := util.ValidateUsername(loginForm.Username); err != nil { + return err + } + + if err := util.ValidatePasswords(loginForm.Password, loginForm.Password2); 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()) + }) + +} diff --git a/routes/auth.go b/routes/auth.go new file mode 100644 index 0000000..015ca4c --- /dev/null +++ b/routes/auth.go @@ -0,0 +1,38 @@ +package routes + +import ( + "fmt" + + "github.com/aziis98/lupus-lite/auth" + "github.com/aziis98/lupus-lite/model" + "github.com/gofiber/fiber/v2" +) + +const UserKey = "github.com/aziis98/lupus-lite/user" + +func requestUser(c *fiber.Ctx) *model.User { + return c.Locals(UserKey).(*model.User) +} + +func RequireLoggedMiddleware(auth auth.AuthService) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Cookies("sid") + if token == "" { + return fmt.Errorf(`request has no session token`) + } + + 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() + } +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..07a8108 --- /dev/null +++ b/util/util.go @@ -0,0 +1,33 @@ +package util + +import ( + "math/rand" + "time" + "unicode" +) + +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) +} + +func IsWhitespaceFree(s string) bool { + for _, r := range s { + if unicode.IsSpace(r) { + return false + } + } + + return true +} diff --git a/validate.go b/util/validate.go similarity index 75% rename from validate.go rename to util/validate.go index b0efed2..8c592e4 100644 --- a/validate.go +++ b/util/validate.go @@ -1,8 +1,10 @@ -package main +package util -import "fmt" +import ( + "fmt" +) -func validateUsername(username string) error { +func ValidateUsername(username string) error { if !IsWhitespaceFree(username) { return fmt.Errorf(`username cannot contain spaces`) } @@ -15,7 +17,7 @@ func validateUsername(username string) error { return nil } -func validatePasswords(password, password2 string) error { +func ValidatePasswords(password, password2 string) error { if password != password2 { return fmt.Errorf(`the two password fields don't match`) } diff --git a/utils.go b/utils.go index 46d5cff..06ab7d0 100644 --- a/utils.go +++ b/utils.go @@ -1,33 +1 @@ package main - -import ( - "math/rand" - "time" - "unicode" -) - -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) -} - -func IsWhitespaceFree(s string) bool { - for _, r := range s { - if unicode.IsSpace(r) { - return false - } - } - - return true -}