diff --git a/authentication.go b/authentication.go
index eff192d..424856f 100644
--- a/authentication.go
+++ b/authentication.go
@@ -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
diff --git a/config.go b/config.go
index 2b25da7..8e9be39 100644
--- a/config.go
+++ b/config.go
@@ -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 {
diff --git a/go.mod b/go.mod
index 76e2dac..1a18043 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index c94b9f3..fe81e08 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/main.go b/main.go
index f954760..91ee25a 100644
--- a/main.go
+++ b/main.go
@@ -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
diff --git a/templates/login.gohtml b/templates/login.gohtml
index 056cf01..5831bef 100644
--- a/templates/login.gohtml
+++ b/templates/login.gohtml
@@ -10,8 +10,11 @@
-
-
+
+
+ {{ if .Data.totp }}
+
+ {{ end }}
{{ include "author" . }}
diff --git a/templates/strings/default.yaml b/templates/strings/default.yaml
index e6d8af1..40c1d4c 100644
--- a/templates/strings/default.yaml
+++ b/templates/strings/default.yaml
@@ -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"
diff --git a/webmentionVerification.go b/webmentionVerification.go
index e905885..37fd8a0 100644
--- a/webmentionVerification.go
+++ b/webmentionVerification.go
@@ -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 {