login_test.go

  1package rtw
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"net/http"
  7	"net/http/httptest"
  8	"testing"
  9	"time"
 10
 11	"git.kilimanjaro.io/rtw/httperr"
 12	"git.kilimanjaro.io/rtw/pages"
 13	"git.kilimanjaro.io/rtw/pkg/abac"
 14	"git.kilimanjaro.io/rtw/pkg/id"
 15	"git.kilimanjaro.io/rtw/pkg/session"
 16	"git.kilimanjaro.io/rtw/user"
 17	"github.com/stretchr/testify/assert"
 18	"github.com/stretchr/testify/require"
 19)
 20
 21func setupLoginTest(t *testing.T) (*user.Service, *httperr.Handler, func()) {
 22	tmpDir := t.TempDir()
 23	us, err := user.NewService(user.WithBasePath(tmpDir))
 24	require.NoError(t, err)
 25
 26	eh := httperr.NewHandler()
 27
 28	cleanup := func() {
 29		us.Shutdown()
 30	}
 31
 32	return us, eh, cleanup
 33}
 34
 35func TestLogin_POST_MigratesAnonymousSession(t *testing.T) {
 36	us, eh, cleanup := setupLoginTest(t)
 37	defer cleanup()
 38
 39	// Create a registered user
 40	email := "[email protected]"
 41	password := "password123"
 42	userID, err := us.Signup(&email, nil, password)
 43	require.NoError(t, err)
 44
 45	// Create an anonymous user and session
 46	anonID, err := us.CreateNewAnonSession(httptest.NewRecorder())
 47	require.NoError(t, err)
 48
 49	// Give anonymous user admin permission on a document
 50	docID := id.Key(42)
 51	require.NoError(t, us.SetDocumentPermission(docID, anonID, abac.Admin))
 52
 53	// Create a valid anonymous session
 54	anonSessionID := session.NewID()
 55	require.NoError(t, session.Persist(us.DB(), anonSessionID, anonID, time.Now().UTC().Add(24*time.Hour)))
 56
 57	// Build the handler chain: Session middleware → Login handler
 58	mw := Session(us, eh, CreateAnonymousSessions(false), NoExpiredRedirect())
 59	handler := mw(pages.NewLoginSignupHandler(us, eh, false))
 60
 61	// POST /login with the anonymous session cookie
 62	body, _ := json.Marshal(map[string]string{
 63		"user":     email,
 64		"password": password,
 65	})
 66	req := httptest.NewRequest(http.MethodPost, "/login", bytes.NewReader(body))
 67	req.Header.Set("Content-Type", "application/json")
 68	req.AddCookie(&http.Cookie{
 69		Name:  "__Host-session-id",
 70		Value: anonSessionID,
 71	})
 72	rr := httptest.NewRecorder()
 73
 74	handler.ServeHTTP(rr, req)
 75
 76	// Assert: Should return 200 with user JSON
 77	assert.Equal(t, http.StatusOK, rr.Code)
 78
 79	var respUser user.User
 80	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &respUser))
 81	assert.Equal(t, email, respUser.Email)
 82	assert.True(t, respUser.Registered)
 83
 84	// Assert: Permission migrated to the registered user
 85	perm, err := us.GetDocumentPermission(docID, userID)
 86	require.NoError(t, err)
 87	assert.True(t, abac.Has(perm, abac.Admin))
 88}
 89
 90func TestLogin_GET_RegisteredSessionRedirectsToHome(t *testing.T) {
 91	us, eh, cleanup := setupLoginTest(t)
 92	defer cleanup()
 93
 94	// Create a registered user
 95	email := "[email protected]"
 96	password := "password123"
 97	userID, err := us.Signup(&email, nil, password)
 98	require.NoError(t, err)
 99
100	// Create a valid registered session
101	sessionID := session.NewID()
102	require.NoError(t, session.Persist(us.DB(), sessionID, userID, time.Now().UTC().Add(24*time.Hour)))
103
104	// Build the handler chain
105	mw := Session(us, eh, CreateAnonymousSessions(false), NoExpiredRedirect())
106	handler := mw(pages.NewLoginSignupHandler(us, eh, false))
107
108	// GET /login with the registered session cookie
109	req := httptest.NewRequest(http.MethodGet, "/login", nil)
110	req.AddCookie(&http.Cookie{
111		Name:  "__Host-session-id",
112		Value: sessionID,
113	})
114	rr := httptest.NewRecorder()
115
116	handler.ServeHTTP(rr, req)
117
118	// Assert: Should redirect to user home
119	assert.Equal(t, http.StatusFound, rr.Code)
120	location := rr.Header().Get("Location")
121	assert.Equal(t, us.HomeURL(userID), location)
122}
123
124func TestLogin_GET_ExpiredSession_NoRedirectLoop(t *testing.T) {
125	us, eh, cleanup := setupLoginTest(t)
126	defer cleanup()
127
128	// Create an expired session cookie (no corresponding DB record)
129	expiredSessionID := session.NewID()
130
131	// Build the handler chain
132	mw := Session(us, eh, CreateAnonymousSessions(false), NoExpiredRedirect())
133	handler := mw(pages.NewLoginSignupHandler(us, eh, false))
134
135	// GET /login with the expired session cookie
136	req := httptest.NewRequest(http.MethodGet, "/login", nil)
137	req.AddCookie(&http.Cookie{
138		Name:  "__Host-session-id",
139		Value: expiredSessionID,
140	})
141	rr := httptest.NewRecorder()
142
143	handler.ServeHTTP(rr, req)
144
145	// Assert: Should NOT redirect — should render login page (200)
146	assert.Equal(t, http.StatusOK, rr.Code)
147
148	// Assert: Expired cookie should be cleared (MaxAge = -1)
149	cookies := rr.Result().Cookies()
150	var sessionCookie *http.Cookie
151	for _, c := range cookies {
152		if c.Name == "__Host-session-id" {
153			sessionCookie = c
154			break
155		}
156	}
157	require.NotNil(t, sessionCookie, "session cookie should be set to expire")
158	assert.Equal(t, -1, sessionCookie.MaxAge)
159}
160
161func TestSignup_POST_MigratesAnonymousSession(t *testing.T) {
162	us, eh, cleanup := setupLoginTest(t)
163	defer cleanup()
164
165	// Create an anonymous user and session
166	anonID, err := us.CreateNewAnonSession(httptest.NewRecorder())
167	require.NoError(t, err)
168
169	// Give anonymous user admin permission on a document
170	docID := id.Key(42)
171	require.NoError(t, us.SetDocumentPermission(docID, anonID, abac.Admin))
172
173	// Create a valid anonymous session
174	anonSessionID := session.NewID()
175	require.NoError(t, session.Persist(us.DB(), anonSessionID, anonID, time.Now().UTC().Add(24*time.Hour)))
176
177	// Build the handler chain: Session middleware → Signup handler
178	mw := Session(us, eh, CreateAnonymousSessions(false), NoExpiredRedirect())
179	handler := mw(pages.NewLoginSignupHandler(us, eh, true))
180
181	// POST /signup with the anonymous session cookie
182	body, _ := json.Marshal(map[string]string{
183		"user":     "[email protected]",
184		"handle":   "newuser",
185		"password": "password123",
186	})
187	req := httptest.NewRequest(http.MethodPost, "/signup", bytes.NewReader(body))
188	req.Header.Set("Content-Type", "application/json")
189	req.AddCookie(&http.Cookie{
190		Name:  "__Host-session-id",
191		Value: anonSessionID,
192	})
193	rr := httptest.NewRecorder()
194
195	handler.ServeHTTP(rr, req)
196
197	// Assert: Should return 200 with user JSON
198	assert.Equal(t, http.StatusOK, rr.Code)
199
200	var respUser user.User
201	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &respUser))
202	assert.Equal(t, "[email protected]", respUser.Email)
203	assert.Equal(t, "newuser", respUser.Handle)
204	assert.True(t, respUser.Registered)
205
206	// Assert: Permission migrated to the newly registered user
207	perm, err := us.GetDocumentPermission(docID, respUser.ID)
208	require.NoError(t, err)
209	assert.True(t, abac.Has(perm, abac.Admin))
210}