Send arbitrary JSON to gorilla websocket connection - go

I have the following function where I want to send arbitrary JSON data to a websocket connection. The data depends on what was sent to the server. How this can be achieved without creating different structs for each thing I want to send?
I am thinking about something like this:
func Register(c *websocket.Conn, m WebsocketGeneralClientMessage) {
var d RegisterData
json.Unmarshal(m.Data, &d)
if len(d.Email) < 6 || len(d.Email) > 64 {
c.WriteJSON(WebsocketGeneralServerMessage{
Type: "error", Data: map[string]interface{"text": "This is an error"}})
return
}
if len(d.Password) < 6 || len(d.Password) > 32 {
c.WriteJSON(WebsocketGeneralServerMessage{
Type: "error", Data: map[string]interface{"text": "This is another error"}})
return
}
err := checkmail.ValidateFormat(d.Email)
if err != nil {
c.WriteJSON(WebsocketGeneralServerMessage{
Type: "error", Data: map[string]interface{"text": "This is different than all the previous errors"}})
return
}
uuid, email, password, err := DB.RegisterAccount(requestEmail, requestPassword)
if err != nil {
c.WriteJSON(WebsocketGeneralServerMessage{
Type: "error", Data: map[string]interface{"text": "An error occured while saving the account to the database"}})
return
}
c.WriteJSON(WebsocketGeneralServerMessage{
Type: "success", Data: map[string]interface{"uuid": uuid}})
return
}
type WebsocketGeneralServerMessage struct {
Type string
Data map[string]interface{}
}
However, the compiler says:
syntax error: unexpected literal "text", expecting method or interface name
so it seems this syntax is not valid. What can be done here?

It's map[string]interface{}{"text": .... You missed the {}.

Related

Connect to a socket server in golang

I am using this library in golang to connect to socket server.https://github.com/hesh915/go-socket.io-client I have to pass an auth object as server is expecting an object which will authenticate the user, but in this implementation, query seems to take map as a parameter. I get an EOF error while sending the stringified struct to Query as given below:
opts.Query["auth"] = str
I have created a nested structure for that purpose, tried converting struct into byte data using marshal and type casted into string, passed it as key value pair to map but that didn't seem to work out. Here is how my auth nested struct looks like:
{"userInfo":{"id":"","username":"","permissionsInfo":{"permissions"["",""]}},"additionalInfo":""}
How to send this information with the implementation given below:
opts := &socketio_client.Options{
Transport: "websocket",
Query: make(map[string]string),
}
opts.Query["user"] = "user"
opts.Query["pwd"] = "pass"
uri := "http://192.168.1.70:9090/socket.io/"
I have provided the link to the library.
Minimal Reproducible Example
type User struct {
Userinfo UserInfo `json:"userInfo"`
AdditionalInfo string `json:"additionalInfo"`
}
type UserInfo struct {
Id string `json:"id"`
Username string `json:"username"`
Permissionsinfo PermissionsInfo `json:"permissionInfo"`
}
type PermissionsInfo struct {
Permissions []string `json:"permissions"`
}
func main() {
user := &User{
Userinfo: UserInfo{
Id: "",
Username: "",
Permissionsinfo: PermissionsInfo{
Permissions: []string{"", ""},
}},
AdditionalInfo: "",
}
b, _ := json.Marshal(user)
fmt.Println(string(b))
opts := &socketio_client.Options{
Transport: "websocket",
Query: make(map[string]string),
}
opts.Query["auth"] = string(b)
uri := origin + "/socket.io"
client, err := socketio_client.NewClient(uri, opts)
if err != nil {
if err == io.EOF {
fmt.Printf("NewClient error:%v\n", err)
return
}
return
}
client.On("error", func() {
fmt.Println("on error")
})
}
The error to the above code, that i'm getting:
NewClient error EOF
My server end has to receive the auth params as socket.handshake.auth and that's understandable because the above functionality doesn't supports for auth instead it does for query. How can I achieve this functionality? Any help would be appreciated.

