How to access db while running a webdriver using chromedp? - go

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)
}

Related

Fetch new key on JWT Signature Validation

To give some context, I am trying to verify a JWT signature using Public Key. The public key expires after some amount of hrs which could change. Now the problem is that if the validation fails, I don't know if it failed because of an invalid Token, or the Public Key got expired.
To solve this, I am doing the following:
Fetch a new key from some URL when there is a validation error
Use the updated key to validate the token
If it still fails, then the JWT validation fails
Code
I have a HTTP Handler inside which a new instance of tokenValidator is created and then validate() is called on it.
keystore that is used to create the tokenValidator is initialized outside the handler
type tokenValidator struct {
keyStore *keystore.KeyStore
}
func (t *tokenValidator) validate(){
// get token
.....
// validate token
var err error
err = t.validateSignature(token, publicKeyName)
if err != nil {
t.keyStore.UpdateKeys() // fetch new keys and try again
err = t.validateSignature(token, publicKeyName)
if err != nil {
return nil, fmt.Errorf("pf token signature validation failed: %w", err)
}
}
}
Keystore
Now, I don't want all the failed validation requests to fetch the key from the URL instead I want to use the updated key for all the requests waiting to acquire the lock.
So, for example, if there are 100 parallel requests, I want the request that acquires lock to update the key. All the others should use the updated key.
type KeyStore struct {
Keys jwks.JWKPublicKeys // map[string]rsa.PublicKey
mu sync.Mutex
isKeyUpdated bool
}
func (k *KeyStore) UpdateKeys() error {
k.isKeyUpdated = false
k.mu.Lock()
defer k.mu.Unlock()
var err error
if !k.isKeyUpdated {
keysMap, err := retrieveKeysFromURL()
if err == nil {
k.Keys = *keysMap // update the Keys Map
k.isKeyUpdated = true
}
}
return err
}
I am new to concurrency-related topics and was wondering if this could be improved. Or if there is some other better solution to this problem?
Thanks
The idea that you reload keys when token validation fails is a big security risk. Someone can bombard your API with invalid keys to launch a DoS attack by forcing you to reload keys in a never-ending loop. Ideally, you should find a way to determine if a key has expired or not.
You can keep a cache using:
cache map[string]*Key
where
type Key struct {
once sync.Once
key rsa.PublicKey
}
When you expire a key, you can simply do:
cache.cache[id]=&Key{}
which will insert an empty key in the cache. To get the key value from the cache:
func GetPublicKey(id string) rsa.PublicKey {
cache.Lock()
k:=cache.cache[id]
if k==nil {
cache.cache[id]=&Key{}
}
cache.Unlock()
k.once.Do(func() {
k.key=loadPublicKey()
})
return k.key
}
This way, everybody will wait until one of the goroutines initializes the key.

Go http client setup for multiple endpoints?

I reuse the http client connection to make external calls to a single endpoint. An excerpt of the program is shown below:
var AppCon MyApp
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
if AppCon == (MyApp{}) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: &http.Client{Timeout: 30 * time.Second},
RESTContext: restContext,
}
cj, err := cookiejar.New(nil)
if err != nil {
return &AppCon, err
}
AppCon.cookie = cj
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
go func(){
// Do something i.e c.GetUser("123456")
}()
}
}
I now have the requirement to set up a client connections with different endpoints/credentials received via queue messages.
The problem I foresee is I can't just simply modify AppCon with the new endpoint details since a pointer to MyApp is returned, resulting in resetting c. This can impact a goroutine making a HTTP call to an unintended endpoint. To make matters non trivial, the program is not meant to have awareness of the endpoints (I was considering using a switch statement) but rather receive what it needs via queue messages.
Given the issues I've called out are correct, are there any recommendations on how to solve it?
EDIT 1
Based on the feedback provided, I am inclined to believe this will solve my problem:
Remove the use of a Singleton of MyApp
Decouple the http client from MyApp which will enable it for reuse
var httpClient *http.Client
func New(user, pass string, platformURL *url.URL, restContext string) (*MyApp, error) {
AppCon = MyApp{
user: user,
password: pass,
URL: platformURL,
Client: func() *http.Client {
if httpClient == nil {
httpClient = &http.Client{Timeout: 30 * time.Second}
}
return httpClient
}()
RESTContext: restContext,
}
return &AppCon, nil
}
// This is an example only. There are many more functions which accept *MyApp as a pointer.
func(ma *MyApp) GetUser(name string) (string, error){
// Return user
}
func main(){
for {
// Get messages from a queue
// The message returned from the queue provide info on which methods to call
// 'm' is a struct with message metadata
c, err := New(m.un, m.pass, m.url)
// Must pass a reference
go func(c *MyApp){
// Do something i.e c.GetUser("123456")
}(c)
}
}
Disclaimer: this is not a direct answer to your question but rather an attempt to direct you to a proper way of solving your problem.
Try to avoid a singleton pattern for you MyApp. In addition, New is misleading, it doesn't actually create a new object every time. Instead you could be creating a new instance every time, while preserving the http client connection.
Don't use constructions like this: AppCon == (MyApp{}), one day you will shoot in your leg doing this. Use instead a pointer and compare it to nil.
Avoid race conditions. In your code you start a goroutine and immediately proceed to the new iteration of the for loop. Considering you re-use the whole MyApp instance, you essentially introduce a race condition.
Using cookies, you make your connection kinda stateful, but your task seems to require stateless connections. There might be something wrong in such an approach.

