Merge pull request #38 from leandrofars/dev

Api auth
This commit is contained in:
Leandro Antônio Farias Machado 2023-05-03 00:08:52 -03:00 committed by GitHub
commit a1ccd87b43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 260 additions and 13 deletions

View File

@ -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

View File

@ -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=

View File

@ -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
}

View 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
}

View File

@ -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))
},
)
}

View File

@ -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
}

View 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
}

View File

@ -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 {

View File

@ -0,0 +1,62 @@
import * as React from 'react';
import IconButton from '@mui/material/IconButton';
import Box from '@mui/material/Box';
import { useTheme, ThemeProvider, createTheme } from '@mui/material/styles';
import Brightness4Icon from '@mui/icons-material/Brightness4';
import Brightness7Icon from '@mui/icons-material/Brightness7';
const ColorModeContext = React.createContext({ toggleColorMode: () => {} });
function MyApp() {
const theme = useTheme();
const colorMode = React.useContext(ColorModeContext);
return (
<Box
sx={{
display: 'flex',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'background.default',
color: 'text.primary',
borderRadius: 1,
p: 3,
}}
>
{theme.palette.mode} mode
<IconButton sx={{ ml: 1 }} onClick={colorMode.toggleColorMode} color="inherit">
{theme.palette.mode === 'dark' ? <Brightness7Icon /> : <Brightness4Icon />}
</IconButton>
</Box>
);
}
export default function ToggleColorMode() {
const [mode, setMode] = React.useState<'light' | 'dark'>('light');
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
},
}),
[],
);
const theme = React.useMemo(
() =>
createTheme({
palette: {
mode,
},
}),
[mode],
);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
</ColorModeContext.Provider>
);
}