How to sanitize strings in Golang striping html, javascript, sql etc

I'm writing a REST API, through which users can register/login and a number of other things that would require them to send JSON in the request body. The JSON sent in the request body will look something like this for registering:
{
"name": "luke",
"email": "l#g.com",
"date_of_birth": "2012-04-23T18:25:43.511Z",
"location": "Galway",
"profile_image_id": 1,
"password": "password",
"is_subscribee_account": false,
"broker_id": 1
}
This data will then be decoded into a struct and the individual items inserted into the database. I'm not familiar with how I should sanitize the data to remove any Javascript, HTML, SQL etc that may interfere with the correct running of the application and possibly result in a compromised system.
The below code is what I have currently to read in and validate the data but it doesn't sanitize the data for html, javascript, sql etc.
Any input would be appreciated on how I should go about sanitizing it. The REST API is written in Go.
//The below code decodes the JSON into the dst variable.
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
maxBytes := 1_048_576
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err := dec.Decode(dst)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
var invalidUnmarshalError *json.InvalidUnmarshalError
switch {
case errors.As(err, &syntaxError):
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
return errors.New("body contains badly-formed JSON")
case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case errors.Is(err, io.EOF):
return errors.New("body must not be empty")
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return fmt.Errorf("body contains unknown key %s", fieldName)
case err.Error() == "http: request body too large":
return fmt.Errorf("body must not be larger than %d bytes", maxBytes)
case errors.As(err, &invalidUnmarshalError):
panic(err)
default:
return err
}
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must only contain a single JSON value")
}
return nil
}
//The below code validates the email, password and user. If the first parameter in V.Check() is false the following two parameter are returned in the response to the user.
func ValidateEmail(v *validator.Validator, email string) {
v.Check(email != "", "email", "must be provided")
v.Check(validator.Matches(email, validator.EmailRX), "email", "must be a valid email address")
}
func ValidatePasswordPlaintext(v *validator.Validator, password string) {
v.Check(password != "", "password", "must be provided")
v.Check(len(password) >= 8, "password", "must be at least 8 bytes long")
v.Check(len(password) <= 72, "password", "must not be more than 72 bytes long")
}
func ValidateUser(v *validator.Validator, user *User) {
v.Check(user.Name != "", "name", "must be provided")
v.Check(len(user.Name) <= 500, "name", "must not be more than 500 bytes long")
ValidateEmail(v, user.Email)
if user.Password.plaintext != nil {
ValidatePasswordPlaintext(v, *user.Password.plaintext)
}
if user.Password.hash == nil {
panic("missing password hash for user")
}
}
How do I serialize the struct data with this in mind? So it wont cause issues if later inserted in a webpage. Is there a package function that I can pass the string data to that will return safe data?

Go AWS SES Unable to find credentials

