diff --git a/LICENSE b/LICENSE index 5923ce0..6268dc9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2023] [Leandro Antônio Farias Machado] + Copyright 2024 Leandro Antônio Farias Machado Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index b6a87d4..c5724eb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@
-
-
In case you want a more complete and real-world simulation of devices you might use Oktopus TR-369 Agent Simulator.
diff --git a/agent/run.sh b/agent/run.sh new file mode 100644 index 0000000..996e4a4 --- /dev/null +++ b/agent/run.sh @@ -0,0 +1 @@ +obuspa -p -v 4 -r ./oktopus-mqtt-obuspa.txt -i lo \ No newline at end of file diff --git a/backend/services/controller/go.mod b/backend/services/controller/go.mod old mode 100755 new mode 100644 index 9841213..211958d --- a/backend/services/controller/go.mod +++ b/backend/services/controller/go.mod @@ -3,21 +3,22 @@ module github.com/leandrofars/oktopus go 1.18 require ( - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/eclipse/paho.golang v0.10.0 + github.com/go-stomp/stomp v2.1.4+incompatible + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/uuid v1.3.0 github.com/googollee/go-socket.io v1.7.0 github.com/gorilla/mux v1.8.0 github.com/joho/godotenv v1.5.1 github.com/rs/cors v1.9.0 go.mongodb.org/mongo-driver v1.11.3 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.17.0 golang.org/x/net v0.17.0 + golang.org/x/sys v0.15.0 google.golang.org/protobuf v1.28.1 ) require ( - github.com/go-stomp/stomp v2.1.4+incompatible // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang/snappy v0.0.1 // indirect github.com/gomodule/redigo v1.8.4 // indirect @@ -30,6 +31,5 @@ require ( github.com/xdg-go/stringprep v1.0.3 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/backend/services/controller/go.sum b/backend/services/controller/go.sum index c6c36ac..3996cfc 100644 --- a/backend/services/controller/go.sum +++ b/backend/services/controller/go.sum @@ -1,14 +1,14 @@ 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/go-stomp/stomp v2.1.4+incompatible h1:D3SheUVDOz9RsjVWkoh/1iCOwD0qWjyeTZMUZ0EXg2Y= github.com/go-stomp/stomp v2.1.4+incompatible/go.mod h1:VqCtqNZv1226A1/79yh+rMiFUcfY3R109np+7ke4n0c= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -60,8 +60,8 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= @@ -71,13 +71,13 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -85,6 +85,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/backend/services/controller/internal/api/auth/auth.go b/backend/services/controller/internal/api/auth/auth.go index e1a2712..d93eeda 100644 --- a/backend/services/controller/internal/api/auth/auth.go +++ b/backend/services/controller/internal/api/auth/auth.go @@ -2,9 +2,12 @@ package auth import ( "errors" - "github.com/dgrijalva/jwt-go" + "fmt" + "log" "os" "time" + + "github.com/golang-jwt/jwt/v5" ) func getJwtKey() []byte { @@ -18,16 +21,17 @@ func getJwtKey() []byte { type JWTClaim struct { Username string `json:"username"` Email string `json:"email"` - jwt.StandardClaims + jwt.RegisteredClaims } func GenerateJWT(email string, username string) (tokenString string, err error) { expirationTime := time.Now().Add(4 * time.Hour) claims := &JWTClaim{ - Email: email, - Username: username, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: expirationTime.Unix(), + username, + email, + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + Issuer: "Oktopus", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) @@ -40,21 +44,25 @@ func ValidateToken(signedToken string) (email string, err error) { signedToken, &JWTClaim{}, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") return getJwtKey(), nil }, ) if err != nil { + log.Println(err) 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 diff --git a/backend/services/controller/internal/api/device.go b/backend/services/controller/internal/api/device.go index af4e798..0993583 100644 --- a/backend/services/controller/internal/api/device.go +++ b/backend/services/controller/internal/api/device.go @@ -2,11 +2,12 @@ package api import ( "encoding/json" - "go.mongodb.org/mongo-driver/bson" "log" "net/http" "strconv" + "go.mongodb.org/mongo-driver/bson" + "github.com/gorilla/mux" "github.com/leandrofars/oktopus/internal/db" usp_msg "github.com/leandrofars/oktopus/internal/usp_message" @@ -37,11 +38,15 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { const PAGE_SIZE_DEFAULT = 20 // Get specific device - id := mux.Vars(r)["id"] + id := r.URL.Query().Get("id") if id != "" { device, err := a.Db.RetrieveDevice(id) if err != nil { - log.Println(err) + if err == mongo.ErrNoDocuments { + json.NewEncoder(w).Encode("Device id: " + id + " not found") + return + } + json.NewEncoder(w).Encode(err) w.WriteHeader(http.StatusInternalServerError) return } @@ -59,7 +64,7 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { var page_number int64 if page_n == "" { - page_number = 1 + page_number = 0 } else { page_number, err = strconv.ParseInt(page_n, 10, 64) if err != nil { @@ -93,6 +98,7 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode("Unable to get devices count from database") + return } skip := page_number * (page_size - 1) @@ -103,9 +109,12 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { //TODO: Create filters //TODO: Create sorting + sort := bson.M{} + sort["status"] = 1 + filter := bson.A{ //bson.M{"$match": filter}, - //bson.M{"$sort": sort}, + bson.M{"$sort": sort}, // shows online devices first bson.M{"$skip": skip}, bson.M{"$limit": page_size}, } @@ -117,12 +126,15 @@ func (a *Api) retrieveDevices(w http.ResponseWriter, r *http.Request) { return } - err = json.NewEncoder(w).Encode(devices) + err = json.NewEncoder(w).Encode(map[string]interface{}{ + "pages": total / page_size, + "page": page_number, + "size": page_size, + "devices": devices, + }) if err != nil { log.Println(err) } - - return } func (a *Api) deviceCreateMsg(w http.ResponseWriter, r *http.Request) { diff --git a/backend/services/socketio/package-lock.json b/backend/services/socketio/package-lock.json index 7e3dbe9..954c709 100644 --- a/backend/services/socketio/package-lock.json +++ b/backend/services/socketio/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@koa/cors": "^4.0.0", + "@koa/cors": "^5.0.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", @@ -18,9 +18,9 @@ } }, "node_modules/@koa/cors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-4.0.0.tgz", - "integrity": "sha512-Y4RrbvGTlAaa04DBoPBWJqDR5gPj32OOz827ULXfgB1F7piD1MB/zwn8JR2LAnvdILhxUbXbkXGWuNVsFuVFCQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", "dependencies": { "vary": "^1.1.2" }, @@ -1084,9 +1084,9 @@ }, "dependencies": { "@koa/cors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-4.0.0.tgz", - "integrity": "sha512-Y4RrbvGTlAaa04DBoPBWJqDR5gPj32OOz827ULXfgB1F7piD1MB/zwn8JR2LAnvdILhxUbXbkXGWuNVsFuVFCQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", "requires": { "vary": "^1.1.2" } diff --git a/backend/services/socketio/package.json b/backend/services/socketio/package.json index 7c6dbf5..bf28997 100644 --- a/backend/services/socketio/package.json +++ b/backend/services/socketio/package.json @@ -11,7 +11,7 @@ "author": "", "license": "ISC", "dependencies": { - "@koa/cors": "^4.0.0", + "@koa/cors": "^5.0.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", diff --git a/devops/nginx/nginx.conf b/devops/nginx/nginx.conf index 597e147..c548171 100644 --- a/devops/nginx/nginx.conf +++ b/devops/nginx/nginx.conf @@ -34,6 +34,44 @@ http { # for more information. include /etc/nginx/conf.d/*.conf; + server { + if ($host = oktopus.app.br) { + return 301 https://$host$request_uri; + } + listen 80; + listen [::]:80; + server_name oktopus.app.br; + return 404; + } + + server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name oktopus.app.br; + root /usr/share/nginx/html; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + ssl_certificate "/etc/letsencrypt/live/oktopus.app.br/fullchain.pem"; + ssl_certificate_key "/etc/letsencrypt/live/oktopus.app.br/privkey.pem"; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 10m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + error_page 404 /404.html; + location = /404.html { + } + + location / { + proxy_pass http://127.0.0.1:3001; + proxy_read_timeout 60; + proxy_connect_timeout 60; + proxy_redirect off; + } + } + server { if ($host = oktopustr369.com) { return 301 https://$host$request_uri; diff --git a/frontend/src/pages/devices.js b/frontend/src/pages/devices.js index 5defbb1..6933b39 100644 --- a/frontend/src/pages/devices.js +++ b/frontend/src/pages/devices.js @@ -1,6 +1,18 @@ import React, { useState, useEffect } from 'react'; import Head from 'next/head'; -import { Box, Container, Unstable_Grid2 as Grid } from '@mui/material'; +import { + Box, + Container, + Unstable_Grid2 as Grid, + Card, + OutlinedInput, + InputAdornment, + SvgIcon, + Stack, + Pagination, + CircularProgress +} from '@mui/material'; +import MagnifyingGlassIcon from '@heroicons/react/24/solid/MagnifyingGlassIcon'; import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; import { OverviewLatestOrders } from 'src/sections/overview/overview-latest-orders'; import { useAuth } from 'src/hooks/use-auth'; @@ -10,9 +22,14 @@ const Page = () => { const router = useRouter() const auth = useAuth(); const [devices, setDevices] = useState([]); + const [deviceFound, setDeviceFound] = useState(false) + const [pages, setPages] = useState(0); + const [page, setPage] = useState(null); + const [Loading, setLoading] = useState(true); + useEffect(() => { - + setLoading(true) if (auth.user.token) { console.log("auth.user.token =", auth.user.token) }else{ @@ -36,13 +53,106 @@ const Page = () => { return response.json() }) .then(json => { - return setDevices(json) + setPages(json.pages + 1) + setPage(json.page) + setDevices(json.devices) + setLoading(false) + return setDeviceFound(true) }) .catch(error => { return console.error('Error:', error) }); }, [auth.user]); + const handleChangePage = (event, value) => { + console.log("new page: ", value) + setPage(value) + fetchDevicePerPage(value) + } + + const fetchDevicePerPage = async (p) => { + setLoading(true) + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + } + + p = p - 1 + p = p.toString() + + fetch(process.env.NEXT_PUBLIC_REST_ENPOINT+'/device?page_number='+p, requestOptions) + .then(response => { + if (response.status === 401) + router.push("/auth/login") + return response.json() + }) + .then(json => { + setDevices(json.devices) + setLoading(false) + return + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + const fetchDevicePerId = async (id) => { + setLoading(true) + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + } + + if (id == ""){ + return fetch(process.env.NEXT_PUBLIC_REST_ENPOINT+'/device', requestOptions) + .then(response => { + if (response.status === 401) + router.push("/auth/login") + return response.json() + }) + .then(json => { + setPages(json.pages + 1) + setPage(json.page) + setDevices(json.devices) + setLoading(false) + return setDeviceFound(true) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + let response = await fetch(process.env.NEXT_PUBLIC_REST_ENPOINT+'/device?id='+id, requestOptions) + if (response.status === 401) + router.push("/auth/login") + let json = await response.json() + if (json.MTP != undefined){ + setDevices([json]) + setDeviceFound(true) + setLoading(false) + setPages(1) + setPage(1) + }else{ + setDeviceFound(false) + setDevices([]) + setPages(1) + setPage(1) + setLoading(false) + } + + } + return ( <> @@ -50,6 +160,7 @@ const Page = () => { Oktopus | TR-369 +Device Not Found
+