Your Name 4 years ago
commit
963e70dbcb
  1. 76
      README.md
  2. 12
      bee.json
  3. BIN
      compiler.jar
  4. 0
      conf/app.ini
  5. 91
      conf/compress.json
  6. 144
      conf/global/app.ini
  7. 0
      conf/global/locale_en-US.ini
  8. 0
      conf/global/locale_zh-CN.ini
  9. 49
      modules/area/form.go
  10. 1
      modules/area/util.go
  11. 42
      modules/article/form.go
  12. 166
      modules/attachment/storage.go
  13. 308
      modules/auth/auth.go
  14. 61
      modules/auth/auth_test.go
  15. 421
      modules/auth/form.go
  16. 85
      modules/auth/form_oauth.go
  17. 76
      modules/auth/mail.go
  18. 61
      modules/auth/user_test.go
  19. 51
      modules/equip/form.go
  20. 43
      modules/mailer/mail.go
  21. 119
      modules/mailer/mailer.go
  22. 66
      modules/models/alarm.go
  23. 55
      modules/models/area.go
  24. 118
      modules/models/article.go
  25. 143
      modules/models/attachment.go
  26. 127
      modules/models/auth.go
  27. 60
      modules/models/equip.go
  28. 74
      modules/models/orm_fields.go
  29. 46
      modules/models/orm_helper.go
  30. 54
      modules/models/person.go
  31. 60
      modules/models/sms.go
  32. 56
      modules/models/subsystem.go
  33. 58
      modules/models/subsystem_area.go
  34. 50
      modules/person/form.go
  35. 45
      modules/subsystem/form.go
  36. 129
      modules/utils/assert.go
  37. 56
      modules/utils/dayu.go
  38. 684
      modules/utils/forms.go
  39. 49
      modules/utils/markdown.go
  40. 164
      modules/utils/paginator.go
  41. 186
      modules/utils/sphinx.go
  42. 146
      modules/utils/template.go
  43. 414
      modules/utils/tools.go
  44. 149
      routers/admin/admin_area.go
  45. 149
      routers/admin/admin_base.go
  46. 32
      routers/admin/admin_dashboard.go
  47. 156
      routers/admin/admin_equip.go
  48. 156
      routers/admin/admin_person.go
  49. 15
      routers/admin/admin_report.go
  50. 155
      routers/admin/admin_subsystem.go
  51. 127
      routers/admin/admin_user.go
  52. 86
      routers/admin/base.go
  53. 99
      routers/api/alarm.go
  54. 54
      routers/api/area.go
  55. 207
      routers/api/equip.go
  56. 40
      routers/api/markdown.go
  57. 50
      routers/api/person.go
  58. 140
      routers/api/report.go
  59. 326
      routers/api/sms.go
  60. 19
      routers/api/sms_test.go
  61. 298
      routers/api/subsystem.go
  62. 21
      routers/api/xsrf.go
  63. 114
      routers/attachment/upload.go
  64. 313
      routers/auth/auth.go
  65. 194
      routers/auth/auth_social.go
  66. 193
      routers/auth/settings.go
  67. 574
      routers/base/base.go
  68. 15
      routers/base/errors.go
  69. 60
      routers/base/robot.go
  70. 29
      routers/base/tmp.go
  71. 483
      setting/conf.go
  72. 193
      smsp.go
  73. 1
      static/css/admin.min.css
  74. 5
      static/css/app.min.css
  75. 399
      static/css/ie7.min.css
  76. BIN
      static/css/img/diy/1_close.png
  77. BIN
      static/css/img/diy/1_open.png
  78. BIN
      static/css/img/diy/2.png
  79. BIN
      static/css/img/diy/3.png
  80. BIN
      static/css/img/diy/4.png
  81. BIN
      static/css/img/diy/5.png
  82. BIN
      static/css/img/diy/6.png
  83. BIN
      static/css/img/diy/7.png
  84. BIN
      static/css/img/diy/8.png
  85. BIN
      static/css/img/diy/9.png
  86. BIN
      static/css/img/line_conn.gif
  87. BIN
      static/css/img/loading.gif
  88. BIN
      static/css/img/zTreeStandard.gif
  89. BIN
      static/css/img/zTreeStandard.png
  90. 1
      static/css/jquery.bxslider.min.css
  91. 1
      static/css/lib.min.css
  92. 1
      static/css/responsive-slider.min.css
  93. 1
      static/css/swiper.min.css
  94. 1
      static/css/unslider.min.css
  95. 1
      static/css/ztree.min.css
  96. BIN
      static/fonts/FontAwesome.otf
  97. BIN
      static/fonts/Simple-Line-Icons.eot
  98. 211
      static/fonts/Simple-Line-Icons.svg
  99. BIN
      static/fonts/Simple-Line-Icons.ttf
  100. BIN
      static/fonts/Simple-Line-Icons.woff
  101. Some files were not shown because too many files have changed in this diff Show More

76
README.md

