feat(api): user jwt authentication
This commit is contained in:
parent
5daf95c99c
commit
bfb2acf0fa
|
|
@ -9,6 +9,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/uuid v1.3.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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:rhrV37IEwauUyx8FHrvmXOKo+QRKng5ncoN1vJiJMcs=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/leandrofars/oktopus/internal/api/auth"
|
||||
"github.com/leandrofars/oktopus/internal/api/middleware"
|
||||
"github.com/leandrofars/oktopus/internal/db"
|
||||
"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) {
|
||||
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) {
|
||||
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 {
|
||||
iot.Use(func(handler http.Handler) http.Handler {
|
||||
return middleware.Middleware(handler)
|
||||
})
|
||||
|
||||
|
|
@ -274,3 +277,61 @@ func (a *Api) deviceExists(sn string, w http.ResponseWriter) {
|
|||
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
|
||||
|
||||
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 {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
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 {
|
||||
devices *mongo.Collection
|
||||
users *mongo.Collection
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
|
|
@ -22,7 +23,9 @@ func NewDatabase(ctx context.Context, mongoUri string) Database {
|
|||
|
||||
log.Println("Connected to MongoDB-->", mongoUri)
|
||||
devices := client.Database("oktopus").Collection("devices")
|
||||
users := client.Database("oktopus").Collection("users")
|
||||
db.devices = devices
|
||||
db.users = users
|
||||
db.ctx = ctx
|
||||
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)
|
||||
}
|
||||
|
||||
//TODO: verify record operation type
|
||||
var msg usp_msg.Msg
|
||||
err = proto.Unmarshal(record.GetNoSessionContext().Payload, &msg)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user