mirror of
https://dev.sigpipe.me/dashie/git.txt
synced 2025-10-06 02:42:43 +02:00
Can register, login, logout
This commit is contained in:
@@ -6,7 +6,11 @@ init_cmds = [
|
||||
watch_all = true
|
||||
watch_dirs = [
|
||||
"$WORKDIR/cmd",
|
||||
"$WORKDIR/routers"
|
||||
"$WORKDIR/routers",
|
||||
"$WORKDIR/context",
|
||||
"$WORKDIR/stuff",
|
||||
"$WORKDIR/models",
|
||||
"$WORKDIR/conf/locale",
|
||||
]
|
||||
watch_exts = [".go"]
|
||||
ignore_files = [".+_test.go"]
|
||||
|
File diff suppressed because one or more lines are too long
17
cmd/web.go
17
cmd/web.go
@@ -8,12 +8,13 @@ import (
|
||||
"dev.sigpipe.me/dashie/git.txt/routers"
|
||||
"dev.sigpipe.me/dashie/git.txt/bindata"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/template"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/form"
|
||||
"path"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/go-macaron/csrf"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/i18n"
|
||||
//"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"strings"
|
||||
"fmt"
|
||||
log "gopkg.in/clog.v1"
|
||||
@@ -120,16 +121,24 @@ func runWeb(ctx *cli.Context) error {
|
||||
|
||||
m := newMacaron()
|
||||
|
||||
//bindIgnErr := binding.BindIgnErr
|
||||
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
|
||||
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
m.Get("/", routers.Home)
|
||||
|
||||
m.Group("/user", func() {
|
||||
m.Group("/login", func() {
|
||||
m.Combo("").Get(user.Login)
|
||||
m.Combo("").Get(user.Login).Post(bindIgnErr(form.Login{}), user.LoginPost)
|
||||
})
|
||||
m.Get("/register", user.Register)
|
||||
m.Post("/register", bindIgnErr(form.Register{}), user.RegisterPost)
|
||||
}, reqSignOut)
|
||||
|
||||
m.Group("/user", func() {
|
||||
m.Get("/logout", user.Logout)
|
||||
})
|
||||
m.Get("/register", user.Register)
|
||||
|
||||
|
||||
// robots.txt
|
||||
m.Get("/robots.txt", func(ctx *context.Context) {
|
||||
|
@@ -103,6 +103,15 @@ CSRF_COOKIE_NAME = _csrf
|
||||
[security]
|
||||
; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!!
|
||||
SECRET_KEY = !#@FDEWREWR&*(
|
||||
INSTALL_LOCK = false
|
||||
; Auto-login remember days
|
||||
LOGIN_REMEMBER_DAYS = 7
|
||||
COOKIE_USERNAME = gitxt_celestia
|
||||
COOKIE_REMEMBER_NAME = gitxt_luna
|
||||
COOKIE_SECURE = false
|
||||
; Enable to set cookie to indicate user login status
|
||||
ENABLE_LOGIN_STATUS_COOKIE = false
|
||||
LOGIN_STATUS_COOKIE_NAME = login_status
|
||||
|
||||
[i18n]
|
||||
LANGS = en-US,fr-FR
|
||||
|
@@ -1,11 +1,23 @@
|
||||
[login]
|
||||
title = "Sign In"
|
||||
sign_in = "Sign In"
|
||||
username = "Username"
|
||||
username_placeholder = "Your username."
|
||||
email = "Email"
|
||||
email_placeholder = "Email address..."
|
||||
email_placeholder = "Your email."
|
||||
password = "Password"
|
||||
password_placeholder = "Password..."
|
||||
password_placeholder = "Your nice password."
|
||||
repeat_password_placeholder = "And the same one here."
|
||||
remember_me = "Remember me"
|
||||
|
||||
[register]
|
||||
title = "Register"
|
||||
title = "Register"
|
||||
register = "Register"
|
||||
not_allowed = "Registration not allowed"
|
||||
|
||||
[form]
|
||||
password_not_match = "Passwords doesn't match"
|
||||
username_password_incorrect = "Invalid username or password"
|
||||
username_been_taken = "Username already taken, sorry"
|
||||
username_reserved = "Username reserved, please choose another username"
|
||||
username_pattern_not_allowed = "Invalid username pattern"
|
||||
|
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/go-macaron/i18n"
|
||||
"html/template"
|
||||
"dev.sigpipe.me/dashie/git.txt/models"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/form"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/auth"
|
||||
)
|
||||
|
||||
// Context represents context of a request.
|
||||
@@ -42,6 +44,36 @@ func (ctx *Context) HTML(status int, name string) {
|
||||
ctx.Context.HTML(status, name)
|
||||
}
|
||||
|
||||
// Success responses template with status http.StatusOK.
|
||||
func (c *Context) Success(name string) {
|
||||
c.HTML(http.StatusOK, name)
|
||||
}
|
||||
|
||||
// JSONSuccess responses JSON with status http.StatusOK.
|
||||
func (c *Context) JSONSuccess(data interface{}) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
// RenderWithErr used for page has form validation but need to prompt error to users.
|
||||
func (ctx *Context) RenderWithErr(msg, tpl string, f interface{}) {
|
||||
if f != nil {
|
||||
form.Assign(f, ctx.Data)
|
||||
}
|
||||
ctx.Flash.ErrorMsg = msg
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
ctx.HTML(http.StatusOK, tpl)
|
||||
}
|
||||
|
||||
// Handle handles and logs error by given status.
|
||||
func (ctx *Context) Handle(status int, title string, err error) {
|
||||
@@ -55,6 +87,27 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
||||
ctx.HTML(status, fmt.Sprintf("status/%d", status))
|
||||
}
|
||||
|
||||
// NotFound renders the 404 page.
|
||||
func (ctx *Context) NotFound() {
|
||||
ctx.Handle(http.StatusNotFound, "", nil)
|
||||
}
|
||||
|
||||
// ServerError renders the 500 page.
|
||||
func (c *Context) ServerError(title string, err error) {
|
||||
c.Handle(http.StatusInternalServerError, title, err)
|
||||
}
|
||||
|
||||
// NotFoundOrServerError use error check function to determine if the error
|
||||
// is about not found. It responses with 404 status code for not found error,
|
||||
// or error context description for logging purpose of 500 server error.
|
||||
func (c *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
|
||||
if errck(err) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
c.ServerError(title, err)
|
||||
}
|
||||
|
||||
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
|
||||
modtime := time.Now()
|
||||
for _, p := range params {
|
||||
@@ -97,7 +150,7 @@ func Contexter() macaron.Handler {
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
|
||||
// Get user from session if logined.
|
||||
// ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Context, ctx.Session)
|
||||
ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Context, ctx.Session)
|
||||
|
||||
if ctx.User != nil {
|
||||
ctx.IsLogged = true
|
||||
|
201
models/error.go
Normal file
201
models/error.go
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrNameReserved struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrNameReserved(err error) bool {
|
||||
_, ok := err.(ErrNameReserved)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNameReserved) Error() string {
|
||||
return fmt.Sprintf("name is reserved [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrNamePatternNotAllowed struct {
|
||||
Pattern string
|
||||
}
|
||||
|
||||
func IsErrNamePatternNotAllowed(err error) bool {
|
||||
_, ok := err.(ErrNamePatternNotAllowed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNamePatternNotAllowed) Error() string {
|
||||
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
|
||||
}
|
||||
|
||||
// ____ ___
|
||||
// | | \______ ___________
|
||||
// | | / ___// __ \_ __ \
|
||||
// | | /\___ \\ ___/| | \/
|
||||
// |______//____ >\___ >__|
|
||||
// \/ \/
|
||||
|
||||
type ErrUserAlreadyExist struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrUserAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrUserAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("user already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrEmailAlreadyUsed struct {
|
||||
Email string
|
||||
}
|
||||
|
||||
func IsErrEmailAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrEmailAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
type ErrUserOwnRepos struct {
|
||||
UID int64
|
||||
}
|
||||
|
||||
func IsErrUserOwnRepos(err error) bool {
|
||||
_, ok := err.(ErrUserOwnRepos)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserOwnRepos) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ ___. .__ .__ ____ __.
|
||||
// \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__.
|
||||
// | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | |
|
||||
// | | | | / \_\ \ |_| \ \___ | | \ ___/\___ |
|
||||
// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
|
||||
// \/ \/ \/ \/\/
|
||||
|
||||
type ErrKeyUnableVerify struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
func IsErrKeyUnableVerify(err error) bool {
|
||||
_, ok := err.(ErrKeyUnableVerify)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyUnableVerify) Error() string {
|
||||
return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
|
||||
}
|
||||
|
||||
type ErrKeyNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
func IsErrKeyNotExist(err error) bool {
|
||||
_, ok := err.(ErrKeyNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
type ErrKeyAlreadyExist struct {
|
||||
OwnerID int64
|
||||
Content string
|
||||
}
|
||||
|
||||
func IsErrKeyAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrKeyAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
}
|
||||
|
||||
type ErrKeyNameAlreadyUsed struct {
|
||||
OwnerID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrKeyNameAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrKeyNameAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
type ErrKeyAccessDenied struct {
|
||||
UserID int64
|
||||
KeyID int64
|
||||
Note string
|
||||
}
|
||||
|
||||
func IsErrKeyAccessDenied(err error) bool {
|
||||
_, ok := err.(ErrKeyAccessDenied)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyAccessDenied) Error() string {
|
||||
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
|
||||
err.UserID, err.KeyID, err.Note)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNotExist struct {
|
||||
ID int64
|
||||
KeyID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrDeployKeyNotExist(err error) bool {
|
||||
_, ok := err.(ErrDeployKeyNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyAlreadyExist struct {
|
||||
KeyID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrDeployKeyAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrDeployKeyAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNameAlreadyUsed struct {
|
||||
RepoID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrDeployKeyNameAlreadyUsed(err error) bool {
|
||||
_, ok := err.(ErrDeployKeyNameAlreadyUsed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
}
|
12
models/errors/errors.go
Normal file
12
models/errors/errors.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// New is a wrapper of real errors.New function.
|
||||
func New(text string) error {
|
||||
return errors.New(text)
|
||||
}
|
45
models/errors/user.go
Normal file
45
models/errors/user.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
type EmptyName struct{}
|
||||
|
||||
func IsEmptyName(err error) bool {
|
||||
_, ok := err.(EmptyName)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err EmptyName) Error() string {
|
||||
return "empty name"
|
||||
}
|
||||
|
||||
type UserNotExist struct {
|
||||
UserID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsUserNotExist(err error) bool {
|
||||
_, ok := err.(UserNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err UserNotExist) Error() string {
|
||||
return fmt.Sprintf("user does not exist [user_id: %d, name: %s]", err.UserID, err.Name)
|
||||
}
|
||||
|
||||
type UserNotKeyOwner struct {
|
||||
KeyID int64
|
||||
}
|
||||
|
||||
func IsUserNotKeyOwner(err error) bool {
|
||||
_, ok := err.(UserNotKeyOwner)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err UserNotKeyOwner) Error() string {
|
||||
return fmt.Sprintf("user is not the owner of public key [key_id: %d]", err.KeyID)
|
||||
}
|
187
models/user.go
187
models/user.go
@@ -2,14 +2,29 @@ package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"dev.sigpipe.me/dashie/git.txt/models/errors"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"crypto/subtle"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/tool"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"dev.sigpipe.me/dashie/git.txt/setting"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserName string `xorm:"UNIQUE NOT NULL"`
|
||||
Password string `xorm:"NOT NULL"`
|
||||
LowerName string `xorm:"UNIQUE NOT NULL"`
|
||||
Email string `xorm:"NOT NULL"`
|
||||
|
||||
Password string `xorm:"NOT NULL"`
|
||||
Rands string `xorm:"VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)"`
|
||||
|
||||
// Permissions
|
||||
IsAdmin bool `xorm:"DEFAULT 0"`
|
||||
IsActive bool `xorm:"DEFAULT 0"`
|
||||
@@ -41,4 +56,174 @@ type SshKey struct {
|
||||
|
||||
// Relations
|
||||
// UserID
|
||||
}
|
||||
|
||||
func countUsers(e Engine) int64 {
|
||||
count, _ := e.Where("type=0").Count(new(User))
|
||||
return count
|
||||
}
|
||||
|
||||
// CountUsers returns number of users.
|
||||
func CountUsers() int64 {
|
||||
return countUsers(x)
|
||||
}
|
||||
|
||||
func getUserByID(e Engine, id int64) (*User, error) {
|
||||
u := new(User)
|
||||
has, err := e.Id(id).Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, errors.UserNotExist{id, ""}
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetUserByID returns the user object by given ID if exists.
|
||||
func GetUserByID(id int64) (*User, error) {
|
||||
return getUserByID(x, id)
|
||||
}
|
||||
|
||||
// IsUserExist checks if given user name exist,
|
||||
// the user name should be noncased unique.
|
||||
// If uid is presented, then check will rule out that one,
|
||||
// it is used when update a user name in settings page.
|
||||
func IsUserExist(uid int64, name string) (bool, error) {
|
||||
if len(name) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return x.Where("id != ?", uid).Get(&User{LowerName: strings.ToLower(name)})
|
||||
}
|
||||
|
||||
var (
|
||||
reservedUsernames = []string{"assets", "css", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
|
||||
reservedUserPatterns = []string{"*.keys"}
|
||||
)
|
||||
|
||||
// isUsableName checks if name is reserved or pattern of name is not allowed
|
||||
// based on given reserved names and patterns.
|
||||
// Names are exact match, patterns can be prefix or suffix match with placeholder '*'.
|
||||
func isUsableName(names, patterns []string, name string) error {
|
||||
name = strings.TrimSpace(strings.ToLower(name))
|
||||
if utf8.RuneCountInString(name) == 0 {
|
||||
return errors.EmptyName{}
|
||||
}
|
||||
|
||||
for i := range names {
|
||||
if name == names[i] {
|
||||
return ErrNameReserved{name}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pat := range patterns {
|
||||
if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
|
||||
(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
|
||||
return ErrNamePatternNotAllowed{pat}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsUsableUsername(name string) error {
|
||||
return isUsableName(reservedUsernames, reservedUserPatterns, name)
|
||||
}
|
||||
|
||||
// EncodePasswd encodes password to safe format.
|
||||
func (u *User) EncodePasswd() {
|
||||
newPasswd := pbkdf2.Key([]byte(u.Password), []byte(u.Salt), 10000, 50, sha256.New)
|
||||
u.Password = fmt.Sprintf("%x", newPasswd)
|
||||
}
|
||||
|
||||
// ValidatePassword checks if given password matches the one belongs to the user.
|
||||
func (u *User) ValidatePassword(passwd string) bool {
|
||||
newUser := &User{Password: passwd, Salt: u.Salt}
|
||||
newUser.EncodePasswd()
|
||||
return subtle.ConstantTimeCompare([]byte(u.Password), []byte(newUser.Password)) == 1
|
||||
}
|
||||
|
||||
// GetUserSalt returns a ramdom user salt token.
|
||||
func GetUserSalt() (string, error) {
|
||||
return tool.RandomString(10)
|
||||
}
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
func UserPath(userName string) string {
|
||||
return filepath.Join(setting.RepositoryRoot, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
// Create a new user and do some validation
|
||||
func CreateUser(u *User) (err error) {
|
||||
if err = IsUsableUsername(u.UserName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isExist, err := IsUserExist(0, u.UserName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrUserAlreadyExist{u.UserName}
|
||||
}
|
||||
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
u.LowerName = strings.ToLower(u.UserName)
|
||||
|
||||
if u.Rands, err = GetUserSalt(); err != nil {
|
||||
return err
|
||||
}
|
||||
if u.Salt, err = GetUserSalt(); err != nil {
|
||||
return err
|
||||
}
|
||||
u.EncodePasswd()
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(u); err != nil {
|
||||
return err
|
||||
} else if err = os.MkdirAll(UserPath(u.UserName), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// Update an user
|
||||
func updateUser(e Engine, u *User) error {
|
||||
u.LowerName = strings.ToLower(u.UserName)
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
_, err := e.Id(u.ID).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateUser(u *User) error {
|
||||
return updateUser(x, u)
|
||||
}
|
||||
|
||||
// Login validates user name and password.
|
||||
func UserLogin(username, password string) (*User, error) {
|
||||
var user *User
|
||||
if strings.Contains(username, "@") {
|
||||
user = &User{Email: strings.ToLower(username)}
|
||||
} else {
|
||||
user = &User{LowerName: strings.ToLower(username)}
|
||||
}
|
||||
|
||||
hasUser, err := x.Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasUser {
|
||||
if user.ValidatePassword(password) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errors.UserNotExist{user.ID, user.UserName}
|
||||
}
|
||||
|
||||
return nil, errors.UserNotExist{user.ID, user.UserName}
|
||||
}
|
@@ -2,6 +2,12 @@ package user
|
||||
|
||||
import (
|
||||
"dev.sigpipe.me/dashie/git.txt/context"
|
||||
"dev.sigpipe.me/dashie/git.txt/setting"
|
||||
"dev.sigpipe.me/dashie/git.txt/stuff/form"
|
||||
"dev.sigpipe.me/dashie/git.txt/models"
|
||||
log "gopkg.in/clog.v1"
|
||||
"dev.sigpipe.me/dashie/git.txt/models/errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -12,6 +18,14 @@ const (
|
||||
RESET_PASSWORD = "user/auth/reset_password"
|
||||
)
|
||||
|
||||
// isValidRedirect returns false if the URL does not redirect to same site.
|
||||
// False: //url, http://url
|
||||
// True: /url
|
||||
func isValidRedirect(url string) bool {
|
||||
return len(url) >= 2 && url[0] == '/' && url[1] != '/'
|
||||
}
|
||||
|
||||
// Login
|
||||
func Login(ctx *context.Context) {
|
||||
ctx.Title("login.title")
|
||||
|
||||
@@ -20,7 +34,132 @@ func Login(ctx *context.Context) {
|
||||
ctx.HTML(200, LOGIN)
|
||||
}
|
||||
|
||||
func LoginPost(ctx *context.Context, f form.Login) {
|
||||
ctx.Title("login.title")
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.Success(LOGIN)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := models.UserLogin(f.UserName, f.Password)
|
||||
if err != nil {
|
||||
if errors.IsUserNotExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), LOGIN, &f)
|
||||
} else {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
afterLogin(ctx, u, f.Remember)
|
||||
return
|
||||
}
|
||||
|
||||
// After login func, may be useful with Two-Factor
|
||||
func afterLogin(ctx *context.Context, u *models.User, remember bool) {
|
||||
if remember {
|
||||
days := 86400 * setting.LoginRememberDays
|
||||
ctx.SetCookie(setting.CookieUserName, u.UserName, days, setting.AppSubURL, "", setting.CookieSecure, true)
|
||||
ctx.SetSuperSecureCookie(u.Rands+u.Password, setting.CookieRememberName, u.UserName, days, setting.AppSubURL, "", setting.CookieSecure, true)
|
||||
}
|
||||
|
||||
ctx.Session.Set("uid", u.ID)
|
||||
ctx.Session.Set("uname", u.UserName)
|
||||
|
||||
// Clear CSRF and force regenerate one
|
||||
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
|
||||
if setting.EnableLoginStatusCookie {
|
||||
ctx.SetCookie(setting.LoginStatusCookieName, "true", 0, setting.AppSubURL)
|
||||
}
|
||||
|
||||
redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to"))
|
||||
if isValidRedirect(redirectTo) {
|
||||
ctx.Redirect(redirectTo)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
|
||||
// Registration
|
||||
func Register(ctx *context.Context) {
|
||||
ctx.Title("register.title")
|
||||
if ! setting.CanRegister {
|
||||
ctx.Flash.Error(ctx.Tr("register.not_allowed"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.HTML(200, REGISTER)
|
||||
}
|
||||
|
||||
func RegisterPost(ctx *context.Context, f form.Register) {
|
||||
ctx.Title("register.title")
|
||||
|
||||
if ! setting.CanRegister {
|
||||
ctx.Flash.Error(ctx.Tr("register.not_allowed"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, REGISTER)
|
||||
return
|
||||
}
|
||||
|
||||
if f.Password != f.Repeat {
|
||||
ctx.Data["Err_Password"] = true
|
||||
ctx.Data["Err_Retype"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), REGISTER, &f)
|
||||
return
|
||||
}
|
||||
|
||||
u := &models.User{
|
||||
UserName: f.UserName,
|
||||
Email: f.Email,
|
||||
Password: f.Password,
|
||||
IsActive: true, // FIXME: implement user activation by email
|
||||
}
|
||||
if err := models.CreateUser(u); err != nil {
|
||||
switch {
|
||||
case models.IsErrUserAlreadyExist(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), REGISTER, &f)
|
||||
case models.IsErrNameReserved(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_reserved"), REGISTER, &f)
|
||||
case models.IsErrNamePatternNotAllowed(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_pattern_not_allowed"), REGISTER, &f)
|
||||
default:
|
||||
ctx.Handle(500, "CreateUser", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Trace("Account created: %s", u.UserName)
|
||||
|
||||
// Auto set Admin if first user
|
||||
if models.CountUsers() == 1 {
|
||||
u.IsAdmin = true
|
||||
u.IsActive = true // bypass email activation
|
||||
if err := models.UpdateUser(u); err != nil {
|
||||
ctx.Handle(500, "UpdateUser", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: send activation email
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login")
|
||||
}
|
||||
|
||||
// Logout
|
||||
func Logout(ctx *context.Context) {
|
||||
ctx.Session.Delete("uid")
|
||||
ctx.Session.Delete("uname")
|
||||
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL)
|
||||
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL)
|
||||
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL)
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
@@ -78,7 +78,14 @@ var (
|
||||
CSRFCookieName string
|
||||
|
||||
// Security settings
|
||||
SecretKey string
|
||||
InstallLock bool
|
||||
SecretKey string
|
||||
LoginRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
CookieSecure bool
|
||||
EnableLoginStatusCookie bool
|
||||
LoginStatusCookieName string
|
||||
|
||||
// Cache settings
|
||||
CacheAdapter string
|
||||
@@ -225,6 +232,17 @@ func InitConfig() {
|
||||
Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
|
||||
dateLangs = Cfg.Section("i18n.datelang").KeysHash()
|
||||
|
||||
sec = Cfg.Section("security")
|
||||
InstallLock = sec.Key("INSTALL_LOCK").MustBool()
|
||||
SecretKey = sec.Key("SECRET_KEY").String()
|
||||
LoginRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
|
||||
CookieUserName = sec.Key("COOKIE_USERNAME").String()
|
||||
CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
|
||||
CookieSecure = sec.Key("COOKIE_SECURE").MustBool(false)
|
||||
EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false)
|
||||
LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status")
|
||||
|
||||
|
||||
initLogging()
|
||||
initSession()
|
||||
initCache()
|
||||
|
@@ -45,12 +45,26 @@ footer .right {
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
.form-signin input[id="user_name"] {
|
||||
margin-bottom: -15px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
.form-signin input[id="email"] {
|
||||
margin-bottom: -15px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[id="password"] {
|
||||
margin-bottom: -15px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.form-signin input[id="repeat"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
62
stuff/auth/auth.go
Normal file
62
stuff/auth/auth.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"dev.sigpipe.me/dashie/git.txt/models"
|
||||
"dev.sigpipe.me/dashie/git.txt/models/errors"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
log "gopkg.in/clog.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
func IsAPIPath(url string) bool {
|
||||
return strings.HasPrefix(url, "/api/")
|
||||
}
|
||||
|
||||
// SignedInID returns the id of signed in user.
|
||||
func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
|
||||
if !models.HasEngine {
|
||||
return 0
|
||||
}
|
||||
|
||||
uid := sess.Get("uid")
|
||||
if uid == nil {
|
||||
return 0
|
||||
}
|
||||
if id, ok := uid.(int64); ok {
|
||||
if _, err := models.GetUserByID(id); err != nil {
|
||||
if !errors.IsUserNotExist(err) {
|
||||
log.Error(2, "GetUserByID: %v", err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SignedInUser returns the user object of signed user.
|
||||
// It returns a bool value to indicate whether user uses basic auth or not.
|
||||
func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool) {
|
||||
if !models.HasEngine {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
uid := SignedInID(ctx, sess)
|
||||
|
||||
if uid <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
u, err := models.GetUserByID(uid)
|
||||
if err != nil {
|
||||
log.Error(4, "GetUserById: %v", err)
|
||||
return nil, false
|
||||
}
|
||||
return u, false
|
||||
}
|
29
stuff/form/auth.go
Normal file
29
stuff/form/auth.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// Register
|
||||
type Register struct {
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Repeat string
|
||||
}
|
||||
|
||||
func (f *Register) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// Login
|
||||
type Login struct {
|
||||
UserName string `binding:"Required;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Remember bool
|
||||
}
|
||||
|
||||
func (f *Login) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
153
stuff/form/form.go
Normal file
153
stuff/form/form.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
"github.com/Unknwon/com"
|
||||
"strings"
|
||||
"regexp"
|
||||
"fmt"
|
||||
log "gopkg.in/clog.v1"
|
||||
)
|
||||
|
||||
const ERR_ALPHA_DASH_DOT_SLASH = "AlphaDashDotSlashError"
|
||||
|
||||
var AlphaDashDotSlashPattern = regexp.MustCompile("[^\\d\\w-_\\./]")
|
||||
|
||||
func init() {
|
||||
binding.SetNameMapper(com.ToSnakeCase)
|
||||
binding.AddRule(&binding.Rule{
|
||||
IsMatch: func(rule string) bool {
|
||||
return rule == "AlphaDashDotSlash"
|
||||
},
|
||||
IsValid: func(errs binding.Errors, name string, v interface{}) (bool, binding.Errors) {
|
||||
if AlphaDashDotSlashPattern.MatchString(fmt.Sprintf("%v", v)) {
|
||||
errs.Add([]string{name}, ERR_ALPHA_DASH_DOT_SLASH, "AlphaDashDotSlash")
|
||||
return false, errs
|
||||
}
|
||||
return true, errs
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type Form interface {
|
||||
binding.Validator
|
||||
}
|
||||
|
||||
// Assign assign form values back to the template data.
|
||||
func Assign(form interface{}, data map[string]interface{}) {
|
||||
typ := reflect.TypeOf(form)
|
||||
val := reflect.ValueOf(form)
|
||||
|
||||
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
|
||||
} else if len(fieldName) == 0 {
|
||||
fieldName = com.ToSnakeCase(field.Name)
|
||||
}
|
||||
|
||||
data[fieldName] = val.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
func getRuleBody(field reflect.StructField, prefix string) string {
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if strings.HasPrefix(rule, prefix) {
|
||||
return rule[len(prefix) : len(rule)-1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getSize(field reflect.StructField) string {
|
||||
return getRuleBody(field, "Size(")
|
||||
}
|
||||
|
||||
func getMinSize(field reflect.StructField) string {
|
||||
return getRuleBody(field, "MinSize(")
|
||||
}
|
||||
|
||||
func getMaxSize(field reflect.StructField) string {
|
||||
return getRuleBody(field, "MaxSize(")
|
||||
}
|
||||
|
||||
func getInclude(field reflect.StructField) string {
|
||||
return getRuleBody(field, "Include(")
|
||||
}
|
||||
|
||||
func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
|
||||
log.Trace("Validating form")
|
||||
|
||||
if errs.Len() == 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
Assign(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 errs[0].FieldNames[0] == field.Name {
|
||||
data["Err_"+field.Name] = true
|
||||
|
||||
trName := field.Tag.Get("locale")
|
||||
if len(trName) == 0 {
|
||||
trName = l.Tr("form." + field.Name)
|
||||
} else {
|
||||
trName = l.Tr(trName)
|
||||
}
|
||||
|
||||
switch errs[0].Classification {
|
||||
case binding.ERR_REQUIRED:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.require_error")
|
||||
case binding.ERR_ALPHA_DASH:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
|
||||
case binding.ERR_ALPHA_DASH_DOT:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
|
||||
case ERR_ALPHA_DASH_DOT_SLASH:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_slash_error")
|
||||
case binding.ERR_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.size_error", getSize(field))
|
||||
case binding.ERR_MIN_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.min_size_error", getMinSize(field))
|
||||
case binding.ERR_MAX_SIZE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.max_size_error", getMaxSize(field))
|
||||
case binding.ERR_EMAIL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.url_error")
|
||||
case binding.ERR_INCLUDE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.include_error", getInclude(field))
|
||||
default:
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
|
||||
}
|
||||
return errs
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
@@ -4,8 +4,12 @@ import (
|
||||
"encoding/hex"
|
||||
"crypto/sha1"
|
||||
"crypto/md5"
|
||||
"math/big"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// MD5Bytes encodes string to MD5 bytes.
|
||||
func MD5Bytes(str string) []byte {
|
||||
m := md5.New()
|
||||
@@ -31,4 +35,31 @@ func ShortSHA1(sha1 string) string {
|
||||
return sha1[:10]
|
||||
}
|
||||
return sha1
|
||||
}
|
||||
|
||||
// RandomString returns generated random string in given length of characters.
|
||||
// It also returns possible error during generation.
|
||||
func RandomString(n int) (string, error) {
|
||||
buffer := make([]byte, n)
|
||||
max := big.NewInt(int64(len(alphanum)))
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
index, err := randomInt(max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer[i] = alphanum[index]
|
||||
}
|
||||
|
||||
return string(buffer), nil
|
||||
}
|
||||
|
||||
func randomInt(max *big.Int) (int, error) {
|
||||
rand, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(rand.Int64()), nil
|
||||
}
|
@@ -1,20 +1,24 @@
|
||||
{{if .Flash.ErrorMsg}}
|
||||
<div class="ui negative message">
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>{{.Flash.ErrorMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Flash.WarningMsg}}
|
||||
<div class="ui warning message">
|
||||
<div class="alert alert-warning alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>{{.Flash.WarningMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Flash.SuccessMsg}}
|
||||
<div class="ui positive message">
|
||||
<div class="alert alert-success alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>{{.Flash.SuccessMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Flash.InfoMsg}}
|
||||
<div class="ui info message">
|
||||
<div class="alert alert-info alert-dismissible">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<p>{{.Flash.InfoMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
@@ -45,10 +45,10 @@
|
||||
<li><a href="{{AppSubURL}}/new">New git.txt</a></li>
|
||||
{{end}}
|
||||
{{if .IsLogged}}
|
||||
<li><a href="{{AppSubURL}}/logout">Logout</a></li>
|
||||
<li><a href="{{AppSubURL}}/user/logout">Logout</a></li>
|
||||
{{else}}
|
||||
{{if CanRegister}}
|
||||
<li><a href="{{AppSubURL}}/register">Register</a></li>
|
||||
<li><a href="{{AppSubURL}}/user/register">Register</a></li>
|
||||
{{end}}
|
||||
<li><a href="{{AppSubURL}}/user/login">Login</a></li>
|
||||
{{end}}
|
||||
@@ -58,6 +58,7 @@
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{{template "base/alert" .}}
|
||||
{{/*
|
||||
</div>
|
||||
</body>
|
||||
|
@@ -1,14 +1,19 @@
|
||||
{{template "base/head" .}}
|
||||
|
||||
|
||||
<form class="form-signin" action="{{.Link}}" method="post">
|
||||
{{.CSRFTokenHTML}}
|
||||
<h2 class="form-signin-heading">{{.i18n.Tr "login.sign_in"}}</h2>
|
||||
{{template "base/alert" .}}
|
||||
<label for="inputEmail" class="sr-only">{{.i18n.Tr "login.email"}}</label>
|
||||
<input type="email" id="inputEmail" class="form-control {{if .Err_UserName}}error{{end}}" placeholder='{{.i18n.Tr "login.email_placeholder"}}' required autofocus>
|
||||
<label for="inputPassword" class="sr-only">{{.i18n.Tr "login.password"}}</label>
|
||||
<input type="password" id="inputPassword" class="form-control {{if .Err_UserName}}error{{end}}" placeholder='{{.i18n.Tr "login.password_placeholder"}}' required>
|
||||
|
||||
<div class="user_name form-group {{if .Err_UserName}}has-error{{end}}">
|
||||
<label for="user_name" class="sr-only">{{.i18n.Tr "login.username"}}</label>
|
||||
<input type="string" name="user_name" id="user_name" class="form-control" placeholder='{{.i18n.Tr "login.username_placeholder"}}' required autofocus>
|
||||
</div>
|
||||
|
||||
<div class="password form-group {{if .Err_Password}}has-error{{end}}">
|
||||
<label for="password" class="sr-only">{{.i18n.Tr "login.password"}}</label>
|
||||
<input type="password" name="password" id="password" class="form-control" placeholder='{{.i18n.Tr "login.password_placeholder"}}' required>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" value="remember-me"> {{.i18n.Tr "login.remember_me"}}
|
||||
|
30
templates/user/auth/register.tmpl
Normal file
30
templates/user/auth/register.tmpl
Normal file
@@ -0,0 +1,30 @@
|
||||
{{template "base/head" .}}
|
||||
|
||||
<form class="form-signin" action="{{.Link}}" method="post">
|
||||
{{.CSRFTokenHTML}}
|
||||
<h2 class="form-signin-heading">{{.i18n.Tr "register.register"}}</h2>
|
||||
|
||||
<div class="user_name form-group {{if .Err_UserName}}has-error{{end}}">
|
||||
<label for="user_name" class="sr-only">{{.i18n.Tr "login.username"}}</label>
|
||||
<input type="text" name="user_name" id="user_name" class="form-control" value="{{.user_name}}" placeholder='{{.i18n.Tr "login.username_placeholder"}}' required autofocus>
|
||||
</div>
|
||||
|
||||
<div class="email form-group {{if .Err_Email}}has-error{{end}}">
|
||||
<label for="email" class="sr-only">{{.i18n.Tr "login.email"}}</label>
|
||||
<input type="email" name="email" id="email" class="form-control" value="{{.email}}" placeholder='{{.i18n.Tr "login.email_placeholder"}}' required autofocus>
|
||||
</div>
|
||||
|
||||
<div class="password form-group {{if .Err_Password}}has-error{{end}}">
|
||||
<label for="password" class="sr-only">{{.i18n.Tr "login.password"}}</label>
|
||||
<input type="password" name="password" id="password" class="form-control" value="{{.password}}" placeholder='{{.i18n.Tr "login.password_placeholder"}}' required>
|
||||
</div>
|
||||
|
||||
<div class="repeat form-group {{if .Err_Repeat}}has-error{{end}}">
|
||||
<label for="repeat" class="sr-only">{{.i18n.Tr "login.password"}}</label>
|
||||
<input type="password" name="repeat" id="repeat" class="form-control" value="{{.repeat}}" placeholder='{{.i18n.Tr "login.repeat_password_placeholder"}}' required>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{{.i18n.Tr "login.sign_in"}}</button>
|
||||
</form>
|
||||
|
||||
{{template "base/footer" .}}
|
Reference in New Issue
Block a user