package oltp
import (
"path/filepath"
"testing"
)
// An invite is an account that exists but cannot sign in until its
// owner sets a password through the one-time link. Redeeming spends the
// link; a second redemption fails.
func TestInviteClaimsAnAccountOnce(t *testing.T) {
db, err := Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
token, err := db.InviteAccount("eve", "[email protected]", false)
if err != nil || token == "" {
t.Fatalf("invite: %q %v", token, err)
}
accts, _ := db.Accounts()
if len(accts) != 1 || !accts[0].Pending || accts[0].Email != "[email protected]" {
t.Fatalf("invited account listed as %+v", accts)
}
if _, ok := db.Authenticate("eve", ""); ok {
t.Fatalf("an unclaimed invite signs in with an empty password")
}
if name, ok := db.InviteFor(token); !ok || name != "eve" {
t.Fatalf("InviteFor = %q %v", name, ok)
}
if _, ok := db.InviteFor("not-a-token"); ok {
t.Fatalf("an unknown token is live")
}
if name, err := db.RedeemInvite(token, "hunter22"); err != nil || name != "eve" {
t.Fatalf("redeem: %q %v", name, err)
}
if _, ok := db.Authenticate("eve", "hunter22"); !ok {
t.Fatalf("the claimed account cannot sign in")
}
if _, err := db.RedeemInvite(token, "again"); err == nil {
t.Fatalf("a spent invite was redeemed twice")
}
if _, err := db.InviteAccount("eve", "x@y", false); err == nil {
t.Fatalf("an account with a password was re-invited")
}
if a, _ := db.AccountByName("eve"); a.Pending {
t.Errorf("claimed account still pending: %+v", a)
}
}
// Disabling shuts the door at once: sign-in fails and every live
// session is gone, on every device. Re-enabling reopens sign-in.
func TestDisableEndsSessionsAndBlocksSignIn(t *testing.T) {
db, err := Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := db.AddAccount("dan", "pw", false); err != nil {
t.Fatal(err)
}
tok1, _, ok1 := db.NewSessionFrom("dan", "pw", "10.0.0.1", "phone")
tok2, _, ok2 := db.NewSessionFrom("dan", "pw", "10.0.0.2", "laptop")
if !ok1 || !ok2 {
t.Fatal("two devices could not sign in")
}
ss, _ := db.Sessions("dan")
if len(ss) != 2 || ss[0].IP == "" || ss[0].Agent == "" || ss[0].ID == "" {
t.Fatalf("sessions = %+v", ss)
}
if a, _ := db.AccountByName("dan"); a.LastLogin == "" {
t.Errorf("last login not recorded")
}
// Ending one device leaves the other signed in.
if err := db.EndSessionID(SessionID(tok1), "dan"); err != nil {
t.Fatal(err)
}
if _, ok := db.SessionAccount(tok1); ok {
t.Errorf("ended session still resolves")
}
if _, ok := db.SessionAccount(tok2); !ok {
t.Errorf("the other device was signed out too")
}
// The owner guard: another account cannot end dan's session.
if err := db.EndSessionID(SessionID(tok2), "mallory"); err != nil {
t.Fatal(err)
}
if _, ok := db.SessionAccount(tok2); !ok {
t.Errorf("a different account ended dan's session")
}
if err := db.SetDisabled("dan", true); err != nil {
t.Fatal(err)
}
if _, ok := db.SessionAccount(tok2); ok {
t.Errorf("a disabled account's session still resolves")
}
if _, _, ok := db.NewSession("dan", "pw"); ok {
t.Errorf("a disabled account signed in")
}
if a, _ := db.AccountByName("dan"); a.Disabled == "" {
t.Errorf("account not marked disabled")
}
if err := db.SetDisabled("dan", false); err != nil {
t.Fatal(err)
}
if _, _, ok := db.NewSession("dan", "pw"); !ok {
t.Errorf("a re-enabled account cannot sign in")
}
if err := db.SetDisabled("nobody", true); err == nil {
t.Errorf("disabling an unknown account succeeded")
}
}