diff --git a/config/env.sample.ini b/config/env.sample.ini index 15917f37..ae663d53 100644 --- a/config/env.sample.ini +++ b/config/env.sample.ini @@ -115,6 +115,10 @@ content = 发票,共产党 client_id = xxx client_secret = xxx +[gitea] +client_id = xxx +client_secret = xxx + [account] ; 是否验证邮箱 verify_email = 0 diff --git a/go.mod b/go.mod index 9eaaa5d2..f22b5ab4 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/studygolang/studygolang go 1.12 require ( + code.gitea.io/sdk/gitea v0.0.0-20191106151626-e4082d89cc3b github.com/PuerkitoBio/goquery v1.5.0 github.com/Unknwon/goconfig v0.0.0-20190425194916-3dba17dd7b9e // indirect github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect diff --git a/go.sum b/go.sum index 6bc8b3d5..f6d9579d 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,9 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +code.gitea.io/sdk v0.0.0-20191106151626-e4082d89cc3b h1:bAdeOAgzWZ2R8uMTiq4/K0ViBl/j8XGOEok+DciPN9Y= +code.gitea.io/sdk/gitea v0.0.0-20191106151626-e4082d89cc3b h1:T26uiLOnyGHLGvE1+as/j97ceSHk5gt9NgAMaBf/BZw= +code.gitea.io/sdk/gitea v0.0.0-20191106151626-e4082d89cc3b/go.mod h1:8IxkM1gyiwEjfO0m47bcmr3u3foR15+LoVub43hCHd0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= diff --git a/http/controller/oauth.go b/http/controller/oauth.go index 56e491c3..628352e9 100644 --- a/http/controller/oauth.go +++ b/http/controller/oauth.go @@ -23,6 +23,9 @@ type OAuthController struct{} func (self OAuthController) RegisterRoute(g *echo.Group) { g.GET("/oauth/github/callback", self.GithubCallback) g.GET("/oauth/github/login", self.GithubLogin) + + g.GET("/oauth/gitea/callback", self.GiteaCallback) + g.GET("/oauth/gitea/login", self.GiteaLogin) } func (OAuthController) GithubLogin(ctx echo.Context) error { @@ -67,3 +70,46 @@ func (OAuthController) GithubCallback(ctx echo.Context) error { return ctx.Redirect(http.StatusSeeOther, "/") } + +func (OAuthController) GiteaLogin(ctx echo.Context) error { + uri := ctx.QueryParam("uri") + url := logic.DefaultThirdUser.GiteaAuthCodeUrl(context.EchoContext(ctx), uri) + return ctx.Redirect(http.StatusSeeOther, url) +} + +func (OAuthController) GiteaCallback(ctx echo.Context) error { + code := ctx.FormValue("code") + + me, ok := ctx.Get("user").(*model.Me) + if ok { + // 已登录用户,绑定 github + logic.DefaultThirdUser.BindGitea(context.EchoContext(ctx), code, me) + + redirectURL := ctx.QueryParam("redirect_url") + if redirectURL == "" { + redirectURL = "/account/edit#connection" + } + return ctx.Redirect(http.StatusSeeOther, redirectURL) + } + + user, err := logic.DefaultThirdUser.LoginFromGitea(context.EchoContext(ctx), code) + if err != nil || user.Uid == 0 { + var errMsg = "" + if err != nil { + errMsg = err.Error() + } else { + errMsg = "服务内部错误" + } + + return render(ctx, "login.html", map[string]interface{}{"error": errMsg}) + } + + // 登录成功,种cookie + SetLoginCookie(ctx, user.Username) + + if user.Balance == 0 { + return ctx.Redirect(http.StatusSeeOther, "/balance") + } + + return ctx.Redirect(http.StatusSeeOther, "/") +} diff --git a/logic/third_user.go b/logic/third_user.go index 68cee0f2..6a10979b 100644 --- a/logic/third_user.go +++ b/logic/third_user.go @@ -9,9 +9,10 @@ package logic import ( "encoding/json" "errors" + "io/ioutil" + . "github.com/studygolang/studygolang/db" "github.com/studygolang/studygolang/model" - "io/ioutil" "github.com/polaris1119/logger" @@ -21,8 +22,10 @@ import ( ) var githubConf *oauth2.Config +var giteaConf *oauth2.Config const GithubAPIBaseUrl = "https://api.github.com" +const GiteaAPIBaseUrl = "https://gitea.com/api/v1" func init() { githubConf = &oauth2.Config{ @@ -34,6 +37,15 @@ func init() { TokenURL: "https://github.com/login/oauth/access_token", }, } + + giteaConf = &oauth2.Config{ + ClientID: config.ConfigFile.MustValue("gitea", "client_id"), + ClientSecret: config.ConfigFile.MustValue("gitea", "client_secret"), + Endpoint: oauth2.Endpoint{ + AuthURL: "https://gitea.com/login/oauth/authorize", + TokenURL: "https://gitea.com/login/oauth/access_token", + }, + } } type ThirdUserLogic struct{} @@ -200,6 +212,166 @@ func (self ThirdUserLogic) BindGithub(ctx context.Context, code string, me *mode return nil } +func (ThirdUserLogic) GiteaAuthCodeUrl(ctx context.Context, redirectURL string) string { + // Redirect user to consent page to ask for permission + // for the scopes specified above. + giteaConf.RedirectURL = redirectURL + return giteaConf.AuthCodeURL("state", oauth2.AccessTypeOffline) +} + +func (self ThirdUserLogic) LoginFromGitea(ctx context.Context, code string) (*model.User, error) { + objLog := GetLogger(ctx) + + giteaUser, token, err := self.giteaTokenAndUser(ctx, code) + if err != nil { + objLog.Errorln("LoginFromGithub githubTokenAndUser error:", err) + return nil, err + } + + bindUser := &model.BindUser{} + // 是否已经授权过了 + _, err = MasterDB.Where("username=? AND type=?", giteaUser.UserName, model.BindTypeGitea).Get(bindUser) + if err != nil { + objLog.Errorln("LoginFromGithub Get BindUser error:", err) + return nil, err + } + + if bindUser.Uid > 0 { + // 更新 token 信息 + change := map[string]interface{}{ + "access_token": token.AccessToken, + "refresh_token": token.RefreshToken, + } + if !token.Expiry.IsZero() { + change["expire"] = int(token.Expiry.Unix()) + } + _, err = MasterDB.Table(new(model.BindUser)).Where("uid=?", bindUser.Uid).Update(change) + if err != nil { + objLog.Errorln("LoginFromGithub update token error:", err) + return nil, err + } + + user := DefaultUser.FindOne(ctx, "uid", bindUser.Uid) + return user, nil + } + + exists := DefaultUser.EmailOrUsernameExists(ctx, giteaUser.Email, giteaUser.UserName) + if exists { + // TODO: 考虑改进? + objLog.Errorln("LoginFromGithub Github 对应的用户信息被占用") + return nil, errors.New("Github 对应的用户信息被占用,可能你注册过本站,用户名密码登录试试!") + } + + session := MasterDB.NewSession() + defer session.Close() + session.Begin() + + // 有可能获取不到 email?加上 @gitea.com做邮箱后缀 + if giteaUser.Email == "" { + giteaUser.Email = giteaUser.UserName + "@gitea.com" + } + // 生成本站用户 + user := &model.User{ + Email: giteaUser.Email, + Username: giteaUser.UserName, + Name: model.DisplayName(giteaUser), + City: "", + Company: "", + Gitea: giteaUser.UserName, + Website: "", + Avatar: giteaUser.AvatarURL, + IsThird: 1, + Status: model.UserStatusAudit, + } + err = DefaultUser.doCreateUser(ctx, session, user) + if err != nil { + session.Rollback() + objLog.Errorln("LoginFromGithub doCreateUser error:", err) + return nil, err + } + + bindUser = &model.BindUser{ + Uid: user.Uid, + Type: model.BindTypeGithub, + Email: user.Email, + Tuid: int(giteaUser.ID), + Username: giteaUser.UserName, + Name: model.DisplayName(giteaUser), + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, + Avatar: giteaUser.AvatarURL, + } + if !token.Expiry.IsZero() { + bindUser.Expire = int(token.Expiry.Unix()) + } + _, err = session.Insert(bindUser) + if err != nil { + session.Rollback() + objLog.Errorln("LoginFromGitea bindUser error:", err) + return nil, err + } + + session.Commit() + + return user, nil +} + +func (self ThirdUserLogic) BindGitea(ctx context.Context, code string, me *model.Me) error { + objLog := GetLogger(ctx) + + giteaUser, token, err := self.giteaTokenAndUser(ctx, code) + if err != nil { + objLog.Errorln("LoginFromGitea githubTokenAndUser error:", err) + return err + } + + bindUser := &model.BindUser{} + // 是否已经授权过了 + _, err = MasterDB.Where("username=? AND type=?", giteaUser.UserName, model.BindTypeGitea).Get(bindUser) + if err != nil { + objLog.Errorln("LoginFromGitea Get BindUser error:", err) + return err + } + + if bindUser.Uid > 0 { + // 更新 token 信息 + bindUser.AccessToken = token.AccessToken + bindUser.RefreshToken = token.RefreshToken + if !token.Expiry.IsZero() { + bindUser.Expire = int(token.Expiry.Unix()) + } + _, err = MasterDB.Where("uid=?", bindUser.Uid).Update(bindUser) + if err != nil { + objLog.Errorln("LoginFromGitea update token error:", err) + return err + } + + return nil + } + + bindUser = &model.BindUser{ + Uid: me.Uid, + Type: model.BindTypeGithub, + Email: giteaUser.Email, + Tuid: int(giteaUser.ID), + Username: giteaUser.UserName, + Name: model.DisplayName(giteaUser), + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, + Avatar: giteaUser.AvatarURL, + } + if !token.Expiry.IsZero() { + bindUser.Expire = int(token.Expiry.Unix()) + } + _, err = MasterDB.Insert(bindUser) + if err != nil { + objLog.Errorln("LoginFromGitea insert bindUser error:", err) + return err + } + + return nil +} + func (ThirdUserLogic) UnBindUser(ctx context.Context, bindId interface{}, me *model.Me) error { if !DefaultUser.HasPasswd(ctx, me.Uid) { return errors.New("请先设置密码!") @@ -243,8 +415,39 @@ func (ThirdUserLogic) githubTokenAndUser(ctx context.Context, code string) (*mod } if githubUser.Id == 0 { - return nil, nil, errors.New("get github user info error") + return nil, nil, errors.New("get gitea user info error") } return githubUser, token, nil } + +func (ThirdUserLogic) giteaTokenAndUser(ctx context.Context, code string) (*model.GiteaUser, *oauth2.Token, error) { + token, err := giteaConf.Exchange(ctx, code) + if err != nil { + return nil, nil, err + } + + httpClient := giteaConf.Client(ctx, token) + resp, err := httpClient.Get(GiteaAPIBaseUrl + "/user") + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, nil, err + } + + giteaUser := &model.GiteaUser{} + err = json.Unmarshal(respBytes, giteaUser) + if err != nil { + return nil, nil, err + } + + if giteaUser.ID == 0 { + return nil, nil, errors.New("get gitea user info error") + } + + return giteaUser, token, nil +} diff --git a/model/github_user.go b/model/github_user.go index c40d5a9b..f54749ef 100644 --- a/model/github_user.go +++ b/model/github_user.go @@ -6,6 +6,10 @@ package model +import ( + "code.gitea.io/sdk/gitea" +) + type GithubUser struct { Id int `json:"id"` Login string `json:"login"` @@ -16,3 +20,12 @@ type GithubUser struct { Blog string `json:"blog"` Location string `json:"location"` } + +type GiteaUser = gitea.User + +func DisplayName(g *GiteaUser) string { + if g.FullName == "" { + return g.UserName + } + return g.FullName +} diff --git a/model/user.go b/model/user.go index 4a97b41b..1dfbfaf1 100644 --- a/model/user.go +++ b/model/user.go @@ -75,6 +75,7 @@ type User struct { City string `json:"city"` Company string `json:"company"` Github string `json:"github"` + Gitea string `json:"gitea"` Weibo string `json:"weibo"` Website string `json:"website"` Monlog string `json:"monlog"` @@ -177,6 +178,7 @@ type UserRole struct { const ( BindTypeGithub = iota + BindTypeGitea ) type BindUser struct { diff --git a/template/common/layout.html b/template/common/layout.html index 7bcf7acd..f2dd5848 100644 --- a/template/common/layout.html +++ b/template/common/layout.html @@ -198,6 +198,10 @@ GitHub 登录 + + + Gitea 登录 +
diff --git a/template/common/my_info.html b/template/common/my_info.html index 7d9ecd02..bef8f695 100644 --- a/template/common/my_info.html +++ b/template/common/my_info.html @@ -99,6 +99,10 @@