Test account api (WIP)

This commit is contained in:
binwiederhier 2022-12-28 22:16:11 -05:00
parent 367d024a2d
commit 3512db1fe7
6 changed files with 144 additions and 27 deletions

View file

@ -44,7 +44,7 @@ const (
DefaultVisitorRequestLimitReplenish = 5 * time.Second
DefaultVisitorEmailLimitBurst = 16
DefaultVisitorEmailLimitReplenish = time.Hour
DefaultVisitorAccountCreateLimitBurst = 2
DefaultVisitorAccountCreateLimitBurst = 3
DefaultVisitorAccountCreateLimitReplenish = 24 * time.Hour
DefaultVisitorAttachmentTotalSizeLimit = 100 * 1024 * 1024 // 100 MB
DefaultVisitorAttachmentDailyBandwidthLimit = 500 * 1024 * 1024 // 500 MB

View file

@ -50,15 +50,12 @@ import (
- figure out what settings are "web" or "phone"
UI:
- Subscription dotmenu dropdown: Move to nav bar, or make same as profile dropdown
- Translations
- aria-labels
- Home UI sign-in/login to top right
-
rate limiting:
- login/account endpoints
Pages:
- Home
- Password reset
- Pricing
- change email
Polishing:
aria-label for everything
Tests:
- APIs
- CRUD tokens
@ -66,6 +63,12 @@ import (
- userManager can be nil
- visitor with/without user
- userManager.<NEWSTUFF>
Later:
- Password reset
- Pricing
- change email
*/
// Server is the main server, providing the UI and API for ntfy
@ -1417,7 +1420,7 @@ func (s *Server) ensureUserManager(next handleFunc) handleFunc {
func (s *Server) ensureUser(next handleFunc) handleFunc {
return s.ensureUserManager(func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if v.user == nil {
return errHTTPNotFound
return errHTTPUnauthorized
}
return next(w, r, v)
})

View file

@ -109,20 +109,16 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
}
func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
if v.user == nil {
return errHTTPUnauthorized
}
if err := s.userManager.RemoveUser(v.user.Name); err != nil {
return err
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
// FIXME return something
return nil
}
func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
newPassword, err := util.ReadJSONWithLimit[apiAccountCreateRequest](r.Body, jsonBodyBytesLimit)
newPassword, err := util.ReadJSONWithLimit[apiAccountPasswordChangeRequest](r.Body, jsonBodyBytesLimit)
if err != nil {
return err
}

View file

@ -1,14 +1,16 @@
package server
import (
"fmt"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"io"
"testing"
"time"
)
func TestAccount_Create_Success(t *testing.T) {
func TestAccount_Signup_Success(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = true
s := newTestServer(t, conf)
@ -33,7 +35,34 @@ func TestAccount_Create_Success(t *testing.T) {
require.Equal(t, "user", account.Role)
}
func TestAccount_Create_Disabled(t *testing.T) {
func TestAccount_Signup_UserExists(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = true
s := newTestServer(t, conf)
rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
require.Equal(t, 200, rr.Code)
rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
require.Equal(t, 409, rr.Code)
require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
}
func TestAccount_Signup_LimitReached(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = true
s := newTestServer(t, conf)
for i := 0; i < 3; i++ {
rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
require.Equal(t, 200, rr.Code)
}
rr := request(t, s, "POST", "/v1/account", `{"username":"thiswontwork", "password":"mypass"}`, nil)
require.Equal(t, 429, rr.Code)
require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
}
func TestAccount_Signup_Disabled(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = false
s := newTestServer(t, conf)
@ -42,3 +71,79 @@ func TestAccount_Create_Disabled(t *testing.T) {
require.Equal(t, 400, rr.Code)
require.Equal(t, 40022, toHTTPError(t, rr.Body.String()).Code)
}
func TestAccount_Get_Anonymous(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.VisitorRequestLimitReplenish = 86 * time.Second
conf.VisitorEmailLimitReplenish = time.Hour
conf.VisitorAttachmentTotalSizeLimit = 5123
conf.AttachmentFileSizeLimit = 512
s := newTestServer(t, conf)
s.smtpSender = &testMailer{}
rr := request(t, s, "GET", "/v1/account", "", nil)
require.Equal(t, 200, rr.Code)
account, _ := util.ReadJSON[apiAccountResponse](io.NopCloser(rr.Body))
require.Equal(t, "*", account.Username)
require.Equal(t, string(user.RoleAnonymous), account.Role)
require.Equal(t, "ip", account.Limits.Basis)
require.Equal(t, int64(1004), account.Limits.Messages) // I hate this
require.Equal(t, int64(24), account.Limits.Emails) // I hate this
require.Equal(t, int64(5123), account.Limits.AttachmentTotalSize)
require.Equal(t, int64(512), account.Limits.AttachmentFileSize)
require.Equal(t, int64(0), account.Stats.Messages)
require.Equal(t, int64(1004), account.Stats.MessagesRemaining)
require.Equal(t, int64(0), account.Stats.Emails)
require.Equal(t, int64(24), account.Stats.EmailsRemaining)
rr = request(t, s, "POST", "/mytopic", "", nil)
require.Equal(t, 200, rr.Code)
rr = request(t, s, "POST", "/mytopic", "", map[string]string{
"Email": "phil@ntfy.sh",
})
require.Equal(t, 200, rr.Code)
rr = request(t, s, "GET", "/v1/account", "", nil)
require.Equal(t, 200, rr.Code)
account, _ = util.ReadJSON[apiAccountResponse](io.NopCloser(rr.Body))
require.Equal(t, int64(2), account.Stats.Messages)
require.Equal(t, int64(1002), account.Stats.MessagesRemaining)
require.Equal(t, int64(1), account.Stats.Emails)
require.Equal(t, int64(23), account.Stats.EmailsRemaining)
}
func TestAccount_Delete_Success(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = true
s := newTestServer(t, conf)
rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
require.Equal(t, 200, rr.Code)
rr = request(t, s, "GET", "/v1/account", "", map[string]string{
"Authorization": util.BasicAuth("phil", "mypass"),
})
require.Equal(t, 200, rr.Code)
rr = request(t, s, "DELETE", "/v1/account", "", map[string]string{
"Authorization": util.BasicAuth("phil", "mypass"),
})
require.Equal(t, 200, rr.Code)
rr = request(t, s, "GET", "/v1/account", "", map[string]string{
"Authorization": util.BasicAuth("phil", "mypass"),
})
require.Equal(t, 401, rr.Code)
}
func TestAccount_Delete_Not_Allowed(t *testing.T) {
conf := newTestConfigWithUsers(t)
conf.EnableSignup = true
s := newTestServer(t, conf)
rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
require.Equal(t, 200, rr.Code)
rr = request(t, s, "DELETE", "/v1/account", "", nil)
require.Equal(t, 401, rr.Code)
}

View file

@ -225,6 +225,10 @@ type apiAccountCreateRequest struct {
Password string `json:"password"`
}
type apiAccountPasswordChangeRequest struct {
Password string `json:"password"`
}
type apiAccountTokenResponse struct {
Token string `json:"token"`
Expires int64 `json:"expires"`