Authenticate Service Account for Remote Config REST API using Go - firebase

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.

Related

How to handle errors with firebase admin sdk for Go?

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!

Are there any options for logging request times in grpc-node?

Are there any options for logging request times in grpc-node? I've been able to log response times using opentelemetry & jaegar (to display response times). I couldn't find any npm packages for this either, but just wanted to ask you guys if you did find any options for grpc-node.
You don't need a package to do it, you can do it using a simple gRPC client interceptor.
This is how you would do it in Golang. Just check how you can create a gRPC interceptor in Node. Sorry for not having a JS example, hope it helps some how.
func UnaryRequestTImeInterceptor() grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req interface{},
reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
reqTime := time.Since(start)
return err
}
}

cannot find package "firebase.google.com/go" in google cloud function

I'm trying to run some example code that stores some random data using a cloud function in the Firestore server, however, Cloud functions deploy command refuses to build the command:
// Package p contains an HTTP Cloud Function.
package p
import (
//...
firebase "firebase.google.com/go"
"log"
"net/http"
"os"
)
// Store1 Stores data on FireBase
func Store1(w http.ResponseWriter, r *http.Request) {
// Use the application default credentials
ctx := context.Background()
conf := &firebase.Config{ProjectID: "firefirefire"}
app, err := firebase.NewApp(ctx, conf)
if err != nil {
log.Fatalln(err)
}
client, err := app.Firestore(ctx)
if err != nil {
log.Fatalln(err)
}
defer client.Close()
_, _, err = client.Collection("users").Add(ctx, map[string]interface{}{
"first": "Ada",
"last": "Lovelace",
"born": 1815,
})
if err != nil {
log.Fatalf("Failed adding alovelace: %v", err)
}
fmt.Println("ENV:" + os.Getenv("VAR1"))
fmt.Fprint(w, html.EscapeString(d.Message))
}
This is what I get as an error:
localhost:store1 b$ ./deploy.sh
Updated property [functions/region].
Deploying function (may take a while - up to 2 minutes)...failed.
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed: /tmp/sgb/gopath/src/serverlessapp/vendor/p/store1.go:6:2: cannot find package "firebase.google.com/go" in any of:
/tmp/sgb/gopath/src/serverlessapp/vendor/firebase.google.com/go (vendor tree)
/go/src/firebase.google.com/go (from $GOROOT)
/tmp/sgb/gopath/src/firebase.google.com/go (from $GOPATH)
As you can see, the problem seems to be that Google doesn't have firebase.google.com/go on Cloud functions engine and as a result I can't have my serverless configuration do the firebase thing.
Should I move to CloudSQL and just pay the $11 fee ?
Should I continue to try to get Firebase to work?
Should I try a Firebase Function instead?
Please advise.
According to your description and codes,if I understand your issue clearly, you are trying to trigger an HTTP Cloud Function to write data into Cloud Firestore.
While you deploy the Cloud Function, package "firebase.google.com/go" cloud not be found.”. Becasue ”firebase.google.com/go” which is the entry point to the Firebase Admin SDK. However, Cloud Function, the Google Cloud Client Library for Go is installed. Based on your requirement, It seems Cloud Functions for Firebase may provide the solution you need.

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