Code design for handle funcs in go web app

I'm learning go and ran into some design issues while developing web app. The app has main route "/" where user can submit a simple form. With those form values I am calling external API and unmarshaling response into some struct. Now from here I want to make another call based on retrieved values to another external API and I'm not sure what's the proper way of doing this. Here is a snippet for better understandment:
func main() {
http.HandleFunc("/", mainHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func mainHandler(w http.ResponseWriter, r *http.Request) {
//renders form template
//makes post and retrieves data from api
//here with retrieved data I want to make another call to different API,
// but mainHandler would get too big and complex. I'm not sure how should I pass this data to
// another handler or redirect to another handler with this data.
}
The handlers' semantics should be designed to match the desired HTTP behavior, regardless of the code complexity. If you want to handle a single client request by doing a bunch of stuff, that should be a single handler. If the handler becomes too complex, break it up. Handlers are just functions and can be broken up exactly like any other function - by extracting some part of it into another function and calling that new function. To take you pseudocode:
func mainHandler(w http.ResponseWriter, r *http.Request) {
err := renderTemplate(w)
if err != nil { ... }
err, data := postToApi()
if err != nil { ... }
err, data2 := postToApi2(data)
if err != nil { ... }
}
There's no reason for those functions to be handlers themselves or to get the client involved with a redirect. Just break up your logic the way you normally break up logic - it doesn't matter that it's an HTTP handler.
Hi golearner, in the mainHandler just render the form and make another handler kinda "/formaction" to handle the form, in that way you can easily organize your code.

Fetching Logged in User Info for display

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!

Google Cloud Bigtable authentication with Go

I'm trying to insert a simple record as in GoDoc. But this returns,
rpc error: code = 7 desc = "User can't access project: tidy-groove"
When I searched for grpc codes, it says..
PermissionDenied Code = 7
// Unauthenticated indicates the request does not have valid
// authentication credentials for the operation.
I've enabled Big table in my console and created a cluster and a service account and recieved the json. What I'm doing wrong here?
package main
import (
"fmt"
"golang.org/x/net/context"
"golang.org/x/oauth2/google"
"google.golang.org/cloud"
"google.golang.org/cloud/bigtable"
"io/ioutil"
)
func main() {
fmt.Println("Start!")
put()
}
func getClient() *bigtable.Client {
jsonKey, err := ioutil.ReadFile("TestProject-7854ea9op741.json")
if err != nil {
fmt.Println(err.Error())
}
config, err := google.JWTConfigFromJSON(
jsonKey,
bigtable.Scope,
) // or bigtable.AdminScope, etc.
if err != nil {
fmt.Println(err.Error())
}
ctx := context.Background()
client, err := bigtable.NewClient(ctx, "tidy-groove", "asia-east1-b", "test1-bigtable", cloud.WithTokenSource(config.TokenSource(ctx)))
if err != nil {
fmt.Println(err.Error())
}
return client
}
func put() {
ctx := context.Background()
client := getClient()
tbl := client.Open("table1")
mut := bigtable.NewMutation()
mut.Set("links", "maps.google.com", bigtable.Now(), []byte("1"))
mut.Set("links", "golang.org", bigtable.Now(), []byte("1"))
err := tbl.Apply(ctx, "com.google.cloud", mut)
if err != nil {
fmt.Println(err.Error())
}
}
I've solved the problem. It's nothing wrong with the code, but config json itself. So anyone who out there want to authenticate and came here by google search... This code is correct and working perfectly. What I've done wrong is follows.
First I made a service account and got the json. But google warned me that im not an owner of project hence it wont be added to accept list but anyway it let me download the json.
Then I deleted that key from console and requested project owner to create a key for me.
There he has created another key with the same name I given.. And since he's the owner no error/warning msgs displayed and successfully json file was downloaded.
When I tried with that... my question begun. That's when i posted this question.
After that with no solutions. I asked owner to delete that key and create another key but with a different name..
Then it worked! It seems if you try to create a key with non-owner account and then again create with same name ( after deleting original of course ) has no effect. Hope this helps everyone out there :)
Take a look at: helloworld.go or search.go which uses GOOGLE_APPLICATION_CREDENTIALS environment variable.
For most environments, you no longer even need to set GOOGLE_APPLICATION_CREDENTIALS. Google Cloud Platform, Managed VMs or Google App Engine all have the right thing set for you. Your desktop environment will also be correct if you've used gcloud init or it's predecessor gcloud auth login followed by gcloud config set project <projectID>.

Resources