├── go.mod ├── .travis.yml ├── api ├── user.go └── services.go ├── pkg ├── errors.go ├── facebook │ ├── user.go │ └── facebook.go ├── google │ ├── user.go │ └── google.go ├── linkedin │ ├── user.go │ └── linkedin.go ├── microsoftid │ ├── user.go │ └── microsoftid.go ├── http.go └── github │ ├── user.go │ └── github.go ├── _examples ├── github │ └── main.go ├── facebook │ └── main.go ├── linkedin │ └── main.go ├── google │ └── main.go └── microsoftid │ └── main.go ├── .circleci └── config.yml ├── social_test.go ├── README.md └── social.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wuriyanto48/go-social 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.7.x 5 | - master 6 | 7 | script: 8 | - go test ./... -------------------------------------------------------------------------------- /api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // User data structure 4 | type User struct { 5 | ID string 6 | Name string 7 | Email string 8 | Birthday string 9 | Gender string 10 | Avatar string 11 | Error error 12 | } 13 | -------------------------------------------------------------------------------- /api/services.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Result generic result 8 | type Result interface{} 9 | 10 | // Service interface 11 | type Service interface { 12 | GetAuthURI() (string, error) 13 | GetAccessToken(context.Context, string) error 14 | GetUser(context.Context) (Result, error) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/errors.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "fmt" 4 | 5 | // ErrorEmptyValue structure 6 | type ErrorEmptyValue struct { 7 | field string 8 | message string 9 | } 10 | 11 | // NewErrorEmptyValue function 12 | func NewErrorEmptyValue(field string) *ErrorEmptyValue { 13 | return &ErrorEmptyValue{field: field, message: "%s cannot be empty"} 14 | } 15 | 16 | // Error function 17 | func (e *ErrorEmptyValue) Error() string { 18 | return fmt.Sprintf(e.message, e.field) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/facebook/user.go: -------------------------------------------------------------------------------- 1 | package facebook 2 | 3 | // User data structure 4 | type User struct { 5 | ID string `json:"id"` 6 | Name string `json:"name"` 7 | Email string `json:"email"` 8 | Birthday string `json:"birthday"` 9 | Gender string `json:"gender"` 10 | Picture *Picture `json:"picture,omitempty"` 11 | Error interface{} `json:"error"` 12 | } 13 | 14 | // Picture structure 15 | type Picture struct { 16 | Data struct { 17 | Height int `json:"height"` 18 | IsSilhouette bool `json:"is_silhouette"` 19 | URL string `json:"url"` 20 | Width int `json:"width"` 21 | } `json:"data"` 22 | } 23 | -------------------------------------------------------------------------------- /pkg/google/user.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | // User google data structure 4 | type User struct { 5 | FamilyName string `json:"family_name"` 6 | GivenName string `json:"given_name"` 7 | VerifiedEmail bool `json:"verified_email"` 8 | Name string `json:"name"` 9 | Picture string `json:"picture"` 10 | Gender string `json:"gender"` 11 | Email string `json:"email"` 12 | ID string `json:"id"` 13 | // HD paylod for account using GSuite 14 | HD string `json:"hd,omitempty"` 15 | Error *Error `json:"error,omitempty"` 16 | } 17 | 18 | // Error data structure 19 | type Error struct { 20 | Errors interface{} `json:"errors"` 21 | Message string `json:"message"` 22 | Code int `json:"code"` 23 | } 24 | -------------------------------------------------------------------------------- /pkg/linkedin/user.go: -------------------------------------------------------------------------------- 1 | package linkedin 2 | 3 | // User structure 4 | type User struct { 5 | EmailAddress string `json:"emailAddress"` 6 | FirstName string `json:"firstName"` 7 | Headline string `json:"headline"` 8 | ID string `json:"id"` 9 | LastName string `json:"lastName"` 10 | NumConnections int `json:"numConnections"` 11 | PictureURL string `json:"pictureUrl"` 12 | SiteStandardProfileRequest struct { 13 | URL string `json:"url"` 14 | } `json:"siteStandardProfileRequest"` 15 | ErrorCode int `json:"errorCode"` 16 | Message string `json:"message"` 17 | RequestID string `json:"requestId"` 18 | Status int `json:"status"` 19 | Timestamp int64 `json:"timestamp"` 20 | } 21 | -------------------------------------------------------------------------------- /_examples/github/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/wuriyanto48/go-social" 8 | "github.com/wuriyanto48/go-social/pkg/github" 9 | ) 10 | 11 | //https://github.com/login/oauth/authorize?client_id={your_client_id}&redirect_uri=http://localhost:8080/callback&scope=user,repo 12 | func main() { 13 | g, err := social.New(social.Github, "client_id", "client_secret", "", "http://localhost:8080/callback", "", 0) 14 | 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | 19 | ctx := context.Background() 20 | 21 | err = g.GetAccessToken(ctx, "code") 22 | 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | 27 | result, err := g.GetUser(ctx) 28 | 29 | if err != nil { 30 | fmt.Println(err) 31 | } 32 | 33 | user, _ := result.(*github.User) 34 | 35 | fmt.Println(user.Bio) 36 | } 37 | -------------------------------------------------------------------------------- /_examples/facebook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/wuriyanto48/go-social" 8 | "github.com/wuriyanto48/go-social/pkg/facebook" 9 | ) 10 | 11 | //https://www.facebook.com/dialog/oauth?client_id={your_client_id}&redirect_uri=http://localhost:8080/callback&response_type=code 12 | func main() { 13 | f, err := social.New(social.Facebook, "client_id", "client_secret", "", "http://localhost:8080/callback", "", 0) 14 | 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | 19 | ctx := context.Background() 20 | 21 | err = f.GetAccessToken(ctx, "code") 22 | 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | 27 | result, err := f.GetUser(ctx) 28 | 29 | if err != nil { 30 | fmt.Println(err) 31 | } 32 | 33 | user, _ := result.(*facebook.User) 34 | 35 | fmt.Println(user.Picture) 36 | } 37 | -------------------------------------------------------------------------------- /_examples/linkedin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/wuriyanto48/go-social" 8 | "github.com/wuriyanto48/go-social/pkg/linkedin" 9 | ) 10 | 11 | //https://www.linkedin.com/oauth/v2/authorization?redirect_uri=http://localhost:8080/callback&response_type=code&client_id={client_id}&state=xwyz 12 | func main() { 13 | l, err := social.New(social.Linkedin, "client_id", "client_secret", "", "http://localhost:8080/callback", "", 0) 14 | 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | 19 | ctx := context.Background() 20 | 21 | err = l.GetAccessToken(ctx, "code") 22 | 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | 27 | result, err := l.GetUser(ctx) 28 | 29 | if err != nil { 30 | fmt.Println(err) 31 | } 32 | 33 | user, _ := result.(*linkedin.User) 34 | 35 | fmt.Println(user) 36 | } 37 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2 5 | jobs: 6 | build: 7 | docker: 8 | # specify the version 9 | - image: circleci/golang:1.10.7 10 | 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url 17 | #### expecting it in the form of 18 | #### /go/src/github.com/circleci/go-tool 19 | #### /go/src/bitbucket.org/circleci/go-tool 20 | working_directory: /go/src/github.com/wuriyanto48/go-social 21 | steps: 22 | - checkout 23 | 24 | # specify any bash command here prefixed with `run: ` 25 | - run: go get -v -t -d ./... 26 | - run: go test -v ./... -------------------------------------------------------------------------------- /_examples/google/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/wuriyanto48/go-social" 8 | "github.com/wuriyanto48/go-social/pkg/google" 9 | ) 10 | 11 | //https://accounts.google.com/o/oauth2/auth?redirect_uri=http://localhost:8080/callback&response_type=code&client_id={your_client_id}&scope=https://www.googleapis.com/auth/analytics.readonly+https://www.googleapis.com/auth/userinfo.email&approval_prompt=force&access_type=offline 12 | func main() { 13 | g, err := social.New(social.Google, "client_id", "client_secret", "", "http://localhost:8080/callback", "", 0) 14 | 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | 19 | ctx := context.Background() 20 | 21 | err = g.GetAccessToken(ctx, "code") 22 | 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | 27 | result, err := g.GetUser(ctx) 28 | 29 | if err != nil { 30 | fmt.Println(err) 31 | } 32 | 33 | user, _ := result.(*google.User) 34 | 35 | fmt.Println(user) 36 | } 37 | -------------------------------------------------------------------------------- /_examples/microsoftid/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/wuriyanto48/go-social" 8 | "github.com/wuriyanto48/go-social/pkg/microsoftid" 9 | ) 10 | 11 | //https://login.microsoftonline.com/{your_tenant_id}/oauth2/v2.0/authorize?client_id={your_client_id}&response_type=code&scope=https://graph.microsoft.com/User.Read&redirect_uri=http://localhost:3000/callback 12 | func main() { 13 | g, err := social.New(social.MicrosoftID, "client_id", "client_secret", "tenant_id", "http://localhost:3000/callback", "https://graph.microsoft.com/User.Read", 0) 14 | 15 | if err != nil { 16 | fmt.Println(err) 17 | } 18 | 19 | ctx := context.Background() 20 | 21 | fmt.Println(g.GetAuthURI()) 22 | 23 | err = g.GetAccessToken(ctx, "code") 24 | 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | 30 | result, err := g.GetUser(ctx) 31 | 32 | if err != nil { 33 | fmt.Println(err) 34 | return 35 | } 36 | 37 | user, _ := result.(*microsoftid.User) 38 | 39 | fmt.Println(user) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/microsoftid/user.go: -------------------------------------------------------------------------------- 1 | package microsoftid 2 | 3 | // User Microsoft Identity Platform data structure 4 | type User struct { 5 | OdataContext string `json:"@odata.context"` 6 | BusinessPhones []string `json:"businessPhones"` 7 | DisplayName string `json:"displayName"` 8 | GivenName string `json:"givenName"` 9 | JobTitle string `json:"jobTitle"` 10 | Mail string `json:"mail"` 11 | MobilePhone string `json:"mobilePhone"` 12 | OfficeLocation string `json:"officeLocation"` 13 | PreferredLanguage string `json:"preferredLanguage"` 14 | Surname string `json:"surname"` 15 | UserPrincipalName string `json:"userPrincipalName"` 16 | ID string `json:"id"` 17 | Error *Error `json:"error,omitempty"` 18 | } 19 | 20 | // Error data structure 21 | type Error struct { 22 | Code string `json:"code"` 23 | Message string `json:"message"` 24 | InnerError struct { 25 | Date string `json:"date"` 26 | RequestID string `json:"request-id"` 27 | ClientRequestID string `json:"client-request-id"` 28 | } `json:"innerError"` 29 | } 30 | -------------------------------------------------------------------------------- /social_test.go: -------------------------------------------------------------------------------- 1 | package social 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/wuriyanto48/go-social/pkg/facebook" 7 | ) 8 | 9 | func TestSocial(t *testing.T) { 10 | expected := "Google" 11 | 12 | googleType := Google 13 | 14 | if googleType.String() != expected { 15 | t.Error("Error Social type conversion") 16 | } 17 | } 18 | 19 | func TestProviderType(t *testing.T) { 20 | 21 | t.Run("Should return Facebook", func(t *testing.T) { 22 | expected1 := "https://graph.facebook.com/oauth/access_token" 23 | expected2 := "https://graph.facebook.com/v2/oauth/access_token" 24 | 25 | result, err := New(Facebook, "client_id", "client_secret", "tenant_id", "http://localhost:8080/callback", "scope", 0) 26 | 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | 31 | // convert result to facebook 32 | actual, _ := result.(*facebook.Facebook) 33 | 34 | if actual.TokenURI != expected1 { 35 | t.Error("value is not Facebook") 36 | } 37 | 38 | // test mutate 39 | actual.TokenURI = "https://graph.facebook.com/v2/oauth/access_token" 40 | 41 | if actual.TokenURI != expected2 { 42 | t.Error("value is not Facebook") 43 | } 44 | 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/http.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | // HTTPClient structure 12 | type HTTPClient struct { 13 | client *http.Client 14 | } 15 | 16 | // NewHTTPClient function for intialize HTTPClient object 17 | // Parameter, timeout in time.Duration 18 | func NewHTTPClient(timeout int) *HTTPClient { 19 | return &HTTPClient{ 20 | client: &http.Client{ 21 | Timeout: time.Duration(timeout) * time.Second, 22 | }, 23 | } 24 | } 25 | 26 | // newRequest function for initalize http request, 27 | // paramters, http method, uri path, body, and headers 28 | func (c *HTTPClient) newRequest(ctx context.Context, method string, fullPath string, body io.Reader, headers map[string]string) (*http.Request, error) { 29 | req, err := http.NewRequest(method, fullPath, body) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | req = req.WithContext(ctx) 35 | 36 | if headers != nil { 37 | for key, value := range headers { 38 | req.Header.Set(key, value) 39 | } 40 | } 41 | 42 | return req, nil 43 | } 44 | 45 | // Execute function for call http request 46 | func (c *HTTPClient) Execute(ctx context.Context, method, path string, body io.Reader, v interface{}, headers map[string]string) error { 47 | req, err := c.newRequest(ctx, method, path, body, headers) 48 | 49 | if err != nil { 50 | return err 51 | } 52 | 53 | res, err := c.client.Do(req) 54 | 55 | if err != nil { 56 | return err 57 | } 58 | 59 | defer res.Body.Close() 60 | 61 | if v != nil { 62 | return json.NewDecoder(res.Body).Decode(v) 63 | } 64 | 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Pluggable OAuth library for social login (Google, Facebook, Github, Linkedin) 2 | 3 | 4 | [![GoDoc](https://godoc.org/github.com/wuriyanto48/go-social?status.svg)](https://godoc.org/github.com/wuriyanto48/go-social) 5 | [![Build Status](https://travis-ci.org/wuriyanto48/go-social.svg?branch=master)](https://travis-ci.org/wuriyanto48/go-social) 6 | [![CircleCI](https://circleci.com/gh/wuriyanto48/go-social.svg?style=svg)](https://circleci.com/gh/wuriyanto48/go-social) 7 | 8 | [![forthebadge](https://forthebadge.com/images/badges/made-with-go.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/fuck-it-ship-it.svg)](https://forthebadge.com) [![forthebadge](https://forthebadge.com/images/badges/contains-technical-debt.svg)](https://forthebadge.com) 9 | 10 | ### Install 11 | ```shell 12 | $ go get github.com/wuriyanto48/go-social 13 | ``` 14 | 15 | ### Usage 16 | 17 | OAuth2 using Facebook login 18 | 19 | * Getting Authorization Code First 20 | `https://www.facebook.com/dialog/oauth?client_id={your_client_id}&redirect_uri=http://localhost:8080/callback&response_type=code` 21 | 22 | * Place the Authorization Code to the second parameter of `GetAccessToken(ctx, "authorization_code")` function 23 | 24 | ```go 25 | package main 26 | 27 | import ( 28 | "context" 29 | "fmt" 30 | 31 | "github.com/wuriyanto48/go-social" 32 | "github.com/wuriyanto48/go-social/pkg/facebook" 33 | ) 34 | 35 | func main() { 36 | f, err := social.New(social.Facebook, "client_id", "client_secret", "", "http://localhost:8080/callback", "", 0) 37 | 38 | if err != nil { 39 | fmt.Println(err) 40 | } 41 | 42 | // using context for cancellation 43 | ctx := context.Background() 44 | 45 | err = f.GetAccessToken(ctx, "authorization_code") 46 | 47 | if err != nil { 48 | fmt.Println(err) 49 | } 50 | 51 | result, err := f.GetUser(ctx) 52 | 53 | if err != nil { 54 | fmt.Println(err) 55 | } 56 | 57 | user, _ := result.(*facebook.User) 58 | 59 | fmt.Println(user.Picture) 60 | } 61 | 62 | ``` 63 | 64 | ### Todo 65 | - Add Twitter implementation -------------------------------------------------------------------------------- /social.go: -------------------------------------------------------------------------------- 1 | package social 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/wuriyanto48/go-social/api" 7 | "github.com/wuriyanto48/go-social/pkg/facebook" 8 | "github.com/wuriyanto48/go-social/pkg/github" 9 | "github.com/wuriyanto48/go-social/pkg/google" 10 | "github.com/wuriyanto48/go-social/pkg/linkedin" 11 | "github.com/wuriyanto48/go-social/pkg/microsoftid" 12 | ) 13 | 14 | // Type generic type of social login 15 | type Type int 16 | 17 | // Provider structure 18 | type provider struct { 19 | providers map[string]api.Service 20 | } 21 | 22 | const ( 23 | // Facebook type 24 | Facebook Type = iota 25 | 26 | // Google Type 27 | Google 28 | 29 | // Linkedin Type 30 | Linkedin 31 | 32 | // Twitter Type 33 | Twitter 34 | 35 | // Github Type 36 | Github 37 | 38 | // MicrosoftID Type 39 | MicrosoftID 40 | ) 41 | 42 | const ( 43 | // DefaultHTTPClientTimeout for HTTP Client 44 | DefaultHTTPClientTimeout = 5 45 | ) 46 | 47 | // String function 48 | // returns string of Type 49 | func (t Type) String() string { 50 | switch t { 51 | case Facebook: 52 | return "Facebook" 53 | case Google: 54 | return "Google" 55 | case Linkedin: 56 | return "Linkedin" 57 | case Twitter: 58 | return "Twitter" 59 | case Github: 60 | return "Github" 61 | case MicrosoftID: 62 | return "MicrosoftID" 63 | default: 64 | panic("unknown social media") 65 | } 66 | } 67 | 68 | func newProvider(clientID, clientSecret, tenantID, redirectURI, scope string, timeout int) *provider { 69 | providers := make(map[string]api.Service) 70 | providers["Facebook"] = facebook.New(clientID, clientSecret, redirectURI, timeout) 71 | providers["Google"] = google.New(clientID, clientSecret, redirectURI, timeout) 72 | providers["Github"] = github.New(clientID, clientSecret, redirectURI, timeout) 73 | providers["Linkedin"] = linkedin.New(clientID, clientSecret, redirectURI, timeout) 74 | providers["MicrosoftID"] = microsoftid.New(clientID, clientSecret, tenantID, redirectURI, scope, timeout) 75 | return &provider{providers} 76 | } 77 | 78 | // New , return api.Service implementation 79 | func New(socialType Type, clientID, clientSecret, tenantID, redirectURI, scope string, timeout int) (api.Service, error) { 80 | // set default http client timeout to 5 seconds 81 | if timeout <= 0 { 82 | timeout = DefaultHTTPClientTimeout 83 | } 84 | 85 | providers := newProvider(clientID, clientSecret, tenantID, redirectURI, scope, timeout) 86 | provider, ok := providers.providers[socialType.String()] 87 | 88 | if !ok { 89 | return nil, errors.New("invalid provider type") 90 | } 91 | 92 | return provider, nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/github/user.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // User structure 8 | type User struct { 9 | Login string `json:"login"` 10 | ID int `json:"id"` 11 | NodeID string `json:"node_id"` 12 | AvatarURL string `json:"avatar_url"` 13 | GravatarID string `json:"gravatar_id"` 14 | URL string `json:"url"` 15 | HTMLURL string `json:"html_url"` 16 | FollowersURL string `json:"followers_url"` 17 | FollowingURL string `json:"following_url"` 18 | GistsURL string `json:"gists_url"` 19 | StarredURL string `json:"starred_url"` 20 | SubscriptionsURL string `json:"subscriptions_url"` 21 | OrganizationsURL string `json:"organizations_url"` 22 | ReposURL string `json:"repos_url"` 23 | EventsURL string `json:"events_url"` 24 | ReceivedEventsURL string `json:"received_events_url"` 25 | Type string `json:"type"` 26 | SiteAdmin bool `json:"site_admin"` 27 | Name string `json:"name"` 28 | Company string `json:"company"` 29 | Blog string `json:"blog"` 30 | Location string `json:"location"` 31 | Email string `json:"email"` 32 | Hireable bool `json:"hireable"` 33 | Bio string `json:"bio"` 34 | PublicRepos int `json:"public_repos"` 35 | PublicGists int `json:"public_gists"` 36 | Followers int `json:"followers"` 37 | Following int `json:"following"` 38 | CreatedAt time.Time `json:"created_at"` 39 | UpdatedAt time.Time `json:"updated_at"` 40 | PrivateGists int `json:"private_gists"` 41 | TotalPrivateRepos int `json:"total_private_repos"` 42 | OwnedPrivateRepos int `json:"owned_private_repos"` 43 | DiskUsage int `json:"disk_usage"` 44 | Collaborators int `json:"collaborators"` 45 | TwoFactorAuthentication bool `json:"two_factor_authentication"` 46 | Plan struct { 47 | Name string `json:"name"` 48 | Space int `json:"space"` 49 | Collaborators int `json:"collaborators"` 50 | PrivateRepos int `json:"private_repos"` 51 | } `json:"plan"` 52 | Message string `json:"message"` 53 | DocumentationURL string `json:"documentation_url"` 54 | } 55 | -------------------------------------------------------------------------------- /pkg/github/github.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/wuriyanto48/go-social/api" 11 | "github.com/wuriyanto48/go-social/pkg" 12 | ) 13 | 14 | const ( 15 | // DefaultAuthURI default Authorization URI for Github 16 | DefaultAuthURI = "https://github.com/login/oauth/authorize" 17 | 18 | // DefaultTokenURI default Token URI for Github 19 | DefaultTokenURI = "https://github.com/login/oauth/access_token" 20 | 21 | // DefaultAPIRURI default API URI for Github 22 | DefaultAPIRURI = "https://api.github.com/user" 23 | ) 24 | 25 | // Github struct 26 | type Github struct { 27 | ClientID string 28 | ClientSecret string 29 | AuthURI string 30 | APIURI string 31 | TokenURI string 32 | RedirectURI string 33 | Token string 34 | httpClient *pkg.HTTPClient 35 | } 36 | 37 | // New function, Github's Constructor 38 | func New(clientID, clientSecret, redirectURI string, timeout int) *Github { 39 | httpClient := pkg.NewHTTPClient(timeout) 40 | return &Github{ 41 | ClientID: clientID, 42 | ClientSecret: clientSecret, 43 | RedirectURI: redirectURI, 44 | httpClient: httpClient, 45 | AuthURI: DefaultAuthURI, 46 | APIURI: DefaultAPIRURI, 47 | TokenURI: DefaultTokenURI, 48 | } 49 | } 50 | 51 | // GetAuthURI function 52 | func (g *Github) GetAuthURI() (string, error) { 53 | return "", nil 54 | } 55 | 56 | // GetAccessToken function 57 | func (g *Github) GetAccessToken(ctx context.Context, authorizationCode string) error { 58 | 59 | if g.ClientID == "" { 60 | return pkg.NewErrorEmptyValue("client id") 61 | } 62 | 63 | if g.ClientSecret == "" { 64 | return pkg.NewErrorEmptyValue("client secret") 65 | } 66 | 67 | if g.RedirectURI == "" { 68 | pkg.NewErrorEmptyValue("redirect uri") 69 | } 70 | 71 | form := url.Values{} 72 | form.Add("grant_type", "authorization_code") 73 | form.Add("code", authorizationCode) 74 | form.Add("client_id", g.ClientID) 75 | form.Add("client_secret", g.ClientSecret) 76 | form.Add("redirect_uri", g.RedirectURI) 77 | 78 | headers := map[string]string{ 79 | "Content-Type": "application/x-www-form-urlencoded", 80 | "Accept": "application/json", 81 | } 82 | 83 | var response struct { 84 | AccessToken string `json:"access_token"` 85 | TokenType string `json:"token_type"` 86 | Scope string `json:"scope"` 87 | Error string `json:"error"` 88 | ErrorDescription string `json:"error_description"` 89 | ErrorURI string `json:"error_uri"` 90 | } 91 | 92 | err := g.httpClient.Execute(ctx, "POST", g.TokenURI, strings.NewReader(form.Encode()), &response, headers) 93 | 94 | if err != nil { 95 | return err 96 | } 97 | 98 | if len(response.Error) > 0 { 99 | return errors.New(response.ErrorDescription) 100 | } 101 | 102 | g.Token = response.AccessToken 103 | 104 | return nil 105 | } 106 | 107 | // GetUser function 108 | func (g *Github) GetUser(ctx context.Context) (api.Result, error) { 109 | 110 | if g.Token == "" { 111 | return nil, pkg.NewErrorEmptyValue("access token") 112 | } 113 | 114 | headers := map[string]string{ 115 | "Authorization": fmt.Sprintf("Bearer %s", g.Token), 116 | } 117 | 118 | var response User 119 | 120 | err := g.httpClient.Execute(ctx, "GET", g.APIURI, nil, &response, headers) 121 | 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | if len(response.Message) > 0 { 127 | return nil, errors.New(response.Message) 128 | } 129 | 130 | return &response, nil 131 | } 132 | -------------------------------------------------------------------------------- /pkg/google/google.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/wuriyanto48/go-social/api" 11 | "github.com/wuriyanto48/go-social/pkg" 12 | ) 13 | 14 | const ( 15 | // DefaultAuthURI default Authorization URI for Google 16 | DefaultAuthURI = "https://accounts.google.com/o/oauth2/auth" 17 | 18 | // DefaultTokenURI default Token URI for Google 19 | DefaultTokenURI = "https://accounts.google.com/o/oauth2/token" 20 | 21 | // DefaultAPIRURI default API URI for Google 22 | DefaultAPIRURI = "https://www.googleapis.com/oauth2/v2/userinfo" 23 | ) 24 | 25 | // Google struct 26 | type Google struct { 27 | ClientID string 28 | ClientSecret string 29 | AuthURI string 30 | APIURI string 31 | TokenURI string 32 | RedirectURI string 33 | Token string 34 | httpClient *pkg.HTTPClient 35 | } 36 | 37 | // New function, Google's Constructor 38 | func New(clientID, clientSecret, redirectURI string, timeout int) *Google { 39 | httpClient := pkg.NewHTTPClient(timeout) 40 | return &Google{ 41 | ClientID: clientID, 42 | ClientSecret: clientSecret, 43 | RedirectURI: redirectURI, 44 | httpClient: httpClient, 45 | AuthURI: DefaultAuthURI, 46 | APIURI: DefaultAPIRURI, 47 | TokenURI: DefaultTokenURI, 48 | } 49 | } 50 | 51 | // GetAuthURI function 52 | func (g *Google) GetAuthURI() (string, error) { 53 | return "", nil 54 | } 55 | 56 | // GetAccessToken function 57 | func (g *Google) GetAccessToken(ctx context.Context, authorizationCode string) error { 58 | 59 | if g.ClientID == "" { 60 | return pkg.NewErrorEmptyValue("client id") 61 | } 62 | 63 | if g.ClientSecret == "" { 64 | return pkg.NewErrorEmptyValue("client secret") 65 | } 66 | 67 | if g.RedirectURI == "" { 68 | pkg.NewErrorEmptyValue("redirect uri") 69 | } 70 | 71 | form := url.Values{} 72 | form.Add("grant_type", "authorization_code") 73 | form.Add("code", authorizationCode) 74 | form.Add("client_id", g.ClientID) 75 | form.Add("client_secret", g.ClientSecret) 76 | form.Add("redirect_uri", g.RedirectURI) 77 | 78 | headers := map[string]string{ 79 | "Content-Type": "application/x-www-form-urlencoded", 80 | } 81 | 82 | var response struct { 83 | AccessToken string `json:"access_token"` 84 | ExpiresIn int `json:"expires_in"` 85 | RefreshToken string `json:"refresh_token"` 86 | Scope string `json:"scope"` 87 | TokenType string `json:"token_type"` 88 | IDToken string `json:"id_token"` 89 | Error string `json:"error"` 90 | ErrorDescription string `json:"error_description"` 91 | } 92 | 93 | err := g.httpClient.Execute(ctx, "POST", g.TokenURI, strings.NewReader(form.Encode()), &response, headers) 94 | 95 | if err != nil { 96 | return err 97 | } 98 | 99 | if len(response.Error) > 0 { 100 | return errors.New(response.ErrorDescription) 101 | } 102 | 103 | g.Token = response.AccessToken 104 | 105 | return nil 106 | } 107 | 108 | // GetUser function 109 | func (g *Google) GetUser(ctx context.Context) (api.Result, error) { 110 | 111 | if g.Token == "" { 112 | return nil, pkg.NewErrorEmptyValue("access token") 113 | } 114 | 115 | headers := map[string]string{ 116 | "Authorization": fmt.Sprintf("Bearer %s", g.Token), 117 | } 118 | 119 | var response User 120 | 121 | err := g.httpClient.Execute(ctx, "GET", g.APIURI, nil, &response, headers) 122 | 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | if response.Error != nil { 128 | return nil, errors.New(response.Error.Message) 129 | } 130 | 131 | return &response, nil 132 | } 133 | -------------------------------------------------------------------------------- /pkg/linkedin/linkedin.go: -------------------------------------------------------------------------------- 1 | package linkedin 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/wuriyanto48/go-social/api" 11 | "github.com/wuriyanto48/go-social/pkg" 12 | ) 13 | 14 | const ( 15 | // DefaultAuthURI default Authorization URI for Linkedin 16 | DefaultAuthURI = "https://www.linkedin.com/oauth/v2/authorization" 17 | 18 | // DefaultTokenURI default Token URI for Linkedin 19 | DefaultTokenURI = "https://www.linkedin.com/oauth/v2/accessToken" 20 | 21 | // DefaultAPIRURI default API URI for Linkedin 22 | DefaultAPIRURI = "https://api.linkedin.com/v1/people/~" 23 | ) 24 | 25 | // Linkedin struct 26 | type Linkedin struct { 27 | ClientID string 28 | ClientSecret string 29 | AuthURI string 30 | APIURI string 31 | TokenURI string 32 | RedirectURI string 33 | Token string 34 | State string 35 | httpClient *pkg.HTTPClient 36 | } 37 | 38 | // New function, Linkedin's Constructor 39 | func New(clientID, clientSecret, redirectURI string, timeout int) *Linkedin { 40 | httpClient := pkg.NewHTTPClient(timeout) 41 | return &Linkedin{ 42 | ClientID: clientID, 43 | ClientSecret: clientSecret, 44 | RedirectURI: redirectURI, 45 | httpClient: httpClient, 46 | AuthURI: DefaultAuthURI, 47 | APIURI: DefaultAPIRURI, 48 | TokenURI: DefaultTokenURI, 49 | } 50 | } 51 | 52 | // GetAuthURI function 53 | func (l *Linkedin) GetAuthURI() (string, error) { 54 | return "", nil 55 | } 56 | 57 | // GetAccessToken function 58 | func (l *Linkedin) GetAccessToken(ctx context.Context, authorizationCode string) error { 59 | 60 | if l.ClientID == "" { 61 | return pkg.NewErrorEmptyValue("client id") 62 | } 63 | 64 | if l.ClientSecret == "" { 65 | return pkg.NewErrorEmptyValue("client secret") 66 | } 67 | 68 | if l.RedirectURI == "" { 69 | pkg.NewErrorEmptyValue("redirect uri") 70 | } 71 | 72 | form := url.Values{} 73 | form.Add("grant_type", "authorization_code") 74 | form.Add("code", authorizationCode) 75 | form.Add("client_id", l.ClientID) 76 | form.Add("client_secret", l.ClientSecret) 77 | form.Add("redirect_uri", l.RedirectURI) 78 | 79 | headers := map[string]string{ 80 | "Content-Type": "application/x-www-form-urlencoded", 81 | } 82 | 83 | var response struct { 84 | AccessToken string `json:"access_token"` 85 | ExpiresIn int `json:"expires_in"` 86 | Error string `json:"error"` 87 | ErrorDescription string `json:"error_description"` 88 | } 89 | 90 | err := l.httpClient.Execute(ctx, "POST", l.TokenURI, strings.NewReader(form.Encode()), &response, headers) 91 | 92 | if err != nil { 93 | return err 94 | } 95 | 96 | if len(response.Error) > 0 { 97 | return errors.New(response.ErrorDescription) 98 | } 99 | 100 | l.Token = response.AccessToken 101 | 102 | return nil 103 | } 104 | 105 | // GetUser function 106 | func (l *Linkedin) GetUser(ctx context.Context) (api.Result, error) { 107 | 108 | if l.Token == "" { 109 | return nil, pkg.NewErrorEmptyValue("access token") 110 | } 111 | 112 | headers := map[string]string{ 113 | "Authorization": fmt.Sprintf("Bearer %s", l.Token), 114 | } 115 | 116 | var response User 117 | 118 | uri := fmt.Sprintf("%s:(id,num-connections,picture-url,email-address,firstName,lastName,headline,siteStandardProfileRequest)?format=json", l.APIURI) 119 | 120 | err := l.httpClient.Execute(ctx, "GET", uri, nil, &response, headers) 121 | 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | if len(response.Message) > 0 { 127 | return nil, errors.New(response.Message) 128 | } 129 | 130 | return &response, nil 131 | } 132 | -------------------------------------------------------------------------------- /pkg/facebook/facebook.go: -------------------------------------------------------------------------------- 1 | package facebook 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/wuriyanto48/go-social/api" 11 | "github.com/wuriyanto48/go-social/pkg" 12 | ) 13 | 14 | const ( 15 | // DefaultAuthURI default Authorization URI for Facebook 16 | DefaultAuthURI = "https://www.facebook.com/dialog/oauth" 17 | 18 | // DefaultTokenURI default Token URI for Facebook 19 | DefaultTokenURI = "https://graph.facebook.com/oauth/access_token" 20 | 21 | // DefaultAPIRURI default API URI for Facebook 22 | DefaultAPIRURI = "https://graph.facebook.com" 23 | ) 24 | 25 | // Facebook struct 26 | type Facebook struct { 27 | ClientID string 28 | ClientSecret string 29 | AuthURI string 30 | APIURI string 31 | TokenURI string 32 | RedirectURI string 33 | Token string 34 | httpClient *pkg.HTTPClient 35 | } 36 | 37 | // New function, Facebook's Constructor 38 | func New(clientID, clientSecret, redirectURI string, timeout int) *Facebook { 39 | httpClient := pkg.NewHTTPClient(timeout) 40 | return &Facebook{ 41 | ClientID: clientID, 42 | ClientSecret: clientSecret, 43 | RedirectURI: redirectURI, 44 | httpClient: httpClient, 45 | AuthURI: DefaultAuthURI, 46 | APIURI: DefaultAPIRURI, 47 | TokenURI: DefaultTokenURI, 48 | } 49 | } 50 | 51 | // GetAuthURI function 52 | func (f *Facebook) GetAuthURI() (string, error) { 53 | return "", nil 54 | } 55 | 56 | // GetAccessToken function 57 | func (f *Facebook) GetAccessToken(ctx context.Context, authorizationCode string) error { 58 | 59 | if f.ClientID == "" { 60 | return pkg.NewErrorEmptyValue("client id") 61 | } 62 | 63 | if f.ClientSecret == "" { 64 | return pkg.NewErrorEmptyValue("client secret") 65 | } 66 | 67 | if f.RedirectURI == "" { 68 | pkg.NewErrorEmptyValue("redirect uri") 69 | } 70 | 71 | form := url.Values{} 72 | form.Add("grant_type", "authorization_code") 73 | form.Add("code", authorizationCode) 74 | form.Add("client_id", f.ClientID) 75 | form.Add("client_secret", f.ClientSecret) 76 | form.Add("redirect_uri", f.RedirectURI) 77 | 78 | headers := map[string]string{ 79 | "Content-Type": "application/x-www-form-urlencoded", 80 | } 81 | 82 | var response struct { 83 | AccessToken string `json:"access_token"` 84 | TokenType string `json:"token_type"` 85 | ExpiresIn int `json:"expires_in"` 86 | Error *struct { 87 | Message string `json:"message"` 88 | Type string `json:"type"` 89 | Code int `json:"code"` 90 | FbtraceID string `json:"fbtrace_id"` 91 | } `json:"error,omitempty"` 92 | } 93 | 94 | err := f.httpClient.Execute(ctx, "POST", f.TokenURI, strings.NewReader(form.Encode()), &response, headers) 95 | 96 | if err != nil { 97 | return err 98 | } 99 | 100 | // prevent error 101 | if response.Error != nil { 102 | return errors.New(response.Error.Message) 103 | } 104 | 105 | f.Token = response.AccessToken 106 | 107 | return nil 108 | } 109 | 110 | // GetUser function 111 | func (f *Facebook) GetUser(ctx context.Context) (api.Result, error) { 112 | 113 | if f.Token == "" { 114 | return nil, pkg.NewErrorEmptyValue("access token") 115 | } 116 | 117 | var response User 118 | 119 | uri := fmt.Sprintf("%s/me?fields=id,name,email,birthday,gender,picture{height,is_silhouette,url,width}&access_token=%s", f.APIURI, f.Token) 120 | 121 | err := f.httpClient.Execute(ctx, "GET", uri, nil, &response, nil) 122 | 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | if response.Error != nil { 128 | return nil, errors.New("get user facebook error") 129 | } 130 | 131 | return &response, nil 132 | } 133 | -------------------------------------------------------------------------------- /pkg/microsoftid/microsoftid.go: -------------------------------------------------------------------------------- 1 | package microsoftid 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/wuriyanto48/go-social/api" 11 | "github.com/wuriyanto48/go-social/pkg" 12 | ) 13 | 14 | const ( 15 | // DefaultAuthURI default Authorization URI for Microsoft Identity Platform 16 | DefaultAuthURI = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize?client_id=%s&response_type=code&scope=%s&redirect_uri=%s" 17 | 18 | // DefaultTokenURI default Token URI for Microsoft Identity Platform 19 | DefaultTokenURI = "https://login.microsoftonline.com/%s/oauth2/v2.0/token" 20 | 21 | // DefaultAPIRURI default API URI for Microsoft Identity Platform 22 | DefaultAPIRURI = "https://graph.microsoft.com/v1.0/me" 23 | ) 24 | 25 | // MicrosoftID struct 26 | type MicrosoftID struct { 27 | TenantID string 28 | ClientID string 29 | ClientSecret string 30 | AuthURI string 31 | APIURI string 32 | TokenURI string 33 | RedirectURI string 34 | Scope string 35 | Token string 36 | httpClient *pkg.HTTPClient 37 | } 38 | 39 | // New function, MicrosoftID's Constructor 40 | func New(clientID, clientSecret, tenantID, redirectURI, scope string, timeout int) *MicrosoftID { 41 | httpClient := pkg.NewHTTPClient(timeout) 42 | return &MicrosoftID{ 43 | TenantID: tenantID, 44 | ClientID: clientID, 45 | ClientSecret: clientSecret, 46 | RedirectURI: redirectURI, 47 | Scope: scope, 48 | httpClient: httpClient, 49 | AuthURI: DefaultAuthURI, 50 | APIURI: DefaultAPIRURI, 51 | TokenURI: DefaultTokenURI, 52 | } 53 | } 54 | 55 | // GetAuthURI function 56 | func (g *MicrosoftID) GetAuthURI() (string, error) { 57 | return fmt.Sprintf(g.AuthURI, g.TenantID, g.ClientID, g.Scope, g.RedirectURI), nil 58 | } 59 | 60 | // GetAccessToken function 61 | func (g *MicrosoftID) GetAccessToken(ctx context.Context, authorizationCode string) error { 62 | 63 | if g.ClientID == "" { 64 | return pkg.NewErrorEmptyValue("client id") 65 | } 66 | 67 | if g.ClientSecret == "" { 68 | return pkg.NewErrorEmptyValue("client secret") 69 | } 70 | 71 | if g.RedirectURI == "" { 72 | pkg.NewErrorEmptyValue("redirect uri") 73 | } 74 | 75 | form := url.Values{} 76 | form.Add("grant_type", "authorization_code") 77 | form.Add("code", authorizationCode) 78 | form.Add("client_id", g.ClientID) 79 | form.Add("client_secret", g.ClientSecret) 80 | form.Add("redirect_uri", g.RedirectURI) 81 | 82 | headers := map[string]string{ 83 | "Content-Type": "application/x-www-form-urlencoded", 84 | } 85 | 86 | var response struct { 87 | AccessToken string `json:"access_token"` 88 | ExpiresIn int `json:"expires_in"` 89 | ExtExpiresIn int `json:"ext_expires_in"` 90 | RefreshToken string `json:"refresh_token"` 91 | Scope string `json:"scope"` 92 | TokenType string `json:"token_type"` 93 | IDToken string `json:"id_token"` 94 | Error string `json:"error"` 95 | ErrorDescription string `json:"error_description"` 96 | ErrorCodes []int `json:"error_codes"` 97 | Timestamp string `json:"timestamp"` 98 | TraceID string `json:"trace_id"` 99 | CorrelationID string `json:"correlation_id"` 100 | ErrorURI string `json:"error_uri"` 101 | } 102 | 103 | g.TokenURI = fmt.Sprintf(g.TokenURI, g.TenantID) 104 | 105 | err := g.httpClient.Execute(ctx, "POST", g.TokenURI, strings.NewReader(form.Encode()), &response, headers) 106 | 107 | if err != nil { 108 | return err 109 | } 110 | 111 | if len(response.Error) > 0 { 112 | return errors.New(response.ErrorDescription) 113 | } 114 | 115 | g.Token = response.AccessToken 116 | 117 | return nil 118 | } 119 | 120 | // GetUser function 121 | func (g *MicrosoftID) GetUser(ctx context.Context) (api.Result, error) { 122 | 123 | if g.Token == "" { 124 | return nil, pkg.NewErrorEmptyValue("access token") 125 | } 126 | 127 | headers := map[string]string{ 128 | "Authorization": fmt.Sprintf("Bearer %s", g.Token), 129 | } 130 | 131 | var response User 132 | 133 | err := g.httpClient.Execute(ctx, "GET", g.APIURI, nil, &response, headers) 134 | 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | if response.Error != nil { 140 | return nil, errors.New(response.Error.Message) 141 | } 142 | 143 | return &response, nil 144 | } 145 | --------------------------------------------------------------------------------