Fetching Logged in User Info for display - go

I am using https://github.com/kataras/iris Go web framework. I have:
User Registered
User Verified & Logged in
Session created and set with key username with user (table & struct) username
Now, here is my code for logged in user:
// Loaded All DB and other required value above
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
ctx.View("index.html");
});
}
In authcheck middleware
func authcheck(ctx context.Context) {
// Loaded session.
// Fetched Session key "isLoggedIn"
// If isLoggedIn == "no" or "" (empty)
// Redirected to login page
// else
ctx.Next()
}
My Session function
func connectSess() *sessions.Sessions {
// Creating Gorilla SecureCookie Session
// returning session
}
Now, my problem is, how do I share Logged User value to all routes in template. My Current option is:
// Loaded all DB and required value
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
allRoutes.Get("dashboard", func(ctx context.Context) {
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)
ctx.View("index.html");
});
}
But problem with above code is, I will have to write session for each route and run query again for each route I run and than share.
I feel, there must be better way of doing it , rather than loading session twice for each route one in authCheck middleware and second inside allRoutes.Get route.
I need ideas on how this can be optimised and user data can be shared to template by just writing code one time and not repeating below for each route
// Load Session again
// Fetch username stored in session
// Run Query against DB
// Share the user struct value.
// Example ctx.ViewData("user", user)

it's easy you can use the ctx.Values().Set/Get to make something shareable between your route's handlers or middleware(s).
// load session manager once
sess := connectSess()
func authCheck(ctx context.Context) {
session := sess.Start(ctx)
// Load your user here.
// [...]
// Save the returning user to the local storage of this handlers chain, once.
ctx.Values().Set("user", user) // <-- IMPORTANT
}
app.Get("/", func(ctx context.Context) {
// Get the user from our handlers chain's local storage.
user := ctx.Values().Get("user") // <-- IMPORTANT
// Bind the "{{.user}}" to the user instance.
ctx.ViewData("user", user)
// Render the template file.
ctx.View("index.html")
})
app.Get("dashboard", func(ctx context.Context) {
// The same, get the user from the local storage...
user := ctx.Values().Get("user") // <-- IMPORTANT
ctx.ViewData("user", user)
ctx.View("index.html")
})
That's all, pretty simple, right?
But I have some notes for you, read them if you have more time.
When you're on root "/" you don't have to create a party for it(.Party) in order to add middlewares (begin(Use) or finish(Done)), use just the iris.Application instance, app.Use/Done.
Don't write this:
allRoutes := app.Party("/", logThisMiddleware, authCheck) {
allRoutes.Get("/", myHandler)
}
Do that instead:
app.Use(logThisMiddleware, authCheck)
app.Get("/", myHandler)
It's easier to read and understand.
I've also noticed that you're using ; at the end of your functions, your editor and gocode tool will remove those, when you write a program using the Go Programming Language you shouldn't do that, remove all ;.
Last, please read the documentation and the examples, we have many of them at https://github.com/kataras/iris/tree/master/_examples , hopes you the best!

Related

How to access db while running a webdriver using chromedp?

I want to automate OTP submission on a bank page. I will get the OTP in my database only after webdriver have clicked confirm on the bank page. After confirming, I need to fetch OTP from the db and then automate OTP submission.
ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
defer cancel()
// run chromedp tasks
err := chromedp.Run(ctx,
chromedp.Navigate(bankUrl),
chromedp.WaitVisible(`#username`),
chromedp.SendKeys(`#username`, `usernameXXX`),
chromedp.WaitVisible(`#label2`, ),
chromedp.SendKeys(`#label2`, `passwordxxx` ),
chromedp.Click(`//input[#title="Login"]`),
chromedp.WaitVisible(`#Go`),
chromedp.Click(`#Go`),
chromedp.WaitVisible(`#confirmButton`),
chromedp.Click(`#confirmButton`),
chromedp.WaitVisible(`//input[#type="password"]`),
// perform fetch OTP below, this raise error
otp := fetchOTPFromDb()
chromedp.SendKeys(`//input[#type="password"]`, otp),
chromedp.WaitVisible(`#confirmButton`),
chromedp.Click(`#confirmButton`))
if err != nil {
log.Fatal(err)
}
The problem is that chromedp.Run expects all args to be of the type chromedp.Tasks, so I can't call custom functions there and I get error while fetching the OTP from db. How do I go around this?
The solution is to wrap the otp fetch within an Action.Do call, then return the result of a call to chromdp.SendKeys to set the HTML input value.
It is required to work that way because the One Time Password does not exist until the page was fetched, thus, its read must happen while manipulating the resource.
Like this
package main
import "context"
type OTPAction struct {
// DB ....
}
func (a OTPAction) Do(ctx context.Context) error {
// fetch OTP here
otp := "otp test"
return chromedp.SendKeys(`//input[#id="user-message"]`, otp).Do(ctx)
}

