Working on auth rework
This commit is contained in:
parent
f2d1de9b31
commit
fc9ad06afb
|
|
@ -4,3 +4,4 @@ PORT=8083
|
|||
BIND_IP=localhost
|
||||
AUTH_USERNAME=admin
|
||||
AUTH_PASSWORD=password
|
||||
AUTH_KEY=password
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import (
|
|||
)
|
||||
|
||||
type Config struct {
|
||||
Mode string `env:"GIN_MODE"`
|
||||
DatabaseUrl string `env:"DATABASE_URL"`
|
||||
Port int `env:"PORT"`
|
||||
Ip string `env:"BIND_IP"`
|
||||
AuthUsername string `env:"AUTH_USERNAME"`
|
||||
AuthPassword string `env:"AUTH_PASSWORD"`
|
||||
Mode string
|
||||
DatabaseUrl string
|
||||
Port int
|
||||
Ip string
|
||||
AuthUsername string
|
||||
AuthPassword string
|
||||
AuthKey string
|
||||
}
|
||||
|
||||
func LoadConfig() *Config {
|
||||
|
|
@ -28,6 +29,7 @@ func LoadConfig() *Config {
|
|||
Ip: os.Getenv("BIND_IP"),
|
||||
AuthUsername: os.Getenv("AUTH_USERNAME"),
|
||||
AuthPassword: os.Getenv("AUTH_PASSWORD"),
|
||||
AuthKey: os.Getenv("SENSOR_AUTH_KEY"),
|
||||
}
|
||||
|
||||
return &config
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"basic-sensor-receiver/app"
|
||||
"basic-sensor-receiver/middleware"
|
||||
"basic-sensor-receiver/routes"
|
||||
"fmt"
|
||||
"log"
|
||||
|
|
@ -23,19 +24,21 @@ func main() {
|
|||
|
||||
router := gin.Default()
|
||||
|
||||
protected := router.Group("/", gin.BasicAuth(gin.Accounts{
|
||||
server.Config.AuthUsername: server.Config.AuthPassword,
|
||||
}))
|
||||
router.StaticFile("/", "client/index.html")
|
||||
router.Static("/js", "client/js")
|
||||
router.Static("/css", "client/css")
|
||||
|
||||
protected.StaticFile("/", "client/index.html")
|
||||
protected.Static("/js", "client/js")
|
||||
protected.Static("/css", "client/css")
|
||||
router.POST("/api/login", routes.Login(server))
|
||||
|
||||
protected.GET("/api/sensors", routes.GetSensors(server))
|
||||
protected.GET("/api/sensors/:sensor/values", routes.HandleGetSensorValues(server))
|
||||
protected.POST("/api/sensors/:sensor/values", routes.HandlePostSensorValues(server))
|
||||
protected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(server))
|
||||
protected.PUT("/api/sensors/:sensor/config/:key", routes.HandlePutSensorConfig(server))
|
||||
loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server))
|
||||
loginProtected.GET("/api/sensors", routes.GetSensors(server))
|
||||
loginProtected.GET("/api/sensors/:sensor/values", routes.HandleGetSensorValues(server))
|
||||
loginProtected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,34 @@ type AuthService struct {
|
|||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ type Services struct {
|
|||
SensorValues *SensorValuesService
|
||||
Sensors *SensorsService
|
||||
Sessions *SessionsService
|
||||
Users *UsersService
|
||||
Auth *AuthService
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +29,6 @@ func InitializeServices(ctx *Context) *Services {
|
|||
services.Sensors = &SensorsService{ctx: ctx}
|
||||
services.Sessions = &SessionsService{ctx: ctx}
|
||||
services.Auth = &AuthService{ctx: ctx}
|
||||
services.Users = &UsersService{ctx: ctx}
|
||||
|
||||
return &services
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue