Working on auth rework

This commit is contained in:
Jan Zípek 2022-08-21 20:51:14 +02:00
parent f2d1de9b31
commit fc9ad06afb
Signed by: kamen
GPG Key ID: A17882625B33AC31
8 changed files with 239 additions and 22 deletions

View File

@ -4,3 +4,4 @@ PORT=8083
BIND_IP=localhost BIND_IP=localhost
AUTH_USERNAME=admin AUTH_USERNAME=admin
AUTH_PASSWORD=password AUTH_PASSWORD=password
AUTH_KEY=password

View File

@ -6,12 +6,13 @@ import (
) )
type Config struct { type Config struct {
Mode string `env:"GIN_MODE"` Mode string
DatabaseUrl string `env:"DATABASE_URL"` DatabaseUrl string
Port int `env:"PORT"` Port int
Ip string `env:"BIND_IP"` Ip string
AuthUsername string `env:"AUTH_USERNAME"` AuthUsername string
AuthPassword string `env:"AUTH_PASSWORD"` AuthPassword string
AuthKey string
} }
func LoadConfig() *Config { func LoadConfig() *Config {
@ -28,6 +29,7 @@ func LoadConfig() *Config {
Ip: os.Getenv("BIND_IP"), Ip: os.Getenv("BIND_IP"),
AuthUsername: os.Getenv("AUTH_USERNAME"), AuthUsername: os.Getenv("AUTH_USERNAME"),
AuthPassword: os.Getenv("AUTH_PASSWORD"), AuthPassword: os.Getenv("AUTH_PASSWORD"),
AuthKey: os.Getenv("SENSOR_AUTH_KEY"),
} }
return &config return &config

View File

@ -2,6 +2,7 @@ package main
import ( import (
"basic-sensor-receiver/app" "basic-sensor-receiver/app"
"basic-sensor-receiver/middleware"
"basic-sensor-receiver/routes" "basic-sensor-receiver/routes"
"fmt" "fmt"
"log" "log"
@ -23,19 +24,21 @@ func main() {
router := gin.Default() router := gin.Default()
protected := router.Group("/", gin.BasicAuth(gin.Accounts{ router.StaticFile("/", "client/index.html")
server.Config.AuthUsername: server.Config.AuthPassword, router.Static("/js", "client/js")
})) router.Static("/css", "client/css")
protected.StaticFile("/", "client/index.html") router.POST("/api/login", routes.Login(server))
protected.Static("/js", "client/js")
protected.Static("/css", "client/css")
protected.GET("/api/sensors", routes.GetSensors(server)) loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server))
protected.GET("/api/sensors/:sensor/values", routes.HandleGetSensorValues(server)) loginProtected.GET("/api/sensors", routes.GetSensors(server))
protected.POST("/api/sensors/:sensor/values", routes.HandlePostSensorValues(server)) loginProtected.GET("/api/sensors/:sensor/values", routes.HandleGetSensorValues(server))
protected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(server)) loginProtected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(server))
protected.PUT("/api/sensors/:sensor/config/:key", routes.HandlePutSensorConfig(server)) loginProtected.PUT("/api/sensors/:sensor/config/:key", routes.HandlePutSensorConfig(server))
loginProtected.POST("/api/logout", routes.Logout(server))
keyProtected := router.Group("/", middleware.KeyAuthMiddleware(server))
keyProtected.POST("/api/sensors/:sensor/values", routes.HandlePostSensorValues(server))
router.Run(fmt.Sprintf("%s:%d", server.Config.Ip, server.Config.Port)) router.Run(fmt.Sprintf("%s:%d", server.Config.Ip, server.Config.Port))
} }

34
server/middleware/auth.go Normal file
View File

@ -0,0 +1,34 @@
package middleware
import (
"basic-sensor-receiver/app"
"net/http"
"github.com/gin-gonic/gin"
)
func LoginAuthMiddleware(server *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
_, err := server.Services.Auth.FromContext(c)
if err != nil {
c.AbortWithError(http.StatusUnauthorized, err)
return
}
c.Next()
}
}
func KeyAuthMiddleware(server *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetHeader("authorization") != server.Config.AuthKey {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Next()
}
}

57
server/routes/auth.go Normal file
View File

@ -0,0 +1,57 @@
package routes
import (
"basic-sensor-receiver/app"
"net/http"
"github.com/gin-gonic/gin"
)
type postLoginBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
func Login(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postLoginBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
if body.Password != s.Config.AuthPassword || body.Username != s.Config.AuthUsername {
c.AbortWithStatus(401)
return
}
if err := s.Services.Auth.Login(c); err != nil {
c.AbortWithError(500, err)
}
c.Writer.WriteHeader(http.StatusOK)
}
}
func Logout(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postLoginBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
if body.Password != s.Config.AuthPassword || body.Username != s.Config.AuthUsername {
c.AbortWithStatus(401)
return
}
if err := s.Services.Auth.Login(c); err != nil {
c.AbortWithError(500, err)
}
c.Writer.WriteHeader(http.StatusOK)
}
}

