From cbe0b4b1eb5f012221632e05ee665168c450abe4 Mon Sep 17 00:00:00 2001 From: Dimas Restu H Date: Wed, 20 Apr 2022 22:50:51 +0700 Subject: [PATCH] construct base function for whatsapp multidevice --- .dockerignore | 1 + .env.example | 18 +- .gitignore | 1 + Dockerfile | 2 +- cmd/main/main.go | 4 +- dbs/.gitkeep | 0 go.mod | 25 ++- go.sum | 33 ++-- internal/auth/auth.go | 50 +++++ .../{index/auth/jwt.go => auth/types/data.go} | 4 +- internal/auth/types/request.go | 5 + internal/auth/types/response.go | 5 + internal/index/index.go | 45 ----- internal/index/model/request.go | 5 - internal/index/model/response.go | 5 - internal/route.go | 34 ++-- internal/whatsapp/model/request.go | 20 -- internal/whatsapp/types/request.go | 20 ++ internal/whatsapp/types/response.go | 6 + internal/whatsapp/whatsapp.go | 92 ++++++++- pkg/auth/auth.go | 24 +-- pkg/router/response.go | 5 + pkg/router/router.go | 4 +- pkg/whatsapp/whatsapp.go | 175 ++++++++++++++++++ 24 files changed, 445 insertions(+), 138 deletions(-) create mode 100644 dbs/.gitkeep create mode 100644 internal/auth/auth.go rename internal/{index/auth/jwt.go => auth/types/data.go} (79%) create mode 100644 internal/auth/types/request.go create mode 100644 internal/auth/types/response.go delete mode 100644 internal/index/model/request.go delete mode 100644 internal/index/model/response.go delete mode 100644 internal/whatsapp/model/request.go create mode 100644 internal/whatsapp/types/request.go create mode 100644 internal/whatsapp/types/response.go diff --git a/.dockerignore b/.dockerignore index 54cb063..6e2d670 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ dist vendor +dbs/* .env **/.DS_Store **/.gitkeep \ No newline at end of file diff --git a/.env.example b/.env.example index cabb699..28c32bf 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,17 @@ -API_BASE_URL=/api/v1/whatsapp \ No newline at end of file +# ----------------------------------- +# HTTP Server Configuration +# ----------------------------------- +# HTTP_BASE_URL=/ + +# HTTP_CORS_ORIGIN=* +# HTTP_BODY_LIMIT_SIZE=8m +# HTTP_GZIP_LEVEL=1 + +# ----------------------------------- +# Authentication Configuration +# ----------------------------------- +# AUTH_BASIC_USERNAME=admin +# AUTH_BASIC_PASSWORD=password + +# AUTH_JWT_SECRET=secret +# AUTH_JWT_EXPIRED_HOUR=24 diff --git a/.gitignore b/.gitignore index c5f4973..6f67b53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ dist vendor +dbs/* .env *.DS_Store !*.gitkeep \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f7d4444..bf7d631 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Builder Image # --------------------------------------------------- -FROM dimaskiddo/alpine:go-1.15 AS go-builder +FROM dimaskiddo/alpine:go-1.17 AS go-builder WORKDIR /usr/src/app diff --git a/cmd/main/main.go b/cmd/main/main.go index 6446c87..d6d3a46 100644 --- a/cmd/main/main.go +++ b/cmd/main/main.go @@ -42,8 +42,8 @@ func main() { // Router CORS e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{router.CORSOrigin}, - AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization, echo.HeaderXRequestedWith}, - AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE, echo.OPTIONS}, + AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, + AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE}, })) // Router Security diff --git a/dbs/.gitkeep b/dbs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod index e570412..5edd007 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,34 @@ module github.com/dimaskiddo/go-whatsapp-multidevice-rest -go 1.15 +go 1.17 require ( github.com/go-playground/validator/v10 v10.6.1 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/joho/godotenv v1.3.0 github.com/labstack/echo/v4 v4.7.2 + github.com/mattn/go-sqlite3 v1.14.11 github.com/sirupsen/logrus v1.8.1 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543 +) + +require ( + filippo.io/edwards25519 v1.0.0-rc.1 // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/labstack/gommon v0.3.1 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b // indirect + golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect + google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/go.sum b/go.sum index 0c69d44..2f940c0 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -9,6 +11,10 @@ github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0 github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= @@ -21,9 +27,13 @@ github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHR github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ= +github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -32,34 +42,35 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b h1:BGm0ceth6OtoXgBksEyeruiCL2ngZCwt86DUmUI6TmQ= +go.mau.fi/libsignal v0.0.0-20220315232917-871a40435d3b/go.mod h1:XYWsswZT1LfDmguWKYDuj+OugtdGX6CP3iwTtOcAGt4= +go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543 h1:bSRqMKTczahOls67gsx/CxY0GiaGkqs0RQoyWhIb+Dg= +go.mau.fi/whatsmeow v0.0.0-20220416192948-8b34d886d543/go.mod h1:P7OA9XyJ/0dWHhUPKNESpC1wVOErnhY4pLEaMC1a8yg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..a2fdebc --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,50 @@ +package auth + +import ( + "encoding/json" + "time" + + "github.com/golang-jwt/jwt" + "github.com/labstack/echo/v4" + + typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" + + "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth" + "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" +) + +// Auth +func Auth(c echo.Context) error { + var reqAuthBasicInfo typAuth.RequestAuthBasicInfo + var resAuthJWTData typAuth.ResponseAuthJWTData + + // Parse Basic Auth Information from Rewrited Body Request + // By Basic Auth Middleware + _ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo) + + // Create JWT Claims + jwtClaims := &typAuth.AuthJWTClaims{ + typAuth.AuthJWTClaimsPayload{ + JID: reqAuthBasicInfo.Username, + }, + jwt.StandardClaims{ + IssuedAt: time.Now().Unix(), + ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(), + }, + } + + // Create JWT Token + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims) + + // Generate Encoded JWT Token + jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret)) + if err != nil { + return router.ResponseInternalError(c, "") + } + + // Set Encoded JWT Token as Response Data + resAuthJWTData.Token = jwtTokenEncoded + + // Return JWT Token in JSON Response + return router.ResponseSuccessWithData(c, "Successfully Authenticated", resAuthJWTData) +} diff --git a/internal/index/auth/jwt.go b/internal/auth/types/data.go similarity index 79% rename from internal/index/auth/jwt.go rename to internal/auth/types/data.go index 30a4009..3623e06 100644 --- a/internal/index/auth/jwt.go +++ b/internal/auth/types/data.go @@ -1,4 +1,4 @@ -package model +package types import ( "github.com/golang-jwt/jwt" @@ -10,5 +10,5 @@ type AuthJWTClaims struct { } type AuthJWTClaimsPayload struct { - MSISDN string `json:"msisdn"` + JID string `json:"jid"` } diff --git a/internal/auth/types/request.go b/internal/auth/types/request.go new file mode 100644 index 0000000..26999ee --- /dev/null +++ b/internal/auth/types/request.go @@ -0,0 +1,5 @@ +package types + +type RequestAuthBasicInfo struct { + Username string +} diff --git a/internal/auth/types/response.go b/internal/auth/types/response.go new file mode 100644 index 0000000..de5e7f5 --- /dev/null +++ b/internal/auth/types/response.go @@ -0,0 +1,5 @@ +package types + +type ResponseAuthJWTData struct { + Token string `json:"token"` +} diff --git a/internal/index/index.go b/internal/index/index.go index d8494fe..63d4550 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -1,57 +1,12 @@ package index import ( - "encoding/json" - "time" - - "github.com/golang-jwt/jwt" "github.com/labstack/echo/v4" - "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth" "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" - - indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth" - "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/model" ) // Index func Index(c echo.Context) error { return router.ResponseSuccess(c, "Go WhatsApp Multi-Device REST is running") } - -// Auth -func Auth(c echo.Context) error { - var reqAuthBasicInfo model.ReqAuthBasicInfo - var resAuthJWTData model.ResAuthJWTData - - // Parse Basic Auth Information from Rewrited Body Request - // By Basic Auth Middleware - _ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo) - - // Create JWT Claims - jwtClaims := &indexAuth.AuthJWTClaims{ - indexAuth.AuthJWTClaimsPayload{ - MSISDN: reqAuthBasicInfo.Username, - }, - jwt.StandardClaims{ - Issuer: "go-whatsapp-multidevice-rest", - IssuedAt: time.Now().Unix(), - ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(), - }, - } - - // Create JWT Token - jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims) - - // Generate Encoded JWT Token - jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret)) - if err != nil { - return router.ResponseInternalError(c, "") - } - - // Set Encoded JWT Token as Response Data - resAuthJWTData.Token = jwtTokenEncoded - - // Return JWT Token in JSON Response - return router.ResponseSuccessWithData(c, "", resAuthJWTData) -} diff --git a/internal/index/model/request.go b/internal/index/model/request.go deleted file mode 100644 index 61246a7..0000000 --- a/internal/index/model/request.go +++ /dev/null @@ -1,5 +0,0 @@ -package model - -type ReqAuthBasicInfo struct { - Username string -} diff --git a/internal/index/model/response.go b/internal/index/model/response.go deleted file mode 100644 index b3c108b..0000000 --- a/internal/index/model/response.go +++ /dev/null @@ -1,5 +0,0 @@ -package model - -type ResAuthJWTData struct { - Token string `json:"token"` -} diff --git a/internal/route.go b/internal/route.go index 43cc798..9917ac8 100644 --- a/internal/route.go +++ b/internal/route.go @@ -7,29 +7,37 @@ import ( "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth" "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" - "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index" - indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth" - "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp" + ctlAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth" + typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" + + ctlIndex "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index" + ctlWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp" ) func Routes(e *echo.Echo) { // Route for Index // --------------------------------------------- - e.GET(router.BaseURL, index.Index) - e.GET(router.BaseURL+"/auth", index.Auth, auth.BasicAuth()) + e.GET(router.BaseURL, ctlIndex.Index) + + // Route for Auth + // --------------------------------------------- + e.GET(router.BaseURL+"/auth", ctlAuth.Auth, auth.BasicAuth()) // Route for WhatsApp // --------------------------------------------- authJWTConfig := middleware.JWTConfig{ - Claims: &indexAuth.AuthJWTClaims{}, + Claims: &typAuth.AuthJWTClaims{}, SigningKey: []byte(auth.AuthJWTSecret), } - e.POST(router.BaseURL+"/login", whatsapp.Login, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/text", whatsapp.SendText, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/location", whatsapp.SendLocation, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/document", whatsapp.SendDocument, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/audio", whatsapp.SendAudio, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/image", whatsapp.SendImage, middleware.JWTWithConfig(authJWTConfig)) - e.POST(router.BaseURL+"/send/video", whatsapp.SendVideo, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/login", ctlWhatsApp.Login, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/send/text", ctlWhatsApp.SendText, middleware.JWTWithConfig(authJWTConfig)) + /* + e.POST(router.BaseURL+"/send/location", ctlWhatsApp.SendLocation, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/send/document", ctlWhatsApp.SendDocument, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/send/audio", ctlWhatsApp.SendAudio, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/send/image", ctlWhatsApp.SendImage, middleware.JWTWithConfig(authJWTConfig)) + e.POST(router.BaseURL+"/send/video", ctlWhatsApp.SendVideo, middleware.JWTWithConfig(authJWTConfig)) + */ + e.POST(router.BaseURL+"/logout", ctlWhatsApp.Logout, middleware.JWTWithConfig(authJWTConfig)) } diff --git a/internal/whatsapp/model/request.go b/internal/whatsapp/model/request.go deleted file mode 100644 index 477c1cc..0000000 --- a/internal/whatsapp/model/request.go +++ /dev/null @@ -1,20 +0,0 @@ -package model - -type ReqLogin struct { - Output string -} - -type ReqSendMessage struct { - MSISDN string - Message string - QuotedID string - QuotedMessage string -} - -type ReqSendLocation struct { - MSISDN string - Latitude float64 - Longitude float64 - QuotedID string - QuotedMessage string -} diff --git a/internal/whatsapp/types/request.go b/internal/whatsapp/types/request.go new file mode 100644 index 0000000..d6a726a --- /dev/null +++ b/internal/whatsapp/types/request.go @@ -0,0 +1,20 @@ +package types + +type RequestLogin struct { + Output string +} + +type RequestSendMessage struct { + RJID string + Message string + // QuotedID string + // QuotedMessage string +} + +type RequestSendLocation struct { + RJID string + Latitude float64 + Longitude float64 + // QuotedID string + // QuotedMessage string +} diff --git a/internal/whatsapp/types/response.go b/internal/whatsapp/types/response.go new file mode 100644 index 0000000..d76ef21 --- /dev/null +++ b/internal/whatsapp/types/response.go @@ -0,0 +1,6 @@ +package types + +type ResponseLogin struct { + Timeout string `json:"timeout"` + QRCode string `json:"qrcode"` +} diff --git a/internal/whatsapp/whatsapp.go b/internal/whatsapp/whatsapp.go index 67a010d..9421038 100644 --- a/internal/whatsapp/whatsapp.go +++ b/internal/whatsapp/whatsapp.go @@ -5,41 +5,113 @@ import ( "github.com/labstack/echo/v4" "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router" + pkgWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/whatsapp" - indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth" + typAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/auth/types" + typWhatsApp "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp/types" ) -func getJWTPayload(c echo.Context) indexAuth.AuthJWTClaimsPayload { +func jwtPayload(c echo.Context) typAuth.AuthJWTClaimsPayload { jwtToken := c.Get("user").(*jwt.Token) - jwtClaims := jwtToken.Claims.(*indexAuth.AuthJWTClaims) + jwtClaims := jwtToken.Claims.(*typAuth.AuthJWTClaims) return jwtClaims.Data } func Login(c echo.Context) error { - return router.ResponseSuccess(c, "") + jid := jwtPayload(c).JID + + var reqLogin typWhatsApp.RequestLogin + reqLogin.Output = c.FormValue("output") + + if reqLogin.Output == "" { + reqLogin.Output = "html" + } + + err := pkgWhatsApp.WhatAppConnect(jid) + if err != nil { + return router.ResponseInternalError(c, err.Error()) + } + + qrCodeImage, qrCodeTimeout, err := pkgWhatsApp.WhatsAppLogin(jid) + if err != nil { + return router.ResponseInternalError(c, err.Error()) + } + + var resLogin typWhatsApp.ResponseLogin + resLogin.QRCode = qrCodeImage + resLogin.Timeout = qrCodeTimeout + + if reqLogin.Output == "html" { + htmlContent := ` + + + WhatsApp MultiDevice Login + + + +

