How to handle errors with firebase admin sdk for Go? - firebase

New to Go and trying to understand how to access the error details. I've already created a user, and now I'm expecting to get a "email-already-exists" error:
fbUser, err := s.auth.CreateUser(ctx, fbUserParams)
if err != nil {
return nil, errors.New("[email] already exists") // <- it could be any other error, and I want to be able to handle it
}
Here's what I see in my debugger:
How can I handle the error so that I can get the Code from it?

I think that the best option you have is using the Errors.As function. You can learn more about it at here: https://pkg.go.dev/errors#As
The error returned by Google Firebase is of type FirebaseError that involves two properties: Code and String. You can try with the following code snippet:
fbUser, err := s.auth.CreateUser(ctx, fbUserParams)
if err != nil {
var firebaseErr *FirebaseError
if errors.As(err, &firebaseErr) {
// here you can access "Code" and "String"
} else {
return nil, errors.New("[email] already exists")
}
}
Thanks to this code you should be able to manage what you need. Pay attention to import correctly the package that provides the type FirebaseError. Maybe read something on the Firebase documentation first.
Hope this helps!

Related

Firebase Admin SDK Event Listener not triggered

I am using the Go Firebase Admin SDK and listening to changes in the Realtime Database.
The problem is that the listener is ONLY triggered if I manually update the data from the Firebase Console, if I change data from another app (in this case Flutter), the listener is NOT triggered even though the changes can be seen in the Firebase Console (so the data definitely changed).
I even tried performing an update via Firebase Database REST API https://firebase.google.com/docs/reference/rest/database#section-streaming
Which had the same result: Data changes are viewable in the Console, but still don't trigger the listener.
Here the way I'm listening to changes in Go:
func listenToFirebase(ref *db.Ref, ctx context.Context) {
iter, err := ref.Listen(ctx)
if err != nil {
fmt.Printf(" Error: failed to create Listener %v\n", err)
return
}
defer iter.Stop()
for {
if iter.Done() {
break
}
event, err := iter.Next()
if err != nil {
// Handle error here based on specific usecase
// We can continue Listening
log.Printf("%v\n", err)
continue
}
fmt.Printf("Listener | Ref Path: %s | event.Path %s | event.Snapshot() = %v\n", ref.Path, event.Path, event.Snapshot())
fmt.Printf("\n")
}
}
The fact that the listener is triggered by updating data from the Console, indicates that the listener is working properly.
P.S.:
The Listen-Method has not yet been integrated to the Go Firebase Admin SDK and is from https://github.com/firebase/firebase-admin-go/issues/229
Turned out there was no problem regarding the Go code.
The problem was the way I updated the Realtime Database from Flutter.
This is what I used to perform updates:
await ref.update({"value": value});
What fixed the problem was to use "set" instead of "update" the following way:
await ref.child("value").set(value);
This way my Go-Listener method then was finally triggered and everything was working as expected.

How to get path from config file

I would like to get path to my sqlite DB from a config file. How could I do that in Go?
This is a code, which I wrote before:
database, _ := sql.Open("sqlite3", "C:\\Users\\username\\project\\source.db")
In this case my path is "hard coded" directly in code. I would like to set a variable, which takes a path from a config data.
My first suggestion is that you use a JSON file rather than a YAML file for configuration, since Go supports it natively; you don't need to use any external packages.
type DBConfig struct {
Path string `json:"path"`
}
func loadConfig(path string) (*DBConfig, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var conf DBConfig
err = json.Unmarshal(data, &conf)
if err != nil {
return nil, err
}
return &conf, nil
}
My second suggestion is that you pass in the path to this config file in as a flag. You can supply a flag when you run your application like this:
$ go build . -o MyApp
$ ./MyApp --config=path/to/config/file
Flags are very powerful and allow you to easily configure your applications without changing much code. Using flags in Golang is simple.
var configPath = flag.String("config", "", "Path to file containing app config")
Just make sure that you add flag.Parse to the top of your main function in order to access them.
Here's a full example.
Good luck!

