[
  {
    "path": ".gitignore",
    "content": ".DS_Store"
  },
  {
    "path": ".travis.yml",
    "content": "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",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [1.0.1] - 2019-03-28\n### Fixed\n- Fix email from Github when is private."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nCreating a new driver for Gocialite is a lot simple, thank also to @vibbix.  \n\n## Create a file\nI suggest you to duplicate `drivers/bitbucket.go` since it's a complete code and name it with your provider name, ex: *myprovider.go*.\n\n## Set variables\n\nChange [line 12](https://github.com/danilopolani/gocialite/blob/master/drivers/bitbucket.go#L12) with:\n\n```go\nconst myProviderDriverName = \"myprovider\"\n```\n\nIt will be used in the `Driver()` function, example: `gocial.Driver(\"myprovider\")`.  \nNow 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.  \nThe relation is `\"json_field_name\": \"StructFieldName\"`, so if in our JSON there's a field called \"first_name\", it will be `\"first_name\": \"FirstName\"`.  \n\nIf there's some nested/complex field, please see the next chapter **User callback hook**.\n\nFinally, 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.  \nIn 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`.\n\nIf your provider has the user endpoint located to `https://api.myprovider.com/me`, the struct will be this:\n\n```go\nvar MyProviderAPIMap = map[string]string{\n\t\"endpoint\":      \"https://api.myprovider.com\",\n\t\"userEndpoint\":  \"/me\",\n}\n```\n\nOf 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.\n\n## User callback hook\n\nIf 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.  \nIn the case of Bitbucket, we use this hook to populate two fields: *avatar* from a nested array/map and *email* from another endpoint.  \n\nThe `client` variable is an `oAuth` client so it's already set up for oAuth details like `access_token`.\n\n## Testing\nUse 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:\n\n```go\nproviderSecrets := map[string]map[string]string{\n\t\t...\n\t\t\"myprovider\": {\n\t\t\t\"clientID\":     \"xxxxxxxxxxxxxx\",\n\t\t\t\"clientSecret\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"redirectURL\":  \"http://localhost:9090/auth/myprovider/callback\",\n\t\t},\n\t}\n```\n\nAnd add the scopes for your app (or an empty slice) in the `providerScopes` variable:\n\n```go\nproviderScopes := map[string][]string{\n\t\t...\n\t\t\"myprovider\": []string{}, // Or []string{\"my_scope\", \"my_other_scope\"}\n\t}\n```\n\nNow 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.  \nIf 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)`).\n\n## PR\n\nNow 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.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Danilo Polani\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Gocialite\n![Travis CI build](https://api.travis-ci.org/danilopolani/gocialite.svg?branch=master)\n![Available Drivers](https://img.shields.io/badge/Drivers-5+-orange.svg)\n[![GoDoc](https://godoc.org/github.com/danilopolani/gocialite?status.svg)](https://godoc.org/github.com/danilopolani/gocialite)\n[![GoReport](https://goreportcard.com/badge/github.com/danilopolani/gocialite)](https://goreportcard.com/report/github.com/danilopolani/gocialite)\n![GitHub contributors](https://img.shields.io/github/contributors/danilopolani/gocialite.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\n## NOT MAINTAINED\nThis project is no longer maintained, you should over a more robust solution like [Goth](https://github.com/markbates/goth).  \n\n----\n\nGocialite is a Socialite inspired package to manage social oAuth authentication without problems.\nThe idea was born when I discovered that Goth is not so flexible: I was using Revel and it was *impossible* to connect them properly.\n\n## Installation\n\nTo install it, just run `go get gopkg.in/danilopolani/gocialite.v1` and include it in your app: `import \"gopkg.in/danilopolani/gocialite.v1\"`.\n\n## Available drivers\n\n- Amazon\n- Asana\n- Bitbucket\n- Facebook\n- Foursquare\n- Github\n- Google\n- LinkedIn\n- Slack\n\n## Create new driver\n\nPlease see [Contributing page](https://github.com/danilopolani/gocialite/blob/master/CONTRIBUTING.md) to learn how to create new driver and test it.\n\n## Set scopes\n\n**Note**: Gocialite set some default scopes for the user profile, for example for *Facebook* it specify `email` and for *Google* `profile, email`.  \nWhen you use the following method, you don't have to rewrite them. \n\nUse the `Scopes([]string)` method of your `Gocial` instance. Example:\n\n```go\ngocial.Scopes([]string{\"public_repo\"})\n```\n\n## Set driver\n\nUse the `Driver(string)` method of your `Gocial` instance. Example:\n\n```go\ngocial.Driver(\"facebook\")\n```\n\nThe driver name will be the provider name in lowercase.\n\n## How to use it\n\n**Note**: All Gocialite methods are chainable.\n\nDeclare a \"global\" variable outside your `main` func:\n\n```go\nimport (\n\t...\n)\n\nvar gocial = gocialite.NewDispatcher()\n\nfunc main() {\n```\n\nThen 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.\nThen, with `Scopes()`, you can set a list of scopes as slice of strings. It's optional.  \nFinally, with `Redirect()` you can obtain the redirect URL. In this method you have to pass three parameters:\n\n1. Client ID\n1. Client Secret\n1. Redirect URL\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\trouter.GET(\"/auth/github\", redirectHandler)\n\n\trouter.Run(\"127.0.0.1:9090\")\n}\n\n// Redirect to correct oAuth URL\nfunc redirectHandler(c *gin.Context) {\n\tauthURL, err := gocial.New().\n\t\tDriver(\"github\"). // Set provider\n\t\tScopes([]string{\"public_repo\"}). // Set optional scope(s)\n\t\tRedirect( // \n\t\t\t\"xxxxxxxxxxxxxx\", // Client ID\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\", // Client Secret\n\t\t\t\"http://localhost:9090/auth/github/callback\", // Redirect URL\n\t\t)\n\n\t// Check for errors (usually driver not valid)\n\tif err != nil {\n\t\tc.Writer.Write([]byte(\"Error: \" + err.Error()))\n\t\treturn\n\t}\n\n\t// Redirect with authURL\n\tc.Redirect(http.StatusFound, authURL) // Redirect with 302 HTTP code\n}\n```\n\nNow create a callback handler route, where we'll receive the content from the provider.  \nIn 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`.  \nThe `Handle()` method returns the user info, the token and error if there's one or `nil`.  \nIf 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:\n\n- ID\n- FirstName\n- LastName\n- FullName\n- Email\n- Avatar (URL)\n- Raw (the full JSON returned by the provider)\n\nNote that they can be empty.\n\n```go\nfunc main() {\n\trouter := gin.Default()\n\n\trouter.GET(\"/auth/github\", redirectHandler)\n\trouter.GET(\"/auth/github/callback\", callbackHandler)\n\n\trouter.Run(\"127.0.0.1:9090\")\n}\n\n// Redirect to correct oAuth URL\n// Handle callback of provider\nfunc callbackHandler(c *gin.Context) {\n\t// Retrieve query params for code and state\n\tcode := c.Query(\"code\")\n\tstate := c.Query(\"state\")\n\n\t// Handle callback and check for errors\n\tuser, token, err := gocial.Handle(state, code)\n\tif err != nil {\n\t\tc.Writer.Write([]byte(\"Error: \" + err.Error()))\n\t\treturn\n\t}\n\n\t// Print in terminal user information\n\tfmt.Printf(\"%#v\", token)\n\tfmt.Printf(\"%#v\", user)\n\n\t// If no errors, show provider name\n\tc.Writer.Write([]byte(\"Hi, \" + user.FullName))\n}\n```\n\nPlease 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.\n\n## Contributors\n\n- [Danilo Polani](https://github.com/danilopolani)\n- [Joseph Buchma](https://github.com/josephbuchma)\n- [Mark Beznos](https://github.com/vibbix)\n- [Davor Kapsa](https://github.com/dvrkps)\n"
  },
  {
    "path": "drivers/amazon.go",
    "content": "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\nconst amazonDriverName = \"amazon\"\n\nfunc init() {\n\tregisterDriver(amazonDriverName, AmazonDefaultScopes, AmazonUserFn, amazon.Endpoint, AmazonAPIMap, AmazonUserMap)\n}\n\n// AmazonUserMap is the map to create the User struct\nvar AmazonUserMap = map[string]string{\n\t\"user_id\": \"ID\",\n\t\"name\":    \"FullName\",\n\t\"email\":   \"Email\",\n}\n\n// AmazonAPIMap is the map for API endpoints\nvar AmazonAPIMap = map[string]string{\n\t\"endpoint\":     \"https://api.amazon.com\",\n\t\"userEndpoint\": \"/user/profile\",\n}\n\n// AmazonUserFn is a callback to parse additional fields for User\nvar AmazonUserFn = func(client *http.Client, u *structs.User) {}\n\n// AmazonDefaultScopes contains the default scopes\nvar AmazonDefaultScopes = []string{\"profile\"}\n"
  },
  {
    "path": "drivers/asana.go",
    "content": "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\nconst asanaDriverName = \"asana\"\n\nfunc init() {\n\tregisterDriver(asanaDriverName, AsanaDefaultScopes, AsanaUserFn, AsanaEndpoint, AsanaAPIMap, AsanaUserMap)\n}\n\n// DailyMotionEndpoint is the oAuth endpoint\nvar AsanaEndpoint = oauth2.Endpoint{\n  AuthURL:  \"https://app.asana.com/-/oauth_authorize\",\n  TokenURL: \"https://app.asana.com/-/oauth_token\",\n}\n\n// AsanaUserMap is the map to create the User struct\nvar AsanaUserMap = map[string]string{}\n\n// AsanaAPIMap is the map for API endpoints\nvar AsanaAPIMap = map[string]string{\n\t\"endpoint\":      \"https://app.asana.com/api/1.0\",\n\t\"userEndpoint\":  \"/users/me?opt_fields=id,name,email,photo\",\n}\n\n// AsanaUserFn is a callback to parse additional fields for User\nvar AsanaUserFn = func(client *http.Client, u *structs.User) {\n  userData := u.Raw[\"data\"].(map[string]interface{})\n  u.ID = fmt.Sprintf(\"%.0f\", userData[\"id\"].(float64))\n  u.Email = userData[\"email\"].(string)\n  u.FullName = userData[\"name\"].(string)\n\n\t// Set avatar\n  if (userData[\"photo\"] != nil) { \n\t u.Avatar = userData[\"photo\"].(map[string]interface{})[\"image_1024x1024\"].(string)\n  }\n}\n\n// AsanaDefaultScopes contains the default scopes\nvar AsanaDefaultScopes = []string{}\n"
  },
  {
    "path": "drivers/bitbucket.go",
    "content": "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/bitbucket\"\n)\n\nconst bitbucketDriverName = \"bitbucket\"\n\nfunc init() {\n\tregisterDriver(bitbucketDriverName, BitbucketDefaultScopes, BitbucketUserFn, bitbucket.Endpoint, BitbucketAPIMap, BitbucketUserMap)\n}\n\n// BitbucketUserMap is the map to create the User struct\nvar BitbucketUserMap = map[string]string{\n\t\"account_id\":   \"ID\",\n\t\"username\":     \"Username\",\n\t\"display_name\": \"FullName\",\n}\n\n// BitbucketAPIMap is the map for API endpoints\nvar BitbucketAPIMap = map[string]string{\n\t\"endpoint\":      \"https://api.bitbucket.org\",\n\t\"userEndpoint\":  \"/2.0/user\",\n\t\"emailEndpoint\": \"/2.0/user/emails\",\n}\n\n// BitbucketUserFn is a callback to parse additional fields for User\nvar BitbucketUserFn = func(client *http.Client, u *structs.User) {\n\t// Set avatar\n\tu.Avatar = u.Raw[\"links\"].(map[string]interface{})[\"avatar\"].(map[string]interface{})[\"href\"].(string)\n\n\t// Retrieve email\n\treq, err := client.Get(BitbucketAPIMap[\"endpoint\"] + BitbucketAPIMap[\"emailEndpoint\"])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer req.Body.Close()\n\tres, _ := ioutil.ReadAll(req.Body)\n\tdata, err := jsonDecode(res)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tu.Email = data[\"values\"].([]interface{})[0].(map[string]interface{})[\"email\"].(string)\n}\n\n// BitbucketDefaultScopes contains the default scopes\nvar BitbucketDefaultScopes = []string{\"account\", \"email\"}\n"
  },
  {
    "path": "drivers/drivers.go",
    "content": "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/oauth2\"\n)\n\nvar (\n\tinitAPIMap           = map[string]map[string]string{}\n\tinitUserMap          = map[string]map[string]string{}\n\tinitEndpointMap      = map[string]oauth2.Endpoint{}\n\tinitCallbackMap      = map[string]func(client *http.Client, u *structs.User){}\n\tinitDefaultScopesMap = map[string][]string{}\n)\n\nfunc registerDriver(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string) {\n\tinitAPIMap[driver] = apimap\n\tinitUserMap[driver] = usermap\n\tinitEndpointMap[driver] = endpoint\n\tinitCallbackMap[driver] = callback\n\tinitDefaultScopesMap[driver] = defaultscopes\n}\n\n// InitializeDrivers adds all the drivers to the register func\nfunc InitializeDrivers(register func(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string)) {\n\tfor k := range initAPIMap {\n\t\tregister(k, initDefaultScopesMap[k], initCallbackMap[k], initEndpointMap[k], initAPIMap[k], initUserMap[k])\n\t}\n}\n\n// Decode a json or return an error\nfunc jsonDecode(js []byte) (map[string]interface{}, error) {\n\tvar decoded map[string]interface{}\n\tif err := json.Unmarshal(js, &decoded); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn decoded, nil\n}\n"
  },
  {
    "path": "drivers/facebook.go",
    "content": "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\nconst facebookDriverName = \"facebook\"\n\nfunc init() {\n\tregisterDriver(facebookDriverName, FacebookDefaultScopes, FacebookUserFn, facebook.Endpoint, FacebookAPIMap, FacebookUserMap)\n}\n\n// FacebookUserMap is the map to create the User struct\nvar FacebookUserMap = map[string]string{\n\t\"id\":         \"ID\",\n\t\"email\":      \"Email\",\n\t\"name\":       \"FullName\",\n\t\"first_name\": \"FirstName\",\n\t\"last_name\":  \"LastName\",\n}\n\n// FacebookAPIMap is the map for API endpoints\nvar FacebookAPIMap = map[string]string{\n\t\"endpoint\":     \"https://graph.facebook.com\",\n\t\"userEndpoint\": \"/me?fields=id,name,first_name,last_name,email\",\n}\n\n// FacebookUserFn is a callback to parse additional fields for User\nvar FacebookUserFn = func(client *http.Client, u *structs.User) {\n\tu.Avatar = FacebookAPIMap[\"endpoint\"] + \"/v2.8/\" + u.ID + \"/picture?width=800\"\n}\n\n// FacebookDefaultScopes contains the default scopes\nvar FacebookDefaultScopes = []string{\"email\"}\n"
  },
  {
    "path": "drivers/foursquare.go",
    "content": "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\nconst foursquareDriverName = \"foursquare\"\n\nfunc init() {\n\tregisterDriver(foursquareDriverName, FoursquareDefaultScopes, FoursquareUserFn, foursquare.Endpoint, FoursquareAPIMap, FoursquareUserMap)\n}\n\n// FoursquareUserMap is the map to create the User struct\nvar FoursquareUserMap = map[string]string{}\n\n// FoursquareAPIMap is the map for API endpoints\nvar FoursquareAPIMap = map[string]string{\n\t\"endpoint\":     \"https://api.foursquare.com\",\n\t\"userEndpoint\": \"/v2/users/self?oauth_token=%ACCESS_TOKEN&v=20171220\",\n}\n\n// FoursquareUserFn is a callback to parse additional fields for User\nvar FoursquareUserFn = func(client *http.Client, u *structs.User) {\n\tuser := u.Raw[\"response\"].(map[string]interface{})[\"user\"].(map[string]interface{})\n\n\tu.ID = user[\"id\"].(string)\n\tu.FirstName = user[\"firstName\"].(string)\n\tu.LastName = user[\"lastName\"].(string)\n\tu.FullName = u.FirstName + \" \" + u.LastName\n\n\tif email, ok := user[\"contact\"].(map[string]interface{})[\"email\"]; ok {\n\t\tu.Email = email.(string)\n\t}\n\tif avatarPrefix, ok := user[\"photo\"].(map[string]interface{})[\"prefix\"]; ok {\n\t\tif avatarSuffix, ok2 := user[\"photo\"].(map[string]interface{})[\"suffix\"]; ok2 {\n\t\t\tu.Avatar = avatarPrefix.(string) + \"original\" + avatarSuffix.(string)\n\t\t}\n\t}\n}\n\n// FoursquareDefaultScopes contains the default scopes\nvar FoursquareDefaultScopes = []string{}\n"
  },
  {
    "path": "drivers/github.go",
    "content": "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/oauth2/github\"\n)\n\nconst githubDriverName = \"github\"\n\nfunc init() {\n\tregisterDriver(githubDriverName, GithubDefaultScopes, GithubUserFn, github.Endpoint, GithubAPIMap, GithubUserMap)\n}\n\n// GithubUserMap is the map to create the User struct\nvar GithubUserMap = map[string]string{\n\t\"id\":         \"ID\",\n\t\"email\":      \"Email\",\n\t\"login\":      \"Username\",\n\t\"avatar_url\": \"Avatar\",\n\t\"name\":       \"FullName\",\n}\n\n// GithubAPIMap is the map for API endpoints\nvar GithubAPIMap = map[string]string{\n\t\"endpoint\":      \"https://api.github.com\",\n\t\"userEndpoint\":  \"/user\",\n\t\"emailEndpoint\": \"/user/emails\",\n}\n\n// GithubUserFn is a callback to parse additional fields for User\nvar GithubUserFn = func(client *http.Client, u *structs.User) {\n\t// Used to parse the email from response\n\ttype additionalEmail struct {\n\t\tEmail string `json:\"email\"`\n\t}\n\tvar email []additionalEmail\n\n\t// Email can be nil because of the \"keep my email private\" setting\n\tif u.Email == \"<nil>\" {\n\t\t// Retrieve email\n\t\treq, err := client.Get(GithubAPIMap[\"endpoint\"] + GithubAPIMap[\"emailEndpoint\"])\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tdefer req.Body.Close()\n\t\terr = json.NewDecoder(req.Body).Decode(&email)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tu.Email = email[0].Email\n\t}\n}\n\n// GithubDefaultScopes contains the default scopes\nvar GithubDefaultScopes = []string{\"user:email\"}\n"
  },
  {
    "path": "drivers/google.go",
    "content": "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\nconst googleDriverName = \"google\"\n\nfunc init() {\n\tregisterDriver(googleDriverName, GoogleDefaultScopes, GoogleUserFn, google.Endpoint, GoogleAPIMap, GoogleUserMap)\n}\n\n// GoogleUserMap is the map to create the User struct\nvar GoogleUserMap = map[string]string{\n\t\"id\":          \"ID\",\n\t\"email\":       \"Email\",\n\t\"name\":        \"FullName\",\n\t\"given_name\":  \"FirstName\",\n\t\"family_name\": \"LastName\",\n\t\"picture\":     \"Avatar\",\n}\n\n// GoogleAPIMap is the map for API endpoints\nvar GoogleAPIMap = map[string]string{\n\t\"endpoint\":     \"https://www.googleapis.com\",\n\t\"userEndpoint\": \"/oauth2/v2/userinfo\",\n}\n\n// GoogleUserFn is a callback to parse additional fields for User\nvar GoogleUserFn = func(client *http.Client, u *structs.User) {}\n\n// GoogleDefaultScopes contains the default scopes\nvar GoogleDefaultScopes = []string{\"profile\", \"email\"}\n"
  },
  {
    "path": "drivers/linkedin.go",
    "content": "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\nconst (\n\tlinkedinDriverName = \"linkedin\"\n)\n\nfunc init() {\n\tregisterDriver(linkedinDriverName, LinkedInDefaultScopes, LinkedInUserFn, linkedin.Endpoint, LinkedInAPIMap, LinkedInUserMap)\n}\n\n// LinkedInUserMap is the map to create the User struct\nvar LinkedInUserMap = map[string]string{\n\t\"id\":            \"ID\",\n\t\"vanityName\":    \"Username\",\n\t\"firstName\":     \"FirstName\",\n\t\"lastName\":      \"LastName\",\n\t\"formattedName\": \"FullName\",\n\t\"emailAddress\":  \"Email\",\n\t\"pictureUrl\":    \"Avatar\",\n}\n\n// LinkedInAPIMap is the map for API endpoints\nvar LinkedInAPIMap = map[string]string{\n\t\"endpoint\":     \"https://api.linkedin.com\",\n\t\"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\",\n}\n\n// LinkedInUserFn is a callback to parse additional fields for User\nvar LinkedInUserFn = func(client *http.Client, u *structs.User) {}\n\n// LinkedInDefaultScopes contains the default scopes\nvar LinkedInDefaultScopes = []string{}\n"
  },
  {
    "path": "drivers/slack.go",
    "content": "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/slack\"\n)\n\nconst slackDriverName = \"slack\"\n\nfunc init() {\n\tregisterDriver(slackDriverName, SlackDefaultScopes, SlackUserFn, slack.Endpoint, SlackAPIMap, SlackUserMap)\n}\n\n// SlackUserMap is the map to create the User struct\nvar SlackUserMap = map[string]string{\n\t\"real_name\":      \"FullName\",\n\t\"first_name\":     \"FirstName\",\n\t\"last_name\":      \"LastName\",\n\t\"email\":          \"Email\",\n\t\"image_original\": \"Avatar\",\n}\n\n// SlackAPIMap is the map for API endpoints\nvar SlackAPIMap = map[string]string{\n\t\"endpoint\":     \"https://slack.com/api\",\n\t\"userEndpoint\": \"/users.profile.get\",\n\t\"authEndpoint\": \"/auth.test\",\n}\n\n// SlackUserFn is a callback to parse additional fields for User\nvar SlackUserFn = func(client *http.Client, u *structs.User) {\n\t// Get user ID\n\treq, err := client.Get(SlackAPIMap[\"endpoint\"] + SlackAPIMap[\"authEndpoint\"])\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer req.Body.Close()\n\tres, _ := ioutil.ReadAll(req.Body)\n\tdata, err := jsonDecode(res)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tu.ID = data[\"user_id\"].(string)\n\n\t// Fetch other user information\n\tuserInfo := u.Raw[\"profile\"].(map[string]interface{})\n\tu.Username = userInfo[\"display_name\"].(string)\n\tu.FullName = userInfo[\"real_name\"].(string)\n\tu.FirstName = userInfo[\"first_name\"].(string)\n\tu.LastName = userInfo[\"last_name\"].(string)\n\tu.Email = userInfo[\"email\"].(string)\n\tu.Avatar = userInfo[\"image_original\"].(string)\n}\n\n// SlackDefaultScopes contains the default scopes\nvar SlackDefaultScopes = []string{\"users.profile:read\"}\n"
  },
  {
    "path": "gocialite.go",
    "content": "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\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/danilopolani/gocialite/drivers\"\n\t\"github.com/danilopolani/gocialite/structs\"\n\t\"golang.org/x/oauth2\"\n\t\"gopkg.in/oleiade/reflections.v1\"\n)\n\n// Dispatcher allows to safely issue concurrent Gocials\ntype Dispatcher struct {\n\tmu sync.RWMutex\n\tg  map[string]*Gocial\n}\n\n// NewDispatcher creates new Dispatcher\nfunc NewDispatcher() *Dispatcher {\n\treturn &Dispatcher{g: make(map[string]*Gocial)}\n}\n\n// New Gocial instance\nfunc (d *Dispatcher) New() *Gocial {\n\td.mu.Lock()\n\tdefer d.mu.Unlock()\n\tstate := randToken()\n\tg := &Gocial{state: state}\n\td.g[state] = g\n\treturn g\n}\n\n// Handle callback. Can be called only once for given state.\nfunc (d *Dispatcher) Handle(state, code string) (*structs.User, *oauth2.Token, error) {\n\td.mu.RLock()\n\tg, ok := d.g[state]\n\td.mu.RUnlock()\n\tif !ok {\n\t\treturn nil, nil, fmt.Errorf(\"invalid CSRF token: %s\", state)\n\t}\n\terr := g.Handle(state, code)\n\td.mu.Lock()\n\tdelete(d.g, state)\n\td.mu.Unlock()\n\treturn &g.User, g.Token, err\n}\n\n// Gocial is the main struct of the package\ntype Gocial struct {\n\tdriver, state string\n\tscopes        []string\n\tconf          *oauth2.Config\n\tUser          structs.User\n\tToken         *oauth2.Token\n}\n\nfunc init() {\n\tdrivers.InitializeDrivers(RegisterNewDriver)\n}\n\nvar (\n\t// Set the basic information such as the endpoint and the scopes URIs\n\tapiMap = map[string]map[string]string{}\n\n\t// Mapping to create a valid \"user\" struct from providers\n\tuserMap = map[string]map[string]string{}\n\n\t// Map correct endpoints\n\tendpointMap = map[string]oauth2.Endpoint{}\n\n\t// Map custom callbacks\n\tcallbackMap = map[string]func(client *http.Client, u *structs.User){}\n\n\t// Default scopes for each driver\n\tdefaultScopesMap = map[string][]string{}\n)\n\n//RegisterNewDriver adds a new driver to the existing set\nfunc RegisterNewDriver(driver string, defaultscopes []string, callback func(client *http.Client, u *structs.User), endpoint oauth2.Endpoint, apimap, usermap map[string]string) {\n\tapiMap[driver] = apimap\n\tuserMap[driver] = usermap\n\tendpointMap[driver] = endpoint\n\tcallbackMap[driver] = callback\n\tdefaultScopesMap[driver] = defaultscopes\n}\n\n// Driver is needed to choose the correct social\nfunc (g *Gocial) Driver(driver string) *Gocial {\n\tg.driver = driver\n\tg.scopes = defaultScopesMap[driver]\n\n\t// BUG: sequential usage of single Gocial instance will have same CSRF token. This is serious security issue.\n\t// NOTE: Dispatcher eliminates this bug.\n\tif g.state == \"\" {\n\t\tg.state = randToken()\n\t}\n\n\treturn g\n}\n\n// Scopes is used to set the oAuth scopes, for example \"user\", \"calendar\"\nfunc (g *Gocial) Scopes(scopes []string) *Gocial {\n\tg.scopes = append(g.scopes, scopes...)\n\treturn g\n}\n\n// Redirect returns an URL for the selected social oAuth login\nfunc (g *Gocial) Redirect(clientID, clientSecret, redirectURL string) (string, error) {\n\t// Check if driver is valid\n\tif !inSlice(g.driver, complexKeys(apiMap)) {\n\t\treturn \"\", fmt.Errorf(\"Driver not valid: %s\", g.driver)\n\t}\n\n\t// Check if valid redirectURL\n\t_, err := url.ParseRequestURI(redirectURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Redirect URL <%s> not valid: %s\", redirectURL, err.Error())\n\t}\n\tif !strings.HasPrefix(redirectURL, \"http://\") && !strings.HasPrefix(redirectURL, \"https://\") {\n\t\treturn \"\", fmt.Errorf(\"Redirect URL <%s> not valid: protocol not valid\", redirectURL)\n\t}\n\n\tg.conf = &oauth2.Config{\n\t\tClientID:     clientID,\n\t\tClientSecret: clientSecret,\n\t\tRedirectURL:  redirectURL,\n\t\tScopes:       g.scopes,\n\t\tEndpoint:     endpointMap[g.driver],\n\t}\n\n\treturn g.conf.AuthCodeURL(g.state), nil\n}\n\n// Handle callback from provider\nfunc (g *Gocial) Handle(state, code string) error {\n\t// Handle the exchange code to initiate a transport.\n\tif g.state != state {\n\t\treturn fmt.Errorf(\"Invalid state: %s\", state)\n\t}\n\n\t// Check if driver is valid\n\tif !inSlice(g.driver, complexKeys(apiMap)) {\n\t\treturn fmt.Errorf(\"Driver not valid: %s\", g.driver)\n\t}\n\n\ttoken, err := g.conf.Exchange(oauth2.NoContext, code)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"oAuth exchanged failed: %s\", err.Error())\n\t}\n\n\tclient := g.conf.Client(oauth2.NoContext, token)\n\n\t// Set gocial token\n\tg.Token = token\n\n\t// Retrieve all from scopes\n\tdriverAPIMap := apiMap[g.driver]\n\tdriverUserMap := userMap[g.driver]\n\tuserEndpoint := strings.Replace(driverAPIMap[\"userEndpoint\"], \"%ACCESS_TOKEN\", token.AccessToken, -1)\n\n\t// Get user info\n\treq, err := client.Get(driverAPIMap[\"endpoint\"] + userEndpoint)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer req.Body.Close()\n\tres, _ := ioutil.ReadAll(req.Body)\n\tdata, err := jsonDecode(res)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error decoding JSON: %s\", err.Error())\n\t}\n\n\t// Scan all fields and dispatch through the mapping\n\tmapKeys := keys(driverUserMap)\n\tgUser := structs.User{}\n\tfor k, f := range data {\n\t\tif !inSlice(k, mapKeys) { // Skip if not in the mapping\n\t\t\tcontinue\n\t\t}\n\n\t\t// Assign the value\n\t\t// Dirty way, but we need to convert also int/float to string\n\t\t_ = reflections.SetField(&gUser, driverUserMap[k], fmt.Sprint(f))\n\t}\n\n\t// Set the \"raw\" user interface\n\tgUser.Raw = data\n\n\t// Custom callback\n\tcallbackMap[g.driver](client, &gUser)\n\n\t// Update the struct\n\tg.User = gUser\n\n\treturn nil\n}\n\n// Generate a random token\nfunc randToken() string {\n\tb := make([]byte, 32)\n\trand.Read(b)\n\treturn base64.StdEncoding.EncodeToString(b)\n}\n\n// Check if a value is in a string slice\nfunc inSlice(v string, s []string) bool {\n\tfor _, scope := range s {\n\t\tif scope == v {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// Decode a json or return an error\nfunc jsonDecode(js []byte) (map[string]interface{}, error) {\n\tvar decoded map[string]interface{}\n\tdecoder := json.NewDecoder(strings.NewReader(string(js)))\n\tdecoder.UseNumber()\n\n\tif err := decoder.Decode(&decoded); err != nil {\n\t\treturn nil, err\n\t}\n\t\n\treturn decoded, nil\n}\n\n// Return the keys of a map\nfunc keys(m map[string]string) []string {\n\tvar keys []string\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\n\treturn keys\n}\n\nfunc complexKeys(m map[string]map[string]string) []string {\n\tvar keys []string\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\n\treturn keys\n}\n"
  },
  {
    "path": "gocialite_test.go",
    "content": "package gocialite\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar gocialTest Gocial\n\nfunc TestScopes(t *testing.T) {\n\tgocialTest.Scopes([]string{\"email\"})\n\tassert.Equal(t, gocialTest.scopes, []string{\"email\"})\n\tassert.NotEqual(t, gocialTest.scopes, []string{})\n\n\tgocialTest.\n\t\tDriver(\"google\").\n\t\tScopes([]string{\"calendar.readonly\"})\n\tassert.Equal(t, gocialTest.scopes, []string{\"profile\", \"email\", \"calendar.readonly\"})\n\tassert.NotEqual(t, gocialTest.scopes, []string{\"profile\", \"email\"})\n\tassert.NotEqual(t, gocialTest.scopes, []string{})\n}\nfunc TestConf(t *testing.T) {\n\tassert := assert.New(t)\n\n\tgocialTest.\n\t\tDriver(\"github\").\n\t\tRedirect(\n\t\t\t\"foo\",\n\t\t\t\"bar\",\n\t\t\t\"http://example.com/auth/callback\",\n\t\t)\n\n\tassert.Equal(gocialTest.conf.ClientID, \"foo\")\n\tassert.NotEqual(gocialTest.conf.ClientID, \"\")\n\tassert.NotNil(gocialTest.conf.ClientID)\n\n\tassert.Equal(gocialTest.conf.ClientSecret, \"bar\")\n\tassert.NotEqual(gocialTest.conf.ClientSecret, \"\")\n\tassert.NotNil(gocialTest.conf.ClientSecret)\n\n\tassert.Equal(gocialTest.conf.RedirectURL, \"http://example.com/auth/callback\")\n\tassert.NotEqual(gocialTest.conf.RedirectURL, \"\")\n\tassert.NotNil(gocialTest.conf.RedirectURL)\n}\nfunc TestDriver(t *testing.T) {\n\tvar err error\n\n\t_, err = gocialTest.Driver(\"unknown\").\n\t\tRedirect(\n\t\t\t\"xxxxxxxx\",\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"http://example.com/auth/callback\",\n\t\t)\n\tassert.NotNil(t, err)\n\n\t_, err = gocialTest.Driver(\"github\").\n\t\tRedirect(\n\t\t\t\"xxxxxxxx\",\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"http://example.com/auth/callback\",\n\t\t)\n\tassert.Nil(t, err)\n}\nfunc TestRedirectURL(t *testing.T) {\n\tvar err error\n\n\t_, err = gocialTest.Driver(\"github\").\n\t\tRedirect(\n\t\t\t\"xxxxxxxx\",\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"/auth/callback\",\n\t\t)\n\tassert.NotNil(t, err)\n\n\t_, err = gocialTest.Driver(\"github\").\n\t\tRedirect(\n\t\t\t\"xxxxxxxx\",\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"http://example.com/auth/callback\",\n\t\t)\n\tassert.Nil(t, err)\n}\n\nfunc TestState(t *testing.T) {\n\tvar err error\n\n\terr = gocialTest.Driver(\"github\").\n\t\tHandle(\"fakeState\", \"foo\")\n\tassert.NotNil(t, err)\n}\n\nfunc TestExchange(t *testing.T) {\n\tvar err error\n\n\t// Generate a state\n\tgocialTest.\n\t\tDriver(\"github\").\n\t\tRedirect(\n\t\t\t\"xxxxxxxx\",\n\t\t\t\"xxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\t\t\"http://example.com/auth/callback\",\n\t\t)\n\n\terr = gocialTest.Handle(gocialTest.state, \"foo\")\n\tassert.NotNil(t, err)\n}\n"
  },
  {
    "path": "structs/user.go",
    "content": "package structs\n\n// User struct\ntype User struct {\n\tID        string                 `json:\"id\"`\n\tUsername  string                 `json:\"username\"`\n\tFirstName string                 `json:\"first_name\"`\n\tLastName  string                 `json:\"last_name\"`\n\tFullName  string                 `json:\"full_name\"`\n\tEmail     string                 `json:\"email\"`\n\tAvatar    string                 `json:\"avatar\"`\n\tRaw       map[string]interface{} `json:\"raw\"` // Raw data\n}\n"
  }
]