How to set data in gin request context?

I have a very simple piece of code. One single middleware is applied to all routes. In this middleware, the header field 'x-sentinel-tenant' is read. If it is blank, an error is returned. If it has some value, then that value is to be extracted and set in the request context for later use.
Here's my middleware code
// VerifyTenant ensures that the user has added tenant information in the header
func VerifyTenant(c *gin.Context) {
requestTenant := c.Request.Header.Get("x-tenant")
if requestTenant == "" {
c.AbortWithStatusJSON(
http.StatusBadRequest,
views.GenerateErrorResponse(
http.StatusBadRequest,
"Please add tenant in the header.",
c.Request.URL.Path,
),
)
return
}
c.Request = c.Request.WithContext(context.WithValue(c, tenant, requestTenant))
}
When I try to access this context in a later part of my code, here's how I get the data when hovered over the ctx value.
I am not sure what am I doing wrong. It's a fairly straight forward piece of code which is behaving very funny. Any leads will be appreciated. Thank you
When I update the context, I expect "tenant" to be added as another field. Which is not happening. Can multiple values not coexist in the same context?
My question - how to correctly set multiple values in gin request context
Here's how I am trying to access tenant
return ctx.Value("Context").(context.Context).Value("tenant").(string)
https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc#Context.Set
func (*Context) Set does exactly what you want. an example is:
func VerifyTenant(ctx *gin.Context) {
requestTenant := ctx.GetHeader("x-tenant")
if requestTenant == "" {
// Abort
return
}
ctx.Set("x-tenant", requestTenant)
}
func Test(ctx *gin.Context){
// when you need to get the value of "x-tenant"
requestTenant := ctx.GetString("x-tenant")
}

Using a key value store in webserver application golang

I tried to get a simple key value store working inside of a go webserver app, which should store some information.
The issue is, I can only create one instance of it, since its writing to the disk and the folder is locked, so I need to find away to access the key value store with my Webserver.
So every current instance can access it (read/write).
How do I do that?
Currently My app looks like that: https://play.golang.org/p/_SmGBZlP0Vi
The Package I wanted to use is this: https://github.com/peterbourgon/diskv
Basically I would create an instance before the main and pass the instance of the key value store, to the rtt function, but that seems not directly be possible in go. Or do I something wrong?
Global Conn Instance
First create a package with a single instance of the key value store and make the connection a package variable that you connect once and then keep open for all future use. Here some pseudo code sample:
package kvstore
var conn *diskv.Conn // or whatever the type of the conn is
func Connect(...) {
// crate connection and fill conn
conn = diskv.New(...)
}
func Write(k, v []byte) error {
return conn.Write(k, v)
}
That way yo have a "global" connection that can be used from everywhere. Simply call kvstore.Write(...) anywhere to write to the store.
Sync concurrent access
If your application uses multiple goroutines that can access the kvstore you (might -- depending if the package you use already does this for you or not) need to sync the access. You can do this by using a mutex for the connection:
var (
conn *diskv.Conn // or whatever the type of the conn is
mutex sync.Mutex
)
func Write(k, v []byte) error {
// everywhere you use the conn object, lock the mutex before and unlock after
mutex.Lock()
defer mutex.Unlock()
return conn.Write(k, v)
}
You can also use the actor pattern. Here a post by Peter Bourgon that explains the actor pattern. With the actor pattern we can make sure the conn object is only used in one goroutine making the use of a mutex unnecessary.
Simple kvstore package implementation
package kvstore
import "github.com/peterbourgon/diskv"
var conn *diskv.Diskv
// Connect opens the global diskv db
func Connect(dir string) {
flatTransform := func(s string) []string { return []string{} }
conn = diskv.New(diskv.Options{
BasePath: dir,
Transform: flatTransform,
CacheSizeMax: 1024 * 1024,
})
}
// Write writes to the global diskv db
func Write(k string, v []byte) error {
return conn.Write(k, v)
}
// Read reads from the global diskv db
func Read(k string) ([]byte, error) {
return conn.Read(k)
}
// Erase deletes a key from the global discv db
func Erase(k string) error {
return conn.Erase(k)
}
Sample usage of kvstore
package main
import (
"github.com/tehsphinx/diskv"
)
func main() {
// call this once in startup sequence.
kvstore.Connect("my-data-dir")
// use this anywhere to write to key value store
kvstore.Write("alpha", []byte{'1', '2', '2'})
// use this anywhere to read from kvstore
kvstore.Read("alpha")
// use this anywhere to delete from kvstore
kvstore.Erase("alpha")
}
Just copy it in two different folders and try. It works.

