Skip to content

Task/sean/str 94 #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ UNIT21_URL=https://sandbox2-api.unit21.com/v1/
TWILIO_ACCOUNT_SID=AC034879a536d54325687e48544403cb4d
TWILIO_AUTH_TOKEN=
TWILIO_SMS_SID=MG367a4f51ea6f67a28db4d126eefc734f
DEV_PHONE_NUMBERS=+14088675309,+14155555555
DEV_PHONE_NUMBERS=+14088675309,+14155555555
STRING_ENCRYPTION_KEY=secret_encryption_key_0123456789
SENDGRID_API_KEY=
24 changes: 24 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func Start(config APIConfig) {
authService := authRoute(config, e)
platformRoute(config, e)
transactRoute(config, authService, e)
userRoute(config, authService, e)
verificationRoute(config, e)
e.Logger.Fatal(e.Start(":" + config.Port))
}

Expand Down Expand Up @@ -74,3 +76,25 @@ func transactRoute(config APIConfig, auth service.Auth, e *echo.Echo) {
handler := handler.NewTransaction(e, service)
handler.RegisterRoutes(e.Group("/transact"), middleware.APIKeyAuth(auth), middleware.BearerAuth())
}

func userRoute(config APIConfig, auth service.Auth, e *echo.Echo) {
repos := service.UserRepos{
User: repository.NewUser(config.DB),
Contact: repository.NewContact(config.DB),
Instrument: repository.NewInstrument(config.DB),
}
service := service.NewUser(repos)
handler := handler.NewUser(e, service)
handler.RegisterRoutes(e.Group("/user"), middleware.APIKeyAuth(auth), middleware.BearerAuth())
}

func verificationRoute(config APIConfig, e *echo.Echo) {
repos := service.UserRepos{
User: repository.NewUser(config.DB),
Contact: repository.NewContact(config.DB),
Instrument: repository.NewInstrument(config.DB),
}
service := service.NewUser(repos)
handler := handler.NewUser(e, service)
handler.RegisterRoutes(e.Group("/verification"))
}
122 changes: 122 additions & 0 deletions api/handler/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package handler

import (
"net/http"

"github.com/String-xyz/string-api/pkg/model"
"github.com/String-xyz/string-api/pkg/service"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog"
)

type User interface {
GetStatus(c echo.Context) error // If wallet addr is associated with user, return current state of their onboarding
Create(c echo.Context) error // Create new user using wallet addr, optionally mark as validated if signature is provided
Sign(c echo.Context) error // Takes in a signed timestamp from user, validating their wallet
Authenticate(c echo.Context) error // Takes e-mail and wallet addr of user, validates email with twilio
Name(c echo.Context) error // Takes name and wallet addr of user, associates name with wallet addr
RegisterRoutes(g *echo.Group, ms ...echo.MiddlewareFunc)
}

type ResultMessage struct {
Status string
}

type user struct {
Service service.User
Group *echo.Group
}

func NewUser(route *echo.Echo, service service.User) User {
return &user{service, nil}
}

func (u user) GetStatus(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
var body model.UserRequest
err := c.Bind(&body)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}
res, err := u.Service.GetStatus(body)
if err != nil {
lg.Err(err).Msg("user getstatus")
return c.String(http.StatusNotFound, "User Not Found")
}
return c.JSON(http.StatusOK, res)
}

func (u user) Create(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
var body model.UserRequest
err := c.Bind(&body)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}
err = u.Service.Create(body)
if err != nil {
lg.Err(err).Msg("user create")
return c.String(http.StatusOK, "User Service Failed")
}
return c.JSON(http.StatusOK, ResultMessage{Status: "User Created"})
}

func (u user) Sign(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
var body model.UserRequest
err := c.Bind(&body)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}
err = u.Service.Sign(body)
if err != nil {
lg.Err(err).Msg("user sign")
return c.String(http.StatusBadRequest, "Signing Wallet Failed")
}
return c.JSON(http.StatusOK, ResultMessage{Status: "Wallet Signed"})
}

func (u user) Authenticate(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
var body model.UserRequest
err := c.Bind(&body)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}

// User needs a token
err = u.Service.Authenticate(body)
if err != nil {
lg.Err(err).Msg("user authenticate")
return c.String(http.StatusBadRequest, "Could Not Send Email Verification")
}
return c.JSON(http.StatusOK, ResultMessage{Status: "Email Validation Sent"})
}

