func newUser() *data.User { return &data.User{ Name: data.NewString("test"), PasswordDigest: data.NewBytes([]byte("digest")), PasswordSalt: data.NewBytes([]byte("salt")), } }
func SetPassword(u *data.User, password string) error { salt := make([]byte, 8) _, err := rand.Read(salt) if err != nil { return err } digest, err := scrypt.Key([]byte(password), salt, 16384, 8, 1, 32) if err != nil { return err } u.PasswordDigest = data.NewBytes(digest) u.PasswordSalt = data.NewBytes(salt) return nil }
func TestExportOPML(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), PasswordDigest: data.NewBytes([]byte("digest")), PasswordSalt: data.NewBytes([]byte("salt")), }) if err != nil { t.Fatal(err) } err = data.InsertSubscription(pool, userID, "http://example.com/feed.rss") if err != nil { t.Fatal(err) } req, err := http.NewRequest("GET", "http://example.com/", nil) if err != nil { t.Fatal(err) } env := &environment{pool: pool} env.user = &data.User{ID: data.NewInt32(userID), Name: data.NewString("test")} w := httptest.NewRecorder() ExportFeedsHandler(w, req, env) if w.Code != 200 { t.Fatalf("Expected HTTP status 200, instead received %d", w.Code) } expected := `<?xml version="1.0" encoding="UTF-8"?> <opml version="1.0"><head><title>The Pithy Reader Export for test</title></head><body><outline text="http://example.com/feed.rss" title="http://example.com/feed.rss" type="rss" xmlUrl="http://example.com/feed.rss"></outline></body></opml>` if w.Body.String() != expected { t.Fatalf("Expected:\n%s\nGot:\n%s\n", expected, w.Body.String()) } }
func TestDataSessions(t *testing.T) { pool := newConnPool(t) userID, err := data.CreateUser(pool, newUser()) if err != nil { t.Fatal(err) } sessionID := []byte("deadbeef") err = data.InsertSession(pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: data.NewInt32(userID), }, ) if err != nil { t.Fatal(err) } user, err := data.SelectUserBySessionID(pool, sessionID) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } err = data.DeleteSession(pool, sessionID) if err != nil { t.Fatal(err) } _, err = data.SelectUserBySessionID(pool, sessionID) if err != data.ErrNotFound { t.Fatalf("Expected %v, got %v", data.ErrNotFound, err) } err = data.DeleteSession(pool, sessionID) if err != data.ErrNotFound { t.Fatalf("Expected %v, got %v", notFound, err) } }
func TestDataUsersLifeCycle(t *testing.T) { pool := newConnPool(t) input := &data.User{ Name: data.NewString("test"), Email: data.NewString("*****@*****.**"), PasswordDigest: data.NewBytes([]byte("digest")), PasswordSalt: data.NewBytes([]byte("salt")), } userID, err := data.CreateUser(pool, input) if err != nil { t.Fatal(err) } user, err := data.SelectUserByName(pool, input.Name.Value) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } user, err = data.SelectUserByEmail(pool, input.Email.Value) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } user, err = data.SelectUserByPK(pool, userID) if err != nil { t.Fatal(err) } if user.ID.Value != userID { t.Errorf("Expected %v, got %v", userID, user.ID) } if user.Name != input.Name { t.Errorf("Expected %v, got %v", input.Name, user.Name) } if user.Email != input.Email { t.Errorf("Expected %v, got %v", input.Email, user.Email) } if bytes.Compare(user.PasswordDigest.Value, input.PasswordDigest.Value) != 0 { t.Errorf("Expected user (%v) and input (%v) PasswordDigest to match, but they did not", user.PasswordDigest, input.PasswordDigest) } if bytes.Compare(user.PasswordSalt.Value, input.PasswordSalt.Value) != 0 { t.Errorf("Expected user (%v), and input (%v) PasswordSalt to match, but they did not", user.PasswordSalt, input.PasswordSalt) } }
func RegisterHandler(w http.ResponseWriter, req *http.Request, env *environment) { var registration struct { Name string `json:"name"` Email string `json:"email"` Password string `json:"password"` } decoder := json.NewDecoder(req.Body) if err := decoder.Decode(®istration); err != nil { w.WriteHeader(422) fmt.Fprintf(w, "Error decoding request: %v", err) return } if registration.Name == "" { w.WriteHeader(422) fmt.Fprintln(w, `Request must include the attribute "name"`) return } if len(registration.Name) > 30 { w.WriteHeader(422) fmt.Fprintln(w, `"name" must be less than 30 characters`) return } err := validatePassword(registration.Password) if err != nil { w.WriteHeader(422) fmt.Fprintln(w, err) return } user := &data.User{} user.Name = data.NewString(registration.Name) user.Email = newStringFallback(registration.Email, data.Undefined) SetPassword(user, registration.Password) userID, err := data.CreateUser(env.pool, user) if err != nil { if err, ok := err.(data.DuplicationError); ok { w.WriteHeader(422) fmt.Fprintf(w, `"%s" is already taken`, err.Field) return } else { http.Error(w, "Internal server error", http.StatusInternalServerError) return } } sessionID, err := genSessionID() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } err = data.InsertSession(env.pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: data.NewInt32(userID), }, ) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } response.Name = registration.Name response.SessionID = hex.EncodeToString(sessionID) encoder := json.NewEncoder(w) encoder.Encode(response) }
func ResetPasswordHandler(w http.ResponseWriter, req *http.Request, env *environment) { var resetPassword struct { Token string `json:"token"` Password string `json:"password"` } decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&resetPassword); err != nil { w.WriteHeader(422) fmt.Fprintf(w, "Error decoding request: %v", err) return } pwr, err := data.SelectPasswordResetByPK(env.pool, resetPassword.Token) if err == data.ErrNotFound { w.WriteHeader(404) return } else if err != nil { w.WriteHeader(500) fmt.Fprintf(w, "Error decoding request: %v", err) return } if pwr.UserID.Status != data.Present { w.WriteHeader(404) return } if pwr.CompletionTime.Status == data.Present { w.WriteHeader(404) return } attrs := &data.User{} SetPassword(attrs, resetPassword.Password) err = data.UpdateUser(env.pool, pwr.UserID.Value, attrs) if err != nil { w.WriteHeader(500) return } user, err := data.SelectUserByPK(env.pool, pwr.UserID.Value) if err != nil { w.WriteHeader(500) return } sessionID, err := genSessionID() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } err = data.InsertSession(env.pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: user.ID, }, ) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } response.Name = user.Name.Value response.SessionID = hex.EncodeToString(sessionID) encoder := json.NewEncoder(w) encoder.Encode(response) }
func CreateSessionHandler(w http.ResponseWriter, req *http.Request, env *environment) { var credentials struct { Name string `json:"name"` Password string `json:"password"` } decoder := json.NewDecoder(req.Body) if err := decoder.Decode(&credentials); err != nil { w.WriteHeader(422) fmt.Fprintf(w, "Error decoding request: %v", err) return } if credentials.Name == "" { w.WriteHeader(422) fmt.Fprintln(w, `Request must include the attribute "name"`) return } if credentials.Password == "" { w.WriteHeader(422) fmt.Fprintln(w, `Request must include the attribute "password"`) return } user, err := data.SelectUserByName(env.pool, credentials.Name) if err != nil { w.WriteHeader(422) fmt.Fprintln(w, "Bad user name or password") return } if !IsPassword(user, credentials.Password) { w.WriteHeader(422) fmt.Fprintln(w, "Bad user name or password") return } sessionID, err := genSessionID() if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } err = data.InsertSession(env.pool, &data.Session{ ID: data.NewBytes(sessionID), UserID: user.ID, }, ) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) var response struct { Name string `json:"name"` SessionID string `json:"sessionID"` } response.Name = credentials.Name response.SessionID = hex.EncodeToString(sessionID) encoder := json.NewEncoder(w) encoder.Encode(response) }