Authenticate Service Account for Remote Config REST API using Go

Over here the Firebase docs explain how you can retrieve a token required to make requests to the Remote Config Rest API.
It provides example code for Python, Java and Node.js. Because there is no code for Go, it sends me to the Google Client Library (for Go). You might be able to understand why I am getting lost there...
The examples use GoogleCredential in Java, ServiceAccountCredentials in Python and google.auth.JWT in Node.js. I was not able to find any of those here. I do not know why there are no clear naming conventions.
I have found
firebaseremoteconfig-gen.go: The code looks like it already implements what the Firebase documentation page tries to achieve "manually". Comparison: doc, package.
Help
Because the "Usage example" of the package ends strangely abrupt and is the opposite of extensive, I do not understand how to make use of it.
I would be helped if someone could tell me how I can use this:
firebaseremoteconfigService, err := firebaseremoteconfig.New(oauthHttpClient)
I could not figure out where I would get oauthHttpClient from. There is an oauth2 package in the repository, but there I face the same problem:
oauth2Service, err := oauth2.New(oauthHttpClient)
I need oauthHttpClient again, so this cannot be a solution.
http.Client could be anything, but I need to authenticate with a service-account.json file, like shown in the three example snippets here.
Tags explanation
I hope that someone has either had experience with integrating Firebase Remote Config with Go, someone knows how Google Client API authentication works or someone is good enough with Go to get how the usage works.
There are a couple of main ways of authenticating with the google APIs, they are documented here:
Link to docs
The ways documented are "3-legged OAuth", "Using API Keys" and finally "Service Accounts".
From the links that you've included in the question; you are looking at the Python / Java / Node examples of "Service Accounts".
Using Service Accounts in go
The oauthHttpClient that you are referring to, is an http client that will attach the authentication information to the requests automatically.
You can create one using this package:
https://godoc.org/golang.org/x/oauth2/google
The examples linked in other languages use a "service account json key file".
Using the method linked below, you can read that keyfile and create a jwt.Config struct that will give you access to the client that you need.
https://godoc.org/golang.org/x/oauth2/google#JWTConfigFromJSON
The go equivalent of the other language examples linked is;
data, err := ioutil.ReadFile("/path/to/your-project-key.json")
if err != nil {
log.Fatal(err)
}
conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/firebase.remoteconfig")
if err != nil {
log.Fatal(err)
}
// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client := conf.Client(oauth2.NoContext)
client.Get("...")
I just started using the same library (from an AppEngine Standard project). This is how I am creating the service client:
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"golang.org/x/oauth2/google"
fb "google.golang.org/api/firebaseremoteconfig/v1"
"google.golang.org/appengine"
"google.golang.org/appengine/log"
)
const (
// Name of our service account file
saFileName = "my-firebase-sa.json"
// OAuth scopes used for remote config API
scopeRemoteConfig = "https://www.googleapis.com/auth/firebase.remoteconfig"
)
func createFirebaseService(ctx context.Context) (*fb.Service, error) {
data, err := ioutil.ReadFile(saFileName)
if err != nil {
return nil, err
}
conf, err := google.JWTConfigFromJSON(data, scopeRemoteConfig)
if err != nil {
return nil, err
}
return fb.New(conf.Client(ctx))
}
And I call it as such:
func fetchConfig(ctx context.Context) (*fb.RemoteConfig, error) {
s, err := createFirebaseService(ctx)
if err != nil {
log.Errorf(ctx, "Failed to create firebase service: %v", err)
return nil, fmt.Errorf("Failed to initialize Firebase service")
}
projectID := "projects/" + appengine.AppID(ctx)
cfg, err := s.Projects.GetRemoteConfig(projectID).Do()
if err != nil {
log.Errorf(ctx, "Failed to call Firebase remote config API: %v", err)
return nil, err
}
return cfg, nil
}
The code is using the Project ID to form its path; after reading through the lib code I noticed it was missing /projects/ from that path; so I just prepended that to my project ID and it works ;-) At least until they fix that and my code stops working..
Hopefully this helps someone.