func (u user) Name(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
var body model.UserRequest
err := c.Bind(&body)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}
err = u.Service.Name(body)
if err != nil {
lg.Err(err).Msg("user name")
return c.String(http.StatusBadRequest, "Could Not Update Name")
}
return c.JSON(http.StatusOK, ResultMessage{Status: "Name Updated Successfully"})
}

func (u user) RegisterRoutes(g *echo.Group, ms ...echo.MiddlewareFunc) {
if g == nil {
panic("No group attached to the User Handler")
}
u.Group = g
g.Use(ms...)
g.GET("", u.GetStatus)
g.POST("", u.Create)
g.PUT("", u.Sign)
g.POST("/email", u.Authenticate)
g.POST("/name", u.Name)
}
44 changes: 44 additions & 0 deletions api/handler/verification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package handler

import (
"net/http"

"github.com/String-xyz/string-api/pkg/service"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog"
)

type Verification interface {
Authenticate(c echo.Context) error // Takes e-mail and wallet addr of user, validates email with twilio
RegisterRoutes(g *echo.Group, ms ...echo.MiddlewareFunc)
}

type verification struct {
Service service.User
Group *echo.Group
}

func NewVerification(route *echo.Echo, service service.User) Verification {
return &verification{service, nil}
}

func (v verification) Authenticate(c echo.Context) error {
lg := c.Get("logger").(*zerolog.Logger)
// Token was provided
token := c.QueryParam("token")
err := v.Service.ReceiveEmailAuthentication(token)
if err != nil {
lg.Err(err).Msg("user authenticate")
return c.String(http.StatusBadRequest, "Invalid Token")
}
return c.JSON(http.StatusOK, ResultMessage{Status: "Email Validated Successfully"})
}

func (v verification) RegisterRoutes(g *echo.Group, ms ...echo.MiddlewareFunc) {
if g == nil {
panic("No group attached to the User Handler")
}
v.Group = g
g.Use(ms...)
g.POST("/email", v.Authenticate)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ require (
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.12.0+incompatible // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tinylib/msgp v1.1.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible h1:/N2vx18Fg1KmQOh6zESc5FJB8pYwt5QFBDflYPh1KVg=
github.com/sendgrid/sendgrid-go v3.12.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
Expand Down
27 changes: 27 additions & 0 deletions pkg/internal/common/base64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package common

import (
"encoding/base64"
"encoding/json"
)

func EncodeToBase64(object interface{}) (string, error) {
buffer, err := json.Marshal(object)
if err != nil {
return "", StringError(err)
}
return base64.StdEncoding.EncodeToString(buffer), nil
}

func DecodeFromBase64[T any](from string) (T, error) {
var result *T = new(T)
buffer, err := base64.StdEncoding.DecodeString(from)
if err != nil {
return *result, StringError(err)
}
err = json.Unmarshal(buffer, &result)
if err != nil {
return *result, StringError(err)
}
return *result, nil
}
66 changes: 66 additions & 0 deletions pkg/internal/common/crypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package common

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"
)

func Encrypt(object interface{}, secret string) (string, error) {
buffer, err := json.Marshal(object)
if err != nil {
return "", StringError(err)
}
return EncryptString(string(buffer), secret)
}

func Decrypt[T any](from string, secret string) (T, error) {
var result *T = new(T)
decrypted, err := DecryptString(from, secret)
if err != nil {
return *result, StringError(err)
}
err = json.Unmarshal([]byte(decrypted), &result)
if err != nil {
return *result, StringError(err)
}
return *result, nil
}

func EncryptString(data string, secret string) (string, error) {
block, err := aes.NewCipher([]byte(secret))
if err != nil {
return "", StringError(err)
}
plainText := []byte(data)
cipherText := make([]byte, aes.BlockSize+len(plainText))
iv := cipherText[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", StringError(err)
}
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(cipherText[aes.BlockSize:], plainText)
return base64.StdEncoding.EncodeToString(cipherText), nil
}

func DecryptString(data string, secret string) (string, error) {
block, err := aes.NewCipher([]byte(secret))
if err != nil {
return "", StringError(err)
}
cipherText, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return "", StringError(err)
}
iv := cipherText[:aes.BlockSize]

cipherText = cipherText[aes.BlockSize:]

cfb := cipher.NewCFBDecrypter(block, iv)
plainText := make([]byte, len(cipherText))
cfb.XORKeyStream(plainText, cipherText)
return string(plainText), nil
}
Loading