Sync topic (begin), rename user fields
This commit is contained in:
parent
b27c608508
commit
7e528d9c10
10 changed files with 107 additions and 82 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tierReset = "-"
|
tierReset = "-"
|
||||||
|
createdByCLI = "cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -196,7 +197,7 @@ func execUserAdd(c *cli.Context) error {
|
||||||
|
|
||||||
password = p
|
password = p
|
||||||
}
|
}
|
||||||
if err := manager.AddUser(username, password, role); err != nil {
|
if err := manager.AddUser(username, password, role, createdByCLI); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)
|
fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)
|
||||||
|
|
|
@ -536,6 +536,7 @@ func TestSqliteCache_Migration_From9(t *testing.T) {
|
||||||
// Create cache to trigger migration
|
// Create cache to trigger migration
|
||||||
cacheDuration := 17 * time.Hour
|
cacheDuration := 17 * time.Hour
|
||||||
c, err := newSqliteCache(filename, "", cacheDuration, 0, 0, false)
|
c, err := newSqliteCache(filename, "", cacheDuration, 0, 0, false)
|
||||||
|
require.Nil(t, err)
|
||||||
checkSchemaVersion(t, c.db)
|
checkSchemaVersion(t, c.db)
|
||||||
|
|
||||||
// Check version
|
// Check version
|
||||||
|
|
|
@ -38,14 +38,18 @@ import (
|
||||||
TODO
|
TODO
|
||||||
Limits & rate limiting:
|
Limits & rate limiting:
|
||||||
login/account endpoints
|
login/account endpoints
|
||||||
purge accounts that were not logged int o in X
|
|
||||||
reset daily Limits for users
|
reset daily Limits for users
|
||||||
|
- set last_stats_reset in migration
|
||||||
|
set sync_topic in migration
|
||||||
|
update last_seen when API is accessed
|
||||||
Make sure account endpoints make sense for admins
|
Make sure account endpoints make sense for admins
|
||||||
|
|
||||||
UI:
|
UI:
|
||||||
- flicker of upgrade banner
|
- flicker of upgrade banner
|
||||||
- JS constants
|
- JS constants
|
||||||
Sync:
|
Sync:
|
||||||
- "account topic" sync mechanism
|
- "account topic" sync mechanism
|
||||||
|
- subscribe to sync topic in UI
|
||||||
- "mute" setting
|
- "mute" setting
|
||||||
- figure out what settings are "web" or "phone"
|
- figure out what settings are "web" or "phone"
|
||||||
Delete visitor when tier is changed to refresh rate limiters
|
Delete visitor when tier is changed to refresh rate limiters
|
||||||
|
@ -54,10 +58,9 @@ import (
|
||||||
- Message rate limiting and reset tests
|
- Message rate limiting and reset tests
|
||||||
Docs:
|
Docs:
|
||||||
- "expires" field in message
|
- "expires" field in message
|
||||||
|
- server.yml: enable-X flags
|
||||||
Refactor:
|
Refactor:
|
||||||
- rename /access -> /reservation
|
- rename /access -> /reservation
|
||||||
Later:
|
|
||||||
- Pricing
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Server is the main server, providing the UI and API for ntfy
|
// Server is the main server, providing the UI and API for ntfy
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
const (
|
const (
|
||||||
jsonBodyBytesLimit = 4096
|
jsonBodyBytesLimit = 4096
|
||||||
subscriptionIDLength = 16
|
subscriptionIDLength = 16
|
||||||
|
createdByAPI = "api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
@ -31,7 +32,7 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
|
||||||
if v.accountLimiter != nil && !v.accountLimiter.Allow() {
|
if v.accountLimiter != nil && !v.accountLimiter.Allow() {
|
||||||
return errHTTPTooManyRequestsLimitAccountCreation
|
return errHTTPTooManyRequestsLimitAccountCreation
|
||||||
}
|
}
|
||||||
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
|
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser, createdByAPI); err != nil { // TODO this should return a User
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
@ -70,6 +71,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *vis
|
||||||
if v.user != nil {
|
if v.user != nil {
|
||||||
response.Username = v.user.Name
|
response.Username = v.user.Name
|
||||||
response.Role = string(v.user.Role)
|
response.Role = string(v.user.Role)
|
||||||
|
response.SyncTopic = v.user.SyncTopic
|
||||||
if v.user.Prefs != nil {
|
if v.user.Prefs != nil {
|
||||||
if v.user.Prefs.Language != "" {
|
if v.user.Prefs.Language != "" {
|
||||||
response.Language = v.user.Prefs.Language
|
response.Language = v.user.Prefs.Language
|
||||||
|
|
|
@ -67,8 +67,8 @@ func TestAccount_Signup_AsUser(t *testing.T) {
|
||||||
conf.EnableSignup = true
|
conf.EnableSignup = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, "unit-test"))
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
|
rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"),
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
@ -133,7 +133,7 @@ func TestAccount_Get_Anonymous(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_ChangeSettings(t *testing.T) {
|
func TestAccount_ChangeSettings(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
user, _ := s.userManager.User("phil")
|
user, _ := s.userManager.User("phil")
|
||||||
token, _ := s.userManager.CreateToken(user)
|
token, _ := s.userManager.CreateToken(user)
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ func TestAccount_ChangeSettings(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
|
func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
|
rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"),
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
@ -210,7 +210,7 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_ChangePassword(t *testing.T) {
|
func TestAccount_ChangePassword(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, map[string]string{
|
rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"),
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
@ -237,7 +237,7 @@ func TestAccount_ChangePassword_NoAccount(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_ExtendToken(t *testing.T) {
|
func TestAccount_ExtendToken(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
|
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"),
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
@ -260,7 +260,7 @@ func TestAccount_ExtendToken(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
|
func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
|
rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
|
"Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
|
||||||
|
@ -271,7 +271,7 @@ func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
|
||||||
|
|
||||||
func TestAccount_DeleteToken(t *testing.T) {
|
func TestAccount_DeleteToken(t *testing.T) {
|
||||||
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
s := newTestServer(t, newTestConfigWithAuthFile(t))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
|
rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "phil"),
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
@ -360,7 +360,7 @@ func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
|
||||||
conf := newTestConfigWithAuthFile(t)
|
conf := newTestConfigWithAuthFile(t)
|
||||||
conf.EnableSignup = true
|
conf.EnableSignup = true
|
||||||
s := newTestServer(t, conf)
|
s := newTestServer(t, conf)
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
rr := request(t, s, "POST", "/v1/account/access", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
|
rr := request(t, s, "POST", "/v1/account/access", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
|
||||||
"Authorization": util.BasicAuth("phil", "adminpass"),
|
"Authorization": util.BasicAuth("phil", "adminpass"),
|
||||||
|
|
|
@ -626,7 +626,7 @@ func TestServer_Auth_Success_Admin(t *testing.T) {
|
||||||
c.AuthFile = filepath.Join(t.TempDir(), "user.db")
|
c.AuthFile = filepath.Join(t.TempDir(), "user.db")
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
||||||
"Authorization": basicAuth("phil:phil"),
|
"Authorization": basicAuth("phil:phil"),
|
||||||
|
@ -641,7 +641,7 @@ func TestServer_Auth_Success_User(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionDenyAll
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", "ben", "mytopic", true, true))
|
require.Nil(t, s.userManager.AllowAccess("", "ben", "mytopic", true, true))
|
||||||
|
|
||||||
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
||||||
|
@ -656,7 +656,7 @@ func TestServer_Auth_Success_User_MultipleTopics(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionDenyAll
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", "ben", "mytopic", true, true))
|
require.Nil(t, s.userManager.AllowAccess("", "ben", "mytopic", true, true))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", "ben", "anothertopic", true, true))
|
require.Nil(t, s.userManager.AllowAccess("", "ben", "anothertopic", true, true))
|
||||||
|
|
||||||
|
@ -677,7 +677,7 @@ func TestServer_Auth_Fail_InvalidPass(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionDenyAll
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
||||||
"Authorization": basicAuth("phil:INVALID"),
|
"Authorization": basicAuth("phil:INVALID"),
|
||||||
|
@ -691,7 +691,7 @@ func TestServer_Auth_Fail_Unauthorized(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionDenyAll
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", "ben", "sometopic", true, true)) // Not mytopic!
|
require.Nil(t, s.userManager.AllowAccess("", "ben", "sometopic", true, true)) // Not mytopic!
|
||||||
|
|
||||||
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
response := request(t, s, "GET", "/mytopic/auth", "", map[string]string{
|
||||||
|
@ -706,7 +706,7 @@ func TestServer_Auth_Fail_CannotPublish(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionReadWrite // Open by default
|
c.AuthDefault = user.PermissionReadWrite // Open by default
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, "unit-test"))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", user.Everyone, "private", false, false))
|
require.Nil(t, s.userManager.AllowAccess("", user.Everyone, "private", false, false))
|
||||||
require.Nil(t, s.userManager.AllowAccess("", user.Everyone, "announcements", true, false))
|
require.Nil(t, s.userManager.AllowAccess("", user.Everyone, "announcements", true, false))
|
||||||
|
|
||||||
|
@ -737,7 +737,7 @@ func TestServer_Auth_ViaQuery(t *testing.T) {
|
||||||
c.AuthDefault = user.PermissionDenyAll
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
s := newTestServer(t, c)
|
s := newTestServer(t, c)
|
||||||
|
|
||||||
require.Nil(t, s.userManager.AddUser("ben", "some pass", user.RoleAdmin))
|
require.Nil(t, s.userManager.AddUser("ben", "some pass", user.RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
u := fmt.Sprintf("/mytopic/json?poll=1&auth=%s", base64.RawURLEncoding.EncodeToString([]byte(basicAuth("ben:some pass"))))
|
u := fmt.Sprintf("/mytopic/json?poll=1&auth=%s", base64.RawURLEncoding.EncodeToString([]byte(basicAuth("ben:some pass"))))
|
||||||
response := request(t, s, "GET", u, "", nil)
|
response := request(t, s, "GET", u, "", nil)
|
||||||
|
@ -1100,7 +1100,7 @@ func TestServer_PublishWithTierBasedMessageLimitAndExpiry(t *testing.T) {
|
||||||
MessagesLimit: 5,
|
MessagesLimit: 5,
|
||||||
MessagesExpiryDuration: -5 * time.Second, // Second, what a hack!
|
MessagesExpiryDuration: -5 * time.Second, // Second, what a hack!
|
||||||
}))
|
}))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
||||||
|
|
||||||
// Publish to reach message limit
|
// Publish to reach message limit
|
||||||
|
@ -1332,7 +1332,7 @@ func TestServer_PublishAttachmentWithTierBasedExpiry(t *testing.T) {
|
||||||
AttachmentTotalSizeLimit: 200_000,
|
AttachmentTotalSizeLimit: 200_000,
|
||||||
AttachmentExpiryDuration: sevenDays, // 7 days
|
AttachmentExpiryDuration: sevenDays, // 7 days
|
||||||
}))
|
}))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
||||||
|
|
||||||
// Publish and make sure we can retrieve it
|
// Publish and make sure we can retrieve it
|
||||||
|
@ -1376,7 +1376,7 @@ func TestServer_PublishAttachmentWithTierBasedLimits(t *testing.T) {
|
||||||
AttachmentTotalSizeLimit: 200_000,
|
AttachmentTotalSizeLimit: 200_000,
|
||||||
AttachmentExpiryDuration: 30 * time.Second,
|
AttachmentExpiryDuration: 30 * time.Second,
|
||||||
}))
|
}))
|
||||||
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, "unit-test"))
|
||||||
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
require.Nil(t, s.userManager.ChangeTier("phil", "test"))
|
||||||
|
|
||||||
// Publish small file as anonymous
|
// Publish small file as anonymous
|
||||||
|
|
|
@ -271,6 +271,7 @@ type apiAccountReservation struct {
|
||||||
type apiAccountResponse struct {
|
type apiAccountResponse struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Role string `json:"role,omitempty"`
|
Role string `json:"role,omitempty"`
|
||||||
|
SyncTopic string `json:"sync_topic,omitempty"`
|
||||||
Language string `json:"language,omitempty"`
|
Language string `json:"language,omitempty"`
|
||||||
Notification *user.NotificationPrefs `json:"notification,omitempty"`
|
Notification *user.NotificationPrefs `json:"notification,omitempty"`
|
||||||
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
|
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
|
||||||
|
|
|
@ -20,6 +20,7 @@ const (
|
||||||
userStatsQueueWriterInterval = 33 * time.Second
|
userStatsQueueWriterInterval = 33 * time.Second
|
||||||
tokenLength = 32
|
tokenLength = 32
|
||||||
tokenExpiryDuration = 72 * time.Hour // Extend tokens by this much
|
tokenExpiryDuration = 72 * time.Hour // Extend tokens by this much
|
||||||
|
syncTopicLength = 16
|
||||||
tokenMaxCount = 10 // Only keep this many tokens in the table per user
|
tokenMaxCount = 10 // Only keep this many tokens in the table per user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,10 +51,15 @@ const (
|
||||||
tier_id INT,
|
tier_id INT,
|
||||||
user TEXT NOT NULL,
|
user TEXT NOT NULL,
|
||||||
pass TEXT NOT NULL,
|
pass TEXT NOT NULL,
|
||||||
role TEXT NOT NULL,
|
role TEXT CHECK (role IN ('anonymous', 'admin', 'user')) NOT NULL,
|
||||||
messages INT NOT NULL DEFAULT (0),
|
prefs JSON NOT NULL DEFAULT '{}',
|
||||||
emails INT NOT NULL DEFAULT (0),
|
sync_topic TEXT NOT NULL,
|
||||||
settings JSON,
|
stats_messages INT NOT NULL DEFAULT (0),
|
||||||
|
stats_emails INT NOT NULL DEFAULT (0),
|
||||||
|
created_by TEXT NOT NULL,
|
||||||
|
created_at INT NOT NULL,
|
||||||
|
last_seen INT NOT NULL,
|
||||||
|
last_stats_reset INT NOT NULL DEFAULT (0),
|
||||||
FOREIGN KEY (tier_id) REFERENCES tier (id)
|
FOREIGN KEY (tier_id) REFERENCES tier (id)
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX idx_user ON user (user);
|
CREATE UNIQUE INDEX idx_user ON user (user);
|
||||||
|
@ -78,7 +84,9 @@ const (
|
||||||
id INT PRIMARY KEY,
|
id INT PRIMARY KEY,
|
||||||
version INT NOT NULL
|
version INT NOT NULL
|
||||||
);
|
);
|
||||||
INSERT INTO user (id, user, pass, role) VALUES (1, '*', '', 'anonymous') ON CONFLICT (id) DO NOTHING;
|
INSERT INTO user (id, user, pass, role, sync_topic, created_by, created_at, last_seen)
|
||||||
|
VALUES (1, '*', '', 'anonymous', '', 'system', UNIXEPOCH(), 0)
|
||||||
|
ON CONFLICT (id) DO NOTHING;
|
||||||
`
|
`
|
||||||
createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;`
|
createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;`
|
||||||
builtinStartupQueries = `
|
builtinStartupQueries = `
|
||||||
|
@ -86,13 +94,13 @@ const (
|
||||||
`
|
`
|
||||||
|
|
||||||
selectUserByNameQuery = `
|
selectUserByNameQuery = `
|
||||||
SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.name, p.paid, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
|
SELECT u.user, u.pass, u.role, u.prefs, u.sync_topic, u.stats_messages, u.stats_emails, p.code, p.name, p.paid, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
|
||||||
FROM user u
|
FROM user u
|
||||||
LEFT JOIN tier p on p.id = u.tier_id
|
LEFT JOIN tier p on p.id = u.tier_id
|
||||||
WHERE user = ?
|
WHERE user = ?
|
||||||
`
|
`
|
||||||
selectUserByTokenQuery = `
|
selectUserByTokenQuery = `
|
||||||
SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.name, p.paid, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
|
SELECT u.user, u.pass, u.role, u.prefs, u.sync_topic, u.stats_messages, u.stats_emails, p.code, p.name, p.paid, p.messages_limit, p.messages_expiry_duration, p.emails_limit, p.reservations_limit, p.attachment_file_size_limit, p.attachment_total_size_limit, p.attachment_expiry_duration
|
||||||
FROM user u
|
FROM user u
|
||||||
JOIN user_token t on u.id = t.user_id
|
JOIN user_token t on u.id = t.user_id
|
||||||
LEFT JOIN tier p on p.id = u.tier_id
|
LEFT JOIN tier p on p.id = u.tier_id
|
||||||
|
@ -106,7 +114,10 @@ const (
|
||||||
ORDER BY u.user DESC
|
ORDER BY u.user DESC
|
||||||
`
|
`
|
||||||
|
|
||||||
insertUserQuery = `INSERT INTO user (user, pass, role) VALUES (?, ?, ?)`
|
insertUserQuery = `
|
||||||
|
INSERT INTO user (user, pass, role, sync_topic, created_by, created_at, last_seen)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`
|
||||||
selectUsernamesQuery = `
|
selectUsernamesQuery = `
|
||||||
SELECT user
|
SELECT user
|
||||||
FROM user
|
FROM user
|
||||||
|
@ -119,8 +130,8 @@ const (
|
||||||
`
|
`
|
||||||
updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
|
updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
|
||||||
updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
|
updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
|
||||||
updateUserSettingsQuery = `UPDATE user SET settings = ? WHERE user = ?`
|
updateUserPrefsQuery = `UPDATE user SET prefs = ? WHERE user = ?`
|
||||||
updateUserStatsQuery = `UPDATE user SET messages = ?, emails = ? WHERE user = ?`
|
updateUserStatsQuery = `UPDATE user SET stats_messages = ?, stats_emails = ? WHERE user = ?`
|
||||||
deleteUserQuery = `DELETE FROM user WHERE user = ?`
|
deleteUserQuery = `DELETE FROM user WHERE user = ?`
|
||||||
|
|
||||||
upsertUserAccessQuery = `
|
upsertUserAccessQuery = `
|
||||||
|
@ -210,8 +221,8 @@ const (
|
||||||
ALTER TABLE user RENAME TO user_old;
|
ALTER TABLE user RENAME TO user_old;
|
||||||
`
|
`
|
||||||
migrate1To2InsertFromOldTablesAndDropNoTx = `
|
migrate1To2InsertFromOldTablesAndDropNoTx = `
|
||||||
INSERT INTO user (user, pass, role)
|
INSERT INTO user (user, pass, role, sync_topic, created_by, created_at, last_seen)
|
||||||
SELECT user, pass, role FROM user_old;
|
SELECT user, pass, role, '', 'admin', UNIXEPOCH(), UNIXEPOCH() FROM user_old;
|
||||||
|
|
||||||
INSERT INTO user_access (user_id, topic, read, write)
|
INSERT INTO user_access (user_id, topic, read, write)
|
||||||
SELECT u.id, a.topic, a.read, a.write
|
SELECT u.id, a.topic, a.read, a.write
|
||||||
|
@ -371,11 +382,11 @@ func (a *Manager) RemoveExpiredTokens() error {
|
||||||
|
|
||||||
// ChangeSettings persists the user settings
|
// ChangeSettings persists the user settings
|
||||||
func (a *Manager) ChangeSettings(user *User) error {
|
func (a *Manager) ChangeSettings(user *User) error {
|
||||||
settings, err := json.Marshal(user.Prefs)
|
prefs, err := json.Marshal(user.Prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := a.db.Exec(updateUserSettingsQuery, string(settings), user.Name); err != nil {
|
if _, err := a.db.Exec(updateUserPrefsQuery, string(prefs), user.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -462,7 +473,7 @@ func (a *Manager) resolvePerms(base, perm Permission) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUser adds a user with the given username, password and role
|
// AddUser adds a user with the given username, password and role
|
||||||
func (a *Manager) AddUser(username, password string, role Role) error {
|
func (a *Manager) AddUser(username, password string, role Role, createdBy string) error {
|
||||||
if !AllowedUsername(username) || !AllowedRole(role) {
|
if !AllowedUsername(username) || !AllowedRole(role) {
|
||||||
return ErrInvalidArgument
|
return ErrInvalidArgument
|
||||||
}
|
}
|
||||||
|
@ -470,7 +481,9 @@ func (a *Manager) AddUser(username, password string, role Role) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = a.db.Exec(insertUserQuery, username, hash, role); err != nil {
|
// INSERT INTO user (user, pass, role, sync_topic, created_by, created_at, last_seen)
|
||||||
|
syncTopic, now := util.RandomString(syncTopicLength), time.Now().Unix()
|
||||||
|
if _, err = a.db.Exec(insertUserQuery, username, hash, role, syncTopic, createdBy, now, now); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -538,15 +551,15 @@ func (a *Manager) userByToken(token string) (*User, error) {
|
||||||
|
|
||||||
func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
|
func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var username, hash, role string
|
var username, hash, role, prefs, syncTopic string
|
||||||
var settings, tierCode, tierName sql.NullString
|
var tierCode, tierName sql.NullString
|
||||||
var paid sql.NullBool
|
var paid sql.NullBool
|
||||||
var messages, emails int64
|
var messages, emails int64
|
||||||
var messagesLimit, messagesExpiryDuration, emailsLimit, reservationsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit, attachmentExpiryDuration sql.NullInt64
|
var messagesLimit, messagesExpiryDuration, emailsLimit, reservationsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit, attachmentExpiryDuration sql.NullInt64
|
||||||
if !rows.Next() {
|
if !rows.Next() {
|
||||||
return nil, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
if err := rows.Scan(&username, &hash, &role, &messages, &emails, &settings, &tierCode, &tierName, &paid, &messagesLimit, &messagesExpiryDuration, &emailsLimit, &reservationsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit, &attachmentExpiryDuration); err != nil {
|
if err := rows.Scan(&username, &hash, &role, &prefs, &syncTopic, &messages, &emails, &tierCode, &tierName, &paid, &messagesLimit, &messagesExpiryDuration, &emailsLimit, &reservationsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit, &attachmentExpiryDuration); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if err := rows.Err(); err != nil {
|
} else if err := rows.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -555,17 +568,16 @@ func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
|
||||||
Name: username,
|
Name: username,
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
Role: Role(role),
|
Role: Role(role),
|
||||||
|
Prefs: &Prefs{},
|
||||||
|
SyncTopic: syncTopic,
|
||||||
Stats: &Stats{
|
Stats: &Stats{
|
||||||
Messages: messages,
|
Messages: messages,
|
||||||
Emails: emails,
|
Emails: emails,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if settings.Valid {
|
if err := json.Unmarshal([]byte(prefs), user.Prefs); err != nil {
|
||||||
user.Prefs = &Prefs{}
|
|
||||||
if err := json.Unmarshal([]byte(settings.String), user.Prefs); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if tierCode.Valid {
|
if tierCode.Valid {
|
||||||
user.Tier = &Tier{
|
user.Tier = &Tier{
|
||||||
Code: tierCode.String,
|
Code: tierCode.String,
|
||||||
|
|
|
@ -13,8 +13,8 @@ const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this sh
|
||||||
|
|
||||||
func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
|
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
||||||
|
@ -92,20 +92,20 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_AddUser_Invalid(t *testing.T) {
|
func TestManager_AddUser_Invalid(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
|
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin, "unit-test"))
|
||||||
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
|
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role", "unit-test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_AddUser_Timing(t *testing.T) {
|
func TestManager_AddUser_Timing(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
start := time.Now().UnixMilli()
|
start := time.Now().UnixMilli()
|
||||||
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
|
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, "unit-test"))
|
||||||
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
|
require.GreaterOrEqual(t, time.Now().UnixMilli()-start, minBcryptTimingMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManager_Authenticate_Timing(t *testing.T) {
|
func TestManager_Authenticate_Timing(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("user", "pass", RoleAdmin))
|
require.Nil(t, a.AddUser("user", "pass", RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
// Timing a correct attempt
|
// Timing a correct attempt
|
||||||
start := time.Now().UnixMilli()
|
start := time.Now().UnixMilli()
|
||||||
|
@ -128,8 +128,8 @@ func TestManager_Authenticate_Timing(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_UserManagement(t *testing.T) {
|
func TestManager_UserManagement(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
|
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
||||||
|
@ -219,7 +219,7 @@ func TestManager_UserManagement(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_ChangePassword(t *testing.T) {
|
func TestManager_ChangePassword(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
|
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
|
||||||
|
|
||||||
_, err := a.Authenticate("phil", "phil")
|
_, err := a.Authenticate("phil", "phil")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -233,7 +233,7 @@ func TestManager_ChangePassword(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_ChangeRole(t *testing.T) {
|
func TestManager_ChangeRole(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
|
||||||
AttachmentTotalSizeLimit: 524288000,
|
AttachmentTotalSizeLimit: 524288000,
|
||||||
AttachmentExpiryDuration: 24 * time.Hour,
|
AttachmentExpiryDuration: 24 * time.Hour,
|
||||||
}))
|
}))
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
require.Nil(t, a.ChangeTier("ben", "pro"))
|
require.Nil(t, a.ChangeTier("ben", "pro"))
|
||||||
require.Nil(t, a.AllowAccess("ben", "ben", "mytopic", true, true))
|
require.Nil(t, a.AllowAccess("ben", "ben", "mytopic", true, true))
|
||||||
require.Nil(t, a.AllowAccess("ben", Everyone, "mytopic", false, false))
|
require.Nil(t, a.AllowAccess("ben", Everyone, "mytopic", false, false))
|
||||||
|
@ -312,7 +312,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Token_Valid(t *testing.T) {
|
func TestManager_Token_Valid(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -337,7 +337,7 @@ func TestManager_Token_Valid(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Token_Invalid(t *testing.T) {
|
func TestManager_Token_Invalid(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
|
u, err := a.AuthenticateToken(strings.Repeat("x", 32)) // 32 == token length
|
||||||
require.Nil(t, u)
|
require.Nil(t, u)
|
||||||
|
@ -350,7 +350,7 @@ func TestManager_Token_Invalid(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Token_Expire(t *testing.T) {
|
func TestManager_Token_Expire(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -398,7 +398,7 @@ func TestManager_Token_Expire(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Token_Extend(t *testing.T) {
|
func TestManager_Token_Extend(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
// Try to extend token for user without token
|
// Try to extend token for user without token
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
|
@ -425,7 +425,7 @@ func TestManager_Token_Extend(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
||||||
a := newTestManager(t, PermissionDenyAll)
|
a := newTestManager(t, PermissionDenyAll)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
// Try to extend token for user without token
|
// Try to extend token for user without token
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
|
@ -469,7 +469,7 @@ func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
|
||||||
func TestManager_EnqueueStats(t *testing.T) {
|
func TestManager_EnqueueStats(t *testing.T) {
|
||||||
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
|
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
// Baseline: No messages or emails
|
// Baseline: No messages or emails
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
|
@ -499,12 +499,14 @@ func TestManager_EnqueueStats(t *testing.T) {
|
||||||
func TestManager_ChangeSettings(t *testing.T) {
|
func TestManager_ChangeSettings(t *testing.T) {
|
||||||
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
|
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
|
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||||
|
|
||||||
// No settings
|
// No settings
|
||||||
u, err := a.User("ben")
|
u, err := a.User("ben")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Nil(t, u.Prefs)
|
require.Nil(t, u.Prefs.Subscriptions)
|
||||||
|
require.Nil(t, u.Prefs.Notification)
|
||||||
|
require.Equal(t, "", u.Prefs.Language)
|
||||||
|
|
||||||
// Save with new settings
|
// Save with new settings
|
||||||
u.Prefs = &Prefs{
|
u.Prefs = &Prefs{
|
||||||
|
|
|
@ -16,6 +16,9 @@ type User struct {
|
||||||
Prefs *Prefs
|
Prefs *Prefs
|
||||||
Tier *Tier
|
Tier *Tier
|
||||||
Stats *Stats
|
Stats *Stats
|
||||||
|
SyncTopic string
|
||||||
|
Created time.Time
|
||||||
|
LastSeen time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auther is an interface for authentication and authorization
|
// Auther is an interface for authentication and authorization
|
||||||
|
|
Loading…
Reference in a new issue