持久化Token

This commit is contained in:
奶爸 2019-12-20 23:58:09 +08:00
parent af146872fe
commit 70f0e92343
13 changed files with 82 additions and 55 deletions

View File

@ -26,18 +26,17 @@ func (cp *commonPage) serve() {
} }
func (cp *commonPage) home(c *gin.Context) { func (cp *commonPage) home(c *gin.Context) {
var admin *model.User
isLogin, ok := c.Get(model.CtxKeyIsUserLogin)
if ok && isLogin.(bool) {
admin = dao.Admin
}
dao.ServerLock.RLock() dao.ServerLock.RLock()
defer dao.ServerLock.RUnlock() defer dao.ServerLock.RUnlock()
c.HTML(http.StatusOK, "page/home", mygin.CommonEnvironment(c, gin.H{ data := gin.H{
"Admin": admin,
"Domain": dao.Conf.Site.Domain, "Domain": dao.Conf.Site.Domain,
"Servers": dao.ServerList, "Servers": dao.ServerList,
})) }
u, ok := c.Get(model.CtxKeyAuthorizedUser)
if ok {
data["Admin"] = u
}
c.HTML(http.StatusOK, "page/home", mygin.CommonEnvironment(c, data))
} }
var upgrader = websocket.Upgrader{} var upgrader = websocket.Upgrader{}

View File

