140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
package store
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestIssueOneTimePasswordAndComplete(t *testing.T) {
|
|
st, err := OpenSQLite(filepath.Join(t.TempDir(), "otp.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.CreateAccount("admin", "", "admin-hash", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, err := HashPassword("normal-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.CreateAccount("alice", "", hash, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.ClearMustChangePassword("alice"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otpHash, err := HashPassword("otp-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
meta, err := st.IssueOneTimePassword("admin", "alice", otpHash)
|
|
if err != nil || meta.ID == "" || meta.Purpose != LoginKeyPurposeOneTimePassword {
|
|
t.Fatalf("issue: %+v %v", meta, err)
|
|
}
|
|
tok, err := st.LoginWithOneTimePassword("alice", "otp-pass-1")
|
|
if err != nil || tok == "" {
|
|
t.Fatalf("otp login: %q %v", tok, err)
|
|
}
|
|
if _, err := st.LoginWithOneTimePassword("alice", "otp-pass-1"); err == nil {
|
|
t.Fatal("consumed otp reused")
|
|
}
|
|
newHash, err := HashPassword("new-pass-2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.CompleteOneTimePasswordChecked(tok, newHash); err != nil {
|
|
t.Fatalf("complete otp: %v", err)
|
|
}
|
|
if _, _, ok := st.SessionInfo(tok); ok {
|
|
t.Fatal("otp session survived completion")
|
|
}
|
|
if _, ok := st.VerifyLogin("alice", "new-pass-2"); !ok {
|
|
t.Fatal("new password cannot login")
|
|
}
|
|
if _, ok := st.VerifyLogin("alice", "normal-pass-1"); ok {
|
|
t.Fatal("old password still valid")
|
|
}
|
|
}
|
|
|
|
func TestOneTimePasswordNormalLoginInvalidatesUnused(t *testing.T) {
|
|
st, err := OpenSQLite(filepath.Join(t.TempDir(), "otp_invalidate.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.CreateAccount("admin", "", "admin-hash", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, err := HashPassword("normal-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.CreateAccount("nora", "", hash, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.ClearMustChangePassword("nora"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otpHash, err := HashPassword("otp-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.IssueOneTimePassword("admin", "nora", otpHash); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, _, ok := st.VerifyLoginWithVersion("nora", "normal-pass-1"); !ok {
|
|
t.Fatal("normal password no longer accepted")
|
|
}
|
|
if err := st.InvalidateUnusedOneTimePasswords("nora"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.LoginWithOneTimePassword("nora", "otp-pass-1"); !errors.Is(err, ErrBadCredentials) {
|
|
t.Fatalf("otp remained valid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOneTimePasswordInitializesHistoricalPasswordlessAccount(t *testing.T) {
|
|
st, err := OpenSQLite(filepath.Join(t.TempDir(), "otp_init.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.CreateAccount("admin", "", "admin-hash", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := st.CreateAccount("legacy-empty", "", "", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before, err := st.GetAccountByOwner("legacy-empty")
|
|
if err != nil || before.HasPassword || before.InitializationState != InitializationLegacy {
|
|
t.Fatalf("fixture: %+v %v", before, err)
|
|
}
|
|
otpHash, err := HashPassword("otp-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.IssueOneTimePassword("admin", "legacy-empty", otpHash); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tok, err := st.LoginWithOneTimePassword("legacy-empty", "otp-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
finalHash, err := HashPassword("final-pass-1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.CompleteOneTimePasswordChecked(tok, finalHash); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
after, err := st.GetAccountByOwner("legacy-empty")
|
|
if err != nil || !after.HasPassword || after.InitializationState != InitializationInitialized {
|
|
t.Fatalf("account not initialized: %+v %v", after, err)
|
|
}
|
|
if _, ok := st.VerifyLogin("legacy-empty", "final-pass-1"); !ok {
|
|
t.Fatal("initialized account cannot login")
|
|
}
|
|
}
|