@ -0,0 +1,76 @@
# WeTalk
An open source project for Gopher community.
### Usage
```
go get -u hssc/gocms
cd $GOPATH/src/hssc/gocms
```
I suggest you [update all Dependencies](#dependencies)
Copy `conf/global/app.ini` to `conf/app.ini` and edit it. All configure has comment in it.
The files in `conf/` can overwrite `conf/global/` in runtime.
**Run WeTalk**
```
bee run watchall
```
### Dependencies
Contrib
* Beego [https://github.com/astaxie/beego](https://github.com/astaxie/beego) (develop branch)
* Social-Auth [https://github.com/beego/social-auth](https://github.com/beego/social-auth)
* Compress [https://github.com/beego/compress](https://github.com/beego/compress)
* i18n [https://github.com/beego/i18n](https://github.com/beego/i18n)
* Mysql [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
* goconfig [https://github.com/Unknwon/goconfig](https://github.com/Unknwon/goconfig)
* fsnotify [https://github.com/howeyc/fsnotify](https://github.com/howeyc/fsnotify)
* resize [https://github.com/nfnt/resize](https://github.com/nfnt/resize)
* blackfriday [https://github.com/slene/blackfriday](https://github.com/slene/blackfriday)
Plz Note: WeTalk always use Beego develop branch
```
go get -u github.com/astaxie/beego
cd $GOPATH/src/github.com/astaxie/beego
git checkout develop
```
Update all Dependencies
```
go get -u github.com/beego/social-auth
go get -u github.com/beego/compress
go get -u github.com/beego/i18n
go get -u github.com/go-sql-driver/mysql
go get -u github.com/Unknwon/goconfig
go get -u github.com/howeyc/fsnotify
go get -u github.com/nfnt/resize
go get -u github.com/slene/blackfriday
```
### Static Files
WeTalk use `Google Closure Compile` and `Yui Compressor` compress js and css files.
So you could need Java Runtime. Or close this feature in code by yourself.
### WeTalk in world
[Go China Community](http://bbs.go-china.org/)
### Contact
Maintain by [slene](https://github.com/slene)
## License
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).

12
bee.json

@ -0,0 +1,12 @@
{
"go_install": true,
"watch_ext": [],
"dir_structure":{
"watch_all": true,
"controllers": ".",
"models": ".",
"others": [
"$GOPATH/src/github.com/astaxie/beego"
]
}
}

BIN
compiler.jar

Binary file not shown.

0
conf/app.ini

91
conf/compress.json

@ -0,0 +1,91 @@
{
"Js": {
"SrcPath": "static_source/js",
"DistPath": "static/js",
"SrcURL": "static_source/js",
"DistURL": "static/js",
"Groups": {
"jquery": {
"DistFile": "jquery.min.js",
"SourceFiles": [
"amazeui/2.7.0/js/jquery.min.js"
],
"SkipFiles": [
"amazeui/2.7.0/js/jquery.min.js"
]
},
"app": {
"DistFile": "app.min.js",
"SourceFiles": [
"jquery.extend.js",
"jquery.paging.js",
"paging.js",
"echarts.js",
"amazeui/2.7.0/js/amazeui.js",
"amazeui/2.7.0/js/app.js"
]
},
"swiper": {
"DistFile": "swiper.jquery.min.js",
"SourceFiles": [
"swiper.jquery.js"
]
},
"ztree": {
"DistFile": "ztree.min.js",
"SourceFiles": [
"ztree/3.5.24/js/jquery.ztree.all.js"
]
},
"datetimepicker": {
"DistFile": "jquery.datetimepicker.min.js",
"SourceFiles": [
"jquery.datetimepicker.full.js"
]
},
"admin": {
"DistFile": "admin.min.js",
"SourceFiles": [
"admin.js"
]
}
}
},
"Css": {
"SrcPath": "static_source/css",
"DistPath": "static/css",
"SrcURL": "static_source/css",
"DistURL": "static/css",
"Groups": {
"lib": {
"DistFile": "lib.min.css",
"SourceFiles": [
"../js/amazeui/2.7.0/css/amazeui.css"
]
},
"admin": {
"DistFile": "admin.min.css",
"SourceFiles": [
"../js/amazeui/2.7.0/css/admin.css"
]
},
"ztree": {
"DistFile": "ztree.min.css",
"SourceFiles": [
"../js/ztree/3.5.24/css/zTreeStyle/zTreeStyle.css"
]
},
"datetimepicker": {
"Name": "datetimepicker",
"DistFile": "jquery.datetimepicker.css",
"SourceFiles": [
"jquery.datetimepicker.css"
]
}
}
}
}

144
conf/global/app.ini

@ -0,0 +1,144 @@
[app]
; run mode dev pro
run_mode = dev
; http bind port
http_port = 8092
; app name
app_name = 短信告警平台
; app host
app_host = 127.0.0.1:8092
; app root url for create link
app_url = http://127.0.0.1:8092/
; if host cannot match app_host then redirect to app_host
; this feature is for beego only http server
; if has a nginx as proxy can set this false
enforce_redirect = false
; top logo in navbar
app_logo = /static/img/logo.png
; per app usage random secret token
; !!! ensure it is unique
secret_key = SLANFINE32543456
; email active link live minutes
acitve_code_live_minutes = 180
; reset password link live minutes
resetpwd_code_live_minutes = 180
; max login retry times
login_max_retries = 5
; login failed block minutes
login_failed_blocks = 10
; browser session cookie life time.
; 0 is the best value.
session_life_time = 0
; default session gc time
; when time reached the session will destory
; except user enable remember login.
session_gc_time = 86400
; login remember days.
; remember and auto login.
; when the user auto login, will reset the remember days.
login_remember_days = 7
; use for store login remember info
cookie_remember_name = wetalk_magic
cookie_user_name = wetalk_powerful
; avatar url prefix
avatar_url = http://1.gravatar.com/avatar/
; date format
date_format = Y-m-d
datetime_format = Y-m-d H:i:s
datetime_short_format = Y-m-d H:i
; time zone of WeTalk system
time_zone = Asia/Shanghai
; enable reltime render markdown, skip cache
realtime_render_markdown = true
[oauth]
github_client_id = your_client_id
github_client_secret = your_client_secret
google_client_id = your_client_id
google_client_secret = your_client_secret
weibo_client_id = your_client_id
weibo_client_secret = your_client_secret
qq_client_id = your_client_id
qq_client_secret = your_client_secret
[session]
; beego session conf
session_provider = file
session_name = wetalk_sess
[sms]
sms_app_key = 23390918
sms_app_secrect = 26c7f468529a3acf1d753cd2d2b966f4
sms_sign_name = 新洲分公司
sms_template_code = SMS_10960050
[orm]
driver_name = mysql
data_source = root:@/smsp?charset=utf8&loc=Asia%2FShanghai
;driver_name = sqlite3
;data_source = data.db
max_idle_conn = 30
max_open_conn = 50
debug_log = true
[mailer]
; display username in mail
mail_name = WeTalk Community
; from email address
mail_from = example@example.com
; mail server setting
mail_host = 127.0.0.1:25
; if use exim / postfix as localhost mail server
; can set user and pass to blank
mail_user = example@example.com
mail_pass = ******
[image]
; image size
image_size_small = 348
image_size_middle = 520
; alphabets for create image url
image_link_alphabets = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
; if use nginx XSendFile then set it
; http://wiki.nginx.org/XSendfile
image_xsend = false
image_xsend_header = X-Accel-Redirect
[search]
enabled = true
native_search = false
sphinx_enabled = false
sphinx_host = 127.0.0.1:9306
sphinx_index = wetalk, wetalk_delta
sphinx_max_conn = 10
[robot]
uas = Googlebot|Googlebot-Mobile|Yahoo! Slurp|YodaoBot|Sosospider|sogou spider|MSNBot|MSNBot|360Spider
disallow =

0
conf/global/locale_en-US.ini

0
conf/global/locale_zh-CN.ini

49
modules/area/form.go

@ -0,0 +1,49 @@
package area
import (
"hssc/smsp/modules/models"
"hssc/smsp/modules/utils"
"github.com/astaxie/beego/validation"
)
type AreaAdminForm struct {
Create bool `form:"-"`
Id int `form:"-"`
Name string `valid:"Required;MaxSize(30)"`
Area int ``
//Categories []models.Category `form:"-"`
Order int ``
}
//func (form *AreaAdminForm) CategorySelectData() [][]string {
// data := make([][]string, 0, len(form.Categories))
// for _, cat := range form.Categories {
// data = append(data, []string{"category." + cat.Name, utils.ToStr(cat.Id)})
// }
// return data
//}
func (form *AreaAdminForm) Valid(v *validation.Validation) {
// qs := models.Areas()
// if models.CheckIsExist(qs, "Name", form.Name, form.Id) {
// v.SetError("Name", "admin.field_need_unique")
// }
}
func (form *AreaAdminForm) SetFromArea(cat *models.Area) {
utils.SetFormValues(cat, form)
if cat.Area != nil {
form.Area = cat.Area.Id
}
}
func (form *AreaAdminForm) SetToArea(cat *models.Area) {
utils.SetFormValues(form, cat, "Id")
if cat.Area == nil {
cat.Area = &models.Area{}
}
cat.Area.Id = form.Area
}

1
modules/area/util.go

@ -0,0 +1 @@
package area

42
modules/article/form.go

@ -0,0 +1,42 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package article
import (
"hssc/smsp/modules/models"
"hssc/smsp/modules/utils"
)
type ArticleAdminForm struct {
Create bool `form:"-"`
Uri string `valid:"Required;MaxSize(60);Match(/[0-9a-z-./]+/)"`
Title string `valid:"Required;MaxSize(60)"`
Content string `form:"type(textarea)" valid:"Required"`
TitleZhCn string `valid:"Required;MaxSize(60)"`
ContentZhCn string `form:"type(textarea)" valid:"Required"`
IsPublish bool ``
}
func (form *ArticleAdminForm) SetFromArticle(article *models.Article) {
utils.SetFormValues(article, form)
}
func (form *ArticleAdminForm) SetToArticle(article *models.Article) {
utils.SetFormValues(form, article)
article.ContentCache = utils.RenderMarkdown(article.Content)
article.ContentCacheZhCn = utils.RenderMarkdown(article.ContentZhCn)
}

166
modules/attachment/storage.go

@ -0,0 +1,166 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package attachment
import (
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"time"
"hssc/smsp/setting"
"github.com/nfnt/resize"
"github.com/astaxie/beego"
"hssc/smsp/modules/models"
"hssc/smsp/modules/utils"
)
func SaveImage(m *models.Image, r io.ReadSeeker, mime string, filename string, created time.Time) error {
var ext string
// test image mime type
switch mime {
case "image/jpeg":
ext = ".jpg"
case "image/png":
ext = ".png"
case "image/gif":
ext = ".gif"
default:
ext = filepath.Ext(filename)
switch ext {
case ".jpg", ".png", ".gif":
default:
return fmt.Errorf("unsupport image format `%s`", filename)
}
}
// decode image
var img image.Image
var err error
switch ext {
case ".jpg":
m.Ext = 1
img, err = jpeg.Decode(r)
case ".png":
m.Ext = 2
img, err = png.Decode(r)
case ".gif":
m.Ext = 3
img, err = gif.Decode(r)
}
if err != nil {
return err
}
m.Width = img.Bounds().Dx()
m.Height = img.Bounds().Dy()
m.Created = created
if err := m.Insert(); err != nil || m.Id <= 0 {
return err
}
path := GenImagePath(m)
os.MkdirAll(path, 0755)
fullPath := GenImageFilePath(m, 0)
if _, err := r.Seek(0, 0); err != nil {
return err
}
var file *os.File
if f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644); err != nil {
return err
} else {
file = f
}
if _, err := io.Copy(file, r); err != nil {
os.RemoveAll(fullPath)
return err
}
if ext != ".gif" {
if m.Width > setting.ImageSizeSmall {
if err := ImageResize(m, img, setting.ImageSizeSmall); err != nil {
os.RemoveAll(fullPath)
return err
}
}
if m.Width > setting.ImageSizeMiddle {
if err := ImageResize(m, img, setting.ImageSizeMiddle); err != nil {
os.RemoveAll(fullPath)
return err
}
}
}
return nil
}
func ImageResize(img *models.Image, im image.Image, width int) error {
savePath := GenImageFilePath(img, width)
im = resize.Resize(uint(width), 0, im, resize.Bilinear)
var file *os.File
if f, err := os.OpenFile(savePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644); err != nil {
return err
} else {
file = f
}
defer file.Close()
var err error
switch img.Ext {
case 1:
err = jpeg.Encode(file, im, &jpeg.Options{90})
case 2:
err = png.Encode(file, im)
default:
return fmt.Errorf("<ImageResize> unsupport image format")
}
return err
}
func GenImagePath(img *models.Image) string {
return "upload/img/" + beego.Date(img.Created, "y/m/d/s/") + utils.ToStr(img.Id) + "/"
}
func GenImageFilePath(img *models.Image, width int) string {
var size string
if width == 0 {
size = "full"
} else {
size = utils.ToStr(width)
}
return GenImagePath(img) + size + img.GetExt()
}

308
modules/auth/auth.go

@ -0,0 +1,308 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package auth
import (
"encoding/hex"
"fmt"
"github.com/astaxie/beego/context"
"strings"
// "time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
"github.com/astaxie/beego/session"
"hssc/smsp/modules/models"
"hssc/smsp/modules/utils"
"hssc/smsp/setting"
)
// CanRegistered checks if the username or e-mail is available.
func CanRegistered(userName string, email string) (bool, bool, error) {
cond := orm.NewCondition()
cond = cond.Or("UserName", userName).Or("Email", email)
var maps []orm.Params
o := orm.NewOrm()
n, err := o.QueryTable("user").SetCond(cond).Values(&maps, "UserName", "Email")
if err != nil {
return false, false, err
}
e1 := true
e2 := true
if n > 0 {
for _, m := range maps {
if e1 && orm.ToStr(m["UserName"]) == userName {
e1 = false
}
if e2 && orm.ToStr(m["Email"]) == email {
e2 = false
}
}
}
return e1, e2, nil
}
// check if exist user by username or email
func HasUser(user *models.User, username string) bool {
var err error
qs := orm.NewOrm()
if strings.IndexRune(username, '@') == -1 {
user.UserName = username
err = qs.Read(user, "UserName")
} else {
user.Email = username
err = qs.Read(user, "Email")
}
if err == nil {
return true
}
return false
}
// register create user
func RegisterUser(user *models.User, username, email, password string) error {
// use random salt encode password
salt := models.GetUserSalt()
pwd := utils.EncodePassword(password, salt)
user.UserName = strings.ToLower(username)
user.Email = strings.ToLower(email)
// save salt and encode password, use $ as split char
user.Password = fmt.Sprintf("%s$%s", salt, pwd)
// save md5 email value for gravatar
user.GrEmail = utils.EncodeMd5(user.Email)
// Use username as default nickname.
user.NickName = user.UserName
return user.Insert()
}
// set a new password to user
func SaveNewPassword(user *models.User, password string) error {
salt := models.GetUserSalt()
user.Password = fmt.Sprintf("%s$%s", salt, utils.EncodePassword(password, salt))
return user.Update("Password", "Rands", "Updated")
}
// get login redirect url from cookie
func GetLoginRedirect(ctx *context.Context) string {
loginRedirect := strings.TrimSpace(ctx.GetCookie("login_to"))
if utils.IsMatchHost(loginRedirect) == false {
loginRedirect = "/"
} else {
ctx.SetCookie("login_to", "", -1, "/")
}
return loginRedirect
}
// login user
func LoginUser(user *models.User, ctx *context.Context, remember bool) {
// werid way of beego session regenerate id...
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
ctx.Input.CruSession = beego.GlobalSessions.SessionRegenerateID(ctx.ResponseWriter, ctx.Request)
ctx.Input.CruSession.Set("auth_user_id", user.Id)
if remember {
WriteRememberCookie(user, ctx)
}
}
func WriteRememberCookie(user *models.User, ctx *context.Context) {
secret := utils.EncodeMd5(user.Rands + user.Password)
days := 86400 * setting.LoginRememberDays
ctx.SetCookie(setting.CookieUserName, user.UserName, days)
ctx.SetSecureCookie(secret, setting.CookieRememberName, user.UserName, days)
}
func DeleteRememberCookie(ctx *context.Context) {
ctx.SetCookie(setting.CookieUserName, "", -1)
ctx.SetCookie(setting.CookieRememberName, "", -1)
}
func LoginUserFromRememberCookie(user *models.User, ctx *context.Context) (success bool) {
userName := ctx.GetCookie(setting.CookieUserName)
if len(userName) == 0 {
return false
}
defer func() {
if !success {
DeleteRememberCookie(ctx)
}
}()
user.UserName = userName
if err := user.Read("UserName"); err != nil {
return false
}
secret := utils.EncodeMd5(user.Rands + user.Password)
value, _ := ctx.GetSecureCookie(secret, setting.CookieRememberName)
if value != userName {
return false
}
LoginUser(user, ctx, true)
return true
}
// logout user
func LogoutUser(ctx *context.Context) {
DeleteRememberCookie(ctx)
ctx.Input.CruSession.Delete("auth_user_id")
ctx.Input.CruSession.Flush()
beego.GlobalSessions.SessionDestroy(ctx.ResponseWriter, ctx.Request)
}
func GetUserIdFromSession(sess session.Store) int {
if id, ok := sess.Get("auth_user_id").(int); ok && id > 0 {
return id
}
return 0
}
// get user if key exist in session
func GetUserFromSession(user *models.User, sess session.Store) bool {
id := GetUserIdFromSession(sess)
if id > 0 {
u := models.User{Id: id}
if u.Read() == nil {
*user = u
return true
}
}
return false
}
// verify username/email and password
func VerifyUser(user *models.User, username, password string) (success bool) {
// search user by username or email
if HasUser(user, username) == false {
return
}
if VerifyPassword(password, user.Password) {
// success
success = true
// re-save discuz password
if len(user.Password) == 39 {
if err := SaveNewPassword(user, password); err != nil {
beego.Error("SaveNewPassword err: ", err.Error())
}
}
}
return
}
// compare raw password and encoded password
func VerifyPassword(rawPwd, encodedPwd string) bool {
// for discuz accounts
if len(encodedPwd) == 39 {
salt := encodedPwd[:6]
encoded := encodedPwd[7:]
return encoded == utils.EncodeMd5(utils.EncodeMd5(rawPwd)+salt)
}
// split
var salt, encoded string
if len(encodedPwd) > 11 {
salt = encodedPwd[:10]
encoded = encodedPwd[11:]
}
return utils.EncodePassword(rawPwd, salt) == encoded
}
// get user by erify code
func getVerifyUser(user *models.User, code string) bool {
if len(code) <= utils.TimeLimitCodeLength {
return false
}
// use tail hex username query user
hexStr := code[utils.TimeLimitCodeLength:]
if b, err := hex.DecodeString(hexStr); err == nil {
user.UserName = string(b)
if user.Read("UserName") == nil {
return true
}
}
return false
}
// verify active code when active account
func VerifyUserActiveCode(user *models.User, code string) bool {
minutes := setting.ActiveCodeLives
if getVerifyUser(user, code) {
// time limit code
prefix := code[:utils.TimeLimitCodeLength]
data := utils.ToStr(user.Id) + user.Email + user.UserName + user.Password + user.Rands
return utils.VerifyTimeLimitCode(data, minutes, prefix)
}
return false
}
// create a time limit code for user active
func CreateUserActiveCode(user *models.User, startInf interface{}) string {
minutes := setting.ActiveCodeLives
data := utils.ToStr(user.Id) + user.Email + user.UserName + user.Password + user.Rands
code := utils.CreateTimeLimitCode(data, minutes, startInf)
// add tail hex username
code += hex.EncodeToString([]byte(user.UserName))
return code
}
// verify code when reset password
func VerifyUserResetPwdCode(user *models.User, code string) bool {
minutes := setting.ResetPwdCodeLives
if getVerifyUser(user, code) {
// time limit code
prefix := code[:utils.TimeLimitCodeLength]
data := utils.ToStr(user.Id) + user.Email + user.UserName + user.Password + user.Rands + user.Updated.String()
return utils.VerifyTimeLimitCode(data, minutes, prefix)
}
return false
}
// create a time limit code for user reset password
func CreateUserResetPwdCode(user *models.User, startInf interface{}) string {
minutes := setting.ResetPwdCodeLives
data := utils.ToStr(user.Id) + user.Email + user.UserName + user.Password + user.Rands + user.Updated.String()
code := utils.CreateTimeLimitCode(data, minutes, startInf)
// add tail hex username
code += hex.EncodeToString([]byte(user.UserName))
return code
}

61
modules/auth/auth_test.go

@ -0,0 +1,61 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package auth
import (
"testing"
"github.com/astaxie/beego/orm"
. "hssc/smsp/modules/utils"
)
var encoded = "9e2a6b0a670d48bc9fae7f79503a0d7e888650ede413810ff762ae767181fb3e7cdec433d435ed2c671bf4b6ecc49ebae5c7"
func init() {
orm.RegisterDataBase("default", "mysql", "root:root@/wetalk?charset=utf8", 30)
orm.RunSyncdb("default", true, false)
}
func TestPasswordVerify(t *testing.T) {
pwd := "111111"
salt := "010101"
ThrowFailNow(t, AssertIs(EncodePassword(pwd, salt), encoded))
ThrowFail(t, AssertIs(VerifyPassword(pwd, salt, encoded), true))
ThrowFail(t, AssertIs(VerifyPassword(pwd, "fake", encoded), false))
ThrowFail(t, AssertIs(VerifyPassword("fake", salt, encoded), false))
}
func TestUserVerifyCode(t *testing.T) {
user := new(User)
user.UserName = "wetalk"
user.Email = "service@beego.me"
user.Password = encoded
user.Rands = GetRandomString(10)
SecretKey = encoded
ActiveCodeLives = 1
ResetPwdCodeLives = 1
ThrowFail(t, NewUser(user))
code := CreateUserActiveCode(user, nil)
ThrowFail(t, AssertIs(VerifyUserActiveCode(user, code), true))
ThrowFail(t, AssertIs(VerifyUserActiveCode(user, code+"1"), false))
code = CreateUserResetPwdCode(user, nil)
ThrowFail(t, AssertIs(VerifyUserResetPwdCode(user, code), true))
ThrowFail(t, AssertIs(VerifyUserResetPwdCode(user, code+"1"), false))
}

421
modules/auth/form.go

@ -0,0 +1,421 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package auth
import (
"strings"
"github.com/astaxie/beego/validation"
"github.com/beego/i18n"
"hssc/smsp/modules/models"
"hssc/smsp/modules/utils"
"hssc/smsp/setting"
)
// Register form
type RegisterForm struct {
UserName string `valid:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
Email string `valid:"Required;Email;MaxSize(80)"`
Password string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
PasswordRe string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
Captcha string `form:"type(captcha)" valid:"Required"`
CaptchaId string `form:"type(empty)"`
Locale i18n.Locale `form:"-"`
}
func (form *RegisterForm) Valid(v *validation.Validation) {
// Check if passwords of two times are same.
if form.Password != form.PasswordRe {
v.SetError("PasswordRe", "auth.repassword_not_match")
return
}
e1, e2, _ := CanRegistered(form.UserName, form.Email)
if !e1 {
v.SetError("UserName", "auth.username_already_taken")
}
if !e2 {
v.SetError("Email", "auth.email_already_taken")
}
if !setting.Captcha.Verify(form.CaptchaId, form.Captcha) {
v.SetError("Captcha", "auth.captcha_wrong")
}
}
func (form *RegisterForm) Labels() map[string]string {
return map[string]string{
"UserName": "auth.login_username",
"Email": "auth.login_email",
"Password": "auth.login_password",
"PasswordRe": "auth.retype_password",
"Captcha": "auth.captcha",
}
}
func (form *RegisterForm) Helps() map[string]string {
return map[string]string{
"UserName": form.Locale.Tr("valid.min_length_is", 5) + ", " + form.Locale.Tr("valid.only_contains", "a-z 0-9 - _"),
"Captcha": "auth.captcha_click_refresh",
}
}
func (form *RegisterForm) Placeholders() map[string]string {
return map[string]string{
"UserName": "auth.plz_enter_username",
"Email": "auth.plz_enter_email",
"Password": "auth.plz_enter_password",
"PasswordRe": "auth.plz_reenter_password",
"Captcha": "auth.plz_enter_captcha",
}
}
// Login form
type LoginForm struct {
UserName string `valid:"Required"`
Password string `form:"type(password)" valid:"Required"`
Remember bool
}
func (form *LoginForm) Labels() map[string]string {
return map[string]string{
"UserName": "auth.username_or_email",
"Password": "auth.login_password",
"Remember": "auth.login_remember_me",
}
}
// Forgot form
type ForgotForm struct {
Email string `valid:"Required;Email;MaxSize(80)"`
User *models.User `form:"-"`
}
func (form *ForgotForm) Labels() map[string]string {
return map[string]string{
"Email": "auth.login_email",
}
}
func (form *ForgotForm) Helps() map[string]string {
return map[string]string{
"Email": "auth.forgotform_email_help",
}
}
func (form *ForgotForm) Valid(v *validation.Validation) {
if HasUser(form.User, form.Email) == false {
v.SetError("Email", "auth.forgotform_wrong_email")
}
}
// Reset password form
type ResetPwdForm struct {
Password string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
PasswordRe string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
}
func (form *ResetPwdForm) Valid(v *validation.Validation) {
// Check if passwords of two times are same.
if form.Password != form.PasswordRe {
v.SetError("PasswordRe", "auth.repassword_not_match")
return
}
}
func (form *ResetPwdForm) Labels() map[string]string {
return map[string]string{
"PasswordRe": "auth.retype_password",
}
}
func (form *ResetPwdForm) Placeholders() map[string]string {
return map[string]string{
"Password": "auth.plz_enter_password",
"PasswordRe": "auth.plz_reenter_password",
}
}
// Settings Profile form
type ProfileForm struct {
NickName string `valid:"Required;MaxSize(30)"`
Url string `valid:"MaxSize(100)"`
Company string `valid:"MaxSize(30)"`
Location string `valid:"MaxSize(30)"`
Info string `form:"type(textarea)" valid:"MaxSize(255)"`
Email string `valid:"Required;Email;MaxSize(100)"`
PublicEmail bool `valid:""`
GrEmail string `valid:"Required;MaxSize(80)"`
Github string `valid:"MaxSize(30)"`
Twitter string `valid:"MaxSize(30)"`
Google string `valid:"MaxSize(30)"`
Weibo string `valid:"MaxSize(30)"`
Linkedin string `valid:"MaxSize(30)"`
Facebook string `valid:"MaxSize(30)"`
Lang int `form:"type(select);attr(rel,select2)" valid:""`
LangAdds models.SliceStringField `form:"type(select);attr(rel,select2);attr(multiple,multiple)" valid:""`
Locale i18n.Locale `form:"-"`
}
func (form *ProfileForm) LangSelectData() [][]string {
langs := setting.Langs
data := make([][]string, 0, len(langs))
for i, lang := range langs {
data = append(data, []string{lang, utils.ToStr(i)})
}
return data
}
func (form *ProfileForm) LangAddsSelectData() [][]string {
langs := setting.Langs
data := make([][]string, 0, len(langs))
for i, lang := range langs {
data = append(data, []string{lang, utils.ToStr(i)})
}
return data
}
func (form *ProfileForm) Valid(v *validation.Validation) {
if len(i18n.GetLangByIndex(form.Lang)) == 0 {
v.SetError("Lang", "Can not be empty")
}
if len(form.LangAdds) > 0 {
adds := make(models.SliceStringField, 0, len(form.LangAdds))
for _, l := range form.LangAdds {
if d, err := utils.StrTo(l).Int(); err == nil {
if form.Lang == d {
continue
}
if len(i18n.GetLangByIndex(form.Lang)) == 0 {
v.SetError("Lang", "Can not be empty")
return
}
adds = append(adds, l)
}
}
form.LangAdds = adds
}
}
func (form *ProfileForm) SetFromUser(user *models.User) {
utils.SetFormValues(user, form)
}
func (form *ProfileForm) SaveUserProfile(user *models.User) error {
// set md5 value if the value is an email
if strings.IndexRune(form.GrEmail, '@') != -1 {
form.GrEmail = utils.EncodeMd5(form.GrEmail)
}
changes := utils.FormChanges(user, form)
if len(changes) > 0 {
// if email changed then need re-active
if user.Email != form.Email {
user.IsActive = false
changes = append(changes, "IsActive")
}
utils.SetFormValues(form, user)
return user.Update(changes...)
}
return nil
}
func (form *ProfileForm) Labels() map[string]string {
return map[string]string{
"Lang": "auth.profile_lang",
"LangAdds": "auth.profile_lang_additional",
"NickName": "model.user_nickname",
"PublicEmail": "auth.profile_publicemail",
"GrEmail": "auth.profile_gremail",
"Info": "auth.profile_info",
"Company": "model.user_company",
"Location": "model.user_location",
"Google": ".Google+",
}
}
func (form *ProfileForm) Helps() map[string]string {
return map[string]string{
"GrEmail": "auth.profile_gremail_help",
"Info": "auth.plz_enter_your_info",
}
}
func (form *ProfileForm) Placeholders() map[string]string {
return map[string]string{
"GrEmail": "auth.plz_enter_gremail",
"Url": "auth.plz_enter_website",
}
}
// Change password form
type PasswordForm struct {
PasswordOld string `form:"type(password)" valid:"Required"`
Password string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
PasswordRe string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
User *models.User `form:"-"`
}
type SettingForm struct {
Value string `form:"type(textarea)" valid:"Required"`
}
func (form *SettingForm) SetFromSetting(setting *models.Setting) {
utils.SetFormValues(setting, form)
}
func (form *SettingForm) Labels() map[string]string {
return map[string]string{
"Value": "setting.home_about",
}
}
func (form *SettingForm) Placeholders() map[string]string {
return map[string]string{
"Value": "setting.plz_enter_home_about",
}
}
func (form *PasswordForm) Valid(v *validation.Validation) {
// Check if passwords of two times are same.
if form.Password != form.PasswordRe {
v.SetError("PasswordRe", "auth.repassword_not_match")
return
}
if VerifyPassword(form.PasswordOld, form.User.Password) == false {
v.SetError("PasswordOld", "auth.old_password_wrong")
}
}
func (form *PasswordForm) Labels() map[string]string {
return map[string]string{
"PasswordOld": "auth.old_password",
"Password": "auth.new_password",
"PasswordRe": "auth.retype_password",
}
}
func (form *PasswordForm) Placeholders() map[string]string {
return map[string]string{
"PasswordOld": "auth.plz_enter_old_password",
"Password": "auth.plz_enter_new_password",
"PasswordRe": "auth.plz_reenter_password",
}
}
type UserAdminForm struct {
Create bool `form:"-"`
Id int `form:"-"`
UserName string `valid:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
Email string `valid:"Required;Email;MaxSize(100)"`
PublicEmail bool ``
NickName string `valid:"Required;MaxSize(30)"`
Url string `valid:"MaxSize(100)"`
Company string `valid:"MaxSize(30)"`
Location string `valid:"MaxSize(30)"`
Info string `form:"type(textarea)" valid:"MaxSize(255)"`
GrEmail string `valid:"Required;MaxSize(80)"`
Github string `valid:"MaxSize(30)"`
Twitter string `valid:"MaxSize(30)"`
Google string `valid:"MaxSize(30)"`
Weibo string `valid:"MaxSize(30)"`
Linkedin string `valid:"MaxSize(30)"`
Facebook string `valid:"MaxSize(30)"`
Followers int ``
Following int ``
IsAdmin bool ``
IsActive bool ``
IsForbid bool ``
Lang int `form:"type(select);attr(rel,select2)" valid:""`
LangAdds models.SliceStringField `form:"type(select);attr(rel,select2);attr(multiple,multiple)" valid:""`
}
func (form *UserAdminForm) LangSelectData() [][]string {
langs := setting.Langs
data := make([][]string, 0, len(langs))
for i, lang := range langs {
data = append(data, []string{lang, utils.ToStr(i)})
}
return data
}
func (form *UserAdminForm) LangAddsSelectData() [][]string {
langs := setting.Langs
data := make([][]string, 0, len(langs))
for i, lang := range langs {
data = append(data, []string{lang, utils.ToStr(i)})
}
return data
}
func (form *UserAdminForm) Valid(v *validation.Validation) {
qs := models.Users()
if models.CheckIsExist(qs, "UserName", form.UserName, form.Id) {
v.SetError("UserName", "auth.username_already_taken")
}
if models.CheckIsExist(qs, "Email", form.Email, form.Id) {
v.SetError("Email", "auth.email_already_taken")
}
if len(i18n.GetLangByIndex(form.Lang)) == 0 {
v.SetError("Lang", "Can not be empty")
}
if len(form.LangAdds) > 0 {
adds := make(models.SliceStringField, 0, len(form.LangAdds))
for _, l := range form.LangAdds {
if d, err := utils.StrTo(l).Int(); err == nil {
if form.Lang == d {
continue
}
if len(i18n.GetLangByIndex(form.Lang)) == 0 {
v.SetError("Lang", "Can not be empty")
return
}
adds = append(adds, l)
}
}
form.LangAdds = adds
}
}
func (form *UserAdminForm) Helps() map[string]string {
return nil
}
func (form *UserAdminForm) Labels() map[string]string {
return nil
}
func (form *UserAdminForm) SetFromUser(user *models.User) {
utils.SetFormValues(user, form)
}
func (form *UserAdminForm) SetToUser(user *models.User) {
// set md5 value if the value is an email
if strings.IndexRune(form.GrEmail, '@') != -1 {
form.GrEmail = utils.EncodeMd5(form.GrEmail)
}
utils.SetFormValues(form, user)
}

85
modules/auth/form_oauth.go

@ -0,0 +1,85 @@
// Copyright 2013 wetalk authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package auth
import (
"github.com/astaxie/beego/validation"
"github.com/beego/i18n"
)
// OAuth connect Register form
type OAuthRegisterForm struct {
UserName string `valid:"Required;AlphaDash;MinSize(5);MaxSize(30)"`
Email string `valid:"Required;Email;MaxSize(80)"`
Password string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
PasswordRe string `form:"type(password)" valid:"Required;MinSize(4);MaxSize(30)"`
Locale i18n.Locale `form:"-"`
}
func (form *OAuthRegisterForm) Valid(v *validation.Validation) {
<