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


[](https://godoc.org/github.com/danilopolani/gocialite)
[](https://goreportcard.com/report/github.com/danilopolani/gocialite)

[](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
}
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
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\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.