I'm trying to send mail using aws sdk v2 for Go.
I'm getting below error. When using s3 client everything is working fine. I checked the permissions associated with the credentials and it has administrator access. Unable to understand what could be the problem.
operation error SES: SendEmail, failed to sign request: failed to retrieve credentials: request canceled, context canceled
config.go
AWSConfig, err = awsConfig.LoadDefaultConfig(context.TODO())
if err != nil {
log.Println("Error configuring aws: ", err)
}
mailer.go
type Mail struct {
To []string
From string
Subject string
Body string
}
func (m *Mail) Send(ctx context.Context) error {
sesClient := ses.NewFromConfig(config.AWSConfig)
result, err := sesClient.SendEmail(ctx, &ses.SendEmailInput{
Destination: &types.Destination{
ToAddresses: m.To,
},
Message: &types.Message{
Body: &types.Body{
Html: &types.Content{
Data: &m.Body,
Charset: &CharSet,
},
},
Subject: &types.Content{
Data: &m.Subject,
Charset: &CharSet,
},
},
Source: &m.From,
ReplyToAddresses: ReplyTo,
ReturnPath: &BounceEmail,
})
if err != nil {
log.Println(fmt.Errorf("[MailSenderUtil]: error sending mail: %w", err))
return err
}
log.Println("[MailSenderUtilResult]: ", InJson(result))
return nil
}
I think you need to add your aws credentials to config.AWSConfig. This is my config
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(constants.AWSAccessID, constants.AWSAccessKey , ""),
Region:aws.String("eu-west-2")},
)
svc := ses.New(sess)
input := &ses.SendEmailInput{
Destination: &ses.Destination{
ToAddresses: []*string{
aws.String(Recipient),
},
},
Message: &ses.Message{
Body: &ses.Body{
Html: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(HtmlBody),
},
Text: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(TextBody),
},
},
Subject: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Subject),
},
},
Source: aws.String(Sender),
// Uncomment to use a configuration set
//ConfigurationSetName: aws.String(ConfigurationSet),
}
// Attempt to send the email.
_, err = svc.SendEmail(input)
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ses.ErrCodeMessageRejected:
logger.Logger.Error(ses.ErrCodeMessageRejected, aerr.OrigErr())
case ses.ErrCodeMailFromDomainNotVerifiedException:
logger.Logger.Error(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.OrigErr())
case ses.ErrCodeConfigurationSetDoesNotExistException:
logger.Logger.Error(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.OrigErr())
default:
logger.Logger.Error("amazon default error", aerr.OrigErr())
}
} else {
logger.Logger.Error("amazon default error (else)", aerr.OrigErr())
}
return
}

How to Make a Nullable Field in Proto3 for a HTTP Response?

