feat(api): user jwt authentication
This commit is contained in:
parent
5daf95c99c
commit
bfb2acf0fa
|
|
@ -9,6 +9,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/eclipse/paho.golang v0.10.0 h1:oUGPjRwWcZQRgDD9wVDV7y7i7yBSxts3vcvcNJo8B4Q=
|
github.com/eclipse/paho.golang v0.10.0 h1:oUGPjRwWcZQRgDD9wVDV7y7i7yBSxts3vcvcNJo8B4Q=
|
||||||
github.com/eclipse/paho.golang v0.10.0/go.mod h1:rhrV37IEwauUyx8FHrvmXOKo+QRKng5ncoN1vJiJMcs=
|
github.com/eclipse/paho.golang v0.10.0/go.mod h1:rhrV37IEwauUyx8FHrvmXOKo+QRKng5ncoN1vJiJMcs=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/leandrofars/oktopus/internal/api/auth"
|
||||||
"github.com/leandrofars/oktopus/internal/api/middleware"
|
"github.com/leandrofars/oktopus/internal/api/middleware"
|
||||||
"github.com/leandrofars/oktopus/internal/db"
|
"github.com/leandrofars/oktopus/internal/db"
|
||||||
"github.com/leandrofars/oktopus/internal/mtp"
|
"github.com/leandrofars/oktopus/internal/mtp"
|
||||||
|
|
@ -36,17 +37,19 @@ func NewApi(port string, db db.Database, b mtp.Broker, msgQueue map[string](chan
|
||||||
|
|
||||||
func StartApi(a Api) {
|
func StartApi(a Api) {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
authentication := r.PathPrefix("/auth").Subrouter()
|
||||||
|
authentication.HandleFunc("/login", a.generateToken).Methods("PUT")
|
||||||
|
authentication.HandleFunc("/register", a.registerUser).Methods("POST")
|
||||||
|
iot := r.PathPrefix("/device").Subrouter()
|
||||||
|
iot.HandleFunc("/", a.retrieveDevices).Methods("GET")
|
||||||
|
iot.HandleFunc("/{sn}/get", a.deviceGetMsg).Methods("PUT")
|
||||||
|
iot.HandleFunc("/{sn}/add", a.deviceCreateMsg).Methods("PUT")
|
||||||
|
iot.HandleFunc("/{sn}/del", a.deviceDeleteMsg).Methods("PUT")
|
||||||
|
iot.HandleFunc("/{sn}/set", a.deviceUpdateMsg).Methods("PUT")
|
||||||
|
//TODO: Create operation action handler
|
||||||
|
iot.HandleFunc("/device/{sn}/act", a.deviceUpdateMsg).Methods("PUT")
|
||||||
|
|
||||||
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
iot.Use(func(handler http.Handler) http.Handler {
|
||||||
return
|
|
||||||
})
|
|
||||||
r.HandleFunc("/devices", a.retrieveDevices).Methods("GET")
|
|
||||||
r.HandleFunc("/device/{sn}/get", a.deviceGetMsg).Methods("PUT")
|
|
||||||
r.HandleFunc("/device/{sn}/add", a.deviceCreateMsg).Methods("PUT")
|
|
||||||
r.HandleFunc("/device/{sn}/del", a.deviceDeleteMsg).Methods("PUT")
|
|
||||||
r.HandleFunc("/device/{sn}/set", a.deviceUpdateMsg).Methods("PUT")
|
|
||||||
|
|
||||||
r.Use(func(handler http.Handler) http.Handler {
|
|
||||||
return middleware.Middleware(handler)
|
return middleware.Middleware(handler)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -274,3 +277,61 @@ func (a *Api) deviceExists(sn string, w http.ResponseWriter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Api) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var user db.User
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&user)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user.HashPassword(user.Password); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Db.RegisterUser(user); err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenRequest struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Api) generateToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var tokenReq TokenRequest
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&tokenReq)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := a.Db.FindUser(tokenReq.Email)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
json.NewEncoder(w).Encode("Invalid Credentials")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialError := user.CheckPassword(tokenReq.Password)
|
||||||
|
if credentialError != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
json.NewEncoder(w).Encode("Invalid Credentials")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := auth.GenerateJWT(user.Email, user.Name)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
54
backend/services/controller/internal/api/auth/auth.go
Normal file
54
backend/services/controller/internal/api/auth/auth.go
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jwtKey = []byte("supersecretkey")
|
||||||
|
|
||||||
|
type JWTClaim struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
jwt.StandardClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateJWT(email string, username string) (tokenString string, err error) {
|
||||||
|
expirationTime := time.Now().Add(1 * time.Hour)
|
||||||
|
claims := &JWTClaim{
|
||||||
|
Email: email,
|
||||||
|
Username: username,
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
ExpiresAt: expirationTime.Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err = token.SignedString(jwtKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func ValidateToken(signedToken string) (email string, err error) {
|
||||||
|
token, err := jwt.ParseWithClaims(
|
||||||
|
signedToken,
|
||||||
|
&JWTClaim{},
|
||||||
|
func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(jwtKey), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims, ok := token.Claims.(*JWTClaim)
|
||||||
|
if !ok {
|
||||||
|
err = errors.New("couldn't parse claims")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if claims.ExpiresAt < time.Now().Local().Unix() {
|
||||||
|
err = errors.New("token expired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email = claims.Email
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,28 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"github.com/leandrofars/oktopus/internal/api/auth"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
func Middleware(next http.Handler) http.Handler {
|
func Middleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(
|
return http.HandlerFunc(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
|
tokenString := r.Header.Get("Authorization")
|
||||||
|
if tokenString == "" {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
email, err := auth.ValidateToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := context.WithValue(r.Context(), "email", email)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
devices *mongo.Collection
|
devices *mongo.Collection
|
||||||
|
users *mongo.Collection
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,7 +23,9 @@ func NewDatabase(ctx context.Context, mongoUri string) Database {
|
||||||
|
|
||||||
log.Println("Connected to MongoDB-->", mongoUri)
|
log.Println("Connected to MongoDB-->", mongoUri)
|
||||||
devices := client.Database("oktopus").Collection("devices")
|
devices := client.Database("oktopus").Collection("devices")
|
||||||
|
users := client.Database("oktopus").Collection("users")
|
||||||
db.devices = devices
|
db.devices = devices
|
||||||
|
db.users = users
|
||||||
db.ctx = ctx
|
db.ctx = ctx
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
backend/services/controller/internal/db/user.go
Normal file
49
backend/services/controller/internal/db/user.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) RegisterUser(user User) error {
|
||||||
|
err := d.users.FindOne(d.ctx, bson.D{{"email", user.Email}}).Err()
|
||||||
|
if err != nil {
|
||||||
|
if err == mongo.ErrNoDocuments {
|
||||||
|
_, err = d.users.InsertOne(d.ctx, user)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Database) FindUser(email string) (User, error) {
|
||||||
|
var result User
|
||||||
|
err := d.users.FindOne(d.ctx, bson.D{{"email", email}}).Decode(&result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) HashPassword(password string) error {
|
||||||
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Password = string(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) CheckPassword(providedPassword string) error {
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(providedPassword))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -252,7 +252,6 @@ func (m *Mqtt) handleApiRequest(api []byte) {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: verify record operation type
|
|
||||||
var msg usp_msg.Msg
|
var msg usp_msg.Msg
|
||||||
err = proto.Unmarshal(record.GetNoSessionContext().Payload, &msg)
|
err = proto.Unmarshal(record.GetNoSessionContext().Payload, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user