Go equivalent of Python's requests.Session for making many requests with the same basic authentication? - go

Consider this example for making an HTTP request in Go with basic authentication:
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
)
var userName = "myUserName"
var password = "myPassword"
func main() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !checkAuth(w, r) {
http.Error(w, "You're not authorized!", http.StatusUnauthorized)
return
}
w.Write([]byte("You're authorized!"))
}))
defer ts.Close()
req, err := http.NewRequest("GET", ts.URL, nil)
check(err)
req.SetBasicAuth(userName, password+"foo")
resp, err := http.DefaultClient.Do(req)
check(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
check(err)
fmt.Println(string(body))
}
// checkAuth checks authentication (cf. https://stackoverflow.com/questions/21936332/idiomatic-way-of-requiring-http-basic-auth-in-go/21937924#21937924)
func checkAuth(w http.ResponseWriter, r *http.Request) bool {
s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
if len(s) != 2 {
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
return pair[0] == userName && pair[1] == password
}
func check(err error) {
if err != nil {
panic(err)
}
}
Note that SetBasicAuth is a method of an *http.Request, so if I want to make many requests, I would have to call this method on each request.
In Python, you can define a requests.Session like in this example (from https://requests.readthedocs.io/en/master/user/advanced/#session-objects):
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# both 'x-test' and 'x-test2' are sent
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})
Is there an idiomatic way of defining the equivalent of a requests.Session in Go (preferably using the standard library)? All I can think of is defining a custom client struct with its own Do() method:
type MyClient struct {
UserName, Password string
}
func (client *MyClient) Do(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(client.UserName, client.Password)
return http.DefaultClient.Do(req)
}
and invoking it in the above script like
client := MyClient{UserName: userName, Password: password}
resp, err := client.Do(req)
Would this be an idiomatic way to avoid multiple calls to SetBasicAuth()?

Related

(Go) Modify websocket body in reverse proxy

Hello Everyone, I'm new to Go.
I'm creating a reverse proxy server using Go.
My Server has websocket. I finally get it connected.
Now I want to change websocket message body.
Sorry If my code is weird to you. Forgive me, I'm new to Go 😢
I'm wraping resp.body to NewReadWriteBody() in which contains wrapper for Read, Write and Closer. And I'm modifying message body inside it.
Here is how I'm doing with it:
package rever
// https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/
// https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
)
type ReadWriteBody struct {
originBody io.ReadWriteCloser
}
func NewReadWriteBody(body io.ReadCloser) *ReadWriteBody {
b := &ReadWriteBody{}
rw, ok := body.(io.ReadWriteCloser)
if !ok {
log.Println("29: error while casting body to ReadWriteCloser")
}
b.originBody = rw
return b
}
func (b *ReadWriteBody) Read(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Read(buf)
if err != nil {
log.Println("43: ", err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.remote"), []byte("mm.local"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Write(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Write(buf)
if err != nil {
log.Println(err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.local"), []byte("mm.remote"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Close() error {
return b.originBody.Close()
}
type transport struct {
http.RoundTripper
}
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
resp, err = t.RoundTripper.RoundTrip(req)
if err != nil {
log.Println("99: ", err.Error())
return nil, err
}
if resp.StatusCode == http.StatusSwitchingProtocols {
resp.Body = NewReadWriteBody(resp.Body)
return resp, nil
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("114: ", err.Error())
return nil, err
}
err = resp.Body.Close()
if err != nil {
log.Println("119", err.Error())
return nil, err
}
b = bytes.ReplaceAll(b, []byte("mm.remote"), []byte("mm.local"))
body := ioutil.NopCloser(bytes.NewReader(b))
resp.Body = body
resp.ContentLength = int64(len(b))
resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
return resp, nil
}
var _ http.RoundTripper = &transport{}
// NewProxy takes target host and creates a reverse proxy
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
log.Println("141: ", err.Error())
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
modifyRequest(req)
}
proxy.ErrorHandler = errorHandler()
dt := http.DefaultTransport.(*http.Transport).Clone()
dt.TLSClientConfig = &tls.Config{}
dt.ForceAttemptHTTP2 = false
proxy.Transport = &transport{dt}
return proxy, nil
}
func modifyRequest(req *http.Request) {
req.Host = "mm.remote"
req.Header.Set("Accept-Encoding", "identity")
}
func errorHandler() func(http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {
// fmt.Printf("Got error while modifying response: %v \n", err)
}
}
// ProxyRequestHandler handles the http request using proxy
func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
}
}
func Main() {
// initialize a reverse proxy and pass the actual backend server url here
proxy, err := NewProxy("https://mm.remote")
if err != nil {
log.Println(err.Error())
panic(err)
}
// handle all requests to your server using the proxy
http.HandleFunc("/", ProxyRequestHandler(proxy))
fmt.Println("Server started")
log.Fatal(http.ListenAndServe(":8008", nil))
}

