Example #1
0
// SignedInId returns the id of signed in user.
func SignedInId(header http.Header, sess session.Store) int64 {
	if !models.HasEngine {
		return 0
	}

	if setting.Service.EnableReverseProxyAuth {
		webAuthUser := header.Get(setting.ReverseProxyAuthUser)
		if len(webAuthUser) > 0 {
			u, err := models.GetUserByName(webAuthUser)
			if err != nil {
				if err != models.ErrUserNotExist {
					log.Error(4, "GetUserByName: %v", err)
				}
				return 0
			}
			return u.Id
		}
	}

	uid := sess.Get("uid")
	if uid == nil {
		return 0
	}
	if id, ok := uid.(int64); ok {
		if _, err := models.GetUserById(id); err != nil {
			if err != models.ErrUserNotExist {
				log.Error(4, "GetUserById: %v", err)
			}
			return 0
		}
		return id
	}
	return 0
}
Example #2
0
func handleServerConn(keyId string, chans <-chan ssh.NewChannel) {
	for newChan := range chans {
		if newChan.ChannelType() != "session" {
			newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChan.Accept()
		if err != nil {
			log.Error(3, "Could not accept channel: %v", err)
			continue
		}

		go func(in <-chan *ssh.Request) {
			defer channel.Close()
			for req := range in {
				ok, payload := false, strings.TrimLeft(string(req.Payload), "\x00")
				fmt.Println("Request:", req.Type, req.WantReply, payload)
				switch req.Type {
				case "env":
					args := strings.Split(strings.Replace(payload, "\x00", "", -1), "\v")
					if len(args) != 2 {
						break
					}
					args[0] = strings.TrimLeft(args[0], "\x04")
					_, _, err := com.ExecCmdBytes("env", args[0]+"="+args[1])
					if err != nil {
						log.Error(3, "env: %v", err)
						channel.Stderr().Write([]byte(err.Error()))
						break
					}
					ok = true
				case "exec":
					os.Setenv("SSH_ORIGINAL_COMMAND", strings.TrimLeft(payload, "'("))
					log.Info("Payload: %v", strings.TrimLeft(payload, "'("))
					cmd := exec.Command("/Users/jiahuachen/Applications/Go/src/github.com/MessageDream/salvation-ng/gogs-ng", "serv", "key-"+keyId)
					cmd.Stdout = channel
					cmd.Stdin = channel
					cmd.Stderr = channel.Stderr()
					if err := cmd.Run(); err != nil {
						log.Error(3, "exec: %v", err)
					} else {
						ok = true
					}
				}
				fmt.Println("Done:", ok)
				req.Reply(ok, nil) // BUG: Git on Mac seems not know this reply and hang?
			}
			fmt.Println("Done!!!")
		}(requests)
	}
}
Example #3
0
// searchEntry : search an LDAP source if an entry (name, passwd) is valide and in the specific filter
func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
	l, err := ldapDial(ls)
	if err != nil {
		log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
		ls.Enabled = false
		return "", false
	}
	defer l.Close()

	nx := fmt.Sprintf(ls.MsAdSAFormat, name)
	err = l.Bind(nx, passwd)
	if err != nil {
		log.Debug("LDAP Authan failed for %s, reason: %s", nx, err.Error())
		return "", false
	}

	search := ldap.NewSearchRequest(
		ls.BaseDN,
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
		fmt.Sprintf(ls.Filter, name),
		[]string{ls.Attributes},
		nil)
	sr, err := l.Search(search)
	if err != nil {
		log.Debug("LDAP Authen OK but not in filter %s", name)
		return "", false
	}
	log.Debug("LDAP Authen OK: %s", name)
	if len(sr.Entries) > 0 {
		r := sr.Entries[0].GetAttributeValue(ls.Attributes)
		return r, true
	}
	return "", true
}
Example #4
0
func validate(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
	if errs.Count() == 0 {
		return
	} else if len(errs.Overall) > 0 {
		for _, err := range errs.Overall {
			log.Error(4, "%s: %v", reflect.TypeOf(f), err)
		}
		return
	}

	data["HasError"] = true
	AssignForm(f, data)

	typ := reflect.TypeOf(f)
	val := reflect.ValueOf(f)

	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		val = val.Elem()
	}

	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)

		fieldName := field.Tag.Get("form")
		// Allow ignored fields in the struct
		if fieldName == "-" {
			continue
		}

		if err, ok := errs.Fields[field.Name]; ok {
			data["Err_"+field.Name] = true
			trName := l.Tr("form." + field.Name)
			switch err {
			case binding.BindingRequireError:
				data["ErrorMsg"] = trName + l.Tr("form.require_error")
			case binding.BindingAlphaDashError:
				data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
			case binding.BindingAlphaDashDotError:
				data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
			case binding.BindingMinSizeError:
				data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
			case binding.BindingMaxSizeError:
				data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
			case binding.BindingEmailError:
				data["ErrorMsg"] = trName + l.Tr("form.email_error")
			case binding.BindingUrlError:
				data["ErrorMsg"] = trName + l.Tr("form.url_error")
			default:
				data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + err
			}
			return
		}
	}
}
Example #5
0
// Contexter initializes a classic context for a request.
func Contexter() macaron.Handler {
	return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {

		ctx := &Context{
			Context: c,
			Locale:  l,
			Cache:   cache,
			csrf:    x,
			Flash:   f,
			Session: sess,
		}
		dbSession, err := models.GetSession()
		if err != nil {
			log.Error("Get DbSession error:%v", err)
			ctx.Handle(500, "Get DbSession", err)
			return
		}
		ctx.DbSession = dbSession
		ctx.Db = models.GetDb(dbSession)
		// Compute current URL for real-time change language.
		link := ctx.Req.RequestURI
		i := strings.Index(link, "?")
		if i > -1 {
			link = link[:i]
		}
		ctx.Data["Link"] = link

		ctx.Data["PageStartTime"] = time.Now()

		// Get user from session if logined.
		ctx.User = auth.SignedInUser(ctx.Req.Header, ctx.Session)
		if ctx.User != nil {
			ctx.IsSigned = true
			ctx.Data["IsSigned"] = ctx.IsSigned
			ctx.Data["SignedUser"] = ctx.User
			ctx.Data["IsAdmin"] = ctx.User.IsAdmin
		}

		// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
		if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
			if err := ctx.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
				ctx.Handle(500, "ParseMultipartForm", err)
				return
			}
		}

		ctx.Data["CsrfToken"] = x.GetToken()
		ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`)

		c.Map(ctx)
	}
}
Example #6
0
func validateApiReq(errs *binding.Errors, data map[string]interface{}, f interface{}, l i18n.Locale) {
	if errs.Count() == 0 {
		return
	} else if len(errs.Overall) > 0 {
		for _, err := range errs.Overall {
			log.Error(4, "%s: %v", reflect.TypeOf(f), err)
		}
		return
	}

	data["HasError"] = true

	typ := reflect.TypeOf(f)
	val := reflect.ValueOf(f)

	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		val = val.Elem()
	}

	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)

		fieldName := field.Tag.Get("form")
		// Allow ignored fields in the struct
		if fieldName == "-" {
			continue
		}

		if err, ok := errs.Fields[field.Name]; ok {
			switch err {
			case binding.BindingRequireError:
				data["ErrorMsg"] = fieldName + " cannot be empty"
			case binding.BindingAlphaDashError:
				data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
			case binding.BindingAlphaDashDotError:
				data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
			case binding.BindingMinSizeError:
				data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters"
			case binding.BindingMaxSizeError:
				data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters"
			case binding.BindingEmailError:
				data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
			case binding.BindingUrlError:
				data["ErrorMsg"] = fieldName + " is not a valid URL"
			default:
				data["ErrorMsg"] = "Unknown error: " + err
			}
			return
		}
	}
}
Example #7
0
// SignedInUser returns the user object of signed user.
func SignedInUser(header http.Header, sess session.Store) *models.User {
	uid := SignedInId(header, sess)
	if uid <= 0 {
		return nil
	}

	u, err := models.GetUserById(uid)
	if err != nil {
		log.Error(4, "GetUserById: %v", err)
		return nil
	}
	return u
}
Example #8
0
func Activate(ctx *middleware.Context) {
	code := ctx.Query("code")
	if len(code) == 0 {
		ctx.Data["IsActivatePage"] = true
		if ctx.User.IsActive {
			ctx.Error(404)
			return
		}
		// Resend confirmation e-mail.
		if setting.Service.RegisterEmailConfirm {
			if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) {
				ctx.Data["ResendLimited"] = true
			} else {
				ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
				mailer.SendActiveMail(ctx.Render, ctx.User)

				if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
					log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
				}
			}
		} else {
			ctx.Data["ServiceNotEnabled"] = true
		}
		ctx.HTML(200, ACTIVATE)
		return
	}

	// Verify code.
	if user := models.VerifyUserActiveCode(code); user != nil {
		user.IsActive = true
		user.Rands = models.GetUserSalt()
		if err := models.UpdateUser(user); err != nil {
			if err == models.ErrUserNotExist {
				ctx.Error(404)
			} else {
				ctx.Handle(500, "UpdateUser", err)
			}
			return
		}

		log.Trace("User activated: %s", user.Name)

		ctx.Session.Set("uid", user.Id)
		ctx.Session.Set("uname", user.Name)
		ctx.Redirect("/")
		return
	}

	ctx.Data["IsActivateFailed"] = true
	ctx.HTML(200, ACTIVATE)
}
Example #9
0
func listen(config *ssh.ServerConfig, port string) {
	listener, err := net.Listen("tcp", "0.0.0.0:"+port)
	if err != nil {
		panic(err)
	}
	for {
		// Once a ServerConfig has been configured, connections can be accepted.
		conn, err := listener.Accept()
		if err != nil {
			log.Error(3, "Fail to accept incoming connection: %v", err)
			continue
		}
		// Before use, a handshake must be performed on the incoming net.Conn.
		sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
		if err != nil {
			log.Error(3, "Fail to handshake: %v", err)
			continue
		}
		// The incoming Request channel must be serviced.
		go ssh.DiscardRequests(reqs)
		go handleServerConn(sConn.Permissions.Extensions["key-id"], chans)
	}
}
Example #10
0
// get user by erify code
func getVerifyUser(code string) (user *User) {
	if len(code) <= base.TimeLimitCodeLength {
		return nil
	}

	// use tail hex username query user
	hexStr := code[base.TimeLimitCodeLength:]
	if b, err := hex.DecodeString(hexStr); err == nil {
		if user, err = GetUserByName(string(b)); user != nil {
			return user
		}
		log.Error(4, "user.getVerifyUser: %v", err)
	}

	return nil
}
Example #11
0
// Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) {
	if err != nil {
		log.Error(4, "%s: %v", title, err)
		if macaron.Env != macaron.PROD {
			ctx.Data["ErrorMsg"] = err
		}
	}

	switch status {
	case 404:
		ctx.Data["Title"] = "Page Not Found"
	case 500:
		ctx.Data["Title"] = "Internal Server Error"
	}
	ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status)))
}
Example #12
0
// Send user register mail with active code
func SendRegisterMail(r macaron.Render, u *models.User) {
	code := CreateUserActiveCode(u, nil)
	subject := "Register success, Welcome"

	data := GetMailTmplData(u)
	data["Code"] = code
	body, err := r.HTMLString(string(AUTH_REGISTER_SUCCESS), data)
	if err != nil {
		log.Error(4, "mail.SendRegisterMail(fail to render): %v", err)
		return
	}

	msg := NewMailMessage([]string{u.Email}, subject, body)
	msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)

	SendAsync(&msg)
}
Example #13
0
// Send reset password email.
func SendResetPasswdMail(r macaron.Render, u *models.User) {
	code := CreateUserActiveCode(u, nil)

	subject := "Reset your password"

	data := GetMailTmplData(u)
	data["Code"] = code
	body, err := r.HTMLString(string(AUTH_RESET_PASSWORD), data)
	if err != nil {
		log.Error(4, "mail.SendResetPasswdMail(fail to render): %v", err)
		return
	}

	msg := NewMailMessage([]string{u.Email}, subject, body)
	msg.Info = fmt.Sprintf("UID: %d, send reset password email", u.Id)

	SendAsync(&msg)
}
Example #14
0
// Send email verify active email.
func SendActiveMail(r macaron.Render, u *models.User) {
	code := CreateUserActiveCode(u, nil)

	subject := "Verify your e-mail address"

	data := GetMailTmplData(u)
	data["Code"] = code
	body, err := r.HTMLString(string(AUTH_ACTIVE), data)
	if err != nil {
		log.Error(4, "mail.SendActiveMail(fail to render): %v", err)
		return
	}

	msg := NewMailMessage([]string{u.Email}, subject, body)
	msg.Info = fmt.Sprintf("UID: %d, send active mail", u.Id)

	SendAsync(&msg)
}
Example #15
0
func processMailQueue() {
	for {
		select {
		case msg := <-mailQueue:
			num, err := Send(msg)
			tos := strings.Join(msg.To, "; ")
			info := ""
			if err != nil {
				if len(msg.Info) > 0 {
					info = ", info: " + msg.Info
				}
				log.Error(4, fmt.Sprintf("Async sent email %d succeed, not send emails: %s%s err: %s", num, tos, info, err))
			} else {
				log.Trace(fmt.Sprintf("Async sent email %d succeed, sent emails: %s%s", num, tos, info))
			}
		}
	}
}
Example #16
0
func ForgotPasswdPost(ctx *middleware.Context) {
	ctx.Data["Title"] = ctx.Tr("auth.forgot_password")

	if setting.MailService == nil {
		ctx.Handle(403, "user.ForgotPasswdPost", nil)
		return
	}
	ctx.Data["IsResetRequest"] = true

	email := ctx.Query("email")
	u, err := models.GetUserByEmail(email)
	if err != nil {
		if err == models.ErrUserNotExist {
			ctx.Data["Err_Email"] = true
			ctx.RenderWithErr(ctx.Tr("auth.email_not_associate"), FORGOT_PASSWORD, nil)
		} else {
			ctx.Handle(500, "user.ResetPasswd(check existence)", err)
		}
		return
	}

	if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
		ctx.Data["ResendLimited"] = true
		ctx.HTML(200, FORGOT_PASSWORD)
		return
	}

	mailer.SendResetPasswdMail(ctx.Render, u)
	if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
		log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
	}

	ctx.Data["Email"] = email
	ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
	ctx.Data["IsResetSent"] = true
	ctx.HTML(200, FORGOT_PASSWORD)
}
Example #17
0
// Exec starts executing a command in given path, it records its process and timeout.
func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
	if timeout == -1 {
		timeout = DEFAULT
	}

	bufOut := new(bytes.Buffer)
	bufErr := new(bytes.Buffer)

	cmd := exec.Command(cmdName, args...)
	cmd.Dir = dir
	cmd.Stdout = bufOut
	cmd.Stderr = bufErr
	if err := cmd.Start(); err != nil {
		return "", err.Error(), err
	}

	pid := Add(desc, cmd)
	done := make(chan error)
	go func() {
		done <- cmd.Wait()
	}()

	var err error
	select {
	case <-time.After(timeout):
		if errKill := Kill(pid); errKill != nil {
			log.Error(4, "Exec(%d:%s): %v", pid, desc, errKill)
		}
		<-done
		return "", ErrExecTimeout.Error(), ErrExecTimeout
	case err = <-done:
	}

	Remove(pid)
	return bufOut.String(), bufErr.String(), err
}
Example #18
0
func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) {
	u, err := models.GetUserByName(ctx.Query("username"))
	if err != nil {
		ctx.JSON(500, map[string]interface{}{
			"ok":    false,
			"error": err.Error(),
		})
		return
	}
	if !u.ValidtePassword(ctx.Query("password")) {
		ctx.JSON(500, map[string]interface{}{
			"ok":    false,
			"error": "username or password is not correct",
		})
		return
	}

	ctxUser := u
	// Not equal means current user is an organization.
	if form.Uid != u.Id {
		org, err := models.GetUserById(form.Uid)
		if err != nil {
			ctx.JSON(500, map[string]interface{}{
				"ok":    false,
				"error": err.Error(),
			})
			return
		}
		ctxUser = org
	}

	if ctx.HasError() {
		ctx.JSON(500, map[string]interface{}{
			"ok":    false,
			"error": ctx.GetErrMsg(),
		})
		return
	}

	if ctxUser.IsOrganization() {
		// Check ownership of organization.
		if !ctxUser.IsOrgOwner(u.Id) {
			ctx.JSON(403, map[string]interface{}{
				"ok":    false,
				"error": "given user is not owner of organization",
			})
			return
		}
	}

	authStr := strings.Replace(fmt.Sprintf("://%s:%s",
		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
	url := strings.Replace(form.HttpsUrl, "://", authStr+"@", 1)
	repo, err := models.MigrateRepository(ctxUser, form.RepoName, form.Description, form.Private,
		form.Mirror, url)
	if err == nil {
		log.Trace("Repository migrated: %s/%s", ctxUser.Name, form.RepoName)
		ctx.JSON(200, map[string]interface{}{
			"ok":   true,
			"data": "/" + ctxUser.Name + "/" + form.RepoName,
		})
		return
	}

	if repo != nil {
		if errDelete := models.DeleteRepository(ctxUser.Id, repo.Id, ctxUser.Name); errDelete != nil {
			log.Error(4, "DeleteRepository: %v", errDelete)
		}
	}

	ctx.JSON(500, map[string]interface{}{
		"ok":    false,
		"error": err.Error(),
	})
}
Example #19
0
func SignUpPost(ctx *middleware.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
	ctx.Data["Title"] = ctx.Tr("sign_up")

	if setting.Service.DisableRegistration {
		ctx.Error(403)
		return
	}

	isOauth := false
	sid, isOauth := ctx.Session.Get("socialId").(int64)
	if isOauth {
		ctx.Data["IsSocialLogin"] = true
	}

	// May redirect from home page.
	if ctx.Query("from") == "home" {
		// Clear input error box.
		ctx.Data["Err_UserName"] = false
		ctx.Data["Err_Email"] = false

		// Make the best guess.
		uname := ctx.Query("uname")
		i := strings.Index(uname, "@")
		if i > -1 {
			ctx.Data["email"] = uname
			ctx.Data["uname"] = uname[:i]
		} else {
			ctx.Data["uname"] = uname
		}
		ctx.Data["password"] = ctx.Query("password")
		ctx.HTML(200, SIGNUP)
		return
	}

	if ctx.HasError() {
		ctx.HTML(200, SIGNUP)
		return
	}

	if !cpt.VerifyReq(ctx.Req) {
		ctx.Data["Err_Captcha"] = true
		ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), SIGNUP, &form)
		return
	} else if form.Password != form.Retype {
		ctx.Data["Err_Password"] = true
		ctx.RenderWithErr(ctx.Tr("form.password_not_match"), SIGNUP, &form)
		return
	}

	u := &models.User{
		Name:     form.UserName,
		Email:    form.Email,
		Passwd:   form.Password,
		IsActive: !setting.Service.RegisterEmailConfirm || isOauth,
	}

	if err := models.CreateUser(u); err != nil {
		switch err {
		case models.ErrUserAlreadyExist:
			ctx.Data["Err_UserName"] = true
			ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), SIGNUP, &form)
		case models.ErrEmailAlreadyUsed:
			ctx.Data["Err_Email"] = true
			ctx.RenderWithErr(ctx.Tr("form.email_been_used"), SIGNUP, &form)
		case models.ErrUserNameIllegal:
			ctx.Data["Err_UserName"] = true
			ctx.RenderWithErr(ctx.Tr("form.illegal_username"), SIGNUP, &form)
		default:
			ctx.Handle(500, "CreateUser", err)
		}
		return
	}
	log.Trace("Account created: %s", u.Name)

	// Bind social account.
	if isOauth {
		if err := models.BindUserOauth2(u.Id, sid); err != nil {
			ctx.Handle(500, "BindUserOauth2", err)
			return
		}
		ctx.Session.Delete("socialId")
		log.Trace("%s OAuth binded: %s -> %d", ctx.Req.RequestURI, form.UserName, sid)
	}

	// Send confirmation e-mail, no need for social account.
	if !isOauth && setting.Service.RegisterEmailConfirm && u.Id > 1 {
		mailer.SendRegisterMail(ctx.Render, u)
		ctx.Data["IsSendRegisterMail"] = true
		ctx.Data["Email"] = u.Email
		ctx.Data["Hours"] = setting.Service.ActiveCodeLives / 60
		ctx.HTML(200, "user/activate")

		if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
			log.Error(4, "Set cache(MailResendLimit) fail: %v", err)
		}
		return
	}

	ctx.Redirect("/user/login")
}
Example #20
0
func SocialSignIn(ctx *middleware.Context) {
	if setting.OauthService == nil {
		ctx.Handle(404, "social.SocialSignIn(oauth service not enabled)", nil)
		return
	}

	next := extractPath(ctx.Query("next"))
	name := ctx.Params(":name")
	connect, ok := social.SocialMap[name]
	if !ok {
		ctx.Handle(404, "social.SocialSignIn(social login not enabled)", errors.New(name))
		return
	}
	appUrl := strings.TrimSuffix(setting.AppUrl, "/")
	if name == "weibo" {
		appUrl = strings.Replace(appUrl, "localhost", "127.0.0.1", 1)
	}

	code := ctx.Query("code")
	if code == "" {
		// redirect to social login page
		connect.SetRedirectUrl(appUrl + ctx.Req.URL.Path)
		ctx.Redirect(connect.AuthCodeURL(next))
		return
	}

	// handle call back
	tk, err := connect.Exchange(code)
	if err != nil {
		ctx.Handle(500, "social.SocialSignIn(Exchange)", err)
		return
	}
	next = extractPath(ctx.Query("state"))
	log.Trace("social.SocialSignIn(Got token)")

	ui, err := connect.UserInfo(tk, ctx.Req.URL)
	if err != nil {
		ctx.Handle(500, fmt.Sprintf("social.SocialSignIn(get info from %s)", name), err)
		return
	}
	log.Info("social.SocialSignIn(social login): %s", ui)

	oa, err := models.GetOauth2(ui.Identity)
	switch err {
	case nil:
		ctx.Session.Set("uid", oa.User.Id)
		ctx.Session.Set("uname", oa.User.Name)
	case models.ErrOauth2RecordNotExist:
		raw, _ := json.Marshal(tk)
		oa = &models.Oauth2{
			Uid:      -1,
			Type:     connect.Type(),
			Identity: ui.Identity,
			Token:    string(raw),
		}
		log.Trace("social.SocialSignIn(oa): %v", oa)
		if err = models.AddOauth2(oa); err != nil {
			log.Error(4, "social.SocialSignIn(add oauth2): %v", err) // 501
			return
		}
	case models.ErrOauth2NotAssociated:
		next = "/user/sign_up"
	default:
		ctx.Handle(500, "social.SocialSignIn(GetOauth2)", err)
		return
	}

	oa.Updated = time.Now()
	if err = models.UpdateOauth2(oa); err != nil {
		log.Error(4, "UpdateOauth2: %v", err)
	}

	ctx.Session.Set("socialId", oa.Id)
	ctx.Session.Set("socialName", ui.Name)
	ctx.Session.Set("socialEmail", ui.Email)
	log.Trace("social.SocialSignIn(social ID): %v", oa.Id)
	ctx.Redirect(next)
}