I want to return an object as an HTTP response where one of its fields is nullable. The problem is proto3 won't let me do it easily. This happens because I parsed a pointer of string to a string, so when the pointer points to null it produces this error
runtime error: invalid memory address or nil pointer dereference
I have attempted to solve this by at least these two work-arounds I learned from the Internet.
1. Using oneof
exercise.proto (the message definition)
message ExercisesData {
string Serial = 1 [json_name="serial"];
string Title = 2 [json_name="title"];
oneof OptionalSubmissionSerial {
string SubmissionSerial = 3 [json_name="submission_serial"];
}
mapper.go (to parse a Go struct to fit the proto message)
exercise := &Exercise.ExercisesData {
Serial: e.Serial,
Title: e.Title,
OptionalSubmissionSerial: &Exercise.ExercisesData_SubmissionSerial{
SubmissionSerial: *e.SubmissionInfo.LatestSubmissionSerial,
},
}
2. Using google/protobuf/wrappers.proto
exercise.proto (the message definition)
import "google/protobuf/wrappers.proto";
message ExercisesData {
string Serial = 1 [json_name="serial"];
string Title = 2 [json_name="title"];
google.protobuf.StringValue SubmissionSerial = 3 [json_name="submission_serial"];
}
mapper.go (to parse a Go struct to fit the proto message)
exercise := &Exercise.ExercisesData {
Serial: e.Serial,
Title: e.Title,
SubmissionSerial: &wrappers.StringValue{
Value: *e.SubmissionInfo.LatestSubmissionSerial,
},
}
Expected Result
Both ways still produce the same error message, the only difference is the line of code it refers to. That's why I am so helpless. The expected HTTP response would look like this
{
"status": "success",
"data": [
{
"serial": "EXC-NT2OBHQT",
"title": "Title of Topic Exercise",
"submission_serial": null
}
]
}
I really hope anyone can help me to find a way to define a nullable field in proto3 for a Http response and how to parse it from a struct. Thank you!
turns out I find another workaround that actually works! It's using google/protobuf/wrappers.proto but I gotta tweak it a lil' bit in the mapper. Here's how it goes:
exercise.proto (the message definition)
import "google/protobuf/wrappers.proto";
message ExercisesData {
string Serial = 1 [json_name="serial"];
string Title = 2 [json_name="title"];
google.protobuf.StringValue SubmissionSerial = 3 [json_name="submission_serial"];
}
mapper.go (to parse a Go struct to fit the proto message)
import "github.com/golang/protobuf/ptypes/wrappers"
exercise := &pbExercise.GetExercisesData{
Serial: e.Serial,
Title: e.Title,
}
if e.SubmissionInfo.LatestSubmissionSerial != nil {
exercise.SubmissionSerial = &wrappers.StringValue{
Value: *e.LatestSubmissionSerial,
}
}
I assume you set syntax = proto3;. I don't think there is a way to achieve what you are after at this moment. Go check this issue.
But if you are willing to live with
{"serial":"serial","title":"title"}
where submission_serial is altogether missing as opposed to
{"serial":"serial","title":"title","submission_serial":null}
than the following works just fine
// ...
data := &ExercisesData{Serial: "serial", Title: "title"}
out, err := proto.Marshal(data)
if err != nil {
log.Fatalln("failed to encode: ", err)
}
buf := &bytes.Buffer{}
marshaler := jsonpb.Marshaler{}
err = marshaler.Marshal(buf, data)
data2 := &ExercisesData{}
if err := proto.Unmarshal(out, data2); err != nil {
log.Fatalln("failed to parse: ", err)
}
// ...

Passing a Struct to the redigo Send function breaks it and data is missing

This is my struct, when I get a socket message I readJson and the structs gets filled with the data and all is fine. It goes through some functions, but once it goes through the Send function it serializes it in a weird way that eventually I get back a bunch of numbers and when I convert it to string, data is missing.
type Reply struct {
Topic string `redis:"topic" json:"topic"`
Ref string `redis:"ref" json:"ref"`
Payload struct {
Status string `redis:"status" json:"status"`
Response map[string]interface{} `redis:"response" json:"response"`
} `json:"payload"`
}
I just want to broadcast messages in this format.
This is where I get the modified and problematic data
func (rr *redisReceiver) run() error {
l := log.WithField("channel", Channel)
conn := rr.pool.Get()
defer conn.Close()
psc := redis.PubSubConn{Conn: conn}
psc.Subscribe(Channel)
go rr.connHandler()
for {
switch v := psc.Receive().(type) {
case redis.Message:
rr.broadcast(v.Data)
case redis.Subscription:
l.WithFields(logrus.Fields{
"kind": v.Kind,
"count": v.Count,
}).Println("Redis Subscription Received")
log.Println("Redis Subscription Received")
case error:
return errors.New("Error while subscribed to Redis channel")
default:
l.WithField("v", v).Info("Unknown Redis receive during subscription")
log.Println("Unknown Redis receive during subscription")
}
}
}
Does Redigo not support that type of data structure?
This is the format I get and the format I'm supposed to get.
//Get
"{{spr_reply sketchpad map[] 1} {ok map[success:Joined successfully]}}"
//Supposed to get
{event: "spr_reply", topic: "sketchpad", ref: "45", payload: {status:
"ok", response: {}}}
On line 55 is where I get back the "corrupted" data - https://play.golang.org/p/TOzJuvewlP
Redigo supports the following conversions to Redis bulk strings:
Go Type Conversion
[]byte Sent as is
string Sent as is
int, int64 strconv.FormatInt(v)
float64 strconv.FormatFloat(v, 'g', -1, 64)
bool true -> "1", false -> "0"
nil ""
all other types fmt.Print(v)
The Reply type is encoding using fmt.Print(v).
It looks like you want to encode the value as JSON. If so, do the encoding in the application. You can remove the redis field tags.
writeToRedis(conn redis.Conn, data Reply) error {
p, err := json.Marshl(data)
if err != nil {
return errors.Wrap(err, "Unable to encode message to json")
}
if err := conn.Send("PUBLISH", Channel, p); err != nil {
return errors.Wrap(err, "Unable to publish message to Redis")
}
if err := conn.Flush(); err != nil {
return errors.Wrap(err, "Unable to flush published message to Redis")
}
return nil
}

Resources