How to clear the session and only visit an about page after login?

This code has two parts. One is to set and clear the session and the second part is login and logout.
What it does?
In the second part, If an email and password are found in the database and the match is true then it set the session and move to the about() function which has an about file. If the logout is called then it clears the session and redirects to the home page.
What it should do?
The problem is that even if I am logged out and the session is cleared, I can still visit an about page. I don't want to be allowed to visit an about page if I am not logged in.
First part
var cookieHandler = securecookie.New(
securecookie.GenerateRandomKey(64),
securecookie.GenerateRandomKey(32),
)
func setSession(email, password string, res http.ResponseWriter) {
value := map[string]string{ "email": email, "password": password}
encoded, err := cookieHandler.Encode("session", value)
if err == nil {
cookie := &http.Cookie{ Name: "session", Value: encoded, Path: "/"}
http.SetCookie(res, cookie)
}
}
func clearSession(res http.ResponseWriter) {
cookie := &http.Cookie{ Name: "session", Value: "", Path: "/", MaxAge: -1}
http.SetCookie(res, cookie)
}
Second part
func about(res http.ResponseWriter, req *http.Request) {
if err := tmpl.ExecuteTemplate(res, "about.html", nil); err != nil {
log.Fatal("template didn't execute", nil)
}
}
func loginAuth(res http.ResponseWriter, req *http.Request) {
email := req.FormValue("email")
password := req.FormValue("password")
match := database.Findaccount(email, password)
if match == true {
setSession(email, password, res)
about(res, req)
fmt.Println("You're logged in")
} else {
tmpl.ExecuteTemplate(res, "login.html", nil)
fmt.Println("Enter the correct email or password")
}
}
func logout(res http.ResponseWriter, req *http.Request) {
clearSession(res)
http.Redirect(res, req, "/", 302)
}
Few things you don't want to do, in general:
Don't use cookie encoder directly. Use a cookie session store.
Don't call an handler within an handler, prefer a redirect. This should prevent writing twice the headers/body on the response.
Don't pass the user/password in the cookie, even encoded, in 2021 we may even prevent sending that through the form at all (you might consider sending only a hash and re hash the hash on the server side to figure out if things are good to go).
Few things you always want to do:
Write tests.
Make use of middlewares.
Always provide small reproducible examples.
That being said, I have written a lookalike code with some stubs (mostly for db), I removed template support (i was not in the mood to write HTML) and more importantly I wrote tests !!
To the question How to clear the session :
Delete the values from the store, write the store
To the question and only visit an about page after login?:
Wrap that handler with a middleware that verifies login data attached to the user cookie store
-- main.go --
package main
import (
"crypto/sha256"
"encoding/gob"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/securecookie"
"github.com/gorilla/sessions"
)
// Note: Don't store your key in your source code. Pass it via an
// environmental variable, or flag (or both), and don't accidentally commit it
// alongside your code. Ensure your key is sufficiently random - i.e. use Go's
// crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
var store = sessions.NewCookieStore(
securecookie.GenerateRandomKey(32),
)
//emulate db package
func dbLookupUser(user, pwd string) bool {
return user == "user" && pwd == "pwd"
}
func dbLookupHash(h string) bool {
return h == hash("user", "pwd")
}
func hash(s ...interface{}) string {
hr := sha256.New()
fmt.Fprint(hr, s...)
return fmt.Sprintf("%x", hr.Sum(nil))
}
// hashKey is a typed key for the session map store to prevent unintented overwrites.
type hashKey string
func init() {
gob.Register(hashKey(""))
}
func loginAuth(res http.ResponseWriter, req *http.Request) {
email := req.FormValue("email")
password := req.FormValue("password")
match := dbLookupUser(email, password)
if match == true {
session, _ := store.Get(req, "session-name")
session.Values["hash"] = hash(email, password)
// Save it before we write to the response/return from the handler.
err := session.Save(req, res)
if err == nil {
// about(res, req) // don't!
// the about handler might want to setup its own http response headers
// That would conflict with what we did here.
// prefer a redirect
http.Redirect(res, req, "/about", http.StatusFound)
return
}
} else {
fmt.Fprintf(res, "try again") // use a templatee instead!
// tmpl.ExecuteTemplate(res, "login.html", nil)
}
}
func logout(res http.ResponseWriter, req *http.Request) {
session, _ := store.Get(req, "session-name")
delete(session.Values, hashKey("hash"))
_ = session.Save(req, res)
http.Redirect(res, req, "/", 302)
}
func about(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "welcome to about page")
}
func requireLogin(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
var h string
if x, ok := session.Values[hashKey("hash")]; ok {
h = x.(string)
}
var match bool
if h != "" {
match = dbLookupHash(h)
}
if !match {
// Write an error and stop the handler chain
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next(w, r)
}
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", loginAuth)
r.HandleFunc("/logout", logout)
r.HandleFunc("/about", requireLogin(about))
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
-- main_test.go --
package main
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestLogin(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
form := url.Values{}
form.Set("email", "user")
form.Set("password", "pwd")
req.Form = form
w := httptest.NewRecorder()
loginAuth(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusFound; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestLoginFailure(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
form := url.Values{}
form.Set("email", "!user")
form.Set("password", "!pwd")
req.Form = form
w := httptest.NewRecorder()
loginAuth(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusOK; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestAboutNotLogged(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
w := httptest.NewRecorder()
requireLogin(about)(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusForbidden; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}
func TestAboutLogged(t *testing.T) {
req := httptest.NewRequest("POST", "http://example.com/foo", nil)
w := httptest.NewRecorder()
session, _ := store.Get(req, "session-name")
session.Values[hashKey("hash")] = hash("user", "pwd")
err := session.Save(req, w)
if err != nil {
t.Fatal(err)
}
hdr := w.Header()
req.Header.Add("Cookie", hdr["Set-Cookie"][0])
w = httptest.NewRecorder()
requireLogin(about)(w, req)
resp := w.Result()
// body, _ := io.ReadAll(resp.Body)
if wanted := http.StatusOK; resp.StatusCode != wanted {
t.Fatalf("invalid response code, got=%v wanted=%v", resp.StatusCode, wanted)
}
// implement more check
}

variable is empty but later has a value

I'm trying to develop a Terraform provider but I have a problem of the first request body. Here is the code:
type Body struct {
id string
}
func resourceServerCreate(d *schema.ResourceData, m interface{}) error {
key := d.Get("key").(string)
token := d.Get("token").(string)
workspace_name := d.Get("workspace_name").(string)
board_name := d.Get("board_name").(string)
resp, err := http.Post("https://api.trello.com/1/organizations?key="+key+"&token="+token+"&displayName="+workspace_name,"application/json",nil)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
//lettura body.
body := new(Body)
json.NewDecoder(resp.Body).Decode(body)
log.Println("[ORCA MADONNA] il log funzia "+body.id)
d.Set("board_id",body.id)
resp1, err1 := http.Post("https://api.trello.com/1/boards?key="+key+"&token="+token+"&idOrganization="+body.id+"&=&name="+board_name,"application/json",nil)
if err1 != nil {
log.Fatalln(resp1)
}
defer resp1.Body.Close()
d.SetId(board_name)
return resourceServerRead(d, m)
}
In the log is empty, but the second call have it and work fine. How is it possible?
Go doesn't force you to check error responses, therefore it's easy to make silly mistakes. Had you checked the return value from Decode(), you would have immediately discovered a problem.
err := json.NewDecoder(resp.Body).Decode(body)
if err != nil {
log.Fatal("Decode error: ", err)
}
Decode error: json: Unmarshal(non-pointer main.Body)
So your most immediate fix is to use & to pass a pointer to Decode():
json.NewDecoder(resp.Body).Decode(&body)
Also of note, some programming editors will highlight this mistake for you:
Here's a working demonstration, including a corrected Body structure as described at json.Marshal(struct) returns “{}”:
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
type JSON = map[string]interface{}
type JSONArray = []interface{}
func ErrFatal(err error, msg string) {
if err != nil {
log.Fatal(msg+": ", err)
}
}
func handleTestRequest(w http.ResponseWriter, req *http.Request) {
w.Write(([]byte)("{\"id\":\"yourid\"}"))
}
func launchTestServer() {
http.HandleFunc("/", handleTestRequest)
go http.ListenAndServe(":8080", nil)
time.Sleep(1 * time.Second) // allow server to get started
}
// Medium: "Don’t use Go’s default HTTP client (in production)"
var restClient = &http.Client{
Timeout: time.Second * 10,
}
func DoREST(method, url string, headers, payload JSON) *http.Response {
requestPayload, err := json.Marshal(payload)
ErrFatal(err, "json.Marshal(payload")
request, err := http.NewRequest(method, url, bytes.NewBuffer(requestPayload))
ErrFatal(err, "NewRequest "+method+" "+url)
for k, v := range headers {
request.Header.Add(k, v.(string))
}
response, err := restClient.Do(request)
ErrFatal(err, "DoRest client.Do")
return response
}
type Body struct {
Id string `json:"id"`
}
func clientDemo() {
response := DoREST("POST", "http://localhost:8080", JSON{}, JSON{})
defer response.Body.Close()
var body Body
err := json.NewDecoder(response.Body).Decode(&body)
ErrFatal(err, "Decode")
fmt.Printf("Body: %#v\n", body)
}
func main() {
launchTestServer()
for i := 0; i < 5; i++ {
clientDemo()
}
}

Golang websocket client, close connection after getting result

How I can implement this kind of scenario:
1.I have LoginHandler which receives some user data - email and signedXml:
func LoginHandler(c *gin.Context) {
var (
err error
data LoginPost
)
if err = c.BindJSON(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error"})
return
}
...
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
2.I need to send signedXml to another server via websocket
3.Save result (success or error)
4.Close connection
Every HTTP request will open connection, send 1 message, get 1 result and finally close socket. I was trying with channel, but no success. Is this possible to implement my case?
UPDATE
package main
import (
"log"
"net/url"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
message := r.FormValue("message")
w.Write([]byte(message))
}
func postHandler(w http.ResponseWriter, r *http.Request) {
var (
message = r.FormValue("message")
u = url.URL{Scheme: "ws", Host: "echo.websocket.org", Path: "/"}
err error
out []byte
conn *websocket.Conn
)
log.Printf("message: %s\n", message)
log.Printf("connecting to %s\n", u.String())
conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
log.Println("write")
if err = conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
log.Fatal("write:", err)
}
log.Println("read")
if _, out, err = conn.ReadMessage(); err != nil {
log.Fatal("read:", err)
}
w.Write(out)
log.Println("close")
conn.Close()
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", indexHandler).Methods("GET")
r.HandleFunc("/post", postHandler).Methods("POST")
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
Call Dial, WriteMessage, ReadMessage and Close in sequence.
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
// handle error
}
err := c.WriteMessage(websocket.TextMessage, signedXML)
if err != nil {
// handle error
}
_, p, err := c.ReadMessage()
if err != nil {
// handle error
}
c.Close()
// p is a []byte with the first received message.

How to pass multiple values in cookie with map

Got stuck while passing multiple values in cookie. I'm not able to find a better way for managing session and cookies. Trying to use github.com/gorilla/securecookie this package.
loginMain.go
package main
import (
"database/sql"
"log"
"net/http"
"shambhavi/packages/loginPkg"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
var router = mux.NewRouter()
var db *sql.DB
func connectDb() *sql.DB {
db, dberr := sql.Open("mysql", "root:root#tcp(127.0.0.1:8889)/shambhavi_db")
if dberr != nil {
log.Println(dberr)
}
return db
}
func login(w http.ResponseWriter, r *http.Request) {
var db *sql.DB = connectDb()
loginPkg.LoginOperation(w, r, db)
}
func main() {
http.HandleFunc("/demo", login)
http.Handle("/", router)
err := http.ListenAndServe(port, nil) // setting listening port
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
LoginPkg.go
package loginPkg
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"shambhavi/packages/sessionPkg"
_ "github.com/go-sql-driver/mysql"
)
var retMap = make(map[string]string)
func ErrorHandler(err error) {
if err != nil {
panic(err)
}
}
func LoginOperation(w http.ResponseWriter, r *http.Request, db *sql.DB) {
fmt.Println("In LoginOperation ")
r.ParseForm()
if len(r.Form["username"][0]) == 0 && len(r.Form["password"][0]) == 0 {
fmt.Fprintf(w, "Something is blank !!!")
} else {
var lvl string
var uFullName string
err := db.QueryRow("SELECT lvl_flag FROM admin_instance WHERE user_name = ? AND passwd = ?", r.FormValue("username"), r.FormValue("password")).Scan(&lvl)
er := db.QueryRow("SELECT emp_name FROM emp_detail WHERE emp_uname = ?", r.FormValue("username")).Scan(&uFullName)
ErrorHandler(er)
retMap["msg"] = "Login successfully"
retMap["err"] = "Not Login"
retMap["lvl"] = lvl
retMap["fullName"] = uFullName
b, _ := json.Marshal(retMap)
if err != nil {
fmt.Println(err)
fmt.Fprintf(w, "%s", b)
} else {
if lvl == "1" || lvl == "2" || lvl == "3" {
sessionPkg.SetSession(w, r, r.FormValue("username"), retMap) // Passing map to the fun, retMap
fmt.Fprintf(w, "%s", b)
usrnm := sessionPkg.GetUserName(r)
fmt.Println("From session variable", usrnm)
} else {
fmt.Println("Chukala ....")
fmt.Fprintf(w, "%s", b)
}
}
}
defer db.Close()
}
The problem lies in following file....
sessionHandler.go
package sessionPkg
import (
"fmt"
"net/http"
"time"
"github.com/gorilla/securecookie"
)
var cookieHandler = securecookie.New(
securecookie.GenerateRandomKey(64),
securecookie.GenerateRandomKey(32))
func SetSession(w http.ResponseWriter, r *http.Request, username string, retMap map[string]string) {
sessionData := map[string]string{
"userName": username,
"lvl": retMap["lvl"],
"fullName": retMap["fullName"],
}
expiration := time.Now().Add(365 * 24 * time.Hour)
//if encoded, err := cookieHandler.Encode("session", sessionData); err == nil {
cookie := http.Cookie{
Name: "session",
Value: sessionData["userName"], //Here i want map or something else that can accept multiple values
Expires: expiration,
//MaxAge: 3600,
}
http.SetCookie(w, &cookie)
//}
}
func GetUserName(request *http.Request) (userName string) {
//fmt.Println(request.Cookie("session"))
cookieValue := make(map[string]string)
if cookie, err := request.Cookie("session"); err == nil {
fmt.Println("cookieValue = ", cookie.Value)
//if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
//fmt.Println(cookie)
cookieValue["userName"] = cookie.Value
//fmt.Println(cookieValue["userName"])
//}
/*else {
fmt.Println("Error ", err)
}*/
}
return cookieValue["userName"]
}
/*func GetFullName(request *http.Request) (fullName string) {
fmt.Println("In GetFullName")
cookieValue := make(map[string]string)
if cookie2, err := request.Cookie("session"); err == nil {
fmt.Println("cookieValue = ", cookie2.Value)
//if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
fmt.Println(cookie2)
cookieValue["fullName"] = cookie2.Value
fmt.Println(cookieValue["fullName"])
//}
}
return cookieValue["fullName"]
}*/
func ClearSession(response http.ResponseWriter) {
cookie := &http.Cookie{
Name: "session",
Value: "",
Path: "/",
MaxAge: -1,
}
http.SetCookie(response, cookie)
}
Problem indicated in code by comment. I want to use session like in PHP. Suggest the better way to secure the cookie and maintain the session. Give some explanation too.
Edited: Explain cookieHandler.Encode() and cookieHandler.Decode(). It is not decoding the data which is passed to it.
var retMap = make(map[string]string) is a global map that you both read and write to, which is unsafe: when you have more than one user concurrently you will be overwriting the contents of this map.
You aren't using the securecookie package to encode your cookie values at all - in fact it's not clear where you're using it at all.
cookie := http.Cookie{
Name: "session",
// This should be encoded.
Value: sessionData["userName"], //Here i want map or something else that can accept multiple values
Expires: expiration,
//MaxAge: 3600,
}
Too many packages: you have a loginpackage, a sessionpackage and a package main. Packages should fulfil a 'theme' - an auth package might make more sense, or even just a single package main until you get more familiar with Go.
You're not hashing your passwords - storing the plain text password in the database and looking it up with the value of r.FormValue("password") is extremely insecure. Read this to see how to hash passwords safely in Go: Golang/App Engine - securely hashing a user's password
You should be using the gorilla/sessions package rather than the lower-level securecookie package.
Modifying the gorilla/sessions example in the documentation:
package main
import (
"net/http"
"github.com/gorilla/sessions"
)
// Use the CookieStore
var store = sessions.NewCookieStore([]byte("something-very-secret"))
func LoginOperation(w http.ResponseWriter, r *http.Request) {
// Your existing DB code - shorter to make the example here clearer
err := db.QueryRow("SELECT lvl_flag FROM admin_instance WHERE user_name = ? AND passwd = ?", r.FormValue("username"), r.FormValue("password")).Scan(&lvl)
// Don't use a global map - create a new one
userDetails := make(map[string]string)
userDetails["msg"] = "Login successfully"
userDetails["err"] = "Not Login"
userDetails["lvl"] = lvl
userDetails["fullName"] = uFullName
// Get a session (existing/new)
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// Set some session values.
session.Values["userDetails"] = userDetails
// Save it before we write to the response/return from the handler.
session.Save(r, w)
}
Later, if you want to retrieve the details:
func RequireAuth(w http.ResponseWriter, r *http.Request) {
// Get a session (existing/new)
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// Type assert our userDetails map out of the session's map[string]interface{}
userDetails, ok := session.Values["userDetails"].(map[string]string)
if !ok {
// User does not have an existing session - treat them as not logged in and/or re-direct them to your login page.
http.Error(w, http.StatusCode(401), 401)
return
}
// Check the user details - e.g. if userDetails["lvl"] == "ACTIVE" { ... }
// TODO
}

Resources