Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 45c7090

Browse files
committed
支持Github登录和绑定
1 parent 5c6a535 commit 45c7090

28 files changed

+568
-127
lines changed

config/db.sql

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,31 +94,39 @@ CREATE TABLE IF NOT EXISTS `user_login` (
9494
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '用户登录表';
9595

9696
CREATE TABLE IF NOT EXISTS `bind_user` (
97-
`uid` int unsigned NOT NULL,
97+
`id` int unsigned NOT NULL AUTO_INCREMENT,
98+
`uid` int unsigned NOT NULL DEFAULT 0 COMMENT '本站用户UID',
9899
`type` tinyint NOT NULL DEFAULT 0 COMMENT '绑定的第三方类型',
99-
`email` varchar(128) NOT NULL DEFAULT '',
100+
`email` varchar(128) NOT NULL DEFAULT '' COMMENT '第三方邮箱',
100101
`tuid` int unsigned NOT NULL DEFAULT 0 COMMENT '第三方uid',
101-
`username` varchar(20) NOT NULL COMMENT '用户名',
102-
`token` varchar(50) NOT NULL COMMENT '第三方access_token',
103-
`refresh` varchar(50) NOT NULL COMMENT '第三方refresh_token',
104-
PRIMARY KEY (`uid`)
102+
`username` varchar(20) NOT NULL DEFAULT '' COMMENT '第三方用户名',
103+
`name` varchar(31) NOT NULL DEFAULT '' COMMENT '姓名',
104+
`access_token` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方access_token',
105+
`refresh_token` varchar(50) NOT NULL DEFAULT '' COMMENT '第三方refresh_token',
106+
`expire` int unsigned NOT NULL DEFAULT 0 COMMENT '过期时间',
107+
`avatar` varchar(127) NOT NULL DEFAULT '' COMMENT '第三方头像',
108+
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
109+
PRIMARY KEY (`id`),
110+
UNIQUE KEY `uniq_user_type` (`username`,`type`),
111+
INDEX idx_uid (`uid`)
105112
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '第三方绑定表';
106113

107114
CREATE TABLE IF NOT EXISTS `user_info` (
108115
`uid` int unsigned NOT NULL AUTO_INCREMENT,
109116
`email` varchar(128) NOT NULL DEFAULT '',
110-
`open` tinyint NOT NULL DEFAULT 1 COMMENT '邮箱是否公开,默认公开',
117+
`open` tinyint NOT NULL DEFAULT 0 COMMENT '邮箱是否公开,默认不公开',
111118
`username` varchar(20) NOT NULL COMMENT '用户名',
112119
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',
113120
`avatar` varchar(128) NOT NULL DEFAULT '' COMMENT '头像(如果为空,则使用http://www.gravatar.com)',
114121
`city` varchar(10) NOT NULL DEFAULT '' COMMENT '居住地',
115-
`company` varchar(64) NOT NULL DEFAULT '',
116-
`github` varchar(20) NOT NULL DEFAULT '',
117-
`weibo` varchar(20) NOT NULL DEFAULT '',
118-
`website` varchar(50) NOT NULL DEFAULT '' COMMENT '个人主页,博客',
122+
`company` varchar(63) NOT NULL DEFAULT '' COMMENT '公司',
123+
`github` varchar(31) NOT NULL DEFAULT '' COMMENT 'Github昵称',
124+
`weibo` varchar(31) NOT NULL DEFAULT '' COMMENT '微博昵称',
125+
`website` varchar(63) NOT NULL DEFAULT '' COMMENT '个人主页,博客',
119126
`monlog` varchar(140) NOT NULL DEFAULT '' COMMENT '个人状态,签名,独白',
120-
`introduce` text NOT NULL COMMENT '个人简介',
127+
`introduce` varchar(2022) NOT NULL COMMENT '个人简介',
121128
`unsubscribe` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否退订本站邮件,0-否;1-是',
129+
`is_third` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否通过第三方账号注册',
122130
`balance` int unsigned NOT NULL DEFAULT 0 COMMENT '财富余额(铜币)',
123131
`status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '用户账号状态。0-默认;1-已审核;2-拒绝;3-冻结;4-停号',
124132
`is_root` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '是否超级用户,不受权限控制:1-是',

config/env.sample.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,9 @@ title = 发票
9191
; 内容关键词
9292
content = 发票,共产党
9393

94+
[github]
95+
client_id = xxx
96+
client_secret = xxx
97+
9498
[include_files]
9599
;path = config/auto_crawl_conf.ini

src/http/controller/account.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,12 @@ func (self AccountController) Edit(ctx echo.Context) error {
234234

235235
if ctx.Request().Method() != "POST" {
236236
user := logic.DefaultUser.FindOne(ctx, "uid", me.Uid)
237+
bindUsers := logic.DefaultUser.FindBindUsers(ctx, me.Uid)
237238
return render(ctx, "user/edit.html", map[string]interface{}{
238239
"user": user,
239240
"default_avatars": logic.DefaultAvatars,
241+
"has_passwd": logic.DefaultUser.HasPasswd(ctx, me.Uid),
242+
"bind_users": bindUsers,
240243
})
241244
}
242245

src/http/controller/oauth.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2017 The StudyGolang Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
// http://studygolang.com
5+
// Author: polaris [email protected]
6+
7+
package controller
8+
9+
import (
10+
"logic"
11+
"model"
12+
"net/http"
13+
14+
. "http"
15+
16+
"github.com/labstack/echo"
17+
)
18+
19+
type OAuthController struct{}
20+
21+
// 注册路由
22+
func (self OAuthController) RegisterRoute(g *echo.Group) {
23+
g.Get("/oauth/github/callback", self.GithubCallback)
24+
g.Get("/oauth/github/login", self.GithubLogin)
25+
}
26+
27+
func (OAuthController) GithubLogin(ctx echo.Context) error {
28+
url := logic.DefaultThirdUser.GithubAuthCodeUrl(ctx)
29+
return ctx.Redirect(http.StatusSeeOther, url)
30+
}
31+
32+
func (OAuthController) GithubCallback(ctx echo.Context) error {
33+
code := ctx.FormValue("code")
34+
35+
me, ok := ctx.Get("user").(*model.Me)
36+
if ok {
37+
// 已登录用户,绑定 github
38+
logic.DefaultThirdUser.BindGithub(ctx, code, me)
39+
return ctx.Redirect(http.StatusSeeOther, "/account/edit#connection")
40+
}
41+
42+
user, err := logic.DefaultThirdUser.LoginFromGithub(ctx, code)
43+
if err != nil || user.Uid == 0 {
44+
var errMsg = ""
45+
if err != nil {
46+
errMsg = err.Error()
47+
} else {
48+
errMsg = "服务内部错误"
49+
}
50+
51+
return render(ctx, "login.html", map[string]interface{}{"error": errMsg})
52+
}
53+
54+
// 登录成功,种cookie
55+
SetCookie(ctx, user.Username)
56+
57+
if user.Balance == 0 {
58+
return ctx.Redirect(http.StatusSeeOther, "/balance")
59+
}
60+
61+
return ctx.Redirect(http.StatusSeeOther, "/")
62+
}

src/http/controller/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func RegisterRoutes(g *echo.Group) {
3131
new(MissionController).RegisterRoute(g)
3232
new(UserRichController).RegisterRoute(g)
3333
new(TopController).RegisterRoute(g)
34+
new(OAuthController).RegisterRoute(g)
3435
new(WebsocketController).RegisterRoute(g)
3536

3637
new(InstallController).RegisterRoute(g)

src/logic/mission.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func (self MissionLogic) RedeemLoginAward(ctx context.Context, me *model.Me) err
108108

109109
userLoginMission.Date = today
110110
userLoginMission.TotalDays++
111+
userLoginMission.UpdatedAt = time.Now()
111112

112113
_, err := session.Where("uid=?", userLoginMission.Uid).Update(userLoginMission)
113114
if err != nil {

src/logic/third_user.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2017 The StudyGolang Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
// http://studygolang.com
5+
// Author:polaris [email protected]
6+
7+
package logic
8+
9+
import (
10+
. "db"
11+
"encoding/json"
12+
"errors"
13+
"io/ioutil"
14+
"model"
15+
16+
"github.com/polaris1119/config"
17+
"golang.org/x/net/context"
18+
"golang.org/x/oauth2"
19+
)
20+
21+
var githubConf *oauth2.Config
22+
23+
const GithubAPIBaseUrl = "https://api.github.com"
24+
25+
func init() {
26+
githubConf = &oauth2.Config{
27+
ClientID: config.ConfigFile.MustValue("github", "client_id"),
28+
ClientSecret: config.ConfigFile.MustValue("github", "client_secret"),
29+
Scopes: []string{"user:email"},
30+
Endpoint: oauth2.Endpoint{
31+
AuthURL: "https://github.com/login/oauth/authorize",
32+
TokenURL: "https://github.com/login/oauth/access_token",
33+
},
34+
}
35+
}
36+
37+
type ThirdUserLogic struct{}
38+
39+
var DefaultThirdUser = ThirdUserLogic{}
40+
41+
func (ThirdUserLogic) GithubAuthCodeUrl(ctx context.Context) string {
42+
// Redirect user to consent page to ask for permission
43+
// for the scopes specified above.
44+
return githubConf.AuthCodeURL("state", oauth2.AccessTypeOffline)
45+
}
46+
47+
func (self ThirdUserLogic) LoginFromGithub(ctx context.Context, code string) (*model.User, error) {
48+
objLog := GetLogger(ctx)
49+
50+
githubUser, token, err := self.githubTokenAndUser(ctx, code)
51+
if err != nil {
52+
objLog.Errorln("LoginFromGithub githubTokenAndUser error:", err)
53+
return nil, err
54+
}
55+
56+
bindUser := &model.BindUser{}
57+
// 是否已经授权过了
58+
_, err = MasterDB.Where("username=? AND type=?", githubUser.Login, model.BindTypeGithub).Get(bindUser)
59+
if err != nil {
60+
objLog.Errorln("LoginFromGithub Get BindUser error:", err)
61+
return nil, err
62+
}
63+
64+
if bindUser.Uid > 0 {
65+
// 更新 token 信息
66+
bindUser.AccessToken = token.AccessToken
67+
bindUser.RefreshToken = token.RefreshToken
68+
if !token.Expiry.IsZero() {
69+
bindUser.Expire = int(token.Expiry.Unix())
70+
}
71+
_, err = MasterDB.Where("uid=?", bindUser.Uid).Update(bindUser)
72+
if err != nil {
73+
objLog.Errorln("LoginFromGithub update token error:", err)
74+
return nil, err
75+
}
76+
77+
user := DefaultUser.FindOne(ctx, "uid", bindUser.Uid)
78+
return user, nil
79+
}
80+
81+
exists := DefaultUser.EmailOrUsernameExists(ctx, githubUser.Email, githubUser.Login)
82+
if exists {
83+
// TODO: 考虑改进?
84+
objLog.Errorln("LoginFromGithub Github 对应的用户信息被占用")
85+
return nil, errors.New("Github 对应的用户信息被占用,可能你注册过本站,用户名密码登录试试!")
86+
}
87+
88+
session := MasterDB.NewSession()
89+
defer session.Close()
90+
session.Begin()
91+
92+
// 生成本站用户
93+
user := &model.User{
94+
Email: githubUser.Email,
95+
Username: githubUser.Login,
96+
Name: githubUser.Name,
97+
City: githubUser.Location,
98+
Company: githubUser.Company,
99+
Github: githubUser.Login,
100+
Website: githubUser.Blog,
101+
IsThird: 1,
102+
Status: model.UserStatusAudit,
103+
}
104+
err = DefaultUser.doCreateUser(ctx, session, user)
105+
if err != nil {
106+
session.Rollback()
107+
objLog.Errorln("LoginFromGithub doCreateUser error:", err)
108+
return nil, err
109+
}
110+
111+
bindUser = &model.BindUser{
112+
Uid: user.Uid,
113+
Type: model.BindTypeGithub,
114+
Email: user.Email,
115+
Tuid: githubUser.Id,
116+
Username: githubUser.Login,
117+
Name: githubUser.Name,
118+
AccessToken: token.AccessToken,
119+
RefreshToken: token.RefreshToken,
120+
Avatar: githubUser.AvatarUrl,
121+
}
122+
if !token.Expiry.IsZero() {
123+
bindUser.Expire = int(token.Expiry.Unix())
124+
}
125+
_, err = session.Insert(bindUser)
126+
if err != nil {
127+
session.Rollback()
128+
objLog.Errorln("LoginFromGithub bindUser error:", err)
129+
return nil, err
130+
}
131+
132+
session.Commit()
133+
134+
return user, nil
135+
}
136+
137+
func (self ThirdUserLogic) BindGithub(ctx context.Context, code string, me *model.Me) error {
138+
objLog := GetLogger(ctx)
139+
140+
githubUser, token, err := self.githubTokenAndUser(ctx, code)
141+
if err != nil {
142+
objLog.Errorln("LoginFromGithub githubTokenAndUser error:", err)
143+
return err
144+
}
145+
146+
bindUser := &model.BindUser{}
147+
// 是否已经授权过了
148+
_, err = MasterDB.Where("username=? AND type=?", githubUser.Login, model.BindTypeGithub).Get(bindUser)
149+
if err != nil {
150+
objLog.Errorln("LoginFromGithub Get BindUser error:", err)
151+
return err
152+
}
153+
154+
if bindUser.Uid > 0 {
155+
// 更新 token 信息
156+
bindUser.AccessToken = token.AccessToken
157+
bindUser.RefreshToken = token.RefreshToken
158+
if !token.Expiry.IsZero() {
159+
bindUser.Expire = int(token.Expiry.Unix())
160+
}
161+
_, err = MasterDB.Where("uid=?", bindUser.Uid).Update(bindUser)
162+
if err != nil {
163+
objLog.Errorln("LoginFromGithub update token error:", err)
164+
return err
165+
}
166+
167+
return nil
168+
}
169+
170+
bindUser = &model.BindUser{
171+
Uid: me.Uid,
172+
Type: model.BindTypeGithub,
173+
Email: githubUser.Email,
174+
Tuid: githubUser.Id,
175+
Username: githubUser.Login,
176+
Name: githubUser.Name,
177+
AccessToken: token.AccessToken,
178+
RefreshToken: token.RefreshToken,
179+
Avatar: githubUser.AvatarUrl,
180+
}
181+
if !token.Expiry.IsZero() {
182+
bindUser.Expire = int(token.Expiry.Unix())
183+
}
184+
_, err = MasterDB.Insert(bindUser)
185+
if err != nil {
186+
objLog.Errorln("LoginFromGithub insert bindUser error:", err)
187+
return err
188+
}
189+
190+
return nil
191+
}
192+
193+
func (ThirdUserLogic) githubTokenAndUser(ctx context.Context, code string) (*model.GithubUser, *oauth2.Token, error) {
194+
token, err := githubConf.Exchange(ctx, code)
195+
if err != nil {
196+
return nil, nil, err
197+
}
198+
199+
httpClient := githubConf.Client(ctx, token)
200+
resp, err := httpClient.Get(GithubAPIBaseUrl + "/user")
201+
if err != nil {
202+
return nil, nil, err
203+
}
204+
defer resp.Body.Close()
205+
206+
respBytes, err := ioutil.ReadAll(resp.Body)
207+
if err != nil {
208+
return nil, nil, err
209+
}
210+
211+
githubUser := &model.GithubUser{}
212+
err = json.Unmarshal(respBytes, githubUser)
213+
if err != nil {
214+
return nil, nil, err
215+
}
216+
217+
if githubUser.Id == 0 {
218+
return nil, nil, errors.New("get github user info error")
219+
}
220+
221+
return githubUser, token, nil
222+
}

0 commit comments

Comments
 (0)