@ -28,14 +28,16 @@ func (ma *memberAPI) serve() {
})) }))
mr.POST("/logout", ma.logout) mr.POST("/logout", ma.logout)
mr.POST("/server", ma.addServer) mr.POST("/server", ma.addOrEditServer)
} }
type serverForm struct { type serverForm struct {
ID uint64
Name string `binding:"required"` Name string `binding:"required"`
} }
func (ma *memberAPI) addServer(c *gin.Context) { func (ma *memberAPI) addOrEditServer(c *gin.Context) {
admin := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
var sf serverForm var sf serverForm
var s model.Server var s model.Server
err := c.ShouldBindJSON(&sf) err := c.ShouldBindJSON(&sf)
@ -43,9 +45,13 @@ func (ma *memberAPI) addServer(c *gin.Context) {
dao.ServerLock.Lock() dao.ServerLock.Lock()
defer dao.ServerLock.Unlock() defer dao.ServerLock.Unlock()
s.Name = sf.Name s.Name = sf.Name
s.Secret = com.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, dao.Admin.ID)) }
if sf.ID == 0 {
s.Secret = com.MD5(fmt.Sprintf("%s%s%d", time.Now(), sf.Name, admin.ID))
s.Secret = s.Secret[:10] s.Secret = s.Secret[:10]
err = dao.DB.Create(&s).Error err = dao.DB.Create(&s).Error
} else {
err = dao.DB.Save(&s).Error
} }
if err != nil { if err != nil {
c.JSON(http.StatusOK, model.Response{ c.JSON(http.StatusOK, model.Response{
@ -65,6 +71,7 @@ type logoutForm struct {
} }
func (ma *memberAPI) logout(c *gin.Context) { func (ma *memberAPI) logout(c *gin.Context) {
admin := c.MustGet(model.CtxKeyAuthorizedUser).(*model.User)
var lf logoutForm var lf logoutForm
if err := c.ShouldBindJSON(&lf); err != nil { if err := c.ShouldBindJSON(&lf); err != nil {
c.JSON(http.StatusOK, model.Response{ c.JSON(http.StatusOK, model.Response{
@ -73,15 +80,17 @@ func (ma *memberAPI) logout(c *gin.Context) {
}) })
return return
} }
if lf.ID != dao.Admin.ID { if lf.ID != admin.ID {
c.JSON(http.StatusOK, model.Response{ c.JSON(http.StatusOK, model.Response{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,
Message: fmt.Sprintf("请求错误:%s", "用户ID不匹配"), Message: fmt.Sprintf("请求错误:%s", "用户ID不匹配"),
}) })
return return
} }
dao.Admin.Token = "" dao.DB.Model(admin).UpdateColumns(model.User{
dao.Admin.TokenExpired = time.Now() Token: "",
TokenExpired: time.Now(),
})
c.JSON(http.StatusOK, model.Response{ c.JSON(http.StatusOK, model.Response{
Code: http.StatusOK, Code: http.StatusOK,
}) })

View File

@ -75,9 +75,9 @@ func (oa *oauth2controller) callback(c *gin.Context) {
return return
} }
user := model.NewUserFromGitHub(gu) user := model.NewUserFromGitHub(gu)
dao.Admin = &user user.IssueNewToken()
dao.Admin.IssueNewToken() dao.DB.Save(&user)
c.SetCookie(dao.Conf.Site.CookieName, dao.Admin.Token, 60*60*24*14, "", "", false, false) c.SetCookie(dao.Conf.Site.CookieName, user.Token, 60*60*24*14, "", "", false, false)
c.Status(http.StatusOK) c.Status(http.StatusOK)
c.Writer.WriteString("<script>window.location.href='/'</script>") c.Writer.WriteString("<script>window.location.href='/'</script>")
} }

View File

@ -17,13 +17,11 @@ import (
func init() { func init() {
var err error var err error
dao.ServerList = make(map[string]*model.Server) dao.ServerList = make(map[string]*model.Server)
dao.Conf, err = model.ReadInConfig("data/config.yaml") dao.Conf = &model.Config{}
err = dao.Conf.Read("data/config.yaml")
if err != nil { if err != nil {
panic(err) panic(err)
} }
dao.Admin = &model.User{
Login: dao.Conf.GitHub.Admin,
}
dao.DB, err = gorm.Open("sqlite3", "data/sqlite.db") dao.DB, err = gorm.Open("sqlite3", "data/sqlite.db")
if err != nil { if err != nil {
panic(err) panic(err)
@ -36,7 +34,7 @@ func init() {
} }
func initDB() { func initDB() {
dao.DB.AutoMigrate(model.Server{}) dao.DB.AutoMigrate(model.Server{}, model.User{})
// load cache // load cache
var servers []model.Server var servers []model.Server
dao.DB.Find(&servers) dao.DB.Find(&servers)

2
go.mod
View File

@ -22,7 +22,5 @@ require (
github.com/spf13/viper v1.6.1 github.com/spf13/viper v1.6.1
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 // indirect
google.golang.org/grpc v1.25.1 google.golang.org/grpc v1.25.1
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect
) )

View File

@ -2,8 +2,8 @@ package model
import "time" import "time"
// CtxKeyIsUserLogin .. // CtxKeyAuthorizedUser ..
const CtxKeyIsUserLogin = "ckiul" const CtxKeyAuthorizedUser = "ckau"
// CtxKeyOauth2State .. // CtxKeyOauth2State ..
const CtxKeyOauth2State = "cko2s" const CtxKeyOauth2State = "cko2s"

View File

@ -1,6 +1,8 @@
package model package model
import ( import (
"fmt"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -18,26 +20,29 @@ type Config struct {
ClientID string ClientID string
ClientSecret string ClientSecret string
} }
v *viper.Viper
} }
// ReadInConfig .. // ReadInConfig ..
func ReadInConfig(path string) (*Config, error) { func (c *Config) Read(path string) error {
viper.SetConfigFile(path) c.v = viper.New()
err := viper.ReadInConfig() c.v.SetConfigFile(path)
err := c.v.ReadInConfig()
if err != nil { if err != nil {
return nil, err return err
}
var c Config
err = viper.Unmarshal(&c)
if err != nil {
return nil, err
} }
viper.OnConfigChange(func(in fsnotify.Event) { err = c.v.Unmarshal(c)
viper.Unmarshal(&c) if err != nil {
return err
}
c.v.OnConfigChange(func(in fsnotify.Event) {
fmt.Println("配置文件更新,重载配置")
c.v.Unmarshal(c)
}) })
go viper.WatchConfig() go c.v.WatchConfig()
return &c, nil return nil
} }

View File

@ -48,5 +48,5 @@ func NewUserFromGitHub(gu *github.User) User {
// IssueNewToken ... // IssueNewToken ...
func (u *User) IssueNewToken() { func (u *User) IssueNewToken() {
u.Token = com.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login)) u.Token = com.MD5(fmt.Sprintf("%d%d%s", time.Now().UnixNano(), u.ID, u.Login))
u.TokenExpired = time.Now().AddDate(0, 0, 14) u.TokenExpired = time.Now().AddDate(0, 2, 0)
} }

View File

@ -2,6 +2,7 @@ package mygin
import ( import (
"net/http" "net/http"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -24,6 +25,7 @@ type AuthorizeOption struct {
func Authorize(opt AuthorizeOption) func(*gin.Context) { func Authorize(opt AuthorizeOption) func(*gin.Context) {
return func(c *gin.Context) { return func(c *gin.Context) {
token, err := c.Cookie(dao.Conf.Site.CookieName) token, err := c.Cookie(dao.Conf.Site.CookieName)
token = strings.TrimSpace(token)
var code uint64 = http.StatusForbidden var code uint64 = http.StatusForbidden
if opt.Guest { if opt.Guest {
code = http.StatusBadRequest code = http.StatusBadRequest
@ -35,12 +37,18 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) {
Link: opt.Redirect, Link: opt.Redirect,
Btn: opt.Btn, Btn: opt.Btn,
} }
var isLogin bool if token != "" {
if err == nil {
isLogin = token == dao.Admin.Token && dao.Admin.Token != "" && }
dao.Admin.TokenExpired.After(time.Now()) var isLogin bool
var u model.User
err = dao.DB.Where("token = ?", token).First(&u).Error
if err == nil {
isLogin = u.TokenExpired.After(time.Now())
}
if isLogin {
c.Set(model.CtxKeyAuthorizedUser, &u)
} }
c.Set(model.CtxKeyIsUserLogin, isLogin)
// 已登录且只能游客访问 // 已登录且只能游客访问
if isLogin && opt.Guest { if isLogin && opt.Guest {
ShowErrorPage(c, commonErr, opt.IsPage) ShowErrorPage(c, commonErr, opt.IsPage)

View File

@ -20,9 +20,9 @@ func CommonEnvironment(c *gin.Context, data map[string]interface{}) gin.H {
} else { } else {
data["Title"] = fmt.Sprintf("%s - %s", t, dao.Conf.Site.Brand) data["Title"] = fmt.Sprintf("%s - %s", t, dao.Conf.Site.Brand)
} }
isLogin, ok := c.Get(model.CtxKeyIsUserLogin) u, ok := c.Get(model.CtxKeyAuthorizedUser)
if ok && isLogin.(bool) { if ok {
data["Admin"] = dao.Admin data["Admin"] = u
} }
return data return data
} }

View File

@ -153,8 +153,10 @@
} }
// 刷新进度条 // 刷新进度条
bars.forEach((b, i) => { bars.forEach((b, i) => {
b.progress('set total', i == 0 ? 100 : b[0].dataset.total); if (b[0] && b[0].dataset) {
b.progress('update progress', b[0].dataset.value); b.progress('set total', i == 0 ? 100 : b[0].dataset.total);
b.progress('update progress', b[0].dataset.value);
}
}) })
} }
} }

View File

@ -3,7 +3,7 @@
{{template "common/menu" .}} {{template "common/menu" .}}
<div class="nb-container"> <div class="nb-container">
<div class="ui container"> <div class="ui container">
<button class="ui right labeled positive icon button" onclick="addServer()"><i class="add icon"></i> 添加服务器 <button class="ui right labeled positive icon button" onclick="addOrEditServer()"><i class="add icon"></i> 添加服务器
</button> </button>
<table class="ui very basic table"> <table class="ui very basic table">
<thead> <thead>
@ -11,6 +11,7 @@
<th>ID</th> <th>ID</th>
<th>备注</th> <th>备注</th>
<th>密钥</th> <th>密钥</th>
<th>管理</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -19,6 +20,16 @@
<td>{{$server.ID}}</td> <td>{{$server.ID}}</td>
<td>{{$server.Name}}</td> <td>{{$server.Name}}</td>
<td>{{$server.Secret}}</td> <td>{{$server.Secret}}</td>
<td>
<div class="ui mini icon buttons">
<button class="ui button" onclick="addOrEditServer({{$server}})">
<i class="edit icon"></i>
</button>
<button class="ui button">
<i class="delete icon"></i>
</button>
</div>
</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View File

@ -19,9 +19,6 @@ var Cache *cache.Cache
// DB .. // DB ..
var DB *gorm.DB var DB *gorm.DB
// Admin ..
var Admin *model.User
// ServerList .. // ServerList ..
var ServerList map[string]*model.Server var ServerList map[string]*model.Server