github.com/volatiletech/authboss@v2.4.1+incompatible/remember/remember_test.go (about) 1 package remember 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha512" 7 "encoding/base64" 8 "net/http" 9 "net/http/httptest" 10 "testing" 11 12 "github.com/volatiletech/authboss" 13 "github.com/volatiletech/authboss/mocks" 14 ) 15 16 func TestInit(t *testing.T) { 17 t.Parallel() 18 19 ab := authboss.New() 20 r := &Remember{} 21 err := r.Init(ab) 22 if err != nil { 23 t.Fatal(err) 24 } 25 } 26 27 type testHarness struct { 28 remember *Remember 29 ab *authboss.Authboss 30 31 session *mocks.ClientStateRW 32 cookies *mocks.ClientStateRW 33 storer *mocks.ServerStorer 34 } 35 36 func testSetup() *testHarness { 37 harness := &testHarness{} 38 39 harness.ab = authboss.New() 40 harness.session = mocks.NewClientRW() 41 harness.cookies = mocks.NewClientRW() 42 harness.storer = mocks.NewServerStorer() 43 44 harness.ab.Config.Core.Logger = mocks.Logger{} 45 harness.ab.Config.Storage.SessionState = harness.session 46 harness.ab.Config.Storage.CookieState = harness.cookies 47 harness.ab.Config.Storage.Server = harness.storer 48 49 harness.remember = &Remember{harness.ab} 50 51 return harness 52 } 53 54 func TestRememberAfterAuth(t *testing.T) { 55 t.Parallel() 56 57 h := testSetup() 58 59 user := &mocks.User{Email: "test@test.com"} 60 61 r := mocks.Request("POST") 62 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyValues, mocks.Values{Remember: true})) 63 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 64 rec := httptest.NewRecorder() 65 w := h.ab.NewResponse(rec) 66 67 if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil { 68 t.Fatal(err) 69 } else if handled { 70 t.Error("should never be handled") 71 } 72 73 // Force flush of headers so cookies are written 74 w.WriteHeader(http.StatusOK) 75 76 if len(h.storer.RMTokens["test@test.com"]) != 1 { 77 t.Error("token was not persisted:", h.storer.RMTokens) 78 } 79 80 if cookie, ok := h.cookies.ClientValues[authboss.CookieRemember]; !ok || len(cookie) == 0 { 81 t.Error("remember me cookie was not set") 82 } 83 } 84 85 func TestRememberAfterAuthSkip(t *testing.T) { 86 t.Parallel() 87 88 h := testSetup() 89 90 r := mocks.Request("POST") 91 rec := httptest.NewRecorder() 92 w := h.ab.NewResponse(rec) 93 94 if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil { 95 t.Fatal(err) 96 } else if handled { 97 t.Error("should never be handled") 98 } 99 100 if len(h.storer.RMTokens["test@test.com"]) != 0 { 101 t.Error("expected no tokens to be created") 102 } 103 104 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyValues, mocks.Values{Remember: false})) 105 106 if handled, err := h.remember.RememberAfterAuth(w, r, false); err != nil { 107 t.Fatal(err) 108 } else if handled { 109 t.Error("should never be handled") 110 } 111 112 if len(h.storer.RMTokens["test@test.com"]) != 0 { 113 t.Error("expected no tokens to be created") 114 } 115 } 116 117 func TestMiddlewareAuth(t *testing.T) { 118 t.Parallel() 119 120 h := testSetup() 121 122 user := &mocks.User{Email: "test@test.com"} 123 hash, token, _ := GenerateToken(user.Email) 124 125 h.storer.Users[user.Email] = user 126 h.storer.RMTokens[user.Email] = []string{hash} 127 h.cookies.ClientValues[authboss.CookieRemember] = token 128 129 r := mocks.Request("POST") 130 rec := httptest.NewRecorder() 131 w := h.ab.NewResponse(rec) 132 133 var err error 134 r, err = h.ab.LoadClientState(w, r) 135 if err != nil { 136 t.Fatal(err) 137 } 138 139 called := false 140 server := Middleware(h.ab)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 141 called = true 142 w.WriteHeader(http.StatusOK) 143 })) 144 145 server.ServeHTTP(w, r) 146 147 if !called { 148 t.Error("it should have called the underlying handler") 149 } 150 151 if h.session.ClientValues[authboss.SessionKey] != user.Email { 152 t.Error("should have saved the pid in the session") 153 } 154 // Elided the rest of the checks, authenticate tests do this 155 } 156 157 func TestAuthenticateSuccess(t *testing.T) { 158 t.Parallel() 159 160 h := testSetup() 161 162 user := &mocks.User{Email: "test@test.com"} 163 hash, token, _ := GenerateToken(user.Email) 164 165 h.storer.Users[user.Email] = user 166 h.storer.RMTokens[user.Email] = []string{hash} 167 h.cookies.ClientValues[authboss.CookieRemember] = token 168 169 r := mocks.Request("POST") 170 rec := httptest.NewRecorder() 171 w := h.ab.NewResponse(rec) 172 173 var err error 174 r, err = h.ab.LoadClientState(w, r) 175 if err != nil { 176 t.Fatal(err) 177 } 178 179 if err = Authenticate(h.ab, w, &r); err != nil { 180 t.Fatal(err) 181 } 182 183 w.WriteHeader(http.StatusOK) 184 185 if cookie := h.cookies.ClientValues[authboss.CookieRemember]; cookie == token { 186 t.Error("the cookie should have been replaced with a new token") 187 } 188 189 if len(h.storer.RMTokens[user.Email]) != 1 { 190 t.Error("one token should have been removed, and one should have been added") 191 } else if h.storer.RMTokens[user.Email][0] == token { 192 t.Error("a new token should have been saved") 193 } 194 195 if h.session.ClientValues[authboss.SessionKey] != user.Email { 196 t.Error("should have saved the pid in the session") 197 } 198 if h.session.ClientValues[authboss.SessionHalfAuthKey] != "true" { 199 t.Error("it should have become a half-authed session") 200 } 201 202 if r.Context().Value(authboss.CTXKeyPID).(string) != "test@test.com" { 203 t.Error("should have set the context value to log the user in") 204 } 205 } 206 207 func TestAuthenticateTokenNotFound(t *testing.T) { 208 t.Parallel() 209 210 h := testSetup() 211 212 user := &mocks.User{Email: "test@test.com"} 213 _, token, _ := GenerateToken(user.Email) 214 215 h.storer.Users[user.Email] = user 216 h.cookies.ClientValues[authboss.CookieRemember] = token 217 218 r := mocks.Request("POST") 219 rec := httptest.NewRecorder() 220 w := h.ab.NewResponse(rec) 221 222 var err error 223 r, err = h.ab.LoadClientState(w, r) 224 if err != nil { 225 t.Fatal(err) 226 } 227 228 if err = Authenticate(h.ab, w, &r); err != nil { 229 t.Fatal(err) 230 } 231 232 w.WriteHeader(http.StatusOK) 233 234 if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 { 235 t.Error("there should be no remember cookie left") 236 } 237 238 if len(h.session.ClientValues[authboss.SessionKey]) != 0 { 239 t.Error("it should have not logged the user in") 240 } 241 242 if r.Context().Value(authboss.CTXKeyPID) != nil { 243 t.Error("the context's pid should be empty") 244 } 245 } 246 247 func TestAuthenticateBadTokens(t *testing.T) { 248 t.Parallel() 249 250 h := testSetup() 251 252 doTest := func(t *testing.T) { 253 t.Helper() 254 255 r := mocks.Request("POST") 256 rec := httptest.NewRecorder() 257 w := h.ab.NewResponse(rec) 258 259 var err error 260 r, err = h.ab.LoadClientState(w, r) 261 if err != nil { 262 t.Fatal(err) 263 } 264 265 if err = Authenticate(h.ab, w, &r); err != nil { 266 t.Fatal(err) 267 } 268 269 w.WriteHeader(http.StatusOK) 270 271 if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 { 272 t.Error("there should be no remember cookie left") 273 } 274 275 if len(h.session.ClientValues[authboss.SessionKey]) != 0 { 276 t.Error("it should have not logged the user in") 277 } 278 279 if r.Context().Value(authboss.CTXKeyPID) != nil { 280 t.Error("the context's pid should be empty") 281 } 282 } 283 284 t.Run("base64", func(t *testing.T) { 285 h.cookies.ClientValues[authboss.CookieRemember] = "a" 286 doTest(t) 287 }) 288 t.Run("cookieformat", func(t *testing.T) { 289 h.cookies.ClientValues[authboss.CookieRemember] = `aGVsbG8=` // hello 290 doTest(t) 291 }) 292 } 293 294 func TestAfterPasswordReset(t *testing.T) { 295 t.Parallel() 296 297 h := testSetup() 298 299 user := &mocks.User{Email: "test@test.com"} 300 hash1, _, _ := GenerateToken(user.Email) 301 hash2, token2, _ := GenerateToken(user.Email) 302 303 h.storer.Users[user.Email] = user 304 h.storer.RMTokens[user.Email] = []string{hash1, hash2} 305 h.cookies.ClientValues[authboss.CookieRemember] = token2 306 307 r := mocks.Request("POST") 308 r = r.WithContext(context.WithValue(r.Context(), authboss.CTXKeyUser, user)) 309 rec := httptest.NewRecorder() 310 w := h.ab.NewResponse(rec) 311 312 if handled, err := h.remember.AfterPasswordReset(w, r, false); err != nil { 313 t.Error(err) 314 } else if handled { 315 t.Error("it should never be handled") 316 } 317 318 w.WriteHeader(http.StatusOK) // Force header flush 319 320 if len(h.storer.RMTokens[user.Email]) != 0 { 321 t.Error("all remember me tokens should have been removed") 322 } 323 if len(h.cookies.ClientValues[authboss.CookieRemember]) != 0 { 324 t.Error("there should be no remember cookie left") 325 } 326 } 327 328 func TestGenerateToken(t *testing.T) { 329 t.Parallel() 330 331 hash, tok, err := GenerateToken("test") 332 if err != nil { 333 t.Fatal(err) 334 } 335 336 rawToken, err := base64.URLEncoding.DecodeString(tok) 337 if err != nil { 338 t.Error(err) 339 } 340 341 index := bytes.IndexByte(rawToken, ';') 342 if index < 0 { 343 t.Fatalf("problem with the token format: %v", rawToken) 344 } 345 346 bytPID := rawToken[:index] 347 if string(bytPID) != "test" { 348 t.Errorf("pid wrong: %s", bytPID) 349 } 350 351 sum := sha512.Sum512(rawToken) 352 gotHash := base64.StdEncoding.EncodeToString(sum[:]) 353 if hash != gotHash { 354 t.Errorf("hash wrong, want: %s, got: %s", hash, gotHash) 355 } 356 }