Implement owner setup and browser authentication sessions
This commit is contained in:
parent
077cf7601a
commit
715423ab8e
21 changed files with 2185 additions and 14 deletions
|
|
@ -2,11 +2,26 @@ package postgres
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"drive.local/drivev2/internal/adapters/authn"
|
||||
"drive.local/drivev2/internal/application"
|
||||
)
|
||||
|
||||
type integrationPasswordHasher struct{}
|
||||
|
||||
func (integrationPasswordHasher) Hash(password string) (string, error) {
|
||||
return "integration:" + password, nil
|
||||
}
|
||||
|
||||
func (integrationPasswordHasher) Verify(password, encoded string) (bool, error) {
|
||||
return encoded == "integration:"+password, nil
|
||||
}
|
||||
|
||||
func TestMigrationsAndStore(t *testing.T) {
|
||||
databaseURL := os.Getenv("DRIVE_TEST_DATABASE_URL")
|
||||
if databaseURL == "" {
|
||||
|
|
@ -38,4 +53,85 @@ func TestMigrationsAndStore(t *testing.T) {
|
|||
if initialized {
|
||||
t.Fatal("OwnerExists = true on a fresh database, want false")
|
||||
}
|
||||
|
||||
authentication := application.NewAuthenticationService(
|
||||
store,
|
||||
integrationPasswordHasher{},
|
||||
authn.CredentialGenerator{},
|
||||
authn.SystemClock{},
|
||||
)
|
||||
inputs := []application.SetupOwnerInput{
|
||||
{Username: "owner-one", DisplayName: "Owner One", Password: "correct horse battery staple"},
|
||||
{Username: "owner-two", DisplayName: "Owner Two", Password: "correct horse battery staple"},
|
||||
}
|
||||
results := make([]application.SetupOwnerResult, len(inputs))
|
||||
errorsByAttempt := make([]error, len(inputs))
|
||||
var waitGroup sync.WaitGroup
|
||||
for index := range inputs {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
results[index], errorsByAttempt[index] = authentication.SetupOwner(ctx, inputs[index])
|
||||
}()
|
||||
}
|
||||
waitGroup.Wait()
|
||||
|
||||
winner := -1
|
||||
losers := 0
|
||||
for index, setupErr := range errorsByAttempt {
|
||||
switch {
|
||||
case setupErr == nil:
|
||||
winner = index
|
||||
case errors.Is(setupErr, application.ErrSetupAlreadyComplete):
|
||||
losers++
|
||||
default:
|
||||
t.Fatalf("setup attempt %d returned unexpected error: %v", index, setupErr)
|
||||
}
|
||||
}
|
||||
if winner < 0 || losers != 1 {
|
||||
t.Fatalf("winner = %d, losing attempts = %d, want one of each", winner, losers)
|
||||
}
|
||||
if len(results[winner].RecoveryCodes) != application.RecoveryCodeCount {
|
||||
t.Fatalf("recovery code count = %d, want %d", len(results[winner].RecoveryCodes), application.RecoveryCodeCount)
|
||||
}
|
||||
|
||||
counts, err := store.queries.GetAuthenticationInvariantCounts(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetAuthenticationInvariantCounts returned an error: %v", err)
|
||||
}
|
||||
if counts.Owners != 1 || counts.Roots != 1 || counts.RecoveryCodes != 10 || counts.Changes != 1 ||
|
||||
counts.AuditEvents != 1 || counts.Sessions != 1 || counts.ActiveSessions != 1 {
|
||||
t.Fatalf("unexpected post-setup counts: %#v", counts)
|
||||
}
|
||||
|
||||
loggedIn, err := authentication.Login(ctx, application.LoginInput{
|
||||
Username: inputs[winner].Username, Password: inputs[winner].Password,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Login returned an error: %v", err)
|
||||
}
|
||||
current, err := authentication.CurrentSession(ctx, loggedIn.SessionToken)
|
||||
if err != nil {
|
||||
t.Fatalf("CurrentSession returned an error: %v", err)
|
||||
}
|
||||
if current.ID != loggedIn.Session.ID {
|
||||
t.Fatalf("current session ID = %q, want %q", current.ID, loggedIn.Session.ID)
|
||||
}
|
||||
if err := authentication.Logout(ctx, loggedIn.SessionToken, loggedIn.CSRFToken, "wrong-csrf"); !errors.Is(err, application.ErrCSRFValidation) {
|
||||
t.Fatalf("Logout with invalid CSRF error = %v, want %v", err, application.ErrCSRFValidation)
|
||||
}
|
||||
if err := authentication.Logout(ctx, loggedIn.SessionToken, loggedIn.CSRFToken, loggedIn.CSRFToken); err != nil {
|
||||
t.Fatalf("Logout returned an error: %v", err)
|
||||
}
|
||||
if _, err := authentication.CurrentSession(ctx, loggedIn.SessionToken); !errors.Is(err, application.ErrSessionNotFound) {
|
||||
t.Fatalf("CurrentSession after logout error = %v, want %v", err, application.ErrSessionNotFound)
|
||||
}
|
||||
|
||||
counts, err = store.queries.GetAuthenticationInvariantCounts(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetAuthenticationInvariantCounts after logout returned an error: %v", err)
|
||||
}
|
||||
if counts.Sessions != 2 || counts.ActiveSessions != 1 || counts.AuditEvents != 3 {
|
||||
t.Fatalf("unexpected post-logout counts: %#v", counts)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue