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}