From 83fe17c6ec96ab165c5091c6cfe57002d3672936 Mon Sep 17 00:00:00 2001 From: itsHenry <2671230065@qq.com> Date: Tue, 27 Dec 2022 22:11:22 +0800 Subject: [PATCH] feat: support github login (#2639) * Support Github Login * improve according to codefactor * fix due to last updates * optimization Co-authored-by: Noah Hsu --- internal/bootstrap/data/setting.go | 5 ++ internal/conf/const.go | 5 ++ internal/db/user.go | 8 +++ internal/model/setting.go | 1 + internal/model/user.go | 1 + server/handles/auth.go | 3 +- server/handles/githublogin.go | 101 +++++++++++++++++++++++++++++ server/router.go | 2 + 8 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 server/handles/githublogin.go diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index d5110afdf38..5f388245a29 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -145,6 +145,11 @@ func InitialSettings() []model.SettingItem { {Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,bleve,none", Group: model.INDEX}, {Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`}, {Key: conf.IndexProgress, Value: "{}", Type: conf.TypeText, Group: model.SINGLE, Flag: model.PRIVATE}, + + // GitHub settings + {Key: conf.GithubClientId, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE}, + {Key: conf.GithubClientSecrets, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE}, + {Key: conf.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC}, } if flags.Dev { initialSettingItems = append(initialSettingItems, []model.SettingItem{ diff --git a/internal/conf/const.go b/internal/conf/const.go index 5c50bfec3c1..df7ef08f4b3 100644 --- a/internal/conf/const.go +++ b/internal/conf/const.go @@ -53,6 +53,11 @@ const ( // single Token = "token" IndexProgress = "index_progress" + + //Github + GithubClientId = "github_client_id" + GithubClientSecrets = "github_client_secrets" + GithubLoginEnabled = "github_login_enabled" ) const ( diff --git a/internal/db/user.go b/internal/db/user.go index cc2d817f173..99f556eccf1 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -21,6 +21,14 @@ func GetUserByName(username string) (*model.User, error) { return &user, nil } +func GetUserByGithubID(githubID int) (*model.User, error) { + user := model.User{GithubID: githubID} + if err := db.Where(user).First(&user).Error; err != nil { + return nil, errors.Wrapf(err, "The Github ID is not associated with a user") + } + return &user, nil +} + func GetUserById(id uint) (*model.User, error) { var u model.User if err := db.First(&u, id).Error; err != nil { diff --git a/internal/model/setting.go b/internal/model/setting.go index 10771da5015..883a8534d70 100644 --- a/internal/model/setting.go +++ b/internal/model/setting.go @@ -8,6 +8,7 @@ const ( GLOBAL ARIA2 INDEX + GITHUB ) const ( diff --git a/internal/model/user.go b/internal/model/user.go index 0d0461a35af..93457b94b67 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -31,6 +31,7 @@ type User struct { // 9: webdav write Permission int32 `json:"permission"` OtpSecret string `json:"-"` + GithubID int `json:"github_id"` } func (u User) IsGuest() bool { diff --git a/server/handles/auth.go b/server/handles/auth.go index 920b7d75b12..70a4d299f9e 100644 --- a/server/handles/auth.go +++ b/server/handles/auth.go @@ -91,7 +91,7 @@ func CurrentUser(c *gin.Context) { } func UpdateCurrent(c *gin.Context) { - var req LoginReq + var req model.User if err := c.ShouldBind(&req); err != nil { common.ErrorResp(c, err, 400) return @@ -101,6 +101,7 @@ func UpdateCurrent(c *gin.Context) { if req.Password != "" { user.Password = req.Password } + user.GithubID = req.GithubID if err := op.UpdateUser(user); err != nil { common.ErrorResp(c, err, 500) } else { diff --git a/server/handles/githublogin.go b/server/handles/githublogin.go new file mode 100644 index 00000000000..426be8fa16f --- /dev/null +++ b/server/handles/githublogin.go @@ -0,0 +1,101 @@ +package handles + +import ( + "errors" + "net/url" + "strconv" + + "github.com/alist-org/alist/v3/internal/db" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/alist-org/alist/v3/server/common" + "github.com/gin-gonic/gin" + "github.com/go-resty/resty/v2" +) + +func GithubLoginRedirect(c *gin.Context) { + method := c.Query("method") + callbackURL := c.Query("callback_url") + withParams := c.Query("with_params") + enabled, err := db.GetSettingItemByKey("github_login_enabled") + clientId, err := db.GetSettingItemByKey("github_client_id") + if err != nil { + common.ErrorResp(c, err, 400) + return + } else if enabled.Value == "true" { + urlValues := url.Values{} + urlValues.Add("client_id", clientId.Value) + if method == "get_github_id" { + urlValues.Add("allow_signup", "true") + } else if method == "github_callback_login" { + urlValues.Add("allow_signup", "false") + } + if method == "" { + common.ErrorStrResp(c, "no method provided", 400) + return + } + if withParams != "" { + urlValues.Add("redirect_uri", common.GetApiUrl(c.Request)+"/api/auth/github_callback"+"?method="+method+"&callback_url="+callbackURL+"&with_params="+withParams) + } else { + urlValues.Add("redirect_uri", common.GetApiUrl(c.Request)+"/api/auth/github_callback"+"?method="+method+"&callback_url="+callbackURL) + } + c.Redirect(302, "https://github.com/login/oauth/authorize?"+urlValues.Encode()) + } else { + common.ErrorStrResp(c, "github Login not enabled", 403) + } +} + +var githubClient = resty.New().SetRetryCount(3) + +func GithubLoginCallback(c *gin.Context) { + argument := c.Query("method") + callbackUrl := c.Query("callback_url") + if argument == "get_github_id" || argument == "github_login" { + enabled, err := db.GetSettingItemByKey("github_login_enabled") + clientId, err := db.GetSettingItemByKey("github_client_id") + clientSecret, err := db.GetSettingItemByKey("github_client_secrets") + if err != nil { + common.ErrorResp(c, err, 400) + return + } else if enabled.Value == "true" { + callbackCode := c.Query("code") + if callbackCode == "" { + common.ErrorStrResp(c, "No code provided", 400) + return + } + resp, err := githubClient.R().SetHeader("content-type", "application/json"). + SetBody(map[string]string{ + "client_id": clientId.Value, + "client_secret": clientSecret.Value, + "code": callbackCode, + "redirect_uri": common.GetApiUrl(c.Request) + "/api/auth/github_callback", + }).Post("https://github.com/login/oauth/access_token") + if err != nil { + common.ErrorResp(c, err, 400) + return + } + accessToken := utils.Json.Get(resp.Body(), "access_token").ToString() + resp, err = githubClient.R().SetHeader("Authorization", "Bearer "+accessToken). + Get("https://api.github.com/user") + ghUserID := utils.Json.Get(resp.Body(), "id").ToInt() + if argument == "get_github_id" { + c.Redirect(302, callbackUrl+"?githubID="+strconv.Itoa(ghUserID)) + } + if argument == "github_login" { + user, err := db.GetUserByGithubID(ghUserID) + if err != nil { + common.ErrorResp(c, err, 400) + } + token, err := common.GenerateToken(user.Username) + withParams := c.Query("with_params") + if withParams == "true" { + c.Redirect(302, callbackUrl+"&token="+token) + } else if withParams == "false" { + c.Redirect(302, callbackUrl+"?token="+token) + } + return + } + } else { + common.ErrorResp(c, errors.New("invalid request"), 500) + } + } +} diff --git a/server/router.go b/server/router.go index e29755c0035..079c9ea0312 100644 --- a/server/router.go +++ b/server/router.go @@ -34,6 +34,8 @@ func Init(r *gin.Engine) { auth.POST("/me/update", handles.UpdateCurrent) auth.POST("/auth/2fa/generate", handles.Generate2FA) auth.POST("/auth/2fa/verify", handles.Verify2FA) + auth.GET("/auth/github", handles.GithubLoginRedirect) + auth.GET("/auth/github_callback", handles.GithubLoginCallback) // no need auth public := api.Group("/public")