+ QR Code Scan +
+ Timeout in ` + resLogin.Timeout + ` +

+ + ` + + return router.ResponseSuccessWithHTML(c, htmlContent) + } + + return router.ResponseSuccessWithData(c, "Successfully Generated QR Code", resLogin) +} + +func Logout(c echo.Context) error { + jid := jwtPayload(c).JID + + err := pkgWhatsApp.WhatsAppLogout(jid) + if err != nil { + return router.ResponseInternalError(c, err.Error()) + } + + return router.ResponseSuccess(c, "Successfully Logged Out") } func SendText(c echo.Context) error { - return router.ResponseSuccess(c, "") + jid := jwtPayload(c).JID + + var reqSendMessage typWhatsApp.RequestSendMessage + reqSendMessage.RJID = c.FormValue("msisdn") + reqSendMessage.Message = c.FormValue("message") + + err := pkgWhatsApp.WhatsAppSendText(jid, reqSendMessage.RJID, reqSendMessage.Message) + if err != nil { + return router.ResponseInternalError(c, err.Error()) + } + + return router.ResponseSuccess(c, "Successfully Send Text Message") } +/* + TODO: Send Media +*/ + +/* func SendLocation(c echo.Context) error { - return router.ResponseSuccess(c, "") + return router.ResponseSuccess(c, "Successfully Send Location Message") } func SendDocument(c echo.Context) error { - return router.ResponseSuccess(c, "") + return router.ResponseSuccess(c, "Successfully Send Document Message") } func SendImage(c echo.Context) error { - return router.ResponseSuccess(c, "") + return router.ResponseSuccess(c, "Successfully Send Image Message") } func SendAudio(c echo.Context) error { - return router.ResponseSuccess(c, "") + return router.ResponseSuccess(c, "Successfully Send Audio Message") } func SendVideo(c echo.Context) error { - return router.ResponseSuccess(c, "") + return router.ResponseSuccess(c, "Successfully Send Video Message") } +*/ diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index eb39263..514641d 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -11,25 +11,9 @@ var AuthJWTSecret string var AuthJWTExpiredHour int func init() { - var err error + AuthBasicUsername, _ = env.GetEnvString("AUTH_BASIC_USERNAME") + AuthBasicPassword, _ = env.GetEnvString("AUTH_BASIC_PASSWORD") - AuthBasicUsername, err = env.GetEnvString("AUTH_BASIC_USERNAME") - if err != nil { - AuthBasicUsername = "administrator" - } - - AuthBasicPassword, err = env.GetEnvString("AUTH_BASIC_PASSWORD") - if err != nil { - AuthBasicPassword = "83e4060e-78e1-4fe5-9977-aeeccd46a2b8" - } - - AuthJWTSecret, err = env.GetEnvString("AUTH_JWT_SECRET") - if err != nil { - AuthJWTSecret = "9e4eb4cf-be25-4a29-bba3-fefb5a30f6ab" - } - - AuthJWTExpiredHour, err = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR") - if err != nil { - AuthJWTExpiredHour = 24 - } + AuthJWTSecret, _ = env.GetEnvString("AUTH_JWT_SECRET") + AuthJWTExpiredHour, _ = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR") } diff --git a/pkg/router/response.go b/pkg/router/response.go index b756a06..901517b 100644 --- a/pkg/router/response.go +++ b/pkg/router/response.go @@ -80,6 +80,11 @@ func ResponseSuccessWithData(c echo.Context, message string, data interface{}) e return c.JSON(response.Code, response) } +func ResponseSuccessWithHTML(c echo.Context, html string) error { + logSuccess(c, http.StatusOK, http.StatusText(http.StatusOK)) + return c.HTML(http.StatusOK, html) +} + func ResponseCreated(c echo.Context, message string) error { var response ResSuccess diff --git a/pkg/router/router.go b/pkg/router/router.go index 56d1cc8..496ab3d 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -10,7 +10,7 @@ var GZipLevel int func init() { var err error - BaseURL, err = env.GetEnvString("API_BASE_URL") + BaseURL, err = env.GetEnvString("HTTP_BASE_URL") if err != nil { BaseURL = "/" } @@ -27,6 +27,6 @@ func init() { GZipLevel, err = env.GetEnvInt("HTTP_GZIP_LEVEL") if err != nil { - GZipLevel = 3 + GZipLevel = 1 } } diff --git a/pkg/whatsapp/whatsapp.go b/pkg/whatsapp/whatsapp.go index cbabb76..256428b 100644 --- a/pkg/whatsapp/whatsapp.go +++ b/pkg/whatsapp/whatsapp.go @@ -1 +1,176 @@ package whatsapp + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "os" + + _ "github.com/mattn/go-sqlite3" + qrCode "github.com/skip2/go-qrcode" + "google.golang.org/protobuf/proto" + + "go.mau.fi/whatsmeow" + waproto "go.mau.fi/whatsmeow/binary/proto" + "go.mau.fi/whatsmeow/store" + "go.mau.fi/whatsmeow/store/sqlstore" + "go.mau.fi/whatsmeow/types" +) + +var WhatsAppClient = make(map[string]*whatsmeow.Client) + +func WhatsAppInit(jid string) (*whatsmeow.Client, error) { + // Prepare SQLite Database File and Connection Address + dbFileName := "dbs/" + jid + ".db" + dbAddress := fmt.Sprintf("file:%s?_foreign_keys=on", dbFileName) + + // Create and Connect to SQLite Database + datastore, err := sqlstore.New("sqlite3", dbAddress, nil) + if err != nil { + return nil, errors.New("Failed to Connect SQLite Database") + } + + // Get First WhatsApp Device from SQLite Database + device, err := datastore.GetFirstDevice() + if err != nil { + return nil, errors.New("Failed to Load WhatsApp Device") + } + + // Set Client Properties + store.CompanionProps.Os = proto.String("Go WhatsApp MultiDevice REST") + store.CompanionProps.PlatformType = waproto.CompanionProps_DESKTOP.Enum() + + // Create New Client Connection + client := whatsmeow.NewClient(device, nil) + + // Return Client Connection + return client, nil +} + +func WhatAppConnect(jid string) error { + if WhatsAppClient[jid] == nil { + // Initialize New WhatsApp Client + client, err := WhatsAppInit(jid) + if err != nil { + return err + } + + // Set Created WhatsApp Client to Map + WhatsAppClient[jid] = client + } + + return nil +} + +func WhatsAppGenerateQR(qrChan <-chan whatsmeow.QRChannelItem) (string, string) { + qrChanCode := make(chan string) + qrChanTimeout := make(chan string) + qrChanBase64 := make(chan string) + + // Get QR Code Data and Timeout + go func() { + for evt := range qrChan { + if evt.Event == "code" { + qrChanCode <- evt.Code + qrChanTimeout <- evt.Timeout.String() + } + } + }() + + // Generate QR Code Data to PNG Base64 Format + go func() { + select { + case tmp := <-qrChanCode: + png, _ := qrCode.Encode(tmp, qrCode.Medium, 256) + qrChanBase64 <- base64.StdEncoding.EncodeToString(png) + } + }() + + // Return QR Code and Timeout Information + return <-qrChanBase64, <-qrChanTimeout +} + +func WhatsAppLogin(jid string) (string, string, error) { + if WhatsAppClient[jid] != nil { + // Make Sure WebSocket Connection is Disconnected + WhatsAppClient[jid].Disconnect() + + if WhatsAppClient[jid].Store.ID == nil { + // Device ID is not Exist + // Generate QR Code + qrChanGenerate, _ := WhatsAppClient[jid].GetQRChannel(context.Background()) + + // Connect WebSocket while Initialize QR Code Data to be Sent + err := WhatsAppClient[jid].Connect() + if err != nil { + return "", "", err + } + + // Get Generated QR Code and Timeout Information + qrImage, qrTimeout := WhatsAppGenerateQR(qrChanGenerate) + + // Return QR Code in Base64 Format and Timeout Information + return "data:image/png;base64," + qrImage, qrTimeout, nil + } else { + // Device ID is Exist + // Reconnect WebSocket + err := WhatsAppClient[jid].Connect() + if err != nil { + return "", "", err + } + } + } + + // Return Error WhatsApp Client is Not Valid + return "", "", errors.New("WhatsApp Client is not Valid") +} + +func WhatsAppLogout(jid string) error { + if WhatsAppClient[jid] != nil { + // Logout WhatsApp Client and Disconnect from WebSocket + err := WhatsAppClient[jid].Logout() + if err != nil { + return err + } + + // Remove SQLite Database File + _ = os.Remove("dbs/" + jid + ".db") + + // Free WhatsApp Client Map + WhatsAppClient[jid] = nil + delete(WhatsAppClient, jid) + + return nil + } + + // Return Error WhatsApp Client is Not Valid + return errors.New("WhatsApp Client is not Valid") +} + +func WhatsAppCreateUserJID(jid string) types.JID { + return types.NewJID(jid, types.DefaultUserServer) +} + +func WhatsAppCreateGroupJID(gjid string) types.JID { + return types.NewJID(gjid, types.GroupServer) +} + +func WhatsAppSendText(jid string, rjid string, message string) error { + if WhatsAppClient[jid] != nil { + if WhatsAppClient[jid].IsConnected() && WhatsAppClient[jid].IsLoggedIn() { + _, err := WhatsAppClient[jid].SendMessage(WhatsAppCreateUserJID(rjid), "", &waproto.Message{ + Conversation: proto.String(message), + }) + + if err != nil { + return err + } + + return nil + } + } + + // Return Error WhatsApp Client is Not Valid + return errors.New("WhatsApp Client is not Valid") +}