This commit is contained in:
Jan-Lukas Else 2021-02-28 08:57:15 +01:00
parent 8a20285029
commit 71777613af
8 changed files with 85 additions and 33 deletions

View File

@ -10,14 +10,26 @@ import (
"time"
"github.com/dgrijalva/jwt-go"
"github.com/pquerna/otp/totp"
)
func checkCredentials(username, password string) bool {
return username == appConfig.User.Nick && password == appConfig.User.Password
func checkCredentials(username, password, totpPasscode string) bool {
return username == appConfig.User.Nick &&
password == appConfig.User.Password &&
(appConfig.User.TOTP == "" || totp.Validate(totpPasscode, appConfig.User.TOTP))
}
func checkUsername(username string) bool {
return username == appConfig.User.Nick
func checkUsernameTOTP(username string, totp bool) bool {
return username == appConfig.User.Nick && totp == (appConfig.User.TOTP != "")
}
func checkAppPasswords(username, password string) bool {
for _, apw := range appConfig.User.AppPasswords {
if apw.Username == username && apw.Password == password {
return true
}
}
return false
}
func jwtKey() []byte {
@ -31,8 +43,8 @@ func authMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r)
return
}
// 1. Check BasicAuth
if username, password, ok := r.BasicAuth(); ok && checkCredentials(username, password) {
// 1. Check BasicAuth (just for app passwords)
if username, password, ok := r.BasicAuth(); ok && checkAppPasswords(username, password) {
next.ServeHTTP(w, r)
return
}
@ -52,10 +64,11 @@ func authMiddleware(next http.Handler) http.Handler {
b = []byte(r.PostForm.Encode())
}
render(w, r, templateLogin, &renderData{
Data: map[string]string{
Data: map[string]interface{}{
"loginmethod": r.Method,
"loginheaders": base64.StdEncoding.EncodeToString(h),
"loginbody": base64.StdEncoding.EncodeToString(b),
"totp": appConfig.User.TOTP != "",
},
})
})
@ -68,7 +81,7 @@ func checkAuthToken(r *http.Request) bool {
return jwtKey(), nil
}); err == nil && tkn.Valid &&
claims.TokenType == "login" &&
checkUsername(claims.Username) {
checkUsernameTOTP(claims.Username, claims.TOTP) {
return true
}
}
@ -105,7 +118,12 @@ func checkLogin(w http.ResponseWriter, r *http.Request) bool {
if r.FormValue("loginaction") != "login" {
return false
}
// Do original request
// Check credential
if !checkCredentials(r.FormValue("username"), r.FormValue("password"), r.FormValue("token")) {
serveError(w, r, "Incorrect credentials", http.StatusUnauthorized)
return true
}
// Prepare original request
loginbody, _ := base64.StdEncoding.DecodeString(r.FormValue("loginbody"))
req, _ := http.NewRequest(r.FormValue("loginmethod"), r.RequestURI, bytes.NewReader(loginbody))
// Copy original headers
@ -115,18 +133,14 @@ func checkLogin(w http.ResponseWriter, r *http.Request) bool {
for k, v := range headers {
req.Header[k] = v
}
// Check credential
if checkCredentials(r.FormValue("username"), r.FormValue("password")) {
tokenCookie, err := createTokenCookie(r.FormValue("username"))
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
// Add cookie to original request
req.AddCookie(tokenCookie)
// Send cookie
http.SetCookie(w, tokenCookie)
// Cookie
tokenCookie, err := createTokenCookie()
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
req.AddCookie(tokenCookie)
http.SetCookie(w, tokenCookie)
// Serve original request
d.ServeHTTP(w, req)
return true
@ -136,14 +150,16 @@ type authClaims struct {
*jwt.StandardClaims
TokenType string
Username string
TOTP bool
}
func createTokenCookie(username string) (*http.Cookie, error) {
func createTokenCookie() (*http.Cookie, error) {
expiration := time.Now().Add(7 * 24 * time.Hour)
tokenString, err := jwt.NewWithClaims(jwt.SigningMethodHS256, &authClaims{
&jwt.StandardClaims{ExpiresAt: expiration.Unix()},
"login",
username,
appConfig.User.Nick,
appConfig.User.TOTP != "",
}).SignedString(jwtKey())
if err != nil {
return nil, err

View File

@ -130,14 +130,20 @@ type comments struct {
}
type configUser struct {
Nick string `mapstructure:"nick"`
Name string `mapstructure:"name"`
Password string `mapstructure:"password"`
Picture string `mapstructure:"picture"`
Emoji string `mapstructure:"emoji"`
Email string `mapstructure:"email"`
Link string `mapstructure:"link"`
Identities []string `mapstructure:"identities"`
Nick string `mapstructure:"nick"`
Name string `mapstructure:"name"`
Password string `mapstructure:"password"`
TOTP string `mapstructure:"totp"`
AppPasswords []*appPassword `mapstructure:"appPasswords"`
Picture string `mapstructure:"picture"`
Email string `mapstructure:"email"`
Link string `mapstructure:"link"`
Identities []string `mapstructure:"identities"`
}
type appPassword struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
type configHooks struct {

1
go.mod
View File

@ -36,6 +36,7 @@ require (
github.com/miekg/dns v1.1.40 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pquerna/otp v1.3.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/snabb/sitemap v1.0.0
github.com/spf13/afero v1.5.1 // indirect

4
go.sum
View File

@ -37,6 +37,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caddyserver/certmagic v0.12.0 h1:1f7kxykaJkOVVpXJ8ZrC6RAO5F6+kKm9U7dBFbLNeug=
github.com/caddyserver/certmagic v0.12.0/go.mod h1:tr26xh+9fY5dN0J6IPAlMj07qpog22PJKa7Nw7j835U=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -231,6 +233,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=

20
main.go
View File

@ -7,6 +7,8 @@ import (
"os/signal"
"runtime/pprof"
"syscall"
"github.com/pquerna/otp/totp"
)
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
@ -26,12 +28,30 @@ func main() {
}
defer pprof.StopCPUProfile()
}
// Initialize config
log.Println("Initialize configuration...")
err := initConfig()
if err != nil {
log.Fatal(err)
}
// Small tools
if len(os.Args) >= 2 {
if os.Args[1] == "totp-secret" {
key, err := totp.Generate(totp.GenerateOpts{
Issuer: appConfig.Server.PublicAddress,
AccountName: appConfig.User.Nick,
})
if err != nil {
log.Fatal(err.Error())
return
}
log.Println("TOTP-Secret:", key.Secret())
return
}
}
// Execute pre-start hooks
preStartHooks()
// Initialize everything else

View File

@ -10,8 +10,11 @@
<input type="hidden" name="loginmethod" value="{{ .Data.loginmethod }}">
<input type="hidden" name="loginheaders" value="{{ .Data.loginheaders }}">
<input type="hidden" name="loginbody" value="{{ .Data.loginbody }}">
<input type="text" name="username" placeholder="{{ string .Blog.Lang "username" }}">
<input type="password" name="password" placeholder="{{ string .Blog.Lang "password" }}">
<input type="text" name="username" autocomplete="username" placeholder="{{ string .Blog.Lang "username" }}">
<input type="password" name="password" autocomplete="current-password" placeholder="{{ string .Blog.Lang "password" }}">
{{ if .Data.totp }}
<input type="text" name="token" inputmode="numeric" pattern="[0-9]*" autocomplete="one-time-code" placeholder="{{ string .Blog.Lang "totp" }}">
{{ end }}
<input class="fw" type="submit" value="{{ string .Blog.Lang "login" }}">
</form>
{{ include "author" . }}

View File

@ -35,6 +35,7 @@ speak: "Read to me, please."
stopspeak: "Stop speaking!"
submit: "Submit"
total: "Total"
totp: "TOTP"
translations: "Translations"
update: "Update"
updatedon: "Updated on"

View File

@ -76,7 +76,8 @@ func (m *mention) verifyMention() error {
req.Header.Set(userAgent, appUserAgent)
if strings.HasPrefix(m.Source, appConfig.Server.PublicAddress) {
// Set authentication
req.SetBasicAuth(appConfig.User.Nick, appConfig.User.Password)
c, _ := createTokenCookie()
req.AddCookie(c)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {