diff --git a/assets/admin.css b/assets/admin.css index a0de282..035cbb0 100644 --- a/assets/admin.css +++ b/assets/admin.css @@ -12,4 +12,13 @@ See the Mulan PSL v2 for more details. */ #admin-right-content { padding-left: 200px; +} + +#login-grid { + height: 100%; + margin: 0; +} + +#login-grid>.column { + max-width: 500px; } \ No newline at end of file diff --git a/assets/admin.js b/assets/admin.js index cd38a0c..be13ef8 100644 --- a/assets/admin.js +++ b/assets/admin.js @@ -8,6 +8,51 @@ $(document).ready(function() { + $('#login-form') + .form({ + fields: { + account: { + identifier : 'account', + rules: [ + { + type : 'empty', + prompt : '账户名不能为空' + }, + { + type : 'length[5]', + prompt : '账户名长度不得少于5位' + } + ] + }, + password: { + identifier : 'password', + rules: [ + { + type : 'empty', + prompt : '密码不能为空' + }, + { + type : 'length[8]', + prompt : '密码长度不得少于8位' + } + ] + }, + captcha: { + identifier : 'captcha-text', + rules: [ + { + type : 'empty', + prompt : '验证码不能为空' + }, + { + type : 'length[6]', + prompt : '验证码长度不得少于6位' + } + ] + } + } + }); + $('.message .close') .on('click', function() { $(this) @@ -378,4 +423,36 @@ function startToGrabRepos() { }); }//end of if }); +} + +function reload_captcha() { + $.ajax({ + type: "POST", + url: '/captcha', + dataType: 'json', + success: function(r) { + $('#captcha-image').html(''); + $('').attr({type: 'hidden', value:r.result ,name: 'captcha-id'}).appendTo('#login-form'); + }, + error: function(e) { + errorToast($.parseJSON(e.responseText).message) + } + }); +} + +function sign_out() { + $('body').modal('confirm','温馨提示','确认退出 RepoStats 管理后端吗?', function(choice){ + if (choice) { + $.ajax({ + type:"POST", + url: "/admin/logout", + success: function() { + successToast('操作成功,再见!') + }, + error: function(e) { + errorToast($.parseJSON(e.responseText).message) + } + }); + } + }); } \ No newline at end of file diff --git a/controller/handlers.go b/controller/handlers.go index e831df6..aa308a1 100644 --- a/controller/handlers.go +++ b/controller/handlers.go @@ -9,15 +9,54 @@ package controller import ( - "log" + "net/http" + "repostats/storage" + "strings" "github.com/gin-gonic/gin" ) -func AdminAuthHanlder() gin.HandlerFunc { - return func(ctx *gin.Context) { - //TODO: auth handler - log.Println("debug only--->" + ctx.Request.URL.Path) - ctx.Next() - } +func AdminAuthHandler() gin.HandlerFunc { + return func(c *gin.Context) { + user, err := c.Cookie("RepoStatsAdmin") + if err != nil { + c.Redirect(http.StatusFound, "/login") + return + } + + cookie, err := c.Cookie("RepoStatsCookie") + if err != nil { + c.Redirect(http.StatusFound, "/login") + return + } + + if len(user) <= 0 || len(cookie) <= 0 { + c.Redirect(http.StatusFound, "/login") + return + } + + found, err := storage.FindAdminByAccount(user) + if err != nil { + c.Redirect(http.StatusFound, "/login") + return + } + + if found.IsEmpty() { + c.Redirect(http.StatusFound, "/login") + return + } + + cValue, err := AdminCookieValue(found) + if err != nil { + c.Redirect(http.StatusFound, "/login") + return + } + + if !strings.EqualFold(cValue, cookie) { + c.Redirect(http.StatusFound, "/login") + return + } + + c.Next() + } //end of func } diff --git a/controller/login_c.go b/controller/login_c.go index 85ab507..632ba1c 100644 --- a/controller/login_c.go +++ b/controller/login_c.go @@ -8,25 +8,121 @@ package controller -import "github.com/gin-gonic/gin" +import ( + "log" + "net/http" + "repostats/model" + "repostats/storage" + "repostats/utils" + "strconv" + "strings" + + "github.com/dchest/captcha" + "github.com/gin-gonic/gin" +) // Login page controller // // Display login page html func Login(ctx *gin.Context) { - + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "登录 - RepoStats", + }) } // Login action // // Ask for account and password to DO the login action func DoLogin(ctx *gin.Context) { + account := ctx.PostForm("account") + password := ctx.PostForm("password") + captchaText := ctx.PostForm("captcha-text") + captchaId := ctx.PostForm("captcha-id") + if utils.EmptyString(account) || utils.EmptyString(password) || len(account) < 5 || len(password) < 8 { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "用户名或密码格式错误!", + }) + return + } + + if utils.EmptyString(captchaText) || utils.EmptyString(captchaId) || len(captchaText) < 6 { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "验证码格式错误!", + }) + return + } + + //验证码有效性验证 + if !captcha.VerifyString(captchaId, captchaText) { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "验证码错误,请刷新页面再重新尝试!", + }) + return + } + + //用户名密码有效性验证 + loginUser, err := storage.FindAdminByAccount(account) + if err != nil || loginUser.IsEmpty() { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "用户名或密码错误", + }) + return + } + + pwd, _ := storage.PasswordBase58Hash(password) + if !strings.EqualFold(loginUser.Password, pwd) { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "用户名或密码错误", + }) + return + } + + //Write Cookie to browser + cValue, err := AdminCookieValue(loginUser) + if err != nil { + ctx.HTML(http.StatusOK, "login.html", gin.H{ + "title": "错误 - RepoStats", + "error": "内部错误,请联系管理员", + }) + return + } + ctx.SetCookie("RepoStatsAdmin", loginUser.Account, 3600, "/", "", false, true) + ctx.SetCookie("RepoStatsCookie", cValue, 3600, "/", "", false, true) + ctx.Redirect(http.StatusFound, "/admin/gitee") } // Logout action // // Clean cookies and redirect to login page func DoLogout(ctx *gin.Context) { - + ctx.SetCookie("RepoStatsAdmin", "", -1, "/", "", false, true) + ctx.SetCookie("RepoStatsCookie", "", -1, "/", "", false, true) + ctx.Redirect(http.StatusFound, "/login") +} + +func ServeCaptchaImage(c *gin.Context) { + captcha.Server(200, 45).ServeHTTP(c.Writer, c.Request) +} + +func RequestCaptchaImage(c *gin.Context) { + imageId := captcha.New() + c.JSON(http.StatusOK, gin.H{ + "result": imageId, + }) +} + +func AdminCookieValue(user model.Admin) (string, error) { + var result string + data, err := utils.Sha256Of(user.Account + "a=" + user.Password + "=e" + strconv.Itoa(user.ID)) + if err != nil { + log.Println(err) + return result, err + } + return utils.Base58Encode(data), nil } diff --git a/go.mod b/go.mod index 8afb9d1..05f6f0e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.16 require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/Masterminds/sprig v2.22.0+incompatible + github.com/btcsuite/btcutil v1.0.2 + github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/gin-gonic/gin v1.7.7 github.com/go-resty/resty/v2 v2.7.0 github.com/google/uuid v1.3.0 // indirect @@ -14,7 +16,7 @@ require ( github.com/jmoiron/sqlx v1.3.4 github.com/lib/pq v1.10.5 github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/remeh/sizedwaitgroup v1.0.0 // indirect - gopkg.in/guregu/null.v4 v4.0.0 // indirect + github.com/remeh/sizedwaitgroup v1.0.0 + gopkg.in/guregu/null.v4 v4.0.0 gopkg.in/ini.v1 v1.66.4 ) diff --git a/go.sum b/go.sum index 924383f..479d969 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,23 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= +github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= @@ -21,21 +36,26 @@ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPr github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -52,6 +72,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= @@ -62,12 +85,17 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= @@ -81,10 +109,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg= gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/model/admin.go b/model/admin.go new file mode 100644 index 0000000..6d9aed5 --- /dev/null +++ b/model/admin.go @@ -0,0 +1,13 @@ +package model + +import "reflect" + +type Admin struct { + ID int `db:"id"` + Account string `db:"account"` + Password string `db:"password"` +} + +func (user Admin) IsEmpty() bool { + return reflect.DeepEqual(user, Admin{}) +} diff --git a/repostats.go b/repostats.go index fbd98f3..c696b04 100644 --- a/repostats.go +++ b/repostats.go @@ -71,10 +71,12 @@ func initRouter(router *gin.Engine) { router.GET("/login", controller.Login) router.POST("/login", controller.DoLogin) + router.GET("/captcha/:imageId", controller.ServeCaptchaImage) + router.POST("/captcha", controller.RequestCaptchaImage) - admin := router.Group("/admin", controller.AdminAuthHanlder()) + admin := router.Group("/admin", controller.AdminAuthHandler()) admin.POST("/logout", controller.DoLogout) - admin.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusFound, "/admin/dashboard") }) + admin.GET("/", func(ctx *gin.Context) { ctx.Redirect(http.StatusFound, "/admin/gitee") }) admin.GET("/gitee", controller.GiteePage) admin.GET("/repos", controller.ReposPage) diff --git a/schedule/gitee_schedule.go b/schedule/gitee_schedule.go index d87e210..0cb5204 100644 --- a/schedule/gitee_schedule.go +++ b/schedule/gitee_schedule.go @@ -88,7 +88,7 @@ func GrabRepo(wg *sizedwaitgroup.SizedWaitGroup, repo gitee_model.Repository, str := strings.Split(repo.FullName, "/") repoInfo, err := network.GetGiteeRepo(str[0], str[1]) if err != nil { - log.Printf("[RepoStats] failed during GetGiteeRepo %s", repo.HTMLURL) + log.Printf("[RepoStats] failed during GetGiteeRepo %s, %s", repo.HTMLURL, err) return err } diff --git a/sql/db.sql b/sql/db.sql index 7ab4738..934ee22 100644 --- a/sql/db.sql +++ b/sql/db.sql @@ -1,3 +1,10 @@ -- Database Structure For RepoStats CREATE DATABASE repostats ENCODING 'UTF8'; +CREATE TABLE public.users ( + id serial4 NOT NULL, + account varchar(200) NOT NULL, + password text NOT NULL, + CONSTRAINT users_pk PRIMARY KEY (id), + CONSTRAINT users_account_un UNIQUE (account) +); \ No newline at end of file diff --git a/storage/admin_s.go b/storage/admin_s.go new file mode 100644 index 0000000..a512f81 --- /dev/null +++ b/storage/admin_s.go @@ -0,0 +1,35 @@ +package storage + +import ( + "repostats/model" + "repostats/utils" + "strings" +) + +func NewAdmin(account string, password string) error { + query := `INSERT INTO public.users (account, "password") VALUES(:account,:password)` + data, err := PasswordBase58Hash(password) + if err != nil { + return err + } + return DbNamedExec(query, model.Admin{Account: account, Password: data}) +} + +func UpdateAdmin(user model.Admin) error { + query := `UPDATE public.users SET account = :account , "password" = :password WHERE id = :id` + return DbNamedExec(query, user) +} + +func FindAdminByAccount(account string) (model.Admin, error) { + var user model.Admin + query := `SELECT * FROM public.users u WHERE lower(u.account) = $1` + return user, DbGet(query, &user, strings.ToLower(account)) +} + +func PasswordBase58Hash(password string) (string, error) { + data, err := utils.Sha256Of(password) + if err != nil { + return "", err + } + return utils.Base58Encode(data), nil +} diff --git a/storage/admin_s_test.go b/storage/admin_s_test.go new file mode 100644 index 0000000..bd165cc --- /dev/null +++ b/storage/admin_s_test.go @@ -0,0 +1,81 @@ +package storage + +import ( + "reflect" + "repostats/utils" + "testing" +) + +func TestNewAdmin(t *testing.T) { + + testSetup(t) + defer testTeardown(t) + + type args struct { + account string + password string + } + tests := []struct { + name string + args args + wantErr bool + }{ + {name: "TestCase1", args: args{account: "repostats", password: "-2aDzm=0(ln_9^1"}, wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := NewAdmin(tt.args.account, tt.args.password); (err != nil) != tt.wantErr { + t.Errorf("NewAdmin() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func testSetup(t *testing.T) { + _, err := utils.InitConfig("../repostats.ini") + if err != nil { + t.Error(err) + utils.ExitOnError(err) + } + + _, err = InitDatabaseService() + if err != nil { + t.Error(err) + utils.ExitOnError(err) + } +} + +func testTeardown(t *testing.T) { + DbClose() +} + +func TestFindAdminByAccount(t *testing.T) { + + testSetup(t) + defer testTeardown(t) + + type args struct { + account string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + {name: "TestCase 2", args: args{account: "repostats"}, want: "EZ2zQjC3fqbkvtggy9p2YaJiLwx1kKPTJxvqVzowtx6t", wantErr: false}, + // {name: "TestCase 2", args: args{account: "repostats1"}, want: "ss", wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := FindAdminByAccount(tt.args.account) + if (err != nil) != tt.wantErr { + t.Errorf("FindAdminByAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got.Password, tt.want) { + t.Errorf("FindAdminByAccount() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/templates/log.html b/templates/log.html new file mode 100644 index 0000000..db5ac83 --- /dev/null +++ b/templates/log.html @@ -0,0 +1,49 @@ +{{define "login.html" -}} +{{- template "header.html" .}} + +
+
+

