Sync topic (begin), rename user fields

This commit is contained in:
binwiederhier 2023-01-09 21:53:21 -05:00
parent b27c608508
commit 7e528d9c10
10 changed files with 107 additions and 82 deletions

View file

@ -16,7 +16,8 @@ import (
)
const (
tierReset = "-"
tierReset = "-"
createdByCLI = "cli"
)
func init() {
@ -196,7 +197,7 @@ func execUserAdd(c *cli.Context) error {
password = p
}
if err := manager.AddUser(username, password, role); err != nil {
if err := manager.AddUser(username, password, role, createdByCLI); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "user %s added with role %s\n", username, role)

View file

@ -536,6 +536,7 @@ func TestSqliteCache_Migration_From9(t *testing.T) {
// Create cache to trigger migration
cacheDuration := 17 * time.Hour
c, err := newSqliteCache(filename, "", cacheDuration, 0, 0, false)
require.Nil(t, err)
checkSchemaVersion(t, c.db)
// Check version

View file

@ -38,14 +38,18 @@ import (
TODO
Limits & rate limiting:
login/account endpoints
purge accounts that were not logged int o in X
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
UI:
- flicker of upgrade banner
- JS constants
Sync:
- "account topic" sync mechanism
- subscribe to sync topic in UI
- "mute" setting
- figure out what settings are "web" or "phone"
Delete visitor when tier is changed to refresh rate limiters
@ -54,10 +58,9 @@ import (
- Message rate limiting and reset tests
Docs:
- "expires" field in message
- server.yml: enable-X flags
Refactor:
- rename /access -> /reservation
Later:
- Pricing
*/
// Server is the main server, providing the UI and API for ntfy

View file

@ -10,6 +10,7 @@ import (
const (
jsonBodyBytesLimit = 4096
subscriptionIDLength = 16
createdByAPI = "api"
)
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() {
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
}
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 {
response.Username = v.user.Name
response.Role = string(v.user.Role)
response.SyncTopic = v.user.SyncTopic
if v.user.Prefs != nil {
if v.user.Prefs.Language != "" {
response.Language = v.user.Prefs.Language

View file

@ -67,8 +67,8 @@ func TestAccount_Signup_AsUser(t *testing.T) {
conf.EnableSignup = true
s := newTestServer(t, conf)
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, "unit-test"))
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{
"Authorization": util.BasicAuth("phil", "phil"),
@ -133,7 +133,7 @@ func TestAccount_Get_Anonymous(t *testing.T) {
func TestAccount_ChangeSettings(t *testing.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")
token, _ := s.userManager.CreateToken(user)
@ -160,7 +160,7 @@ func TestAccount_ChangeSettings(t *testing.T) {
func TestAccount_Subscription_AddUpdateDelete(t *testing.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{
"Authorization": util.BasicAuth("phil", "phil"),
@ -210,7 +210,7 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
func TestAccount_ChangePassword(t *testing.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{
"Authorization": util.BasicAuth("phil", "phil"),
@ -237,7 +237,7 @@ func TestAccount_ChangePassword_NoAccount(t *testing.T) {
func TestAccount_ExtendToken(t *testing.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{
"Authorization": util.BasicAuth("phil", "phil"),
@ -260,7 +260,7 @@ func TestAccount_ExtendToken(t *testing.T) {
func TestAccount_ExtendToken_NoTokenProvided(t *testing.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{
"Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
@ -271,7 +271,7 @@ func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
func TestAccount_DeleteToken(t *testing.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{
"Authorization": util.BasicAuth("phil", "phil"),
@ -360,7 +360,7 @@ func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
conf := newTestConfigWithAuthFile(t)
conf.EnableSignup = true
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{
"Authorization": util.BasicAuth("phil", "adminpass"),

View file

@ -626,7 +626,7 @@ func TestServer_Auth_Success_Admin(t *testing.T) {
c.AuthFile = filepath.Join(t.TempDir(), "user.db")
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{
"Authorization": basicAuth("phil:phil"),
@ -641,7 +641,7 @@ func TestServer_Auth_Success_User(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
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))
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
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", "anothertopic", true, true))
@ -677,7 +677,7 @@ func TestServer_Auth_Fail_InvalidPass(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
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{
"Authorization": basicAuth("phil:INVALID"),
@ -691,7 +691,7 @@ func TestServer_Auth_Fail_Unauthorized(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
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!
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
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, "announcements", true, false))
@ -737,7 +737,7 @@ func TestServer_Auth_ViaQuery(t *testing.T) {
c.AuthDefault = user.PermissionDenyAll
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"))))
response := request(t, s, "GET", u, "", nil)
@ -1100,7 +1100,7 @@ func TestServer_PublishWithTierBasedMessageLimitAndExpiry(t *testing.T) {
MessagesLimit: 5,
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"))
// Publish to reach message limit
@ -1332,7 +1332,7 @@ func TestServer_PublishAttachmentWithTierBasedExpiry(t *testing.T) {
AttachmentTotalSizeLimit: 200_000,
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"))
// Publish and make sure we can retrieve it
@ -1376,7 +1376,7 @@ func TestServer_PublishAttachmentWithTierBasedLimits(t *testing.T) {
AttachmentTotalSizeLimit: 200_000,
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"))
// Publish small file as anonymous

View file

@ -271,6 +271,7 @@ type apiAccountReservation struct {
type apiAccountResponse struct {
Username string `json:"username"`
Role string `json:"role,omitempty"`
SyncTopic string `json:"sync_topic,omitempty"`
Language string `json:"language,omitempty"`
Notification *user.NotificationPrefs `json:"notification,omitempty"`
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`

View file

@ -20,7 +20,8 @@ const (
userStatsQueueWriterInterval = 33 * time.Second
tokenLength = 32
tokenExpiryDuration = 72 * time.Hour // Extend tokens by this much
tokenMaxCount = 10 // Only keep this many tokens in the table per user
syncTopicLength = 16
tokenMaxCount = 10 // Only keep this many tokens in the table per user
)
var (
@ -50,10 +51,15 @@ const (
tier_id INT,
user TEXT NOT NULL,
pass TEXT NOT NULL,
role TEXT NOT NULL,
messages INT NOT NULL DEFAULT (0),
emails INT NOT NULL DEFAULT (0),
settings JSON,
role TEXT CHECK (role IN ('anonymous', 'admin', 'user')) NOT NULL,
prefs JSON NOT NULL DEFAULT '{}',
sync_topic TEXT NOT NULL,
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)
);
CREATE UNIQUE INDEX idx_user ON user (user);
@ -78,7 +84,9 @@ const (
id INT PRIMARY KEY,
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;`
builtinStartupQueries = `
@ -86,13 +94,13 @@ const (
`
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
LEFT JOIN tier p on p.id = u.tier_id
WHERE user = ?
`
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
JOIN user_token t on u.id = t.user_id
LEFT JOIN tier p on p.id = u.tier_id
@ -106,7 +114,10 @@ const (
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 = `
SELECT user
FROM user
@ -117,11 +128,11 @@ const (
ELSE 2
END, user
`
updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
updateUserSettingsQuery = `UPDATE user SET settings = ? WHERE user = ?`
updateUserStatsQuery = `UPDATE user SET messages = ?, emails = ? WHERE user = ?`
deleteUserQuery = `DELETE FROM user WHERE user = ?`
updateUserPassQuery = `UPDATE user SET pass = ? WHERE user = ?`
updateUserRoleQuery = `UPDATE user SET role = ? WHERE user = ?`
updateUserPrefsQuery = `UPDATE user SET prefs = ? WHERE user = ?`
updateUserStatsQuery = `UPDATE user SET stats_messages = ?, stats_emails = ? WHERE user = ?`
deleteUserQuery = `DELETE FROM user WHERE user = ?`
upsertUserAccessQuery = `
INSERT INTO user_access (user_id, topic, read, write, owner_user_id)
@ -210,8 +221,8 @@ const (
ALTER TABLE user RENAME TO user_old;
`
migrate1To2InsertFromOldTablesAndDropNoTx = `
INSERT INTO user (user, pass, role)
SELECT user, pass, role FROM user_old;
INSERT INTO user (user, pass, role, sync_topic, created_by, created_at, last_seen)
SELECT user, pass, role, '', 'admin', UNIXEPOCH(), UNIXEPOCH() FROM user_old;
INSERT INTO user_access (user_id, topic, read, write)
SELECT u.id, a.topic, a.read, a.write
@ -371,11 +382,11 @@ func (a *Manager) RemoveExpiredTokens() error {
// ChangeSettings persists the user settings
func (a *Manager) ChangeSettings(user *User) error {
settings, err := json.Marshal(user.Prefs)
prefs, err := json.Marshal(user.Prefs)
if err != nil {
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 nil
@ -462,7 +473,7 @@ func (a *Manager) resolvePerms(base, perm Permission) error {
}
// 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) {
return ErrInvalidArgument
}
@ -470,7 +481,9 @@ func (a *Manager) AddUser(username, password string, role Role) error {
if err != nil {
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 nil
@ -538,33 +551,32 @@ func (a *Manager) userByToken(token string) (*User, error) {
func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
defer rows.Close()
var username, hash, role string
var settings, tierCode, tierName sql.NullString
var username, hash, role, prefs, syncTopic string
var tierCode, tierName sql.NullString
var paid sql.NullBool
var messages, emails int64
var messagesLimit, messagesExpiryDuration, emailsLimit, reservationsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit, attachmentExpiryDuration sql.NullInt64
if !rows.Next() {
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
} else if err := rows.Err(); err != nil {
return nil, err
}
user := &User{
Name: username,
Hash: hash,
Role: Role(role),
Name: username,
Hash: hash,
Role: Role(role),
Prefs: &Prefs{},
SyncTopic: syncTopic,
Stats: &Stats{
Messages: messages,
Emails: emails,
},
}
if settings.Valid {
user.Prefs = &Prefs{}
if err := json.Unmarshal([]byte(settings.String), user.Prefs); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(prefs), user.Prefs); err != nil {
return nil, err
}
if tierCode.Valid {
user.Tier = &Tier{

View file

@ -13,8 +13,8 @@ const minBcryptTimingMillis = int64(50) // Ideally should be >100ms, but this sh
func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
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", "readme", true, false))
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) {
a := newTestManager(t, PermissionDenyAll)
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin))
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role"))
require.Equal(t, ErrInvalidArgument, a.AddUser(" invalid ", "pass", RoleAdmin, "unit-test"))
require.Equal(t, ErrInvalidArgument, a.AddUser("validuser", "pass", "invalid-role", "unit-test"))
}
func TestManager_AddUser_Timing(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
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)
}
func TestManager_Authenticate_Timing(t *testing.T) {
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
start := time.Now().UnixMilli()
@ -128,8 +128,8 @@ func TestManager_Authenticate_Timing(t *testing.T) {
func TestManager_UserManagement(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
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", "readme", true, false))
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) {
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")
require.Nil(t, err)
@ -233,7 +233,7 @@ func TestManager_ChangePassword(t *testing.T) {
func TestManager_ChangeRole(t *testing.T) {
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", "readme", true, false))
@ -270,7 +270,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
AttachmentTotalSizeLimit: 524288000,
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.AllowAccess("ben", "ben", "mytopic", true, true))
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) {
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")
require.Nil(t, err)
@ -337,7 +337,7 @@ func TestManager_Token_Valid(t *testing.T) {
func TestManager_Token_Invalid(t *testing.T) {
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
require.Nil(t, u)
@ -350,7 +350,7 @@ func TestManager_Token_Invalid(t *testing.T) {
func TestManager_Token_Expire(t *testing.T) {
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")
require.Nil(t, err)
@ -398,7 +398,7 @@ func TestManager_Token_Expire(t *testing.T) {
func TestManager_Token_Extend(t *testing.T) {
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
u, err := a.User("ben")
@ -425,7 +425,7 @@ func TestManager_Token_Extend(t *testing.T) {
func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
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
u, err := a.User("ben")
@ -469,7 +469,7 @@ func TestManager_Token_MaxCount_AutoDelete(t *testing.T) {
func TestManager_EnqueueStats(t *testing.T) {
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
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
u, err := a.User("ben")
@ -499,12 +499,14 @@ func TestManager_EnqueueStats(t *testing.T) {
func TestManager_ChangeSettings(t *testing.T) {
a, err := newManager(filepath.Join(t.TempDir(), "db"), "", PermissionReadWrite, 1500*time.Millisecond)
require.Nil(t, err)
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
// No settings
u, err := a.User("ben")
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
u.Prefs = &Prefs{

View file

@ -9,13 +9,16 @@ import (
// User is a struct that represents a user
type User struct {
Name string
Hash string // password hash (bcrypt)
Token string // Only set if token was used to log in
Role Role
Prefs *Prefs
Tier *Tier
Stats *Stats
Name string
Hash string // password hash (bcrypt)
Token string // Only set if token was used to log in
Role Role
Prefs *Prefs
Tier *Tier
Stats *Stats
SyncTopic string
Created time.Time
LastSeen time.Time
}
// Auther is an interface for authentication and authorization