diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-12-27 13:34:56 -0800 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-12-27 13:37:47 -0800 |
| commit | 2a9faab3de7648344dcc1973a71ada8125c0afa3 (patch) | |
| tree | a6fd29a8d4ea62aaf17fdf214535dbdbb1c00d50 /roseh.moe.go | |
| parent | 26235f94f87b01058cc06e69902f7fb30bffb22f (diff) | |
| download | roseh.moe-2a9faab3de7648344dcc1973a71ada8125c0afa3.tar.zst | |
Add Google Authenticator
Diffstat (limited to 'roseh.moe.go')
| -rw-r--r-- | roseh.moe.go | 42 |
1 files changed, 32 insertions, 10 deletions
diff --git a/roseh.moe.go b/roseh.moe.go index 6fd4f11..993282a 100644 --- a/roseh.moe.go +++ b/roseh.moe.go @@ -4,6 +4,7 @@ import ( "context" "crypto/hmac" "crypto/rand" + "crypto/sha1" "crypto/sha256" "crypto/subtle" "embed" @@ -19,12 +20,14 @@ import ( "net/http/httputil" "net/url" "os" + "strconv" "strings" "sync" "time" "github.com/skip2/go-qrcode" "roseh.moe/pkg/ccl" + "roseh.moe/pkg/roseh.moe/internal/config" "roseh.moe/pkg/roseh.moe/internal/pwhash" "roseh.moe/pkg/wordlist" ) @@ -40,28 +43,29 @@ var ( serverStartTime = time.Now() ) -var notepadPassword, secretKey []byte +var notepadPassword, secretKey, authenticatorKey []byte func loadSecrets() error { fileBytes, err := os.ReadFile(*secretsFile) if err != nil { return err } - var secretsConfig struct { - NotepadPassword []byte - SecretKey []byte - } + var secretsConfig config.SecretsConfig if err := ccl.Unmarshal(fileBytes, &secretsConfig); err != nil { return fmt.Errorf("%s: %s", *secretsFile, err) } if len(secretsConfig.NotepadPassword) == 0 { - return fmt.Errorf("%s: missing notepad-password", *secretsFile) + return fmt.Errorf("%s: missing NotepadPassword", *secretsFile) } if len(secretsConfig.SecretKey) == 0 { - return fmt.Errorf("%s: missing secret-key", *secretsFile) + return fmt.Errorf("%s: missing SecretKey", *secretsFile) + } + if len(secretsConfig.AuthKey) == 0 { + return fmt.Errorf("%s: missing AuthKey", *secretsFile) } notepadPassword = secretsConfig.NotepadPassword secretKey = secretsConfig.SecretKey + authenticatorKey = secretsConfig.AuthKey return nil } @@ -420,9 +424,27 @@ func cookieAuth(w http.ResponseWriter, r *http.Request) bool { return true } -func checkPassword(password string) bool { +func totp(key []byte) []byte { + ts := make([]byte, 8) + binary.BigEndian.PutUint64(ts, uint64(time.Now().Unix()/30)) + hash := hmac.New(sha1.New, key) + hash.Write(ts) + mac := hash.Sum(nil) + offset := mac[len(mac)-1] & 0xf + n := int32(mac[offset])&0x7f<<24 | + int32(mac[offset+1])<<16 | + int32(mac[offset+2])<<8 | + int32(mac[offset+3]) + return strconv.AppendInt(nil, int64(n%1_000_000), 10) +} + +func checkPassword(password, auth string) bool { + otp := totp(authenticatorKey) hash, err := pwhash.Hash(password, notepadPassword[:pwhash.SaltSize]) - return err == nil && subtle.ConstantTimeCompare(hash, notepadPassword[pwhash.SaltSize:]) != 0 + if err != nil { + return false + } + return subtle.ConstantTimeCompare(hash, notepadPassword[pwhash.SaltSize:])&subtle.ConstantTimeCompare([]byte(auth), otp) != 0 } var ( @@ -464,7 +486,7 @@ func verifyRedirect(redirect string) (string, bool) { } func login(w http.ResponseWriter, r *http.Request) { - if !checkPassword(r.FormValue("password")) { + if !checkPassword(r.FormValue("password"), r.FormValue("auth")) { if err := loginTemplate.Execute(w, loginTemplateArgs{ Redirect: r.FormValue("redirect"), Error: true, |
