| @@ -4,7 +4,11 @@ | |||
| package models | |||
| import "errors" | |||
| import ( | |||
| "errors" | |||
| "github.com/gogits/gogs/modules/log" | |||
| ) | |||
| // OT: Oauth2 Type | |||
| const ( | |||
| @@ -16,17 +20,23 @@ const ( | |||
| var ( | |||
| ErrOauth2RecordNotExists = errors.New("not exists oauth2 record") | |||
| ErrOauth2NotAssociatedWithUser = errors.New("not associated with user") | |||
| ErrOauth2NotExist = errors.New("not exist oauth2") | |||
| ) | |||
| type Oauth2 struct { | |||
| Id int64 | |||
| Uid int64 // userId | |||
| Uid int64 `xorm:"unique(s)"` // userId | |||
| User *User `xorm:"-"` | |||
| Type int `xorm:"pk unique(oauth)"` // twitter,github,google... | |||
| Identity string `xorm:"pk unique(oauth)"` // id.. | |||
| Type int `xorm:"unique(s) unique(oauth)"` // twitter,github,google... | |||
| Identity string `xorm:"unique(s) unique(oauth)"` // id.. | |||
| Token string `xorm:"VARCHAR(200) not null"` | |||
| } | |||
| func BindUserOauth2(userId, oauthId int64) error { | |||
| _, err := orm.Id(oauthId).Update(&Oauth2{Uid: userId}) | |||
| return err | |||
| } | |||
| func AddOauth2(oa *Oauth2) (err error) { | |||
| if _, err = orm.Insert(oa); err != nil { | |||
| return err | |||
| @@ -47,3 +57,16 @@ func GetOauth2(identity string) (oa *Oauth2, err error) { | |||
| oa.User, err = GetUserById(oa.Uid) | |||
| return oa, err | |||
| } | |||
| func GetOauth2ById(id int64) (oa *Oauth2, err error) { | |||
| oa = new(Oauth2) | |||
| has, err := orm.Id(id).Get(oa) | |||
| log.Info("oa: %v", oa) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if !has { | |||
| return nil, ErrOauth2NotExist | |||
| } | |||
| return oa, nil | |||
| } | |||
| @@ -22,10 +22,9 @@ import ( | |||
| type SocialConnector interface { | |||
| Identity() string | |||
| Type() int | |||
| Name() string | |||
| Email() string | |||
| Token() string | |||
| TokenString() string | |||
| } | |||
| type SocialGithub struct { | |||
| @@ -34,17 +33,13 @@ type SocialGithub struct { | |||
| Name string `json:"login"` | |||
| Email string `json:"email"` | |||
| } | |||
| WebToken *oauth.Token | |||
| Token *oauth.Token | |||
| } | |||
| func (s *SocialGithub) Identity() string { | |||
| return strconv.Itoa(s.data.Id) | |||
| } | |||
| func (s *SocialGithub) Type() int { | |||
| return models.OT_GITHUB | |||
| } | |||
| func (s *SocialGithub) Name() string { | |||
| return s.data.Name | |||
| } | |||
| @@ -53,8 +48,8 @@ func (s *SocialGithub) Email() string { | |||
| return s.data.Email | |||
| } | |||
| func (s *SocialGithub) Token() string { | |||
| data, _ := json.Marshal(s.WebToken) | |||
| func (s *SocialGithub) TokenString() string { | |||
| data, _ := json.Marshal(s.Token) | |||
| return string(data) | |||
| } | |||
| @@ -62,7 +57,7 @@ func (s *SocialGithub) Token() string { | |||
| func (s *SocialGithub) Update() error { | |||
| scope := "https://api.github.com/user" | |||
| transport := &oauth.Transport{ | |||
| Token: s.WebToken, | |||
| Token: s.Token, | |||
| } | |||
| log.Debug("update github info") | |||
| r, err := transport.Client().Get(scope) | |||
| @@ -122,7 +117,7 @@ func SocialSignIn(ctx *middleware.Context, tokens oauth2.Tokens) { | |||
| next = extractPath(ctx.Query("state")) | |||
| log.Debug("success token: %v", tk) | |||
| gh := &SocialGithub{WebToken: tk} | |||
| gh := &SocialGithub{Token: tk} | |||
| if err = gh.Update(); err != nil { | |||
| // FIXME: handle error page 501 | |||
| log.Error("connect with github error: %s", err) | |||
| @@ -137,9 +132,9 @@ func SocialSignIn(ctx *middleware.Context, tokens oauth2.Tokens) { | |||
| ctx.Session.Set("userName", oa.User.Name) | |||
| case models.ErrOauth2RecordNotExists: | |||
| oa = &models.Oauth2{} | |||
| oa.Uid = 0 | |||
| oa.Type = soc.Type() | |||
| oa.Token = soc.Token() | |||
| oa.Uid = -1 | |||
| oa.Type = models.OT_GITHUB | |||
| oa.Token = soc.TokenString() | |||
| oa.Identity = soc.Identity() | |||
| log.Debug("oa: %v", oa) | |||
| if err = models.AddOauth2(oa); err != nil { | |||
| @@ -147,7 +142,11 @@ func SocialSignIn(ctx *middleware.Context, tokens oauth2.Tokens) { | |||
| return | |||
| } | |||
| case models.ErrOauth2NotAssociatedWithUser: | |||
| // ignore it. judge in /usr/login page | |||
| ctx.Session.Set("socialName", soc.Name()) | |||
| ctx.Session.Set("socialEmail", soc.Email()) | |||
| ctx.Session.Set("socialId", oa.Id) | |||
| ctx.Redirect("/user/sign_up") | |||
| return | |||
| default: | |||
| log.Error(err.Error()) // FIXME: handle error page | |||
| return | |||
| @@ -82,6 +82,7 @@ func SignIn(ctx *middleware.Context) { | |||
| ctx.Data["OauthGitHubEnabled"] = base.OauthService.GitHub.Enabled | |||
| } | |||
| var user *models.User | |||
| // Check auto-login. | |||
| userName := ctx.GetCookie(base.CookieUserName) | |||
| if len(userName) == 0 { | |||
| @@ -90,15 +91,17 @@ func SignIn(ctx *middleware.Context) { | |||
| } | |||
| isSucceed := false | |||
| var err error | |||
| defer func() { | |||
| if !isSucceed { | |||
| log.Trace("%s auto-login cookie cleared: %s", ctx.Req.RequestURI, userName) | |||
| ctx.SetCookie(base.CookieUserName, "", -1) | |||
| ctx.SetCookie(base.CookieRememberName, "", -1) | |||
| return | |||
| } | |||
| }() | |||
| user, err := models.GetUserByName(userName) | |||
| user, err = models.GetUserByName(userName) | |||
| if err != nil { | |||
| ctx.HTML(200, "user/signin") | |||
| return | |||
| @@ -112,6 +115,7 @@ func SignIn(ctx *middleware.Context) { | |||
| } | |||
| isSucceed = true | |||
| ctx.Session.Set("userId", user.Id) | |||
| ctx.Session.Set("userName", user.Name) | |||
| if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { | |||
| @@ -155,6 +159,13 @@ func SignInPost(ctx *middleware.Context, form auth.LogInForm) { | |||
| ctx.SetSecureCookie(secret, base.CookieRememberName, user.Name, days) | |||
| } | |||
| // Bind with social account | |||
| if sid, ok := ctx.Session.Get("socialId").(int64); ok { | |||
| if err = models.BindUserOauth2(user.Id, sid); err != nil { | |||
| log.Error("bind user error: %v", err) | |||
| } | |||
| ctx.Session.Delete("socialId") | |||
| } | |||
| ctx.Session.Set("userId", user.Id) | |||
| ctx.Session.Set("userName", user.Name) | |||
| if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { | |||
| @@ -169,6 +180,7 @@ func SignInPost(ctx *middleware.Context, form auth.LogInForm) { | |||
| func SignOut(ctx *middleware.Context) { | |||
| ctx.Session.Delete("userId") | |||
| ctx.Session.Delete("userName") | |||
| ctx.Session.Delete("socialId") | |||
| ctx.SetCookie(base.CookieUserName, "", -1) | |||
| ctx.SetCookie(base.CookieRememberName, "", -1) | |||
| ctx.Redirect("/") | |||
| @@ -178,11 +190,23 @@ func SignUp(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Sign Up" | |||
| ctx.Data["PageIsSignUp"] = true | |||
| if sid, ok := ctx.Session.Get("socialId").(int64); ok { | |||
| var err error | |||
| if _, err = models.GetOauth2ById(sid); err == nil { | |||
| ctx.Data["IsSocialLogin"] = true | |||
| // FIXME: don't set in error page | |||
| ctx.Data["username"] = ctx.Session.Get("socialName") | |||
| ctx.Data["email"] = ctx.Session.Get("socialEmail") | |||
| } else { | |||
| log.Error("unaccepted oauth error: %s", err) // FIXME: should it show in page | |||
| } | |||
| } | |||
| if base.Service.DisenableRegisteration { | |||
| ctx.Data["DisenableRegisteration"] = true | |||
| ctx.HTML(200, "user/signup") | |||
| return | |||
| } | |||
| log.Info("session: %v", ctx.Session.Get("socialId")) | |||
| ctx.HTML(200, "user/signup") | |||
| } | |||
| @@ -232,6 +256,11 @@ func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) { | |||
| } | |||
| log.Trace("%s User created: %s", ctx.Req.RequestURI, strings.ToLower(form.UserName)) | |||
| // Bind Social Account | |||
| if sid, ok := ctx.Session.Get("socialId").(int64); ok { | |||
| models.BindUserOauth2(u.Id, sid) | |||
| ctx.Session.Delete("socialId") | |||
| } | |||
| // Send confirmation e-mail. | |||
| if base.Service.RegisterEmailConfirm && u.Id > 1 { | |||
| @@ -46,9 +46,9 @@ | |||
| {{if .OauthEnabled}} | |||
| <div class="form-group text-center" id="social-login"> | |||
| <h4>Log In with Social Accounts</h4> | |||
| {{if .OauthGitHubEnabled}}<a href="/user/login/github"><i class="fa fa-github-square fa-3x"></i></a>{{end}} | |||
| {{if .OauthGitHubEnabled}}<a href="/user/login/github?next=/user/sign_up"><i class="fa fa-github-square fa-3x"></i></a>{{end}} | |||
| </div> | |||
| {{end}} | |||
| </form> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| {{template "base/footer" .}} | |||
| @@ -6,19 +6,24 @@ | |||
| {{if .DisenableRegisteration}} | |||
| Sorry, registeration has been disenabled, you can only get account from administrator. | |||
| {{else}} | |||
| <h3>Sign Up</h3> | |||
| {{if .IsSocialLogin}} | |||
| <h3>Social login: 2nd step <small>complete information</small></h3> | |||
| {{else}} | |||
| <h3>Sign Up</h3> | |||
| {{end}} | |||
| {{template "base/alert" .}} | |||
| {{if .IsSocialLogin}} | |||
| {{end}} | |||
| <div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-4 control-label">Username: </label> | |||
| <div class="col-md-6"> | |||
| <input name="username" class="form-control" placeholder="Type your username" value="{{.username}}" required="required"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group {{if .Err_Email}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-4 control-label">Email: </label> | |||
| <div class="col-md-6"> | |||
| <input name="email" class="form-control" placeholder="Type your e-mail address" value="{{.email}}" required="required" title="Email is not valid"> | |||
| <input name="email" class="form-control" placeholder="Type your e-mail address" value="{{.email}}{{.socialEmail}}" required="required" title="Email is not valid"> | |||
| </div> | |||
| </div> | |||
| @@ -44,10 +49,14 @@ | |||
| <div class="form-group"> | |||
| <div class="col-md-offset-4 col-md-6"> | |||
| {{if .IsSocialLogin}} | |||
| <a href="/user/login">Already have an account? Bind now!</a> | |||
| {{else}} | |||
| <a href="/user/login">Already have an account? Sign in now!</a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| </form> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| {{template "base/footer" .}} | |||