Full Code of danilopolani/gocialite for AI

master e2e2f2eecba8 cached
19 files
31.2 KB
8.8k tokens
44 symbols
1 requests
Download .txt
Repository: danilopolani/gocialite
Branch: master
Commit: e2e2f2eecba8
Files: 19
Total size: 31.2 KB

Directory structure:
gitextract_p4top8w6/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── drivers/
│   ├── amazon.go
│   ├── asana.go
│   ├── bitbucket.go
│   ├── drivers.go
│   ├── facebook.go
│   ├── foursquare.go
│   ├── github.go
│   ├── google.go
│   ├── linkedin.go
│   └── slack.go
├── gocialite.go
├── gocialite_test.go
└── structs/
    └── user.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store

================================================
FILE: .travis.yml
================================================
language: go

go:
  - 1.9.x
  - 1.10.x
  - 1.11.x
  - 1.12.x
  - master


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.1] - 2019-03-28
### Fixed
- Fix email from Github when is private.

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing

Creating a new driver for Gocialite is a lot simple, thank also to @vibbix.  

## Create a file
I suggest you to duplicate `drivers/bitbucket.go` since it's a complete code and name it with your provider name, ex: *myprovider.go*.

## Set variables

Change [line 12](https://github.com/danilopolani/gocialite/blob/master/drivers/bitbucket.go#L12) with:

```go
const myProviderDriverName = "myprovider"
```

It will be used in the `Driver()` function, example: `gocial.Driver("myprovider")`.  
Now on [line 19](https://github.com/danilopolani/gocialite/blob/master/drivers/bitbucket.go#L19) you have to create the mapping from API to populate the User struct.  
The relation is `"json_field_name": "StructFieldName"`, so if in our JSON there's a field called "first_name", it will be `"first_name": "FirstName"`.  

If there's some nested/complex field, please see the next chapter **User callback hook**.

Finally, on [lines 26-30](https://github.com/danilopolani/gocialite/blob/master/drivers/bitbucket.go#L26-L30) you have to fill the fields for the endpoint baseurl and the path of the user endpoint.  
In the case of Bitbucket, the email address is retrievable only from another endpoint, so we put in it also `emailEndpoint`, but usually you will need only `userEndpoint`.

If your provider has the user endpoint located to `https://api.myprovider.com/me`, the struct will be this:

```go
var MyProviderAPIMap = map[string]string{
	"endpoint":      "https://api.myprovider.com",
	"userEndpoint":  "/me",
}
```

Of course remember to **rename all the variables**. The ones that start with a capital letter are exported, so remember to write the first letter capitalized.

## User callback hook

If you have some complex field or you need to call some other endpoint in order to retrieve a field, you can do that in this section.  
In the case of Bitbucket, we use this hook to populate two fields: *avatar* from a nested array/map and *email* from another endpoint.  

The `client` variable is an `oAuth` client so it's already set up for oAuth details like `access_token`.

## Testing
Use the [example page](https://github.com/danilopolani/gocialite/wiki/Example) as starting point. Set up the credentials of your app in the `providerSecrets` variable, like:

```go
providerSecrets := map[string]map[string]string{
		...
		"myprovider": {
			"clientID":     "xxxxxxxxxxxxxx",
			"clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
			"redirectURL":  "http://localhost:9090/auth/myprovider/callback",
		},
	}
```

And add the scopes for your app (or an empty slice) in the `providerScopes` variable:

```go
providerScopes := map[string][]string{
		...
		"myprovider": []string{}, // Or []string{"my_scope", "my_other_scope"}
	}
```

Now run `go run example.go` (or the name of your file), navigate to http://localhost:9090/auth/myprovider (or the name of your provider) and you will be redirected to the oAuth login.  
If everything works correctly, when you will be redirected to http://localhost:9090/auth/myprovider/callback, in your terminal you will see the content of the `User` struct populated (line `fmt.Printf("%#v", gocial.User)`).

## PR

Now that everything works, you can open a Pull Request, it will be tested and if it works, it will be merged and added to the README.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Danilo Polani

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Gocialite
![Travis CI build](https://api.travis-ci.org/danilopolani/gocialite.svg?branch=master)
![Available Drivers](https://img.shields.io/badge/Drivers-5+-orange.svg)
[![GoDoc](https://godoc.org/github.com/danilopolani/gocialite?status.svg)](https://godoc.org/github.com/danilopolani/gocialite)
[![GoReport](https://goreportcard.com/badge/github.com/danilopolani/gocialite)](https://goreportcard.com/report/github.com/danilopolani/gocialite)
![GitHub contributors](https://img.shields.io/github/contributors/danilopolani/gocialite.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

## NOT MAINTAINED
This project is no longer maintained, you should over a more robust solution like [Goth](https://github.com/markbates/goth).  

----

Gocialite is a Socialite inspired package to manage social oAuth authentication without problems.
The idea was born when I discovered that Goth is not so flexible: I was using Revel and it was *impossible* to connect them properly.

## Installation

To install it, just run `go get gopkg.in/danilopolani/gocialite.v1` and include it in your app: `import "gopkg.in/danilopolani/gocialite.v1"`.

## Available drivers

- Amazon
- Asana
- Bitbucket
- Facebook
- Foursquare
- Github
- Google
- LinkedIn
- Slack

## Create new driver

Please see [Contributing page](https://github.com/danilopolani/gocialite/blob/master/CONTRIBUTING.md) to learn how to create new driver and test it.

## Set scopes

**Note**: Gocialite set some default scopes for the user profile, for example for *Facebook* it specify `email` and for *Google* `profile, email`.  
When you use the following method, you don't have to rewrite them. 

Use the `Scopes([]string)` method of your `Gocial` instance. Example:

```go
gocial.Scopes([]string{"public_repo"})
```

## Set driver

Use the `Driver(string)` method of your `Gocial` instance. Example:

```go
gocial.Driver("facebook")
```

The driver name will be the provider name in lowercase.

## How to use it

**Note**: All Gocialite methods are chainable.

Declare a "global" variable outside your `main` func:

```go
import (
	...
)

var gocial = gocialite.NewDispatcher()

func main() {
```

Then create a route to use as redirect bridge, for example `/auth/github`. With this route, the user will be redirected to the provider oAuth login. In this case we use Gin Tonic as router. You have to specify the provider with the `Driver()` method.
Then, with `Scopes()`, you can set a list of scopes as slice of strings. It's optional.  
Finally, with `Redirect()` you can obtain the redirect URL. In this method you have to pass three parameters:

1. Client ID
1. Client Secret
1. Redirect URL

```go
func main() {
	router := gin.Default()

	router.GET("/auth/github", redirectHandler)

	router.Run("127.0.0.1:9090")
}

// Redirect to correct oAuth URL
func redirectHandler(c *gin.Context) {
	authURL, err := gocial.New().
		Driver("github"). // Set provider
		Scopes([]string{"public_repo"}). // Set optional scope(s)
		Redirect( // 
			"xxxxxxxxxxxxxx", // Client ID
			"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // Client Secret
			"http://localhost:9090/auth/github/callback", // Redirect URL
		)

	// Check for errors (usually driver not valid)
	if err != nil {
		c.Writer.Write([]byte("Error: " + err.Error()))
		return
	}

	// Redirect with authURL
	c.Redirect(http.StatusFound, authURL) // Redirect with 302 HTTP code
}
```

Now create a callback handler route, where we'll receive the content from the provider.  
In order to validate the oAuth and retrieve the data, you have to invoke the `Handle()` method with two query parameters: `state` and `code`. In your URL, they will look like this: `http://localhost:9090/auth/github/callback?state=xxxxxxxx&code=xxxxxxxx`.  
The `Handle()` method returns the user info, the token and error if there's one or `nil`.  
If there are no errors, in the `user` variable you will find the logged in user information and in the `token` one, the token info (it's a [oauth2.Token struct](https://godoc.org/golang.org/x/oauth2#Token)). The data of the user - which is a [gocialite.User struct](https://github.com/danilopolani/gocialite/blob/master/structs/user.go) - are the following:

- ID
- FirstName
- LastName
- FullName
- Email
- Avatar (URL)
- Raw (the full JSON returned by the provider)

Note that they can be empty.

```go
func main() {
	router := gin.Default()

	router.GET("/auth/github", redirectHandler)
	router.GET("/auth/github/callback", callbackHandler)

	router.Run("127.0.0.1:9090")
}

// Redirect to correct oAuth URL
// Handle callback of provider
func callbackHandler(c *gin.Context) {
	// Retrieve query params for code and state
	code := c.Query("code")
	state := c.Query("state")

	// Handle callback and check for errors
	user, token, err := gocial.Handle(state, code)
	if err != nil {
		c.Writer.Write([]byte("Error: " + err.Error()))
		return
	}

	// Print in terminal user information
	fmt.Printf("%#v", token)
	fmt.Printf("%#v", user)

	// If no errors, show provider name
	c.Writer.Write([]byte("Hi, " + user.FullName))
}
```

Please take a look to [multi provider example](https://github.com/danilopolani/gocialite/wiki/Multi-provider-example) for a full working code with Gin Tonic and variable provider handler.

## Contributors

- [Danilo Polani](https://github.com/danilopolani)
- [Joseph Buchma](https://github.com/josephbuchma)
- [Mark Beznos](https://github.com/vibbix)
- [Davor Kapsa](https://github.com/dvrkps)


================================================
FILE: drivers/amazon.go
================================================
package drivers

import (
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/amazon"
)

const amazonDriverName = "amazon"

func init() {
	registerDriver(amazonDriverName, AmazonDefaultScopes, AmazonUserFn, amazon.Endpoint, AmazonAPIMap, AmazonUserMap)
}

// AmazonUserMap is the map to create the User struct
var AmazonUserMap = map[string]string{
	"user_id": "ID",
	"name":    "FullName",
	"email":   "Email",
}

// AmazonAPIMap is the map for API endpoints
var AmazonAPIMap = map[string]string{
	"endpoint":     "https://api.amazon.com",
	"userEndpoint": "/user/profile",
}

// AmazonUserFn is a callback to parse additional fields for User
var AmazonUserFn = func(client *http.Client, u *structs.User) {}

// AmazonDefaultScopes contains the default scopes
var AmazonDefaultScopes = []string{"profile"}


================================================
FILE: drivers/asana.go
================================================
package drivers

import (
	"net/http"
  "fmt"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2"
)

const asanaDriverName = "asana"

func init() {
	registerDriver(asanaDriverName, AsanaDefaultScopes, AsanaUserFn, AsanaEndpoint, AsanaAPIMap, AsanaUserMap)
}

// DailyMotionEndpoint is the oAuth endpoint
var AsanaEndpoint = oauth2.Endpoint{
  AuthURL:  "https://app.asana.com/-/oauth_authorize",
  TokenURL: "https://app.asana.com/-/oauth_token",
}

// AsanaUserMap is the map to create the User struct
var AsanaUserMap = map[string]string{}

// AsanaAPIMap is the map for API endpoints
var AsanaAPIMap = map[string]string{
	"endpoint":      "https://app.asana.com/api/1.0",
	"userEndpoint":  "/users/me?opt_fields=id,name,email,photo",
}

// AsanaUserFn is a callback to parse additional fields for User
var AsanaUserFn = func(client *http.Client, u *structs.User) {
  userData := u.Raw["data"].(map[string]interface{})
  u.ID = fmt.Sprintf("%.0f", userData["id"].(float64))
  u.Email = userData["email"].(string)
  u.FullName = userData["name"].(string)

	// Set avatar
  if (userData["photo"] != nil) { 
	 u.Avatar = userData["photo"].(map[string]interface{})["image_1024x1024"].(string)
  }
}

// AsanaDefaultScopes contains the default scopes
var AsanaDefaultScopes = []string{}


================================================
FILE: drivers/bitbucket.go
================================================
package drivers

import (
	"io/ioutil"
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/bitbucket"
)

const bitbucketDriverName = "bitbucket"

func init() {
	registerDriver(bitbucketDriverName, BitbucketDefaultScopes, BitbucketUserFn, bitbucket.Endpoint, BitbucketAPIMap, BitbucketUserMap)
}

// BitbucketUserMap is the map to create the User struct
var BitbucketUserMap = map[string]string{
	"account_id":   "ID",
	"username":     "Username",
	"display_name": "FullName",
}

// BitbucketAPIMap is the map for API endpoints
var BitbucketAPIMap = map[string]string{
	"endpoint":      "https://api.bitbucket.org",
	"userEndpoint":  "/2.0/user",
	"emailEndpoint": "/2.0/user/emails",
}

// BitbucketUserFn is a callback to parse additional fields for User
var BitbucketUserFn = func(client *http.Client, u *structs.User) {
	// Set avatar
	u.Avatar = u.Raw["links"].(map[string]interface{})["avatar"].(map[string]interface{})["href"].(string)

	// Retrieve email
	req, err := client.Get(BitbucketAPIMap["endpoint"] + BitbucketAPIMap["emailEndpoint"])
	if err != nil {
		return
	}

	defer req.Body.Close()
	res, _ := ioutil.ReadAll(req.Body)
	data, err := jsonDecode(res)
	if err != nil {
		return
	}

	u.Email = data["values"].([]interface{})[0].(map[string]interface{})["email"].(string)
}

// BitbucketDefaultScopes contains the default scopes
var BitbucketDefaultScopes = []string{"account", "email"}


================================================
FILE: drivers/drivers.go
================================================
package drivers

import (
	"encoding/json"
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2"
)

var (
	initAPIMap           = map[string]map[string]string{}
	initUserMap          = map[string]map[string]string{}
	initEndpointMap      = map[string]oauth2.Endpoint{}
	initCallbackMap      = map[string]func(client *http.Client, u *structs.User){}
	initDefaultScopesMap = map[string][]string{}
)

func registerDriver(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string) {
	initAPIMap[driver] = apimap
	initUserMap[driver] = usermap
	initEndpointMap[driver] = endpoint
	initCallbackMap[driver] = callback
	initDefaultScopesMap[driver] = defaultscopes
}

// InitializeDrivers adds all the drivers to the register func
func InitializeDrivers(register func(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string)) {
	for k := range initAPIMap {
		register(k, initDefaultScopesMap[k], initCallbackMap[k], initEndpointMap[k], initAPIMap[k], initUserMap[k])
	}
}

// Decode a json or return an error
func jsonDecode(js []byte) (map[string]interface{}, error) {
	var decoded map[string]interface{}
	if err := json.Unmarshal(js, &decoded); err != nil {
		return nil, err
	}

	return decoded, nil
}


================================================
FILE: drivers/facebook.go
================================================
package drivers

import (
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/facebook"
)

const facebookDriverName = "facebook"

func init() {
	registerDriver(facebookDriverName, FacebookDefaultScopes, FacebookUserFn, facebook.Endpoint, FacebookAPIMap, FacebookUserMap)
}

// FacebookUserMap is the map to create the User struct
var FacebookUserMap = map[string]string{
	"id":         "ID",
	"email":      "Email",
	"name":       "FullName",
	"first_name": "FirstName",
	"last_name":  "LastName",
}

// FacebookAPIMap is the map for API endpoints
var FacebookAPIMap = map[string]string{
	"endpoint":     "https://graph.facebook.com",
	"userEndpoint": "/me?fields=id,name,first_name,last_name,email",
}

// FacebookUserFn is a callback to parse additional fields for User
var FacebookUserFn = func(client *http.Client, u *structs.User) {
	u.Avatar = FacebookAPIMap["endpoint"] + "/v2.8/" + u.ID + "/picture?width=800"
}

// FacebookDefaultScopes contains the default scopes
var FacebookDefaultScopes = []string{"email"}


================================================
FILE: drivers/foursquare.go
================================================
package drivers

import (
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/foursquare"
)

const foursquareDriverName = "foursquare"

func init() {
	registerDriver(foursquareDriverName, FoursquareDefaultScopes, FoursquareUserFn, foursquare.Endpoint, FoursquareAPIMap, FoursquareUserMap)
}

// FoursquareUserMap is the map to create the User struct
var FoursquareUserMap = map[string]string{}

// FoursquareAPIMap is the map for API endpoints
var FoursquareAPIMap = map[string]string{
	"endpoint":     "https://api.foursquare.com",
	"userEndpoint": "/v2/users/self?oauth_token=%ACCESS_TOKEN&v=20171220",
}

// FoursquareUserFn is a callback to parse additional fields for User
var FoursquareUserFn = func(client *http.Client, u *structs.User) {
	user := u.Raw["response"].(map[string]interface{})["user"].(map[string]interface{})

	u.ID = user["id"].(string)
	u.FirstName = user["firstName"].(string)
	u.LastName = user["lastName"].(string)
	u.FullName = u.FirstName + " " + u.LastName

	if email, ok := user["contact"].(map[string]interface{})["email"]; ok {
		u.Email = email.(string)
	}
	if avatarPrefix, ok := user["photo"].(map[string]interface{})["prefix"]; ok {
		if avatarSuffix, ok2 := user["photo"].(map[string]interface{})["suffix"]; ok2 {
			u.Avatar = avatarPrefix.(string) + "original" + avatarSuffix.(string)
		}
	}
}

// FoursquareDefaultScopes contains the default scopes
var FoursquareDefaultScopes = []string{}


================================================
FILE: drivers/github.go
================================================
package drivers

import (
	"encoding/json"
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/github"
)

const githubDriverName = "github"

func init() {
	registerDriver(githubDriverName, GithubDefaultScopes, GithubUserFn, github.Endpoint, GithubAPIMap, GithubUserMap)
}

// GithubUserMap is the map to create the User struct
var GithubUserMap = map[string]string{
	"id":         "ID",
	"email":      "Email",
	"login":      "Username",
	"avatar_url": "Avatar",
	"name":       "FullName",
}

// GithubAPIMap is the map for API endpoints
var GithubAPIMap = map[string]string{
	"endpoint":      "https://api.github.com",
	"userEndpoint":  "/user",
	"emailEndpoint": "/user/emails",
}

// GithubUserFn is a callback to parse additional fields for User
var GithubUserFn = func(client *http.Client, u *structs.User) {
	// Used to parse the email from response
	type additionalEmail struct {
		Email string `json:"email"`
	}
	var email []additionalEmail

	// Email can be nil because of the "keep my email private" setting
	if u.Email == "<nil>" {
		// Retrieve email
		req, err := client.Get(GithubAPIMap["endpoint"] + GithubAPIMap["emailEndpoint"])
		if err != nil {
			return
		}

		defer req.Body.Close()
		err = json.NewDecoder(req.Body).Decode(&email)
		if err != nil {
			return
		}

		u.Email = email[0].Email
	}
}

// GithubDefaultScopes contains the default scopes
var GithubDefaultScopes = []string{"user:email"}


================================================
FILE: drivers/google.go
================================================
package drivers

import (
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/google"
)

const googleDriverName = "google"

func init() {
	registerDriver(googleDriverName, GoogleDefaultScopes, GoogleUserFn, google.Endpoint, GoogleAPIMap, GoogleUserMap)
}

// GoogleUserMap is the map to create the User struct
var GoogleUserMap = map[string]string{
	"id":          "ID",
	"email":       "Email",
	"name":        "FullName",
	"given_name":  "FirstName",
	"family_name": "LastName",
	"picture":     "Avatar",
}

// GoogleAPIMap is the map for API endpoints
var GoogleAPIMap = map[string]string{
	"endpoint":     "https://www.googleapis.com",
	"userEndpoint": "/oauth2/v2/userinfo",
}

// GoogleUserFn is a callback to parse additional fields for User
var GoogleUserFn = func(client *http.Client, u *structs.User) {}

// GoogleDefaultScopes contains the default scopes
var GoogleDefaultScopes = []string{"profile", "email"}


================================================
FILE: drivers/linkedin.go
================================================
package drivers

import (
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/linkedin"
)

const (
	linkedinDriverName = "linkedin"
)

func init() {
	registerDriver(linkedinDriverName, LinkedInDefaultScopes, LinkedInUserFn, linkedin.Endpoint, LinkedInAPIMap, LinkedInUserMap)
}

// LinkedInUserMap is the map to create the User struct
var LinkedInUserMap = map[string]string{
	"id":            "ID",
	"vanityName":    "Username",
	"firstName":     "FirstName",
	"lastName":      "LastName",
	"formattedName": "FullName",
	"emailAddress":  "Email",
	"pictureUrl":    "Avatar",
}

// LinkedInAPIMap is the map for API endpoints
var LinkedInAPIMap = map[string]string{
	"endpoint":     "https://api.linkedin.com",
	"userEndpoint": "/v1/people/~:(id,first-name,last-name,formatted-name,email-address,picture-url,maiden-name,headline,location,industry,current-share,num-connections,summary,specialties,positions,public-profile-url)?format=json",
}

// LinkedInUserFn is a callback to parse additional fields for User
var LinkedInUserFn = func(client *http.Client, u *structs.User) {}

// LinkedInDefaultScopes contains the default scopes
var LinkedInDefaultScopes = []string{}


================================================
FILE: drivers/slack.go
================================================
package drivers

import (
	"io/ioutil"
	"net/http"

	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2/slack"
)

const slackDriverName = "slack"

func init() {
	registerDriver(slackDriverName, SlackDefaultScopes, SlackUserFn, slack.Endpoint, SlackAPIMap, SlackUserMap)
}

// SlackUserMap is the map to create the User struct
var SlackUserMap = map[string]string{
	"real_name":      "FullName",
	"first_name":     "FirstName",
	"last_name":      "LastName",
	"email":          "Email",
	"image_original": "Avatar",
}

// SlackAPIMap is the map for API endpoints
var SlackAPIMap = map[string]string{
	"endpoint":     "https://slack.com/api",
	"userEndpoint": "/users.profile.get",
	"authEndpoint": "/auth.test",
}

// SlackUserFn is a callback to parse additional fields for User
var SlackUserFn = func(client *http.Client, u *structs.User) {
	// Get user ID
	req, err := client.Get(SlackAPIMap["endpoint"] + SlackAPIMap["authEndpoint"])
	if err != nil {
		return
	}

	defer req.Body.Close()
	res, _ := ioutil.ReadAll(req.Body)
	data, err := jsonDecode(res)
	if err != nil {
		return
	}

	u.ID = data["user_id"].(string)

	// Fetch other user information
	userInfo := u.Raw["profile"].(map[string]interface{})
	u.Username = userInfo["display_name"].(string)
	u.FullName = userInfo["real_name"].(string)
	u.FirstName = userInfo["first_name"].(string)
	u.LastName = userInfo["last_name"].(string)
	u.Email = userInfo["email"].(string)
	u.Avatar = userInfo["image_original"].(string)
}

// SlackDefaultScopes contains the default scopes
var SlackDefaultScopes = []string{"users.profile:read"}


================================================
FILE: gocialite.go
================================================
package gocialite

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"

	"github.com/danilopolani/gocialite/drivers"
	"github.com/danilopolani/gocialite/structs"
	"golang.org/x/oauth2"
	"gopkg.in/oleiade/reflections.v1"
)

// Dispatcher allows to safely issue concurrent Gocials
type Dispatcher struct {
	mu sync.RWMutex
	g  map[string]*Gocial
}

// NewDispatcher creates new Dispatcher
func NewDispatcher() *Dispatcher {
	return &Dispatcher{g: make(map[string]*Gocial)}
}

// New Gocial instance
func (d *Dispatcher) New() *Gocial {
	d.mu.Lock()
	defer d.mu.Unlock()
	state := randToken()
	g := &Gocial{state: state}
	d.g[state] = g
	return g
}

// Handle callback. Can be called only once for given state.
func (d *Dispatcher) Handle(state, code string) (*structs.User, *oauth2.Token, error) {
	d.mu.RLock()
	g, ok := d.g[state]
	d.mu.RUnlock()
	if !ok {
		return nil, nil, fmt.Errorf("invalid CSRF token: %s", state)
	}
	err := g.Handle(state, code)
	d.mu.Lock()
	delete(d.g, state)
	d.mu.Unlock()
	return &g.User, g.Token, err
}

// Gocial is the main struct of the package
type Gocial struct {
	driver, state string
	scopes        []string
	conf          *oauth2.Config
	User          structs.User
	Token         *oauth2.Token
}

func init() {
	drivers.InitializeDrivers(RegisterNewDriver)
}

var (
	// Set the basic information such as the endpoint and the scopes URIs
	apiMap = map[string]map[string]string{}

	// Mapping to create a valid "user" struct from providers
	userMap = map[string]map[string]string{}

	// Map correct endpoints
	endpointMap = map[string]oauth2.Endpoint{}

	// Map custom callbacks
	callbackMap = map[string]func(client *http.Client, u *structs.User){}

	// Default scopes for each driver
	defaultScopesMap = map[string][]string{}
)

//RegisterNewDriver adds a new driver to the existing set
func RegisterNewDriver(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string) {
	apiMap[driver] = apimap
	userMap[driver] = usermap
	endpointMap[driver] = endpoint
	callbackMap[driver] = callback
	defaultScopesMap[driver] = defaultscopes
}

// Driver is needed to choose the correct social
func (g *Gocial) Driver(driver string) *Gocial {
	g.driver = driver
	g.scopes = defaultScopesMap[driver]

	// BUG: sequential usage of single Gocial instance will have same CSRF token. This is serious security issue.
	// NOTE: Dispatcher eliminates this bug.
	if g.state == "" {
		g.state = randToken()
	}

	return g
}

// Scopes is used to set the oAuth scopes, for example "user", "calendar"
func (g *Gocial) Scopes(scopes []string) *Gocial {
	g.scopes = append(g.scopes, scopes...)
	return g
}

// Redirect returns an URL for the selected social oAuth login
func (g *Gocial) Redirect(clientID, clientSecret, redirectURL string) (string, error) {
	// Check if driver is valid
	if !inSlice(g.driver, complexKeys(apiMap)) {
		return "", fmt.Errorf("Driver not valid: %s", g.driver)
	}

	// Check if valid redirectURL
	_, err := url.ParseRequestURI(redirectURL)
	if err != nil {
		return "", fmt.Errorf("Redirect URL <%s> not valid: %s", redirectURL, err.Error())
	}
	if !strings.HasPrefix(redirectURL, "http://") && !strings.HasPrefix(redirectURL, "https://") {
		return "", fmt.Errorf("Redirect URL <%s> not valid: protocol not valid", redirectURL)
	}

	g.conf = &oauth2.Config{
		ClientID:     clientID,
		ClientSecret: clientSecret,
		RedirectURL:  redirectURL,
		Scopes:       g.scopes,
		Endpoint:     endpointMap[g.driver],
	}

	return g.conf.AuthCodeURL(g.state), nil
}

// Handle callback from provider
func (g *Gocial) Handle(state, code string) error {
	// Handle the exchange code to initiate a transport.
	if g.state != state {
		return fmt.Errorf("Invalid state: %s", state)
	}

	// Check if driver is valid
	if !inSlice(g.driver, complexKeys(apiMap)) {
		return fmt.Errorf("Driver not valid: %s", g.driver)
	}

	token, err := g.conf.Exchange(oauth2.NoContext, code)
	if err != nil {
		return fmt.Errorf("oAuth exchanged failed: %s", err.Error())
	}

	client := g.conf.Client(oauth2.NoContext, token)

	// Set gocial token
	g.Token = token

	// Retrieve all from scopes
	driverAPIMap := apiMap[g.driver]
	driverUserMap := userMap[g.driver]
	userEndpoint := strings.Replace(driverAPIMap["userEndpoint"], "%ACCESS_TOKEN", token.AccessToken, -1)

	// Get user info
	req, err := client.Get(driverAPIMap["endpoint"] + userEndpoint)
	if err != nil {
		return err
	}

	defer req.Body.Close()
	res, _ := ioutil.ReadAll(req.Body)
	data, err := jsonDecode(res)
	if err != nil {
		return fmt.Errorf("Error decoding JSON: %s", err.Error())
	}

	// Scan all fields and dispatch through the mapping
	mapKeys := keys(driverUserMap)
	gUser := structs.User{}
	for k, f := range data {
		if !inSlice(k, mapKeys) { // Skip if not in the mapping
			continue
		}

		// Assign the value
		// Dirty way, but we need to convert also int/float to string
		_ = reflections.SetField(&gUser, driverUserMap[k], fmt.Sprint(f))
	}

	// Set the "raw" user interface
	gUser.Raw = data

	// Custom callback
	callbackMap[g.driver](client, &gUser)

	// Update the struct
	g.User = gUser

	return nil
}

// Generate a random token
func randToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return base64.StdEncoding.EncodeToString(b)
}

// Check if a value is in a string slice
func inSlice(v string, s []string) bool {
	for _, scope := range s {
		if scope == v {
			return true
		}
	}

	return false
}

// Decode a json or return an error
func jsonDecode(js []byte) (map[string]interface{}, error) {
	var decoded map[string]interface{}
	decoder := json.NewDecoder(strings.NewReader(string(js)))
	decoder.UseNumber()

	if err := decoder.Decode(&decoded); err != nil {
		return nil, err
	}
	
	return decoded, nil
}

// Return the keys of a map
func keys(m map[string]string) []string {
	var keys []string
	for k := range m {
		keys = append(keys, k)
	}

	return keys
}

func complexKeys(m map[string]map[string]string) []string {
	var keys []string
	for k := range m {
		keys = append(keys, k)
	}

	return keys
}


================================================
FILE: gocialite_test.go
================================================
package gocialite

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

var gocialTest Gocial

func TestScopes(t *testing.T) {
	gocialTest.Scopes([]string{"email"})
	assert.Equal(t, gocialTest.scopes, []string{"email"})
	assert.NotEqual(t, gocialTest.scopes, []string{})

	gocialTest.
		Driver("google").
		Scopes([]string{"calendar.readonly"})
	assert.Equal(t, gocialTest.scopes, []string{"profile", "email", "calendar.readonly"})
	assert.NotEqual(t, gocialTest.scopes, []string{"profile", "email"})
	assert.NotEqual(t, gocialTest.scopes, []string{})
}
func TestConf(t *testing.T) {
	assert := assert.New(t)

	gocialTest.
		Driver("github").
		Redirect(
			"foo",
			"bar",
			"http://example.com/auth/callback",
		)

	assert.Equal(gocialTest.conf.ClientID, "foo")
	assert.NotEqual(gocialTest.conf.ClientID, "")
	assert.NotNil(gocialTest.conf.ClientID)

	assert.Equal(gocialTest.conf.ClientSecret, "bar")
	assert.NotEqual(gocialTest.conf.ClientSecret, "")
	assert.NotNil(gocialTest.conf.ClientSecret)

	assert.Equal(gocialTest.conf.RedirectURL, "http://example.com/auth/callback")
	assert.NotEqual(gocialTest.conf.RedirectURL, "")
	assert.NotNil(gocialTest.conf.RedirectURL)
}
func TestDriver(t *testing.T) {
	var err error

	_, err = gocialTest.Driver("unknown").
		Redirect(
			"xxxxxxxx",
			"xxxxxxxxxxxxxxxxxxxxxxxx",
			"http://example.com/auth/callback",
		)
	assert.NotNil(t, err)

	_, err = gocialTest.Driver("github").
		Redirect(
			"xxxxxxxx",
			"xxxxxxxxxxxxxxxxxxxxxxxx",
			"http://example.com/auth/callback",
		)
	assert.Nil(t, err)
}
func TestRedirectURL(t *testing.T) {
	var err error

	_, err = gocialTest.Driver("github").
		Redirect(
			"xxxxxxxx",
			"xxxxxxxxxxxxxxxxxxxxxxxx",
			"/auth/callback",
		)
	assert.NotNil(t, err)

	_, err = gocialTest.Driver("github").
		Redirect(
			"xxxxxxxx",
			"xxxxxxxxxxxxxxxxxxxxxxxx",
			"http://example.com/auth/callback",
		)
	assert.Nil(t, err)
}

func TestState(t *testing.T) {
	var err error

	err = gocialTest.Driver("github").
		Handle("fakeState", "foo")
	assert.NotNil(t, err)
}

func TestExchange(t *testing.T) {
	var err error

	// Generate a state
	gocialTest.
		Driver("github").
		Redirect(
			"xxxxxxxx",
			"xxxxxxxxxxxxxxxxxxxxxxxx",
			"http://example.com/auth/callback",
		)

	err = gocialTest.Handle(gocialTest.state, "foo")
	assert.NotNil(t, err)
}


================================================
FILE: structs/user.go
================================================
package structs

// User struct
type User struct {
	ID        string                 `json:"id"`
	Username  string                 `json:"username"`
	FirstName string                 `json:"first_name"`
	LastName  string                 `json:"last_name"`
	FullName  string                 `json:"full_name"`
	Email     string                 `json:"email"`
	Avatar    string                 `json:"avatar"`
	Raw       map[string]interface{} `json:"raw"` // Raw data
}
Download .txt
gitextract_p4top8w6/

├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── drivers/
│   ├── amazon.go
│   ├── asana.go
│   ├── bitbucket.go
│   ├── drivers.go
│   ├── facebook.go
│   ├── foursquare.go
│   ├── github.go
│   ├── google.go
│   ├── linkedin.go
│   └── slack.go
├── gocialite.go
├── gocialite_test.go
└── structs/
    └── user.go
Download .txt
SYMBOL INDEX (44 symbols across 13 files)

FILE: drivers/amazon.go
  constant amazonDriverName (line 10) | amazonDriverName = "amazon"
  function init (line 12) | func init() {

FILE: drivers/asana.go
  constant asanaDriverName (line 11) | asanaDriverName = "asana"
  function init (line 13) | func init() {

FILE: drivers/bitbucket.go
  constant bitbucketDriverName (line 11) | bitbucketDriverName = "bitbucket"
  function init (line 13) | func init() {

FILE: drivers/drivers.go
  function registerDriver (line 19) | func registerDriver(driver string, defaultscopes []string, callback func...
  function InitializeDrivers (line 28) | func InitializeDrivers(register func(driver string, defaultscopes []stri...
  function jsonDecode (line 35) | func jsonDecode(js []byte) (map[string]interface{}, error) {

FILE: drivers/facebook.go
  constant facebookDriverName (line 10) | facebookDriverName = "facebook"
  function init (line 12) | func init() {

FILE: drivers/foursquare.go
  constant foursquareDriverName (line 10) | foursquareDriverName = "foursquare"
  function init (line 12) | func init() {

FILE: drivers/github.go
  constant githubDriverName (line 11) | githubDriverName = "github"
  function init (line 13) | func init() {

FILE: drivers/google.go
  constant googleDriverName (line 10) | googleDriverName = "google"
  function init (line 12) | func init() {

FILE: drivers/linkedin.go
  constant linkedinDriverName (line 11) | linkedinDriverName = "linkedin"
  function init (line 14) | func init() {

FILE: drivers/slack.go
  constant slackDriverName (line 11) | slackDriverName = "slack"
  function init (line 13) | func init() {

FILE: gocialite.go
  type Dispatcher (line 21) | type Dispatcher struct
    method New (line 32) | func (d *Dispatcher) New() *Gocial {
    method Handle (line 42) | func (d *Dispatcher) Handle(state, code string) (*structs.User, *oauth...
  function NewDispatcher (line 27) | func NewDispatcher() *Dispatcher {
  type Gocial (line 57) | type Gocial struct
    method Driver (line 96) | func (g *Gocial) Driver(driver string) *Gocial {
    method Scopes (line 110) | func (g *Gocial) Scopes(scopes []string) *Gocial {
    method Redirect (line 116) | func (g *Gocial) Redirect(clientID, clientSecret, redirectURL string) ...
    method Handle (line 143) | func (g *Gocial) Handle(state, code string) error {
  function init (line 65) | func init() {
  function RegisterNewDriver (line 87) | func RegisterNewDriver(driver string, defaultscopes []string, callback f...
  function randToken (line 208) | func randToken() string {
  function inSlice (line 215) | func inSlice(v string, s []string) bool {
  function jsonDecode (line 226) | func jsonDecode(js []byte) (map[string]interface{}, error) {
  function keys (line 239) | func keys(m map[string]string) []string {
  function complexKeys (line 248) | func complexKeys(m map[string]map[string]string) []string {

FILE: gocialite_test.go
  function TestScopes (line 11) | func TestScopes(t *testing.T) {
  function TestConf (line 23) | func TestConf(t *testing.T) {
  function TestDriver (line 46) | func TestDriver(t *testing.T) {
  function TestRedirectURL (line 65) | func TestRedirectURL(t *testing.T) {
  function TestState (line 85) | func TestState(t *testing.T) {
  function TestExchange (line 93) | func TestExchange(t *testing.T) {

FILE: structs/user.go
  type User (line 4) | type User struct
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
  {
    "path": ".gitignore",
    "chars": 9,
    "preview": ".DS_Store"
  },
  {
    "path": ".travis.yml",
    "chars": 72,
    "preview": "language: go\n\ngo:\n  - 1.9.x\n  - 1.10.x\n  - 1.11.x\n  - 1.12.x\n  - master\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 326,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3315,
    "preview": "# Contributing\n\nCreating a new driver for Gocialite is a lot simple, thank also to @vibbix.  \n\n## Create a file\nI sugges"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2018 Danilo Polani\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 5519,
    "preview": "# Gocialite\n![Travis CI build](https://api.travis-ci.org/danilopolani/gocialite.svg?branch=master)\n![Available Drivers]("
  },
  {
    "path": "drivers/amazon.go",
    "chars": 835,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/amazon\"\n)\n\ncon"
  },
  {
    "path": "drivers/asana.go",
    "chars": 1301,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n  \"fmt\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2\"\n)\n\nco"
  },
  {
    "path": "drivers/bitbucket.go",
    "chars": 1431,
    "preview": "package drivers\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/b"
  },
  {
    "path": "drivers/drivers.go",
    "chars": 1414,
    "preview": "package drivers\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oaut"
  },
  {
    "path": "drivers/facebook.go",
    "chars": 1048,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/facebook\"\n)\n\nc"
  },
  {
    "path": "drivers/foursquare.go",
    "chars": 1459,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/foursquare\"\n)\n"
  },
  {
    "path": "drivers/github.go",
    "chars": 1447,
    "preview": "package drivers\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oaut"
  },
  {
    "path": "drivers/google.go",
    "chars": 949,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/google\"\n)\n\ncon"
  },
  {
    "path": "drivers/linkedin.go",
    "chars": 1200,
    "preview": "package drivers\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/linkedin\"\n)\n\nc"
  },
  {
    "path": "drivers/slack.go",
    "chars": 1604,
    "preview": "package drivers\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2/s"
  },
  {
    "path": "gocialite.go",
    "chars": 6177,
    "preview": "package gocialite\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url"
  },
  {
    "path": "gocialite_test.go",
    "chars": 2343,
    "preview": "package gocialite\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar gocialTest Gocial\n\nfunc TestScopes("
  },
  {
    "path": "structs/user.go",
    "chars": 469,
    "preview": "package structs\n\n// User struct\ntype User struct {\n\tID        string                 `json:\"id\"`\n\tUsername  string      "
  }
]

About this extraction

This page contains the full source code of the danilopolani/gocialite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (31.2 KB), approximately 8.8k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!