Track recently used totp timestamps
Some checks are pending
/ test (push) Waiting to run

This commit is contained in:
Melody Becker 2025-04-01 09:16:33 +02:00
parent a6bcbaf5e9
commit 7ae75caaf5
Signed by: mstar
SSH key fingerprint: SHA256:9VAo09aaVNTWKzPW7Hq2LW+ox9OdwmTSHRoD4mlz1yI
3 changed files with 29 additions and 1 deletions

View file

@ -9,6 +9,7 @@ package auth
import ( import (
"time" "time"
"git.mstar.dev/mstar/goutils/other"
"git.mstar.dev/mstar/goutils/sliceutils" "git.mstar.dev/mstar/goutils/sliceutils"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
@ -19,6 +20,7 @@ import (
type Authenticator struct { type Authenticator struct {
webauthn *webauthn.WebAuthn webauthn *webauthn.WebAuthn
recentlyUsedTotpTokens map[string]time.Time
} }
type LoginNextState uint8 type LoginNextState uint8
@ -33,6 +35,17 @@ const (
LoginStartPasskey // Login starts with a passkey LoginStartPasskey // Login starts with a passkey
) )
func New(webauthnConfig *webauthn.Config) (*Authenticator, error) {
webauthn, err := webauthn.New(webauthnConfig)
if err != nil {
return nil, other.Error("auth", "failed to create webauthn handler", err)
}
return &Authenticator{
webauthn: webauthn,
recentlyUsedTotpTokens: make(map[string]time.Time),
}, nil
}
func calcAccessExpirationTimestamp() time.Time { func calcAccessExpirationTimestamp() time.Time {
// For now, the default expiration is one month after creation // For now, the default expiration is one month after creation
// though "never" might also be a good option // though "never" might also be a good option

View file

@ -12,6 +12,7 @@ var (
// A user may not login, for whatever reason // A user may not login, for whatever reason
ErrCantLogin = errors.New("user can't login") ErrCantLogin = errors.New("user can't login")
ErrDecryptionFailure = errors.New("failed to decrypt content") ErrDecryptionFailure = errors.New("failed to decrypt content")
ErrTotpRecentlyUsed = errors.New("totp token was used too recently")
) )
type CombinedError struct { type CombinedError struct {

View file

@ -1,5 +1,7 @@
package auth package auth
// Some helpful comments from: https://waters.me/internet/google-authenticator-implementation-note-key-length-token-reuse/
import ( import (
"time" "time"
@ -15,11 +17,20 @@ import (
) )
const totpUnverifiedSuffix = "-NOT_VERIFIED" const totpUnverifiedSuffix = "-NOT_VERIFIED"
const totpTokenNoLongerRecentlyUsed = time.Second * 90
func (a *Authenticator) PerformTotpLogin( func (a *Authenticator) PerformTotpLogin(
username string, username string,
totpToken string, totpToken string,
) (LoginNextState, string, error) { ) (LoginNextState, string, error) {
// First check if that token has been seen recently for that user
if timestamp, found := a.recentlyUsedTotpTokens[totpToken+"+"+username]; found {
if timestamp.Add(totpTokenNoLongerRecentlyUsed).After(time.Now()) {
return LoginNextFailure, "", ErrTotpRecentlyUsed
} else {
delete(a.recentlyUsedTotpTokens, totpToken+"+"+username)
}
}
if ok, err := a.canUsernameLogin(username); !ok { if ok, err := a.canUsernameLogin(username); !ok {
return 0, "", other.Error("auth", "user may not login", err) return 0, "", other.Error("auth", "user may not login", err)
} }
@ -61,6 +72,9 @@ func (a *Authenticator) PerformTotpLogin(
ErrInvalidCombination, ErrInvalidCombination,
) )
} }
a.recentlyUsedTotpTokens[totpToken+"+"+username] = time.Now()
token := models.AccessToken{ token := models.AccessToken{
User: *acc, User: *acc,
UserId: acc.ID, UserId: acc.ID,