Error initialising firebase in golang app

I am using firebase go sdk (https://github.com/acoshift/go-firebase-admin) and have followed the docs to set up my app.
But when I try to initialize the app with firebase.NewApp I get an error saying
google: could not find default credentials.
Can someone please help
Here is the code snippet
opt = option.WithCredentialsFile(viper.GetString("firebase"))
app, err = firebase.NewApp(context.Background(), nil, opt)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
The problem in opt = option.WithCredentialsFile(viper.GetString("firebase"))
It couldn't find the path to your config file. Try to use path to file and then find how to add it via viper

Inspecting the body of an HTTP request with gocraft Middleware

I've been using the gocraft-web package so far to do some development on an HTTP service. It's really great because you can stick middleware in it to check for stuff like the presence of a Cookie in the header.
At the moment I am wanting to implement request signing. Getting the client to sign the request is easy enough, but I am wanting to check it for all endpoints with a common piece of middleware. Basically the middleware needs to find the key to check against, compute the request HMAC, and check it against the supplied HMAC (presumably in the Authorization Header).
Computing the actual HMAC is really easy in go.
The problem is: reading the message in middleware makes it unavailable to the final endpoint.
The best solution I have come up with (example shown below) is to read everything from the Request in the middleware and stuffing it back into a bytes.Buffer for later reading. Is there a better way to do this? The current implementation seems a bit hackish.
Reading everything into memory sucks, but I can probably just put my service behind a proxy and limit the size of requests anyways. The actual content will always be pretty small(under 5 kilobytes). The extra copy introduced by this approach is likely to be quite slow, but computing the HMAC of a message is not exactly cheap to begin with.
The advantage to this is that it is transparent: it will work with any other go http code that just expects to read from Request.Body without any magic.
I suppose I could be a bit slicker and use a io.TeeReader.
This is my solution so far. If you post to localhost:3300 some JSON it prints the sha512 to the terminal in the server process, but also the response is able to contain a listing of the keys & values in it.
package main
import "fmt"
import "github.com/gocraft/web"
import "net/http"
import "bytes"
import "crypto/sha512"
import "io"
import "encoding/hex"
import "encoding/json"
type Context struct{}
type echoer struct {
*bytes.Buffer
}
func (e echoer) Close() error {
//Just do nothing to make the interface happy
return nil
}
func middlewareThatLooksAtBody(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
var replacement echoer
replacement.Buffer = &bytes.Buffer{}
hash := sha512.New()
hash.Write([]byte(req.Method))
reader := req.Body
var bytes []byte = make([]byte, 64)
for {
amount, err := reader.Read(bytes)
fmt.Printf("Read %d bytes\n", amount)
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
if amount == 0 {
break
}
hash.Write(bytes)
replacement.Write(bytes)
}
//Is this needed?
reader.Close()
//replacement.Seek(0, 0)
req.Body = replacement
fmt.Printf("%v\n", hex.EncodeToString(hash.Sum(nil)))
next(rw, req)
}
func echoJson(rw web.ResponseWriter, req *web.Request) {
dec := json.NewDecoder(req.Body)
var obj map[string]interface{}
err := dec.Decode(&obj)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(rw, "%v\n", err)
return
}
for k, v := range obj {
fmt.Fprintf(rw, "%v = %v\n", k, v)
}
}
func main() {
router := web.New(Context{})
router.Middleware(middlewareThatLooksAtBody)
router.Post("/", echoJson)
http.ListenAndServe("localhost:3300", router)
}
From your description, it looks like you need to read all the bytes from the request body, regardless of what your handlers will do.
If so, then you have at least a couple of options that would avoid the extra copy:
1) Store the read contents inside your gocraft context.
2) Do all body data processing and validation in the middleware and store the results of the processing in the context.
Granted, this means that your handlers now must know that they should look for the contents in the context instead of the req.Body.
I think it's a decent trade-off though, given your requirements.

Resources