+
+ RepoStats +
+

+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+ +
+
+
Login
+
+
+
+
+
+{{- template "footer.html" .}} +{{end -}} \ No newline at end of file diff --git a/templates/menu.html b/templates/menu.html index 8b6f9f4..6f2d231 100644 --- a/templates/menu.html +++ b/templates/menu.html @@ -12,6 +12,6 @@ Pull Request 列表 - 安全退出 + 安全退出 {{end -}} \ No newline at end of file diff --git a/utils/utils.go b/utils/utils.go index 1ac4819..e89ad20 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -9,6 +9,7 @@ package utils import ( + "crypto/sha256" "errors" "log" "net/url" @@ -18,12 +19,13 @@ import ( "strings" "time" + "github.com/btcsuite/btcutil/base58" "github.com/remeh/sizedwaitgroup" ) const ( - MAX_ROUTINE_NUMBER = 25 - GITEE_SCHEDULER_INTERVAL = 4 * time.Hour + MAX_ROUTINE_NUMBER = 20 + GITEE_SCHEDULER_INTERVAL = 6 * time.Hour ) var ( @@ -99,3 +101,16 @@ func ParseGiteeRepoUrl(urlStr string) (string, string, error) { return "", "", errors.New("无法解析该链接") } } + +func Sha256Of(input string) ([]byte, error) { + algorithm := sha256.New() + _, err := algorithm.Write([]byte(strings.TrimSpace(input))) + if err != nil { + return nil, err + } + return algorithm.Sum(nil), nil +} + +func Base58Encode(data []byte) string { + return base58.Encode(data) +}