mirror of
https://github.com/hrfee/jfa-go.git
synced 2025-01-06 00:10:11 +00:00
Compare commits
4 Commits
6d089a9818
...
2f501697db
Author | SHA1 | Date | |
---|---|---|---|
2f501697db | |||
0a71d5b216 | |||
0014db44f0 | |||
885d2ebf0f |
73
api.go
73
api.go
@ -1383,6 +1383,77 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Resets a user's password with a PIN, and optionally set a new password if given.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 400 {object} PasswordValidation
|
||||
// @Failure 500 {object} boolResponse
|
||||
// @Param ResetPasswordDTO body ResetPasswordDTO true "Pin and optional Password."
|
||||
// @Router /reset [post]
|
||||
// @tags Other
|
||||
func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
||||
var req ResetPasswordDTO
|
||||
gc.BindJSON(&req)
|
||||
validation := app.validator.validate(req.Password)
|
||||
valid := true
|
||||
for _, val := range validation {
|
||||
if !val {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
if !valid || req.PIN == "" {
|
||||
// 200 bcs idk what i did in js
|
||||
app.info.Printf("%s: Password reset failed: Invalid password", req.PIN)
|
||||
gc.JSON(400, validation)
|
||||
return
|
||||
}
|
||||
resp, status, err := app.jf.ResetPassword(req.PIN)
|
||||
if status != 200 || err != nil || !resp.Success {
|
||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||
respondBool(status, false, gc)
|
||||
return
|
||||
}
|
||||
if req.Password == "" || len(resp.UsersReset) == 0 {
|
||||
respondBool(200, false, gc)
|
||||
return
|
||||
}
|
||||
user, status, err := app.jf.UserByName(resp.UsersReset[0], false)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" (%d): %v", resp.UsersReset[0], status, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
status, err = app.jf.SetPassword(user.ID, req.PIN, req.Password)
|
||||
if !(status == 200 || status == 204) || err != nil {
|
||||
app.err.Printf("Failed to change password for \"%s\" (%d): %v", resp.UsersReset[0], status, err)
|
||||
respondBool(500, false, gc)
|
||||
return
|
||||
}
|
||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||
// Silently fail for changing ombi passwords
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", resp.UsersReset[0], status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
ombiUser, status, err := app.getOmbiUser(user.ID)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", resp.UsersReset[0], status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
ombiUser["password"] = req.Password
|
||||
status, err = app.ombi.ModifyUser(ombiUser)
|
||||
if status != 200 || err != nil {
|
||||
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
|
||||
respondBool(200, true, gc)
|
||||
return
|
||||
}
|
||||
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
|
||||
}
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Apply settings to a list of users, either from a profile or from another user.
|
||||
// @Produce json
|
||||
// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings"
|
||||
@ -2123,7 +2194,7 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
||||
respondBool(200, true, gc)
|
||||
}
|
||||
|
||||
// @Summary Sets whether to notify a user through telegram or not.
|
||||
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
|
||||
// @Produce json
|
||||
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
||||
// @Success 200 {object} boolResponse
|
||||
|
@ -784,6 +784,15 @@
|
||||
"value": false,
|
||||
"description": "Send users a link to reset their password instead of a PIN. Must be enabled to reset Ombi password at the same time as the Jellyfin password."
|
||||
},
|
||||
"set_password": {
|
||||
"name": "Set password through link",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"depends_true": "link_reset",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Instead of automatically setting the user's password to the PIN, allow them to set a new password through the reset link."
|
||||
},
|
||||
"language": {
|
||||
"name": "Default reset link language",
|
||||
"required": false,
|
||||
@ -1235,6 +1244,14 @@
|
||||
"value": "",
|
||||
"description": "Location of stored invites (json)."
|
||||
},
|
||||
"password_resets": {
|
||||
"name": "Password Resets",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Location of stored non-Jellyfin password resets (json)."
|
||||
},
|
||||
"emails": {
|
||||
"name": "Email Addresses",
|
||||
"required": false,
|
||||
|
16
go.mod
16
go.mod
@ -11,20 +11,19 @@ replace github.com/hrfee/jfa-go/ombi => ./ombi
|
||||
replace github.com/hrfee/jfa-go/logger => ./logger
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.23.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/bwmarrin/discordgo v0.23.2
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a // indirect
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/getlantern/systray v1.1.0 // indirect
|
||||
github.com/getlantern/systray v1.1.0
|
||||
github.com/gin-contrib/pprof v1.3.0
|
||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-openapi/spec v0.20.3 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
@ -32,15 +31,14 @@ require (
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-00010101000000-000000000000
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/mediabrowser v0.3.3
|
||||
github.com/hrfee/mediabrowser v0.3.4
|
||||
github.com/itchyny/timefmt-go v0.1.2
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
github.com/swaggo/gin-swagger v1.3.0
|
||||
|
67
go.sum
67
go.sum
@ -1,6 +1,4 @@
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
@ -13,28 +11,19 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
|
||||
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
|
||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
|
||||
@ -60,7 +49,6 @@ github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSl
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y=
|
||||
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
|
||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||
@ -117,9 +105,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -141,7 +127,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
@ -152,8 +137,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hrfee/mediabrowser v0.3.3 h1:7E05uiol8hh2ytKn3WVLrUIvHAyifYEIy3Y5qtuNh8I=
|
||||
github.com/hrfee/mediabrowser v0.3.3/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/hrfee/mediabrowser v0.3.4 h1:D2FTnuRDXUUAHW80L1kamhVUKNifm8peZVVPNe0yWmA=
|
||||
github.com/hrfee/mediabrowser v0.3.4/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/itchyny/timefmt-go v0.1.2 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
|
||||
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
@ -168,10 +153,8 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -213,15 +196,9 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
@ -230,7 +207,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@ -260,18 +236,13 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
||||
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
|
||||
github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw=
|
||||
github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead h1:jeP6FgaSLNTMP+Yri3qjlACywQLye+huGLmNGhBzm6k=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@ -280,13 +251,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
@ -305,24 +273,15 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c h1:KHUzaHIpjWVlVVNh65G3hhuj3KB1HnjY6Cq5cTvRQT8=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f h1:Si4U+UcgJzya9kpiEUJKQvjr512OLli+gL4poHrz93U=
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -338,27 +297,16 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 h1:rF3Ohx8DRyl8h2zw9qojyLHLhrJpEMgyPOImREEryf0=
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 h1:lCnv+lfrU9FRPGf8NeRuWAAPjNnema5WtBinMgs1fD8=
|
||||
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549 h1:OL5GcZ2XPkte3dpfuFQ9o884vrE3BZQhajdntNMruv4=
|
||||
golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -373,10 +321,6 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -385,15 +329,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@ -409,11 +350,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
@ -428,5 +366,4 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -311,7 +311,7 @@
|
||||
<span class="button ~urge !normal full-width center" id="update-update">{{ .strings.update }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegram_enabled }}
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkTelegram }}</span>
|
||||
@ -329,7 +329,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if .discord_enabled }}
|
||||
{{ if .discordEnabled }}
|
||||
<div id="modal-discord" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||
@ -515,7 +515,7 @@
|
||||
<div id="create-send-to-container">
|
||||
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||
<div class="flex-expand mb-1 mt-half">
|
||||
{{ if .discord_enabled }}
|
||||
{{ if .discordEnabled }}
|
||||
<input type="text" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com | user#1234">
|
||||
<span id="create-send-to-search" class="button ~neutral !normal mr-1">
|
||||
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
||||
@ -556,13 +556,13 @@
|
||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||
<th>{{ .strings.username }}</th>
|
||||
<th>{{ .strings.emailAddress }}</th>
|
||||
{{ if .telegram_enabled }}
|
||||
{{ if .telegramEnabled }}
|
||||
<th>Telegram</th>
|
||||
{{ end }}
|
||||
{{ if .matrix_enabled }}
|
||||
{{ if .matrixEnabled }}
|
||||
<th>Matrix</th>
|
||||
{{ end }}
|
||||
{{ if .discord_enabled }}
|
||||
{{ if .discordEnabled }}
|
||||
<th>Discord</th>
|
||||
{{ end }}
|
||||
<th>{{ .strings.expiry }}</th>
|
||||
|
@ -26,5 +26,9 @@
|
||||
window.matrixRequired = {{ .matrixRequired }};
|
||||
window.matrixUserID = "{{ .matrixUser }}";
|
||||
</script>
|
||||
{{ if .passwordReset }}
|
||||
<script src="js/pwr.js" type="module"></script>
|
||||
{{ else }}
|
||||
<script src="js/form.js" type="module"></script>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -3,7 +3,13 @@
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/bundle.css">
|
||||
{{ template "header.html" . }}
|
||||
<title>{{ .strings.pageTitle }}</title>
|
||||
<title>
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.passwordReset }}
|
||||
{{ else }}
|
||||
{{ .strings.pageTitle }}
|
||||
{{ end }}
|
||||
</title>
|
||||
</head>
|
||||
<body class="max-w-full overflow-x-hidden section">
|
||||
<div id="modal-success" class="modal">
|
||||
@ -80,8 +86,20 @@
|
||||
<div class="page-container">
|
||||
<div class="card ~neutral !low">
|
||||
<div class="row baseline">
|
||||
<span class="col heading">{{ .strings.createAccountHeader }}</span>
|
||||
<span class="col subheading"> {{ .helpMessage }}</span>
|
||||
<span class="col heading">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.passwordReset }}
|
||||
{{ else }}
|
||||
{{ .strings.createAccountHeader }}
|
||||
{{ end }}
|
||||
</span>
|
||||
<span class="col subheading">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.enterYourPassword }}
|
||||
{{ else }}
|
||||
{{ .helpMessage }}
|
||||
{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
@ -89,6 +107,7 @@
|
||||
<aside class="col aside sm ~warning" id="user-expiry-message"></aside>
|
||||
{{ end }}
|
||||
<form class="card ~neutral !normal" id="form-create" href="">
|
||||
{{ if not .passwordReset }}
|
||||
<label class="label supra">
|
||||
{{ .strings.username }}
|
||||
<input type="text" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
|
||||
@ -127,6 +146,7 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<label class="label supra" for="create-password">{{ .strings.password }}</label>
|
||||
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
|
||||
|
||||
@ -134,7 +154,13 @@
|
||||
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
|
||||
<label>
|
||||
<input type="submit" class="unfocused">
|
||||
<span class="button ~urge !normal full-width center supra submit">{{ .strings.createAccountButton }}</span>
|
||||
<span class="button ~urge !normal full-width center supra submit">
|
||||
{{ if .passwordReset }}
|
||||
{{ .strings.reset }}
|
||||
{{ else }}
|
||||
{{ .strings.createAccountButton }}
|
||||
{{ end }}
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
@ -159,4 +185,3 @@
|
||||
{{ template "form-base" . }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -40,6 +40,6 @@
|
||||
</div>
|
||||
<i class="content">{{ .contactMessage }}</i>
|
||||
</div>
|
||||
<script src="{{ .urlBase }}/js/pwr.js" type="module"></script>
|
||||
<script src="{{ .urlBase }}/js/pwr-pin.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -4,10 +4,12 @@
|
||||
},
|
||||
"strings": {
|
||||
"passwordReset": "Password reset",
|
||||
"reset": "Reset",
|
||||
"resetFailed": "Password reset failed",
|
||||
"tryAgain": "Please try again.",
|
||||
"youCanLogin": "You can now log in with the below code as your password.",
|
||||
"youCanLoginOmbi": "You can now log in to Jellyfin & Ombi with the below code as your password.",
|
||||
"changeYourPassword": "Make sure to change your password after you log in."
|
||||
"changeYourPassword": "Make sure to change your password after you log in.",
|
||||
"enterYourPassword": "Enter your new password below."
|
||||
}
|
||||
}
|
||||
|
@ -305,3 +305,8 @@ type MatrixLoginDTO struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ResetPasswordDTO struct {
|
||||
PIN string `json:"pin"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
@ -108,6 +108,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
|
||||
if app.config.Section("password_resets").Key("link_reset").MustBool(false) {
|
||||
router.GET(p+"/reset", app.ResetPassword)
|
||||
if app.config.Section("password_resets").Key("set_password").MustBool(false) {
|
||||
router.POST(p+"/reset", app.ResetSetPassword)
|
||||
}
|
||||
}
|
||||
|
||||
router.GET(p+"/accounts", app.AdminPage)
|
||||
|
83
storage.go
83
storage.go
@ -917,86 +917,3 @@ func storeJSON(path string, obj interface{}) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// // One build of JF 10.7.0 hyphenated user IDs while another one later didn't. These functions will hyphenate/de-hyphenate email storage.
|
||||
//
|
||||
// func hyphenate(userID string) string {
|
||||
// if userID[8] == '-' {
|
||||
// return userID
|
||||
// }
|
||||
// return userID[:8] + "-" + userID[8:12] + "-" + userID[12:16] + "-" + userID[16:20] + "-" + userID[20:]
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) deHyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
||||
// jfUsers, status, err := app.jf.GetUsers(false)
|
||||
// if status != 200 || err != nil {
|
||||
// return nil, status, err
|
||||
// }
|
||||
// newEmails := map[string]interface{}{}
|
||||
// for _, user := range jfUsers {
|
||||
// unHyphenated := user.ID
|
||||
// hyphenated := hyphenate(unHyphenated)
|
||||
// val, ok := old[hyphenated]
|
||||
// if ok {
|
||||
// newEmails[unHyphenated] = val
|
||||
// }
|
||||
// }
|
||||
// return newEmails, status, err
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) hyphenateStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
||||
// jfUsers, status, err := app.jf.GetUsers(false)
|
||||
// if status != 200 || err != nil {
|
||||
// return nil, status, err
|
||||
// }
|
||||
// newEmails := map[string]interface{}{}
|
||||
// for _, user := range jfUsers {
|
||||
// unstripped := user.ID
|
||||
// stripped := strings.ReplaceAll(unstripped, "-", "")
|
||||
// val, ok := old[stripped]
|
||||
// if ok {
|
||||
// newEmails[unstripped] = val
|
||||
// }
|
||||
// }
|
||||
// return newEmails, status, err
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) hyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
||||
// return app.hyphenateStorage(old)
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) deHyphenateEmailStorage(old map[string]interface{}) (map[string]interface{}, int, error) {
|
||||
// return app.deHyphenateStorage(old)
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) hyphenateUserStorage(old map[string]time.Time) (map[string]time.Time, int, error) {
|
||||
// asInterface := map[string]interface{}{}
|
||||
// for k, v := range old {
|
||||
// asInterface[k] = v
|
||||
// }
|
||||
// fixed, status, err := app.hyphenateStorage(asInterface)
|
||||
// if err != nil {
|
||||
// return nil, status, err
|
||||
// }
|
||||
// out := map[string]time.Time{}
|
||||
// for k, v := range fixed {
|
||||
// out[k] = v.(time.Time)
|
||||
// }
|
||||
// return out, status, err
|
||||
// }
|
||||
//
|
||||
// func (app *appContext) deHyphenateUserStorage(old map[string]time.Time) (map[string]time.Time, int, error) {
|
||||
// asInterface := map[string]interface{}{}
|
||||
// for k, v := range old {
|
||||
// asInterface[k] = v
|
||||
// }
|
||||
// fixed, status, err := app.deHyphenateStorage(asInterface)
|
||||
// if err != nil {
|
||||
// return nil, status, err
|
||||
// }
|
||||
// out := map[string]time.Time{}
|
||||
// for k, v := range fixed {
|
||||
// out[k] = v.(time.Time)
|
||||
// }
|
||||
// return out, status, err
|
||||
// }
|
||||
|
146
ts/form.ts
146
ts/form.ts
@ -2,9 +2,9 @@ import { Modal } from "./modules/modal.js";
|
||||
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
||||
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
|
||||
import { loadLangSelector } from "./modules/lang.js";
|
||||
import { initValidator } from "./modules/validator.js";
|
||||
|
||||
interface formWindow extends Window {
|
||||
validationStrings: pwValStrings;
|
||||
invalidPassword: string;
|
||||
successModal: Modal;
|
||||
telegramModal: Modal;
|
||||
@ -31,20 +31,6 @@ interface formWindow extends Window {
|
||||
userExpiryMessage: string;
|
||||
}
|
||||
|
||||
interface pwValString {
|
||||
singular: string;
|
||||
plural: string;
|
||||
}
|
||||
|
||||
interface pwValStrings {
|
||||
length: pwValString;
|
||||
uppercase: pwValString;
|
||||
lowercase: pwValString;
|
||||
number: pwValString;
|
||||
special: pwValString;
|
||||
[ type: string ]: pwValString;
|
||||
}
|
||||
|
||||
loadLangSelector("form");
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
|
||||
@ -235,29 +221,6 @@ if (window.userExpiryEnabled) {
|
||||
calculateTime();
|
||||
}
|
||||
|
||||
var defaultPwValStrings: pwValStrings = {
|
||||
length: {
|
||||
singular: "Must have at least {n} character",
|
||||
plural: "Must have at least {n} characters"
|
||||
},
|
||||
uppercase: {
|
||||
singular: "Must have at least {n} uppercase character",
|
||||
plural: "Must have at least {n} uppercase characters"
|
||||
},
|
||||
lowercase: {
|
||||
singular: "Must have at least {n} lowercase character",
|
||||
plural: "Must have at least {n} lowercase characters"
|
||||
},
|
||||
number: {
|
||||
singular: "Must have at least {n} number",
|
||||
plural: "Must have at least {n} numbers"
|
||||
},
|
||||
special: {
|
||||
singular: "Must have at least {n} special character",
|
||||
plural: "Must have at least {n} special characters"
|
||||
}
|
||||
}
|
||||
|
||||
const form = document.getElementById("form-create") as HTMLFormElement;
|
||||
const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement;
|
||||
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
@ -267,19 +230,7 @@ if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameFie
|
||||
const passwordField = document.getElementById("create-password") as HTMLInputElement;
|
||||
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
|
||||
|
||||
const checkPasswords = () => {
|
||||
if (passwordField.value != rePasswordField.value) {
|
||||
rePasswordField.setCustomValidity(window.invalidPassword);
|
||||
submitButton.disabled = true;
|
||||
submitSpan.setAttribute("disabled", "");
|
||||
} else {
|
||||
rePasswordField.setCustomValidity("");
|
||||
submitButton.disabled = false;
|
||||
submitSpan.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
rePasswordField.addEventListener("keyup", checkPasswords);
|
||||
passwordField.addEventListener("keyup", checkPasswords);
|
||||
var requirements = initValidator(passwordField, rePasswordField, submitButton, submitSpan)
|
||||
|
||||
interface respDTO {
|
||||
response: boolean;
|
||||
@ -371,96 +322,3 @@ const create = (event: SubmitEvent) => {
|
||||
};
|
||||
|
||||
form.onsubmit = create;
|
||||
|
||||
class Requirement {
|
||||
private _name: string;
|
||||
protected _minCount: number;
|
||||
private _content: HTMLSpanElement;
|
||||
private _valid: HTMLSpanElement;
|
||||
private _li: HTMLLIElement;
|
||||
|
||||
get valid(): boolean { return this._valid.classList.contains("~positive"); }
|
||||
set valid(state: boolean) {
|
||||
if (state) {
|
||||
this._valid.classList.add("~positive");
|
||||
this._valid.classList.remove("~critical");
|
||||
this._valid.innerHTML = `<i class="icon ri-check-line" title="valid"></i>`;
|
||||
} else {
|
||||
this._valid.classList.add("~critical");
|
||||
this._valid.classList.remove("~positive");
|
||||
this._valid.innerHTML = `<i class="icon ri-close-line" title="invalid"></i>`;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name: string, el: HTMLLIElement) {
|
||||
this._name = name;
|
||||
this._li = el;
|
||||
this._content = this._li.querySelector("span.requirement-content") as HTMLSpanElement;
|
||||
this._valid = this._li.querySelector("span.requirement-valid") as HTMLSpanElement;
|
||||
this.valid = false;
|
||||
this._minCount = +this._li.getAttribute("min");
|
||||
|
||||
let text = "";
|
||||
if (this._minCount == 1) {
|
||||
text = window.validationStrings[this._name].singular.replace("{n}", "1");
|
||||
} else {
|
||||
text = window.validationStrings[this._name].plural.replace("{n}", ""+this._minCount);
|
||||
}
|
||||
this._content.textContent = text;
|
||||
}
|
||||
|
||||
validate = (count: number) => { this.valid = (count >= this._minCount); }
|
||||
}
|
||||
|
||||
// Incredible code right here
|
||||
const isInt = (s: string): boolean => { return (s in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]); }
|
||||
|
||||
const testStrings = (f: pwValString): boolean => {
|
||||
const testString = (s: string): boolean => {
|
||||
if (s == "" || !s.includes("{n}")) { return false; }
|
||||
return true;
|
||||
}
|
||||
return testString(f.singular) && testString(f.plural);
|
||||
}
|
||||
|
||||
interface Validation { [name: string]: number }
|
||||
|
||||
const validate = (s: string): Validation => {
|
||||
let v: Validation = {};
|
||||
for (let criteria of ["length", "lowercase", "uppercase", "number", "special"]) { v[criteria] = 0; }
|
||||
v["length"] = s.length;
|
||||
for (let c of s) {
|
||||
if (isInt(c)) { v["number"]++; }
|
||||
else {
|
||||
const upper = c.toUpperCase();
|
||||
if (upper == c.toLowerCase()) { v["special"]++; }
|
||||
else {
|
||||
if (upper == c) { v["uppercase"]++; }
|
||||
else if (upper != c) { v["lowercase"]++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
passwordField.addEventListener("keyup", () => {
|
||||
const v = validate(passwordField.value);
|
||||
for (let criteria in requirements) {
|
||||
requirements[criteria].validate(v[criteria]);
|
||||
}
|
||||
});
|
||||
|
||||
var requirements: { [category: string]: Requirement} = {};
|
||||
|
||||
if (!window.validationStrings) {
|
||||
window.validationStrings = defaultPwValStrings;
|
||||
} else {
|
||||
for (let category in window.validationStrings) {
|
||||
if (!testStrings(window.validationStrings[category])) {
|
||||
window.validationStrings[category] = defaultPwValStrings[category];
|
||||
}
|
||||
const el = document.getElementById("requirement-" + category);
|
||||
if (el) {
|
||||
requirements[category] = new Requirement(category, el as HTMLLIElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
154
ts/modules/validator.ts
Normal file
154
ts/modules/validator.ts
Normal file
@ -0,0 +1,154 @@
|
||||
interface valWindow extends Window {
|
||||
validationStrings: pwValStrings;
|
||||
invalidPassword: string;
|
||||
}
|
||||
|
||||
interface pwValString {
|
||||
singular: string;
|
||||
plural: string;
|
||||
}
|
||||
|
||||
interface pwValStrings {
|
||||
length: pwValString;
|
||||
uppercase: pwValString;
|
||||
lowercase: pwValString;
|
||||
number: pwValString;
|
||||
special: pwValString;
|
||||
[ type: string ]: pwValString;
|
||||
}
|
||||
|
||||
declare var window: valWindow;
|
||||
|
||||
class Requirement {
|
||||
private _name: string;
|
||||
protected _minCount: number;
|
||||
private _content: HTMLSpanElement;
|
||||
private _valid: HTMLSpanElement;
|
||||
private _li: HTMLLIElement;
|
||||
|
||||
get valid(): boolean { return this._valid.classList.contains("~positive"); }
|
||||
set valid(state: boolean) {
|
||||
if (state) {
|
||||
this._valid.classList.add("~positive");
|
||||
this._valid.classList.remove("~critical");
|
||||
this._valid.innerHTML = `<i class="icon ri-check-line" title="valid"></i>`;
|
||||
} else {
|
||||
this._valid.classList.add("~critical");
|
||||
this._valid.classList.remove("~positive");
|
||||
this._valid.innerHTML = `<i class="icon ri-close-line" title="invalid"></i>`;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name: string, el: HTMLLIElement) {
|
||||
this._name = name;
|
||||
this._li = el;
|
||||
this._content = this._li.querySelector("span.requirement-content") as HTMLSpanElement;
|
||||
this._valid = this._li.querySelector("span.requirement-valid") as HTMLSpanElement;
|
||||
this.valid = false;
|
||||
this._minCount = +this._li.getAttribute("min");
|
||||
|
||||
let text = "";
|
||||
if (this._minCount == 1) {
|
||||
text = window.validationStrings[this._name].singular.replace("{n}", "1");
|
||||
} else {
|
||||
text = window.validationStrings[this._name].plural.replace("{n}", ""+this._minCount);
|
||||
}
|
||||
this._content.textContent = text;
|
||||
}
|
||||
|
||||
validate = (count: number) => { this.valid = (count >= this._minCount); }
|
||||
}
|
||||
|
||||
export function initValidator(passwordField: HTMLInputElement, rePasswordField: HTMLInputElement, submitButton: HTMLInputElement, submitSpan: HTMLSpanElement): { [category: string]: Requirement } {
|
||||
var defaultPwValStrings: pwValStrings = {
|
||||
length: {
|
||||
singular: "Must have at least {n} character",
|
||||
plural: "Must have at least {n} characters"
|
||||
},
|
||||
uppercase: {
|
||||
singular: "Must have at least {n} uppercase character",
|
||||
plural: "Must have at least {n} uppercase characters"
|
||||
},
|
||||
lowercase: {
|
||||
singular: "Must have at least {n} lowercase character",
|
||||
plural: "Must have at least {n} lowercase characters"
|
||||
},
|
||||
number: {
|
||||
singular: "Must have at least {n} number",
|
||||
plural: "Must have at least {n} numbers"
|
||||
},
|
||||
special: {
|
||||
singular: "Must have at least {n} special character",
|
||||
plural: "Must have at least {n} special characters"
|
||||
}
|
||||
}
|
||||
|
||||
const checkPasswords = () => {
|
||||
if (passwordField.value != rePasswordField.value) {
|
||||
rePasswordField.setCustomValidity(window.invalidPassword);
|
||||
submitButton.disabled = true;
|
||||
submitSpan.setAttribute("disabled", "");
|
||||
} else {
|
||||
rePasswordField.setCustomValidity("");
|
||||
submitButton.disabled = false;
|
||||
submitSpan.removeAttribute("disabled");
|
||||
}
|
||||
};
|
||||
rePasswordField.addEventListener("keyup", checkPasswords);
|
||||
passwordField.addEventListener("keyup", checkPasswords);
|
||||
|
||||
|
||||
// Incredible code right here
|
||||
const isInt = (s: string): boolean => { return (s in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]); }
|
||||
|
||||
const testStrings = (f: pwValString): boolean => {
|
||||
const testString = (s: string): boolean => {
|
||||
if (s == "" || !s.includes("{n}")) { return false; }
|
||||
return true;
|
||||
}
|
||||
return testString(f.singular) && testString(f.plural);
|
||||
}
|
||||
|
||||
interface Validation { [name: string]: number }
|
||||
|
||||
const validate = (s: string): Validation => {
|
||||
let v: Validation = {};
|
||||
for (let criteria of ["length", "lowercase", "uppercase", "number", "special"]) { v[criteria] = 0; }
|
||||
v["length"] = s.length;
|
||||
for (let c of s) {
|
||||
if (isInt(c)) { v["number"]++; }
|
||||
else {
|
||||
const upper = c.toUpperCase();
|
||||
if (upper == c.toLowerCase()) { v["special"]++; }
|
||||
else {
|
||||
if (upper == c) { v["uppercase"]++; }
|
||||
else if (upper != c) { v["lowercase"]++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
passwordField.addEventListener("keyup", () => {
|
||||
const v = validate(passwordField.value);
|
||||
for (let criteria in requirements) {
|
||||
requirements[criteria].validate(v[criteria]);
|
||||
}
|
||||
});
|
||||
|
||||
var requirements: { [category: string]: Requirement } = {};
|
||||
|
||||
if (!window.validationStrings) {
|
||||
window.validationStrings = defaultPwValStrings;
|
||||
} else {
|
||||
for (let category in window.validationStrings) {
|
||||
if (!testStrings(window.validationStrings[category])) {
|
||||
window.validationStrings[category] = defaultPwValStrings[category];
|
||||
}
|
||||
const el = document.getElementById("requirement-" + category);
|
||||
if (el) {
|
||||
requirements[category] = new Requirement(category, el as HTMLLIElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
return requirements
|
||||
}
|
23
ts/pwr-pin.ts
Normal file
23
ts/pwr-pin.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { toClipboard, notificationBox } from "./modules/common.js";
|
||||
|
||||
const pin = document.getElementById("pin") as HTMLSpanElement;
|
||||
|
||||
if (pin) {
|
||||
// Load this individual string into the DOM, so we don't have to load the whole language file.
|
||||
const copy = document.getElementById("copy-notification");
|
||||
const copyString = copy.textContent;
|
||||
copy.remove();
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||
|
||||
pin.onclick = () => {
|
||||
toClipboard(pin.textContent);
|
||||
window.notifications.customPositive("copied", "", copyString);
|
||||
pin.classList.add("~positive");
|
||||
pin.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
pin.classList.add("~urge");
|
||||
pin.classList.remove("~positive");
|
||||
}, 5000);
|
||||
};
|
||||
}
|
100
ts/pwr.ts
100
ts/pwr.ts
@ -1,24 +1,80 @@
|
||||
import { toClipboard, notificationBox } from "./modules/common.js";
|
||||
import { Modal } from "./modules/modal.js";
|
||||
import { initValidator } from "./modules/validator.js";
|
||||
import { _post, addLoader, removeLoader } from "./modules/common.js";
|
||||
|
||||
|
||||
const pin = document.getElementById("pin") as HTMLSpanElement;
|
||||
|
||||
if (pin) {
|
||||
// Load this individual string into the DOM, so we don't have to load the whole language file.
|
||||
const copy = document.getElementById("copy-notification");
|
||||
const copyString = copy.textContent;
|
||||
copy.remove();
|
||||
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement, 5);
|
||||
|
||||
pin.onclick = () => {
|
||||
toClipboard(pin.textContent);
|
||||
window.notifications.customPositive("copied", "", copyString);
|
||||
pin.classList.add("~positive");
|
||||
pin.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
pin.classList.add("~urge");
|
||||
pin.classList.remove("~positive");
|
||||
}, 5000);
|
||||
};
|
||||
interface formWindow extends Window {
|
||||
invalidPassword: string;
|
||||
successModal: Modal;
|
||||
telegramModal: Modal;
|
||||
discordModal: Modal;
|
||||
matrixModal: Modal;
|
||||
confirmationModal: Modal
|
||||
code: string;
|
||||
messages: { [key: string]: string };
|
||||
confirmation: boolean;
|
||||
telegramRequired: boolean;
|
||||
telegramPIN: string;
|
||||
discordRequired: boolean;
|
||||
discordPIN: string;
|
||||
discordStartCommand: string;
|
||||
discordInviteLink: boolean;
|
||||
discordServerName: string;
|
||||
matrixRequired: boolean;
|
||||
matrixUserID: string;
|
||||
userExpiryEnabled: boolean;
|
||||
userExpiryMonths: number;
|
||||
userExpiryDays: number;
|
||||
userExpiryHours: number;
|
||||
userExpiryMinutes: number;
|
||||
userExpiryMessage: string;
|
||||
}
|
||||
|
||||
declare var window: formWindow;
|
||||
|
||||
const form = document.getElementById("form-create") as HTMLFormElement;
|
||||
const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement;
|
||||
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
||||
const passwordField = document.getElementById("create-password") as HTMLInputElement;
|
||||
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
|
||||
|
||||
window.successModal = new Modal(document.getElementById("modal-success"), true);
|
||||
|
||||
var requirements = initValidator(passwordField, rePasswordField, submitButton, submitSpan)
|
||||
|
||||
interface sendDTO {
|
||||
pin: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
form.onsubmit = (event: Event) => {
|
||||
event.preventDefault();
|
||||
addLoader(submitSpan);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let send: sendDTO = {
|
||||
pin: params.get("pin"),
|
||||
password: passwordField.value
|
||||
};
|
||||
_post("/reset", send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
removeLoader(submitSpan);
|
||||
if (req.status == 400) {
|
||||
for (let type in req.response) {
|
||||
if (requirements[type]) { requirements[type].valid = req.response[type] as boolean; }
|
||||
}
|
||||
return;
|
||||
} else if (req.status != 200) {
|
||||
const old = submitSpan.textContent;
|
||||
submitSpan.textContent = window.messages["errorUnknown"];
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
setTimeout(() => {
|
||||
submitSpan.classList.add("~urge");
|
||||
submitSpan.classList.remove("~critical");
|
||||
submitSpan.textContent = old;
|
||||
}, 2000);
|
||||
} else {
|
||||
window.successModal.show();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
};
|
||||
|
20
views.go
20
views.go
@ -139,6 +139,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
||||
|
||||
func (app *appContext) ResetPassword(gc *gin.Context) {
|
||||
isBot := strings.Contains(gc.Request.Header.Get("User-Agent"), "Bot")
|
||||
setPassword := app.config.Section("password_resets").Key("set_password").MustBool(false)
|
||||
pin := gc.Query("pin")
|
||||
if pin == "" {
|
||||
app.NoRouteHandler(gc)
|
||||
@ -148,11 +149,29 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
||||
lang := app.getLang(gc, PWRPage, app.storage.lang.chosenPWRLang)
|
||||
data := gin.H{
|
||||
"urlBase": app.getURLBase(gc),
|
||||
"cssClass": app.cssClass,
|
||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||
"strings": app.storage.lang.PasswordReset[lang].Strings,
|
||||
"success": false,
|
||||
"ombiEnabled": app.config.Section("ombi").Key("enabled").MustBool(false),
|
||||
}
|
||||
if setPassword {
|
||||
data["helpMessage"] = app.config.Section("ui").Key("help_message").String()
|
||||
data["successMessage"] = app.config.Section("ui").Key("success_message").String()
|
||||
data["jfLink"] = app.config.Section("jellyfin").Key("public_server").String()
|
||||
data["validate"] = app.config.Section("password_validation").Key("enabled").MustBool(false)
|
||||
data["requirements"] = app.validator.getCriteria()
|
||||
data["strings"] = app.storage.lang.PasswordReset[lang].Strings
|
||||
data["validationStrings"] = app.storage.lang.Form[lang].validationStringsJSON
|
||||
data["notifications"] = app.storage.lang.Form[lang].notificationsJSON
|
||||
data["langName"] = lang
|
||||
data["passwordReset"] = true
|
||||
data["telegramEnabled"] = false
|
||||
data["discordEnabled"] = false
|
||||
data["matrixEnabled"] = false
|
||||
gcHTML(gc, http.StatusOK, "form-loader.html", data)
|
||||
return
|
||||
}
|
||||
defer gcHTML(gc, http.StatusOK, "password-reset.html", data)
|
||||
// If it's a bot, pretend to be a success so the preview is nice.
|
||||
if isBot {
|
||||
@ -294,6 +313,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
"userExpiryMinutes": inv.UserMinutes,
|
||||
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
||||
"langName": lang,
|
||||
"passwordReset": false,
|
||||
"telegramEnabled": telegramEnabled,
|
||||
"discordEnabled": discordEnabled,
|
||||
"matrixEnabled": matrixEnabled,
|
||||
|
Loading…
Reference in New Issue
Block a user