How to ensure uniqueness of a property in a NoSQL record ( Golang + tiedot )

I'm working on a simple application written in golang, using tiedot as NoSQL database engine.
I need to store some users in the database.
type User struct {
Login string
PasswordHash string
Salt string
}
Of course two users cannot have the same login, and - as this engine does not provide any transaction mechanism - I'm wondering how to ensure that there's no duplicated login in the database when writing.
I first thought that I could just search for user by login before inserting, but as the database will be
used concurently, it is not reliable.
Maybe I could wait for a random time and if there is another user with the same login in the collection, delete it, but that does not sound reliable either.
Is this even possible, or should I switch to a database engine that support transactions ?
Below is my solution. It is not Tiedot specific, but It uses CQRS and can be applied to various DBs.
You can also have other benefits using it, such as caching and bulk write (in case DB supports it) to prevent asking DB on every request.
package main
import (
"sync"
"log"
"errors"
)
type User struct {
Login string
PasswordHash string
Salt string
}
type MutexedUser struct {
sync.RWMutex
Map map[string]User
}
var u = &MutexedUser{}
func main() {
var user User
u.Sync()
// Get new user here
//...
if err := u.Insert(user); err != nil {
// Ask to provide new login
//...
log.Println(err)
}
}
func (u *MutexedUser) Insert(user User) (err error) {
u.Lock()
if _, ok := u.Map[user.Login]; !ok {
u.Map[user.Login] = user
// Add user to DB
//...
u.Unlock()
return err
}
u.Unlock()
return errors.New("duplicated login")
}
func (u *MutexedUser) Read(login string) User {
u.RLock()
value := u.Map[login]
u.RUnlock()
return value
}
func (u *MutexedUser) Sync() (err error) {
var users []User
u.Lock()
defer u.Unlock()
// Read users from DB
//...
u.Map = make(map[string]User)
for _, user := range users {
u.Map[user.Login] = user
}
return err
}
I first thought that I could just search for user by login before inserting, but as the database will be used concurently, it is not reliable.
Right, it creates a race condition. The only way to resolve this is:
Lock the table
Search for the login
Insert if the login is not found
Unlock the table
Table-locks are not a scalable solution, because it creates an expensive bottleneck in your application. It's why non-transactional storage engines like MySQL's MyISAM are being phased out. It's why MongoDB has to use clusters to scale up.
It can work if you have a small dataset size and a light amount of concurrency, so perhaps it's adequate for login creation on a lightly-used website. New logins probably aren't created so frequently that they need to scale up so much.
But users logging in, or password changes, or other changes to account attributes, do happen more frequently.
The solution for this is to make this operation atomic, to avoid race conditions. For example, attempt the insert and have the database engine verify uniqueness and reject the insert if it violates that constraint.
Unfortunately, I don't see any documentation in tiedot that shows that it supports a unique constraint or a uniqueness enforcement on indexes.
Tiedot is 98% written by a single developer, in a period of about 2 years (May 2013 - April 2015). Very little activity since then (see https://www.openhub.net/p/tiedot). I would consider tiedot to be an experimental project, unlikely to expand in feature set.

Golang (iris webframework) share between handlers

I am currently using the iris web framework and since questions cannot be asked on the issue tracker and the community chat is dead I am asking this here hoping someone helps me out.
I need to pass data to the c.Render function
I have a handler that checks if the user is logged or not. If its not logged I should add an extra button to the html page
iris.Use(userHandler{})
type userHandler struct{
Allow bool
}
func (u userHandler) Serve(c *iris.Context) {
...
if isLogged {
// When I call from another middleware (c.Next) c.Render it should know that the user is logged in
}
c.Next()
}
So is it possible to add some default data to the c.Render function?
// retrieve local storage or previous handler,
// this is how handlers can share values, with the context's Values().
logged := ctx.Values().Get("logged")
// set template data {{.isLogged}}
ctx.ViewData("isLogged", logged)
// and finally, render the mypage.html
ctx.View("mypage.html")
"logged" can be set to your middleware/any previous handler by:
ctx.Values().Set("logged", false)
All these are described to the examples, you're welcome to explore some of those there: https://github.com/kataras/iris/tree/master/_examples
Happy Coding!

Resources