summaryrefslogtreecommitdiffstats
path: root/roseh.moe.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-12-27 13:34:56 -0800
committerRose Hogenson <rosehogenson@posteo.net>2025-12-27 13:37:47 -0800
commit2a9faab3de7648344dcc1973a71ada8125c0afa3 (patch)
treea6fd29a8d4ea62aaf17fdf214535dbdbb1c00d50 /roseh.moe.go
parent26235f94f87b01058cc06e69902f7fb30bffb22f (diff)
downloadroseh.moe-2a9faab3de7648344dcc1973a71ada8125c0afa3.tar.zst
Add Google Authenticator
Diffstat (limited to 'roseh.moe.go')
-rw-r--r--roseh.moe.go42
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,