View File

@ -6,12 +6,34 @@ type AuthService struct {
ctx *Context ctx *Context
} }
func (s *AuthService) FromContext(ctx *gin.Context) (*User, error) { func (s *AuthService) FromContext(ctx *gin.Context) (*SessionItem, error) {
return s.ctx.Services.Sessions.FromContext(ctx)
}
func (s *AuthService) Login(ctx *gin.Context) error {
session, err := s.ctx.Services.Sessions.Create()
if err != nil {
return err
}
s.ctx.Services.Sessions.ToContext(ctx, session)
return nil
}
func (s *AuthService) Logout(ctx *gin.Context) error {
session, err := s.ctx.Services.Sessions.FromContext(ctx) session, err := s.ctx.Services.Sessions.FromContext(ctx)
if err != nil { if err != nil {
return nil, err return err
} }
return s.ctx.Services.Users.GetById(session.UserId) if err := s.ctx.Services.Sessions.Destroy(session.Id); err != nil {
return err
}
s.ctx.Services.Sessions.ClearContext(ctx)
return nil
} }

View File

@ -10,7 +10,6 @@ type Services struct {
SensorValues *SensorValuesService SensorValues *SensorValuesService
Sensors *SensorsService Sensors *SensorsService
Sessions *SessionsService Sessions *SessionsService
Users *UsersService
Auth *AuthService Auth *AuthService
} }
@ -30,7 +29,6 @@ func InitializeServices(ctx *Context) *Services {
services.Sensors = &SensorsService{ctx: ctx} services.Sensors = &SensorsService{ctx: ctx}
services.Sessions = &SessionsService{ctx: ctx} services.Sessions = &SessionsService{ctx: ctx}
services.Auth = &AuthService{ctx: ctx} services.Auth = &AuthService{ctx: ctx}
services.Users = &UsersService{ctx: ctx}
return &services return &services
} }

View File

@ -0,0 +1,100 @@
package services
import (
"crypto/rand"
"encoding/hex"
"io"
"time"
"github.com/gin-gonic/gin"
)
type SessionsService struct {
ctx *Context
}
type SessionItem struct {
Id string `json:"id"`
ExpiresAt int64 `json:"expiresAt"`
}
func (s *SessionsService) FromContext(ctx *gin.Context) (*SessionItem, error) {
cookie, err := ctx.Cookie("session.id")
if err != nil {
return nil, err
}
session, err := s.GetById(cookie)
if err != nil {
return nil, err
}
s.Extend(session)
return session, nil
}
func (s *SessionsService) ToContext(ctx *gin.Context, session *SessionItem) {
ctx.SetCookie("session.id", session.Id, int(time.Duration(time.Hour*24).Seconds()), "/", "", true, true)
}
func (s *SessionsService) ClearContext(ctx *gin.Context) {
ctx.SetCookie("session.id", "", int(time.Duration(time.Hour*24).Seconds()), "/", "", true, true)
}
func (s *SessionsService) GetById(id string) (*SessionItem, error) {
item := SessionItem{}
row := s.ctx.DB.QueryRow("SELECT id, expires_at FROM sessions WHERE id = ? AND expires_at > ?", id, time.Now().Unix())
err := row.Scan(&item.Id, &item.ExpiresAt)
if err != nil {
return nil, err
}
return &item, nil
}
func (s *SessionsService) Create() (*SessionItem, error) {
item := SessionItem{
// TODO: The key is not guaranteed to be unique, how do we guarantee that?
Id: hex.EncodeToString(generateRandomKey(128)),
ExpiresAt: generateExpiryDate().Unix(),
}
_, err := s.ctx.DB.Exec("INSERT INTO sessions (id, expires_at) VALUES (?, ?, ?)", item.Id, item.ExpiresAt)
if err != nil {
return nil, err
}
return &item, nil
}
func (s *SessionsService) Destroy(sessionId string) error {
_, err := s.ctx.DB.Exec("DELETE FROM sessions WHERE id = ?", sessionId)
return err
}
func (s *SessionsService) Extend(session *SessionItem) error {
session.ExpiresAt = generateExpiryDate().Unix()
_, err := s.ctx.DB.Exec("UPDATE sessions SET expires_at = ? WHERE id = ?", session.ExpiresAt, session.Id)
return err
}
func generateExpiryDate() time.Time {
return time.Now().Add(time.Hour * 24)
}
func generateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}