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" .}}
+