github.com/ngocphuongnb/tetua@v0.0.7-alpha/packages/auth/github.go (about)

     1  package auth
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strconv"
    10  
    11  	"github.com/ngocphuongnb/tetua/app/auth"
    12  	"github.com/ngocphuongnb/tetua/app/config"
    13  	"github.com/ngocphuongnb/tetua/app/entities"
    14  	"github.com/ngocphuongnb/tetua/app/server"
    15  	"github.com/ngocphuongnb/tetua/app/utils"
    16  	"golang.org/x/oauth2"
    17  	"golang.org/x/oauth2/github"
    18  )
    19  
    20  const GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
    21  const GITHUB_USER_URL = "https://api.github.com/user"
    22  
    23  type GithubAccessTokenResponse struct {
    24  	Scope       string `json:"scope"`
    25  	TokenType   string `json:"token_type"`
    26  	AccessToken string `json:"access_token"`
    27  }
    28  
    29  type GithubUserResponse struct {
    30  	Login     string `json:"login"`
    31  	ID        int    `json:"id"`
    32  	AvatarURL string `json:"avatar_url"`
    33  	Name      string `json:"name"`
    34  	Blog      string `json:"blog"`
    35  	Email     string `json:"email"`
    36  	Bio       string `json:"bio"`
    37  }
    38  
    39  type GithubAuthProvider struct {
    40  	config *oauth2.Config
    41  }
    42  
    43  func NewGithub(cfg map[string]string) server.AuthProvider {
    44  	if cfg["client_id"] == "" || cfg["client_secret"] == "" {
    45  		panic("Github client id or secret is not set")
    46  	}
    47  	return &GithubAuthProvider{
    48  		config: &oauth2.Config{
    49  			ClientID:     cfg["client_id"],
    50  			ClientSecret: cfg["client_secret"],
    51  			RedirectURL:  utils.Url("/auth/github/callback"),
    52  			Endpoint:     github.Endpoint,
    53  		},
    54  	}
    55  }
    56  
    57  func (g *GithubAuthProvider) Name() string {
    58  	return "github"
    59  }
    60  
    61  func (g *GithubAuthProvider) GetGithubAccessToken(code string) (string, error) {
    62  	requestBody := map[string]string{
    63  		"code":          code,
    64  		"client_id":     g.config.ClientID,
    65  		"client_secret": g.config.ClientSecret,
    66  	}
    67  	requestJSON, _ := json.Marshal(requestBody)
    68  	req, err := http.NewRequest(
    69  		"POST",
    70  		GITHUB_ACCESS_TOKEN_URL,
    71  		bytes.NewBuffer(requestJSON),
    72  	)
    73  
    74  	if err != nil {
    75  		return "", err
    76  	}
    77  
    78  	req.Header.Set("Content-Type", "application/json")
    79  	req.Header.Set("Accept", "application/json")
    80  
    81  	resp, err := http.DefaultClient.Do(req)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	body, err := ioutil.ReadAll(resp.Body)
    87  
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  
    92  	var accessTokenResponse GithubAccessTokenResponse
    93  	if err := json.Unmarshal(body, &accessTokenResponse); err != nil {
    94  		return "", err
    95  	}
    96  
    97  	return accessTokenResponse.AccessToken, nil
    98  }
    99  
   100  func (g *GithubAuthProvider) GetGithubUser(accessToken string) (*GithubUserResponse, error) {
   101  	req, err := http.NewRequest(
   102  		"GET",
   103  		GITHUB_USER_URL,
   104  		nil,
   105  	)
   106  
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
   112  	resp, err := http.DefaultClient.Do(req)
   113  
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	body, err := ioutil.ReadAll(resp.Body)
   119  
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	userResponse := &GithubUserResponse{}
   125  	if err := json.Unmarshal(body, userResponse); err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return userResponse, nil
   130  }
   131  
   132  func (g *GithubAuthProvider) GetGithubUserFromAccessCode(code string) (*GithubUserResponse, error) {
   133  	accessToken, err := g.GetGithubAccessToken(code)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return g.GetGithubUser(accessToken)
   139  }
   140  
   141  func (g *GithubAuthProvider) Login(c server.Context) error {
   142  	url := g.config.AuthCodeURL(
   143  		c.Cookies(config.COOKIE_UUID),
   144  		oauth2.AccessTypeOffline,
   145  		oauth2.SetAuthURLParam("scope", "user:email"),
   146  	)
   147  
   148  	return c.Redirect(url)
   149  }
   150  
   151  func (g *GithubAuthProvider) Callback(c server.Context) (u *entities.User, err error) {
   152  	if c.Query("code") == "" {
   153  		return nil, fmt.Errorf("code is empty")
   154  	}
   155  
   156  	githubUser, err := g.GetGithubUserFromAccessCode(c.Query("code"))
   157  
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	return &entities.User{
   163  		Provider:         "github",
   164  		ProviderID:       utils.SanitizePlainText(strconv.Itoa(githubUser.ID)),
   165  		Username:         utils.SanitizePlainText(githubUser.Login),
   166  		Email:            utils.SanitizePlainText(githubUser.Email),
   167  		ProviderAvatar:   utils.SanitizePlainText(githubUser.AvatarURL),
   168  		DisplayName:      utils.SanitizePlainText(githubUser.Name),
   169  		URL:              utils.SanitizePlainText(githubUser.Blog),
   170  		ProviderUsername: utils.SanitizePlainText(githubUser.Login),
   171  		RoleIDs:          []int{auth.ROLE_USER.ID},
   172  		Active:           config.Setting("auto_approve_user") == "yes",
   173  	}, nil
   174  }