完成管理后台登录、登出操作

This commit is contained in:
巴拉迪维 2022-04-25 17:31:05 +08:00
parent e2d2891ebb
commit 4e9f58a0e7
15 changed files with 475 additions and 19 deletions

View File

@ -12,4 +12,13 @@ See the Mulan PSL v2 for more details. */
#admin-right-content { #admin-right-content {
padding-left: 200px; padding-left: 200px;
}
#login-grid {
height: 100%;
margin: 0;
}
#login-grid>.column {
max-width: 500px;
} }

View File

@ -8,6 +8,51 @@
$(document).ready(function() { $(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') $('.message .close')
.on('click', function() { .on('click', function() {
$(this) $(this)
@ -378,4 +423,36 @@ function startToGrabRepos() {
}); });
}//end of if }//end of if
}); });
}
function reload_captcha() {
$.ajax({
type: "POST",
url: '/captcha',
dataType: 'json',
success: function(r) {
$('#captcha-image').html('<img src="/captcha/'+r.result+'.png" />');
$('<input>').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)
}
});
}
});
} }

View File

@ -9,15 +9,54 @@
package controller package controller
import ( import (
"log" "net/http"
"repostats/storage"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func AdminAuthHanlder() gin.HandlerFunc { func AdminAuthHandler() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(c *gin.Context) {
//TODO: auth handler user, err := c.Cookie("RepoStatsAdmin")
log.Println("debug only--->" + ctx.Request.URL.Path) if err != nil {
ctx.Next() 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
} }

View File

@ -8,25 +8,121 @@
package controller 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 // Login page controller
// //
// Display login page html // Display login page html
func Login(ctx *gin.Context) { func Login(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "login.html", gin.H{
"title": "登录 - RepoStats",
})
} }
// Login action // Login action
// //
// Ask for account and password to DO the login action // Ask for account and password to DO the login action
func DoLogin(ctx *gin.Context) { 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 // Logout action
// //
// Clean cookies and redirect to login page // Clean cookies and redirect to login page
func DoLogout(ctx *gin.Context) { 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
} }

8
go.mod
View File

@ -5,7 +5,9 @@ go 1.16
require ( require (
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // 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/gin-gonic/gin v1.7.7
github.com/go-resty/resty/v2 v2.7.0 github.com/go-resty/resty/v2 v2.7.0
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
@ -14,7 +16,7 @@ require (
github.com/jmoiron/sqlx v1.3.4 github.com/jmoiron/sqlx v1.3.4
github.com/lib/pq v1.10.5 github.com/lib/pq v1.10.5
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/remeh/sizedwaitgroup v1.0.0 // indirect github.com/remeh/sizedwaitgroup v1.0.0
gopkg.in/guregu/null.v4 v4.0.0 // indirect gopkg.in/guregu/null.v4 v4.0.0
gopkg.in/ini.v1 v1.66.4 gopkg.in/ini.v1 v1.66.4
) )

31
go.sum
View File

@ -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/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 h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 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.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/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 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 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= 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-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.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.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 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 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/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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 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 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 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 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 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 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 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= 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/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 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/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 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= 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 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 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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-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 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-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 h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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/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 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=
gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI= 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 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

13
model/admin.go Normal file
View File

@ -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{})
}

View File

@ -71,10 +71,12 @@ func initRouter(router *gin.Engine) {
router.GET("/login", controller.Login) router.GET("/login", controller.Login)
router.POST("/login", controller.DoLogin) 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.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("/gitee", controller.GiteePage)
admin.GET("/repos", controller.ReposPage) admin.GET("/repos", controller.ReposPage)

View File

@ -88,7 +88,7 @@ func GrabRepo(wg *sizedwaitgroup.SizedWaitGroup, repo gitee_model.Repository,
str := strings.Split(repo.FullName, "/") str := strings.Split(repo.FullName, "/")
repoInfo, err := network.GetGiteeRepo(str[0], str[1]) repoInfo, err := network.GetGiteeRepo(str[0], str[1])
if err != nil { 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 return err
} }

View File

@ -1,3 +1,10 @@
-- Database Structure For RepoStats -- Database Structure For RepoStats
CREATE DATABASE repostats ENCODING 'UTF8'; 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)
);

35
storage/admin_s.go Normal file
View File

@ -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
}

81
storage/admin_s_test.go Normal file
View File

@ -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)
}
})
}
}

49
templates/log.html Normal file
View File

@ -0,0 +1,49 @@
{{define "login.html" -}}
{{- template "header.html" .}}
<div class="ui inverted borderless large menu">
<div class="ui container">
<a class="ui inverted header item" target="_self" href="https://gitee.com/barat/repostats">
<div class="content">RepoStats</div>
</a>
</div>
</div>
<div id="login-grid" class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui image header">
<div class="content">
RepoStats
</div>
</h2>
<form id="login-form" class="ui large form" action="/login" method="POST">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="account" placeholder="Account">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" name="password" placeholder="Password">
</div>
</div>
<div class="field">
<div class="two fields">
<div class="field">
<div class="ui left icon input">
<i class="image icon"></i>
<input type="text" name="captcha-text" placeholder="验证码">
</div>
</div>
<div id="captcha-image" class="field"><a class="ui input" href="javascript:reload_captcha()">点击获取验证码</a></div>
</div>
</div>
<div class="ui fluid large black submit button">Login</div>
</div>
<div class="ui error message"></div>
</form>
</div>
</div>
{{- template "footer.html" .}}
{{end -}}

View File

@ -12,6 +12,6 @@
<a class="{{if eq .current_url "/admin/prs"}}active {{end}}item" target="_self" href="/admin/prs">Pull Request 列表</a> <a class="{{if eq .current_url "/admin/prs"}}active {{end}}item" target="_self" href="/admin/prs">Pull Request 列表</a>
</div> </div>
</div> </div>
<a class="item" target="_self" href="javascript:sign_out_config()">安全退出</a> <a class="item" target="_self" href="javascript:sign_out()">安全退出</a>
</div><!--end of left-menu--> </div><!--end of left-menu-->
{{end -}} {{end -}}

View File

@ -9,6 +9,7 @@
package utils package utils
import ( import (
"crypto/sha256"
"errors" "errors"
"log" "log"
"net/url" "net/url"
@ -18,12 +19,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/btcsuite/btcutil/base58"
"github.com/remeh/sizedwaitgroup" "github.com/remeh/sizedwaitgroup"
) )
const ( const (
MAX_ROUTINE_NUMBER = 25 MAX_ROUTINE_NUMBER = 20
GITEE_SCHEDULER_INTERVAL = 4 * time.Hour GITEE_SCHEDULER_INTERVAL = 6 * time.Hour
) )
var ( var (
@ -99,3 +101,16 @@ func ParseGiteeRepoUrl(urlStr string) (string, string, error) {
return "", "", errors.New("无法解析该链接") 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)
}