I am using oci go sdk to access the secrets in oci. I get the SecretBundleResponse object but I want to extract the "Content" field which is present in resp.SecretBundleContent. There are no apis available on that object to extract it. Any help?
I am using https://docs.oracle.com/en-us/iaas/api/#/en/secretretrieval/20190301/SecretBundle/GetSecretBundle
req := secrets.GetSecretBundleRequest{
SecretId: common.String(secretID),
Stage: secrets.GetSecretBundleStageCurrent,
}
// Send the request using the service client
resp, err := client.GetSecretBundle(context.Background(), req)
if err != nil {
log.Println("Unable to get secrete bundle: ", err)
return ""
}
log.Println("secret bundle resp Secret Bundle content", resp.SecretBundleContent)
// secret bundle resp Secret Bundle content { Content=MyContent }
Received response from Oracle internal slack channel with the solution. It seems I need to cast SecretBundleContent using Base64SecretBundleContentDetails type to access it's fields.
var content string
base64Details, ok := resp.SecretBundleContent.(secrets.Base64SecretBundleContentDetails)
if ok {
log.Println("base64 content details", *base64Details.Content)
content = *base64Details.Content
}
Related
I read in the docs that in order to use Google Direction API Web Service, I needed to use IP restrictions. So to achieve this feature, I've created a proxy server that serve me the direction I want. The link looks like so:
https://myapp-44th7.uc.r.appspot.com/?origin=22.174181,33.6436763&destination=22.189821,33.640532
When I use an unrestricted API key and I access this URL in a browser, it works fine, I get the desired JSON file. The problem comes when I want to restrict it. I cannot add a single IP address as a restriction because my Android app is used by users with many different IP addresses. I have hard times trying understanding how to restrict this. Please help solve this issue. What IP should I add?
Edit:
package main
import (
"fmt"
"net/http"
"io/ioutil"
"context"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
)
const directionAPIKey = "..............uFvjU"
const directionURL = "https://maps.googleapis.com/maps/api/directions/json?origin=%s&destination=%s&mode=%s&key=%s"
func main() {
http.HandleFunc("/", handler)
appengine.Main()
}
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
direction, err := fetchDirection(ctx, r.FormValue("origin"), r.FormValue("destination"), r.FormValue("mode"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/json; charset=utf-8")
w.Write(direction)
}
func fetchDirection(ctx context.Context, origin string, destination string, mode string) ([]byte, error) {
client := urlfetch.Client(ctx)
resp, err := client.Get(fmt.Sprintf(directionURL, origin, destination, mode, directionAPIKey))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
The directionAPIKey being the key that I use in my URL to access:
https://myapp-44th7.uc.r.appspot.com/?origin=22.174181,33.6436763&destination=22.189821,33.64053&key=..............uFvjU
And the one for which I added the restriction in the GCP. 172.217.23.52 being the IP that comes when I ping myapp-44th7.uc.r.appspot.com.
For you to restrict your API keys with IP restrictions, you need to add your server's public facing IP address as a restriction to your API key. More information here.
Once you have applied IP restrictions to your API key, only requests from your specific server(in your case your proxy server) is allowed to use your restricted API key. This means that all requests which come from different IP addresses other than the one you have set as restriction are blocked. This is the reason why you are getting an error if you tried to send a request directly from your browser using your restricted API key since your browser has a different IP address than your proxy server.
To make sure that your restricted API key works, you can verify if you can successfully send a Directions API request through your proxy server using your restricted API key.
Hope this helps!
I have a problem in sending web service request using Delphi 7 and Indy. My workplace a public hospital as a client and insurance third party as the server. Here is the service catalogue:
URL : {BASE URL from the insurance office}/Delete
Method : DELETE
Format : Json
Content-Type: Application/x-www-form-urlencoded
Request body :
{"request": {"t_obj": {"noObj": "0301X1018V001","user": "myUser"}}}
I use Indy 10.6 and some of the code I wrote is:
Json := '{"request": {"t_obj": {"noObj": "0301X1018V001","user": "myUser"}}}';
req := TStringStream.Create(Utf8Encode(Json));
resp := TStringStream.Create('');
IdHttp1.request.Source := req;
IdHttp1.Request.ContentType := 'Application/x-www-form-urlencoded';
IdHttp1.delete('{BASE URL from the insurance office}/Delete', resp);
showmessage(resp.DataString);
But, when the request sent, it failed to delete.
Any one can help me, please? I'm sorry my English is not good enough.
Thank you.
Application/x-www-form-urlencoded is not a valid media type for sending JSON. Are you sure the server is not actually expecting application/json instead? It should be.
Beside that, a more important reason why your request does not work is because the TIdHTTP.Delete() method simply does not allow sending a post body, so the server never sees the JSON at all. Internally, Delete() calls the TIdCustomHTTP.DoRequest() method passing nil in the ASource parameter, which replaces your assignment of the TIdHTTP.Request.Source property.
To do what you are attempting, you will have to call DoRequest() directly, eg:
// TIdCustomHTTP.DoRequest() is protected,
// so use an accessor class to reach it...
type
TIdHTTPAccess = class(TIdHTTP)
end;
...
Json := '{"request": {"t_obj": {"noObj": "0301X1018V001","user": "myUser"}}}';
req := TStringStream.Create(UTF8Encode(Json));
try
resp := TStringStream.Create('');
try
//IdHttp1.Request.Source := req;
//IdHttp1.Request.ContentType := 'Application/x-www-form-urlencoded';
IdHttp1.Request.ContentType := 'application/json';
//IdHttp1.Delete('{BASE URL from the insurance office}/Delete', resp);
TIdHTTPAccess(IdHttp1).DoRequest(Id_HTTPMethodDelete, '{BASE URL from the insurance office}/Delete', req, resp, []);
ShowMessage(resp.DataString);
finally
resp.Free;
end;
finally
req.Free;
end;
I'm currently using MySQL Workbench on Windows 8.1 to access a remote MySQL database on a Linux server using Standard TCP/IP over SSH. Basically I have the following information:
SSH Hostname: dbserver.myorg.com:ssh-port
SSH Username: myRemoteLoginUsername
SSH Password: (stored in vault)
SSH Key File: path to a local .ppk file
MySQL Hostname: 127.0.0.1
MySQL Server Port: 3306
Username: myRemoteDbUsername
Password: (stored in vault)
Default schema: myRemoteDatabaseName
How can I connect to the database from a Go command application using github.com/go-sql-driver/mysql?
how should my DataSourceName string in the sql.Open statement look like?
db, err := sql.Open("mysql", <DataSourceName> ) {}
Is there any extra work needed to prepare a working DataSourceName string?
On my Windows PC I have putty installed. I read about tunneling and added a Dynamic tunnel for port 3306 (D3306). I expected this will let me connect using a connection to localhost:3306 and automatically forward the request to the remote db whenever I'm connected to the remote host with putty, but this didn't work as expected either.
I promised to provide my example, here it comes. Basically my solution establishes an ssh tunnel to the remote server and queries the remote database through this tunnel. The ssh tunnel is part of the solution.
The first thing I had to do is to convert my PuTTY .ppk private key file into a valid OpenSSH .pem key file. This can easily be done using the Export feature in PuTTYgen. As I wanted to support password encrypted private keys I also needed a function to decrypt the key and reformat it from its decrypted raw format into a valid format accepted by golang.org/x/crypto/ssh/ParsePrivateKey, which is needed to get the list of signers for authentication.
The solution itself consists of a package contained in two files. The main part of the application is done in main.go which contains all relevant data assignments as well as the code related to the database query. Everything related to ssh tunneling and key handling is contained in sshTunnel.go.
The solution does not provide a mechanismn for a secure password store, nor does it ask for a password. The password is provided in the code. However, it would not be too complicated to implement a callback method for password requests.
Please note: from a performance perspective this is not an ideal solution. It also lacks of proper error handling. I have provided this as an example.
The example is a tested and working example. I developed and used this from a Windows 8.1 PC. The database server is on a remote Linux system. All you need to change is the data and the query part in main.go.
Here is the first part contained in main.go:
// mysqlSSHtunnel project main.go
// Establish an ssh tunnel and connect to a remote mysql server using
// go-sql-driver for database queries. Encrypted private key pem files
// are supported.
//
// This is an example to give an idea. It's far from a performant solution. It
// lacks of proper error handling and I'm sure it could really be much better
// implemented. Please forgive me, as I just started with Go about 2 weeks ago.
//
// The database used in this example is from a real Opensimulator installation.
// It queries the migrations table in the opensim database.
//
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"os"
)
// Declare your connection data and user credentials here
const (
// ssh connection related data
sshServerHost = "test.example.com"
sshServerPort = 22
sshUserName = "tester"
sshPrivateKeyFile = "testkey.pem" // exported as OpenSSH key from .ppk
sshKeyPassphrase = "testoster0n" // key file encrytion password
// ssh tunneling related data
sshLocalHost = "localhost" // local localhost ip (client side)
sshLocalPort = 9000 // local port used to forward the connection
sshRemoteHost = "127.0.0.1" // remote local ip (server side)
sshRemotePort = 3306 // remote MySQL port
// MySQL access data
mySqlUsername = "opensim"
mySqlPassword = "h0tgrits"
mySqlDatabase = "opensimdb"
)
// The main entry point of the application
func main() {
fmt.Println("-> mysqlSSHtunnel")
tunnel := sshTunnel() // Initialize sshTunnel
go tunnel.Start() // Start the sshTunnel
// Declare the dsn (aka database connection string)
// dsn := "opensim:h0tgrits#tcp(localhost:9000)/opensimdb"
dsn := fmt.Sprintf("%s:%s#tcp(%s:%d)/%s",
mySqlUsername, mySqlPassword, sshLocalHost, sshLocalPort, mySqlDatabase)
// Open the database
db, err := sql.Open("mysql", dsn)
if err != nil {
dbErrorHandler(err)
}
defer db.Close() // keep it open until we are finished
// Simple select query to check migrations (provided here as an example)
rows, err := db.Query("SELECT * FROM migrations")
if err != nil {
dbErrorHandler(err)
}
defer rows.Close()
// Iterate though the rows returned and print them
for rows.Next() {
var version int
var name string
if err := rows.Scan(&name, &version); err != nil {
dbErrorHandler(err)
}
fmt.Printf("%s, %d\n", name, version)
}
if err := rows.Err(); err != nil {
dbErrorHandler(err)
}
// Done for now
fmt.Println("<- mysqlSSHtunnel")
}
// Simple mySql error handling (yet to implement)
func dbErrorHandler(err error) {
switch err := err.(type) {
default:
fmt.Printf("Error %s\n", err)
os.Exit(-1)
}
}
Now the second part in sshTunnel.go:
// mysqlSSHtunnel project sshTunnel.go
//
// Everything regarding the ssh tunnel goes here. Credits go to Svett Ralchev.
// Look at http://blog.ralch.com/tutorial/golang-ssh-tunneling for an excellent
// explanation and most ssh-tunneling related details used in this code.
//
// PEM key decryption is valid for password proected SSH-2 RSA Keys generated as
// .ppk files for putty and exported as OpenSSH .pem keyfile using PuTTYgen.
//
package main
import (
"bytes"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"golang.org/x/crypto/ssh"
"io"
"io/ioutil"
"net"
)
// Define an endpoint with ip and port
type Endpoint struct {
Host string
Port int
}
// Returns an endpoint as ip:port formatted string
func (endpoint *Endpoint) String() string {
return fmt.Sprintf("%s:%d", endpoint.Host, endpoint.Port)
}
// Define the endpoints along the tunnel
type SSHtunnel struct {
Local *Endpoint
Server *Endpoint
Remote *Endpoint
Config *ssh.ClientConfig
}
// Start the tunnel
func (tunnel *SSHtunnel) Start() error {
listener, err := net.Listen("tcp", tunnel.Local.String())
if err != nil {
return err
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
return err
}
go tunnel.forward(conn)
}
}
// Port forwarding
func (tunnel *SSHtunnel) forward(localConn net.Conn) {
// Establish connection to the intermediate server
serverConn, err := ssh.Dial("tcp", tunnel.Server.String(), tunnel.Config)
if err != nil {
fmt.Printf("Server dial error: %s\n", err)
return
}
// access the target server
remoteConn, err := serverConn.Dial("tcp", tunnel.Remote.String())
if err != nil {
fmt.Printf("Remote dial error: %s\n", err)
return
}
// Transfer the data between and the remote server
copyConn := func(writer, reader net.Conn) {
_, err := io.Copy(writer, reader)
if err != nil {
fmt.Printf("io.Copy error: %s", err)
}
}
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
// Decrypt encrypted PEM key data with a passphrase and embed it to key prefix
// and postfix header data to make it valid for further private key parsing.
func DecryptPEMkey(buffer []byte, passphrase string) []byte {
block, _ := pem.Decode(buffer)
der, err := x509.DecryptPEMBlock(block, []byte(passphrase))
if err != nil {
fmt.Println("decrypt failed: ", err)
}
encoded := base64.StdEncoding.EncodeToString(der)
encoded = "-----BEGIN RSA PRIVATE KEY-----\n" + encoded +
"\n-----END RSA PRIVATE KEY-----\n"
return []byte(encoded)
}
// Get the signers from the OpenSSH key file (.pem) and return them for use in
// the Authentication method. Decrypt encrypted key data with the passphrase.
func PublicKeyFile(file string, passphrase string) ssh.AuthMethod {
buffer, err := ioutil.ReadFile(file)
if err != nil {
return nil
}
if bytes.Contains(buffer, []byte("ENCRYPTED")) {
// Decrypt the key with the passphrase if it has been encrypted
buffer = DecryptPEMkey(buffer, passphrase)
}
// Get the signers from the key
signers, err := ssh.ParsePrivateKey(buffer)
if err != nil {
return nil
}
return ssh.PublicKeys(signers)
}
// Define the ssh tunnel using its endpoint and config data
func sshTunnel() *SSHtunnel {
localEndpoint := &Endpoint{
Host: sshLocalHost,
Port: sshLocalPort,
}
serverEndpoint := &Endpoint{
Host: sshServerHost,
Port: sshServerPort,
}
remoteEndpoint := &Endpoint{
Host: sshRemoteHost,
Port: sshRemotePort,
}
sshConfig := &ssh.ClientConfig{
User: sshUserName,
Auth: []ssh.AuthMethod{
PublicKeyFile(sshPrivateKeyFile, sshKeyPassphrase)},
}
return &SSHtunnel{
Config: sshConfig,
Local: localEndpoint,
Server: serverEndpoint,
Remote: remoteEndpoint,
}
}
Well, you could do that "full Go", I think.
The SSH part and port-forwarding
I'd start with something like this (I failed to google a better example).
Note two problems with this code:
It's not actually correct: it connects to a remote socket
before accepting a client connection
while it should do the reverse: accept a client connection to
a port-forwarded local socket then use the active SSH session to connect
to the remote socket and if this succeeds, spawn two goroutines to shovel
the data between those two sockets.
When configuring the SSH client, it explicitly allows password-based
authentication for unknown reason. You don't need this as you're using
pubkey-based auth.
An obstacle which might trip you there is managing an access to your SSH key.
The problem with it is that a good key should be protected by a passphrase.
You say the key's password is "stored in valut" and I honestly have no idea what "the valut" is.
On the systems I use, an SSH client either asks for the password to decrypt the key or work with a so-called "SSH agent":
On Linux-based systems it's typically an OpenSSH's ssh-agent binary working in the background which is accessed via a Unix-domain socket, and located by inspecting an environment variable named SSH_AUTH_SOCK.
On Windows I use PuTTY, and it has its own agent, pageant.exe.
I'm don't know which means PuTTY SSH client uses to locate it.
To access the OpenSSH agent, golang.org/x/crypto/ssh provides the agent subpackage which can be used to locate the agent and communicate with it.
If you need to obtain keys from pageant, I'm afraid you'll need to figure out what protocol that uses and implement it.
The MySQL part
The next step would be to integrate this with go-sql-driver.
I'd start the simplest way possible:
When you have your SSH port forwarding working,
make it listen for incoming connections on a random port on localhost.
When the connection is opened, get the port from the returned connection
object.
Use that port number to constuct the connection string to pass to an instance of sql.DB you will create to use go-sql-driver.
The driver will then connect to your port-forwarded port, and your SSH layer will do the rest.
After you have this working, I'd explore whether the driver of your choice
allows some more fine-grained tweaking like allowing you to directly pass it an instance of io.ReadWriter (an opened socket) so that you could skip the port-forwarding setup entirely and just produce new TCP connections forwarded through SSH, that is, skip the "listening locally" step.
I use Gorilla sessions (via negroni-sessions) to store my user sessions in cookies. I also use github.com/unrolled/render for my HTML template rendering:
main.go:
package main
import (
...
"github.com/codegangsta/negroni"
"github.com/goincremental/negroni-sessions"
"github.com/goincremental/negroni-sessions/cookiestore"
"github.com/julienschmidt/httprouter"
"github.com/unrolled/render"
...
)
func init() {
...
ren = render.New(render.Options{
Directory: "templates",
Layout: "layout",
Extensions: []string{".html"},
Funcs: []template.FuncMap{TemplateHelpers},
IsDevelopment: false,
})
...
}
func main() {
...
router := httprouter.New()
router.GET("/", HomeHandler)
// Add session store
store := cookiestore.New([]byte("my password"))
store.Options(sessions.Options{
//MaxAge: 1200,
Domain: "",
Path: "/",
})
n := negroni.New(
negroni.NewRecovery(),
sessions.Sessions("cssession", store),
negroni.NewStatic(http.Dir("../static")),
)
n.UseHandler(router)
n.Run(":9000")
}
As you can see above, I use a layout.html master HTML template which is included when any page renders, like my home page:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
ren.HTML(w, http.StatusOK, "home", model)
}
In my layout.html master HTML template, I want to render an admin menu but only if the current user is an admin:
layout.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{ template "title" . }}</title>
...
</head>
<body>
...
<!--Main Menu-->
<nav class="menu">
<ul class="catalog">
<li class="has-submenu">
{{ RenderMenuCategories }}
</li>
<li>Blog</li>
<li>Company</li>
{{ RenderAdminMenu }}
</ul>
</nav>
...
My issue is that the above template helper function RenderAdminMenu() doesn't have access to the HTTP Request object and therefore cannot access the User session object to determine if the user is admin.
I can pass the User object into the template context via the Home page handler, and use an if statement RenderAdminMenu() function, like this
{{ if .User.IsAdmin }}
{{ RenderAdminMenu }}
{{ end }}
...but since I am using a master template, I would have to do that from every web page on the site. Is there a more efficient way?
I was thinking perhaps there might be a way to access some kind of global Context object from within RenderAdminMenu() (or layout.html) which contains the Request details (like you can in ASP.NET)
There's a few things you need to do to tie this together. I'm not going to show a complete example as it would be both fairly lengthy and may not match your code (which you haven't posted). It will contain the basic building blocks though: if you get stuck, come back with a direct question and a code snippet and you'll get a more direct answer :)
Write some middleware or logic in a [login] handler that saves the user data in the session when a user logs in. A userID, email and admin boolean value would be sufficient. e.g.
// In your login handler, once you've retrieved the user &
// matched their password hash (scrypt, of course!) from the DB.
session.Values["user"] = &youruserobject
err := session.Save(r, w)
if err != nil {
// Throw a HTTP 500
}
Note: remember that you need to gob.Register(&youruserobject{}) as per the gorilla/sessions docs if you want to store your own types.
Write a helper to type-assert your type when you pull it out of the session, e.g.
var ErrInvalidUser= errors.New("invalid user stored in session")
func GetUser(session *sessions.Session) (*User, error) {
// You can make the map key a constant to avoid typos/errors
user, ok := session.Values["user"].(*User)
if !ok || user == nil {
return nil, ErrInvalidUser
}
return user, nil
}
// Use it like this in a handler that serves user content
session, err := store.Get("yoursessionname", r)
if err != nil {
// Throw a HTTP 500
}
user, err := GetUser(session)
if err != nil {
// Re-direct back to the login page or
// show a HTTP 403 Forbidden, etc.
}
Write something to check if the returned user is an admin:
func IsAdmin(user *User) bool {
if user.Admin == true && user.ID != "" && user.Email != "" {
return true
}
return false
}
Pass that to the template:
err := template.Execute(w, "sometemplate.html", map[string]interface{}{
"admin": IsAdmin(user),
"someotherdata": someStructWithData,
}
// In your template...
{{ if .admin }}{{ template "admin_menu" }}{{ end }}
Also make sure you're setting an authentication key for your session cookies (read the gorilla docs), preferably an encryption key, and that you're serving your site over HTTPS with the Secure: true flag set as well.
Keep in mind that the above method is also simplified: if you de-flag a user as admin in your DB, the application will continue to detect them as an administrator for as long as their session lasts. By default this can be 7 days, so if you're in a risky environment where admin churn is a real problem, it may pay to have really short sessions OR hit the DB inside the IsAdmin function just to be safe. If it's a personal blog and it's just you, not so much.
Added: If you want to pass the User object directly to the template, you can do that too. Note that it's more performant to do it in your handler/middleware than it is in the template logic. You also get the flexibility of more error handling, and the option of "bailing out" earlier - i.e. if the session contains nothing, you can fire up a HTTP 500 error rather than rendering half a template or having to put lots of logic in your template to handle nil data.
You still need to store your User object (or equivalent) in the session, and retrieve it from session.Values before you can pass it to the template.
func GetUser(r *http.Request) *User {
session, err := store.Get("yoursessionname", r)
if err != nil {
// Throw a HTTP 500
}
if user, ok := session.Values["user"].(*User); ok {
return user
}
return nil
}
// In the handler itself
err := template.Execute(w, "sometemplate.html", map[string]interface{}{
"user": GetUser(r),
"someotherdata": someStructWithData,
}
// In your template...
{{ if .User.admin }}{{ template "admin_menu" }}{{ end }}
It seems you cannot access the Request context from a template or a template helper function afterall (so I accepted the answer above). My solution was to create a Page struct that I pass as the context for every template. It contains the Content as a generic interface, the User object, as well as other useful parameters:
//Page holds the model to be rendered for every HTTP handler.
type Page struct {
MetaTitle string
User User
HeaderStyles string
HeaderScripts string
FooterScripts string
Content interface{}
}
func (pg *Page) Init(r *http.Request) {
if pg.MetaTitle == "" {
pg.MetaTitle = "This is the default <title> content for the page!"
}
user, _ := GetUserFromSession(r)
pg.User = *user
if user.IsAdmin() {
pg.HeaderStyles += `<link href="/css/libs/summernote/summernote.css" rel="stylesheet">`
pg.FooterScripts += `<script src="/js/libs/summernote/summernote.min.js"></script>`
}
}
The Init method allows me to set defaults and use the Page struct more easily, by specifying only the Content for the page, if that's all I need:
package main
import (
"html/template"
"github.com/julienschmidt/httprouter"
)
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var model = struct {
CatalogPicks []PromotionalModelList
ClearanceItems []Model
}{
CatalogPicks: GetCatalogPicks(),
ClearanceItems: GetClearanceItems(),
}
pg := &Page{Content: model}
pg.Init(r)
ren.HTML(w, http.StatusOK, "home", pg)
}
I have a perfectly working drive-integrated app, javascript and go-based, with the following scopes:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/drive
now I try to work with Application folder. If I don't change my scopes, then I get as expected, an error claiming that app scope is not set properly. Now I add the following scope (in api-console and at my app):
https://www.googleapis.com/auth/drive.appdata
now I unfortunately get an error at the oauth.updateToken googelapi function with following error message:
OAuthError: updateToken: 400 Bad Request
Have I missunderstood how Application folder is supposed to be used ?
You need to show user a dialog again and retrieve a new token instead of updating the existing. Create a new auth code URL by config.AuthCodeURL() and ask user for permissions. Once permissions are granted by the user, exchange the code with Google endpoints by calling t.Exchange(code).
var config = &oauth.Config{
ClientId: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
Scope: "YOUR_SCOPES",
RedirectURL: "YOUR_REDIRECT_URI",
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
authUrl := config.AuthCodeURL("state")
fmt.Printf("Go to the following link in your browser: %v\n", authUrl)
t := &oauth.Transport{
Config: config,
Transport: http.DefaultTransport,
}
// Read the code, and exchange it for a token.
fmt.Printf("Enter verification code: ")
var code string
fmt.Scanln(&code)
_, err := t.Exchange(code)
if err != nil {
fmt.Printf("An error occurred exchanging the code: %v\n", err)
}