Hey there!
I’m writing a backend plugin for Grafana and I am unsure on how to proceed an issue.
I’m currently writing a datasource plugin that requires authorizing with Google OAuth2. It goes like this:
- a person goes to a Google page, asking for consent for a specific client ID and scopes
- a person is redirected to some URI
- this code (one-time use) is sent to a Google server, then access and refresh tokens are returned
- these tokens are used to request Google API.
I’ve figured out how to get a code from user: make a link to Google on a frontend side with redirect_uri as the Grafana editing datasource URI. This will redirect user back, then I can get the code from the query string.
Question is, I need to exchange it and receive access and refresh token and somehow store it in the database. Can I do it from within a healthcheck endpoint?
Here’s currently what I have:
// CheckHealth handles health checks sent from Grafana to the plugin.
// The main use case for these health checks is the test button on the
// datasource configuration page which allows users to verify that
// a datasource is working as expected.
func (td *SampleDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
log.DefaultLogger.Info("QueryData", "request", req.PluginContext.DataSourceInstanceSettings)
if val, ok := req.PluginContext.DataSourceInstanceSettings.DecryptedSecureJSONData["code"]; !ok || val == "" {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Code is not provided. Please press \"Sign in with Google\"",
}, nil
}
if val, ok := req.PluginContext.DataSourceInstanceSettings.DecryptedSecureJSONData["clientSecret"]; !ok || val == "" {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Client secret is not provided",
}, nil
}
jsonData, err := simplejson.NewJson([]byte(req.PluginContext.DataSourceInstanceSettings.JSONData))
if err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Could not parse JSON",
}, nil
}
clientID := jsonData.Get("clientId").MustString()
if clientID == "" {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Client ID is not provided",
}, nil
}
redirectURI := jsonData.Get("redirectURI").MustString()
if redirectURI == "" {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Redirect URI is not provided",
}, nil
}
clientSecret := req.PluginContext.DataSourceInstanceSettings.DecryptedSecureJSONData["clientSecret"]
code := req.PluginContext.DataSourceInstanceSettings.DecryptedSecureJSONData["code"]
log.DefaultLogger.Info("QueryData", "clientId", clientID)
log.DefaultLogger.Info("QueryData", "clientSecret", clientSecret)
log.DefaultLogger.Info("QueryData", "code", code)
log.DefaultLogger.Info("QueryData", "redirectURI", redirectURI)
if token, err = getToken(ctx, clientID, clientSecret, code, redirectURI); err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Error authorizing",
}, nil
}
return &backend.CheckHealthResult{
Status: backend.HealthStatusOk,
Message: "Everything is okay.",
}, nil
}
func getToken(ctx context.Context, clientID string, clientSecret string, code string, redirectURI string) (*oauth2.Token, error) {
conf := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: google.Endpoint,
RedirectURL: redirectURI,
}
token, err := conf.Exchange(ctx, code)
log.DefaultLogger.Debug("QueryData", "err", err)
log.DefaultLogger.Debug("QueryData", "token", token)
if err != nil {
return nil, err
}
return token, err
}
I need to store access and refresh tokens somehow on backend based on what user has sent and I’m not sure how to proceed here. How can I do this?
The code is saved to the DB but unfortunately it only can be use once.