Working on auth rework
This commit is contained in:
parent
f2d1de9b31
commit
fc9ad06afb
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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