diff --git a/account.go b/account.go
index 1cf259b..49700b4 100644
--- a/account.go
+++ b/account.go
@@ -13,6 +13,13 @@ package writefreely
import (
"encoding/json"
"fmt"
+ "html/template"
+ "net/http"
+ "regexp"
+ "strings"
+ "sync"
+ "time"
+
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/guregu/null/zero"
@@ -22,12 +29,6 @@ import (
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/author"
"github.com/writeas/writefreely/page"
- "html/template"
- "net/http"
- "regexp"
- "strings"
- "sync"
- "time"
)
type (
@@ -1011,14 +1012,16 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err
obj := struct {
*UserPage
- Email string
- HasPass bool
- IsLogOut bool
+ Email string
+ HasPass bool
+ IsLogOut bool
+ Suspended bool
}{
- UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
- Email: fullUser.EmailClear(app.keys),
- HasPass: passIsSet,
- IsLogOut: r.FormValue("logout") == "1",
+ UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
+ Email: fullUser.EmailClear(app.keys),
+ HasPass: passIsSet,
+ IsLogOut: r.FormValue("logout") == "1",
+ Suspended: fullUser.Suspended,
}
showUserPage(w, "settings", obj)
diff --git a/activitypub.go b/activitypub.go
index 997609d..c10838d 100644
--- a/activitypub.go
+++ b/activitypub.go
@@ -80,6 +80,14 @@ func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Re
if err != nil {
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("fetch collection inbox: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
p := c.PersonObject()
@@ -105,6 +113,14 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("fetch collection inbox: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
if app.cfg.App.SingleUser {
@@ -158,6 +174,14 @@ func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("fetch collection inbox: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
accountRoot := c.FederatedAccount()
@@ -204,6 +228,14 @@ func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("fetch collection inbox: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
accountRoot := c.FederatedAccount()
@@ -238,6 +270,14 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
// TODO: return Reject?
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("fetch collection inbox: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
if debugging {
diff --git a/admin.go b/admin.go
index fe19ad5..dc8580d 100644
--- a/admin.go
+++ b/admin.go
@@ -13,16 +13,17 @@ package writefreely
import (
"database/sql"
"fmt"
+ "net/http"
+ "runtime"
+ "strconv"
+ "time"
+
"github.com/gogits/gogs/pkg/tool"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
- "net/http"
- "runtime"
- "strconv"
- "time"
)
var (
@@ -229,6 +230,31 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
return nil
}
+func handleAdminToggleUserSuspended(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
+ vars := mux.Vars(r)
+ username := vars["username"]
+ if username == "" {
+ return impart.HTTPError{http.StatusFound, "/admin/users"}
+ }
+
+ userToToggle, err := app.db.GetUserForAuth(username)
+ if err != nil {
+ log.Error("failed to get user: %v", err)
+ return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user from username: %v", err)}
+ }
+ if userToToggle.Suspended {
+ err = app.db.SetUserSuspended(userToToggle.ID, false)
+ } else {
+ err = app.db.SetUserSuspended(userToToggle.ID, true)
+ }
+ if err != nil {
+ log.Error("toggle user suspended: %v", err)
+ return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user suspended: %v")}
+ }
+ // TODO: invalidate sessions
+ return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s#status", username)}
+}
+
func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct {
*UserPage
diff --git a/collections.go b/collections.go
index aee74f7..95dd808 100644
--- a/collections.go
+++ b/collections.go
@@ -379,6 +379,7 @@ func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
}
var userID int64
+ var err error
if reqJSON && !c.Web {
accessToken = r.Header.Get("Authorization")
if accessToken == "" {
@@ -395,6 +396,14 @@ func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
}
userID = u.ID
}
+ suspended, err := app.db.IsUserSuspended(userID)
+ if err != nil {
+ log.Error("new collection: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrUserSuspended
+ }
if !author.IsValidUsername(app.cfg, c.Alias) {
return impart.HTTPError{http.StatusPreconditionFailed, "Collection alias isn't valid."}
@@ -724,6 +733,15 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
return err
}
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("view collection: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+
+ if suspended {
+ return ErrCollectionNotFound
+ }
// Serve ActivityStreams data now, if requested
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
ac := c.PersonObject()
@@ -824,6 +842,10 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
return err
}
+ if u.Suspended {
+ return ErrCollectionNotFound
+ }
+
page := getCollectionPage(vars)
c, err := processCollectionPermissions(app, cr, u, w, r)
@@ -916,7 +938,6 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
if reqJSON && !isWeb {
// Ensure an access token was given
accessToken := r.Header.Get("Authorization")
- u = &User{}
u.ID = app.db.GetUserID(accessToken)
if u.ID == -1 {
return ErrBadAccessToken
@@ -928,6 +949,16 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
}
}
+ suspended, err := app.db.IsUserSuspended(u.ID)
+ if err != nil {
+ log.Error("existing collection: get user suspended status: %v", err)
+ return ErrInternalGeneral
+ }
+
+ if suspended {
+ return ErrUserSuspended
+ }
+
if r.Method == "DELETE" {
err := app.db.DeleteCollection(collAlias, u.ID)
if err != nil {
@@ -940,7 +971,6 @@ func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error
}
c := SubmittedCollection{OwnerID: uint64(u.ID)}
- var err error
if reqJSON {
// Decode JSON request
diff --git a/database.go b/database.go
index 34c5234..150a74f 100644
--- a/database.go
+++ b/database.go
@@ -296,7 +296,7 @@ func (db *datastore) CreateCollection(cfg *config.Config, alias, title string, u
func (db *datastore) GetUserByID(id int64) (*User, error) {
u := &User{ID: id}
- err := db.QueryRow("SELECT username, password, email, created FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created)
+ err := db.QueryRow("SELECT username, password, email, created, suspended FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
switch {
case err == sql.ErrNoRows:
return nil, ErrUserNotFound
@@ -308,6 +308,23 @@ func (db *datastore) GetUserByID(id int64) (*User, error) {
return u, nil
}
+// IsUserSuspended returns true if the user account associated with id is
+// currently suspended.
+func (db *datastore) IsUserSuspended(id int64) (bool, error) {
+ u := &User{ID: id}
+
+ err := db.QueryRow("SELECT suspended FROM users WHERE id = ?", id).Scan(&u.Suspended)
+ switch {
+ case err == sql.ErrNoRows:
+ return false, ErrUserNotFound
+ case err != nil:
+ log.Error("Couldn't SELECT user password: %v", err)
+ return false, err
+ }
+
+ return u.Suspended, nil
+}
+
// DoesUserNeedAuth returns true if the user hasn't provided any methods for
// authenticating with the account, such a passphrase or email address.
// Any errors are reported to admin and silently quashed, returning false as the
@@ -347,7 +364,7 @@ func (db *datastore) IsUserPassSet(id int64) (bool, error) {
func (db *datastore) GetUserForAuth(username string) (*User, error) {
u := &User{Username: username}
- err := db.QueryRow("SELECT id, password, email, created FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created)
+ err := db.QueryRow("SELECT id, password, email, created, suspended FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
switch {
case err == sql.ErrNoRows:
// Check if they've entered the wrong, unnormalized username
@@ -370,7 +387,7 @@ func (db *datastore) GetUserForAuth(username string) (*User, error) {
func (db *datastore) GetUserForAuthByID(userID int64) (*User, error) {
u := &User{ID: userID}
- err := db.QueryRow("SELECT id, password, email, created FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created)
+ err := db.QueryRow("SELECT id, password, email, created, suspended FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Suspended)
switch {
case err == sql.ErrNoRows:
return nil, ErrUserNotFound
@@ -1624,7 +1641,11 @@ func (db *datastore) GetMeStats(u *User) userMeStats {
}
func (db *datastore) GetTotalCollections() (collCount int64, err error) {
- err = db.QueryRow(`SELECT COUNT(*) FROM collections`).Scan(&collCount)
+ err = db.QueryRow(`
+ SELECT COUNT(*)
+ FROM collections c
+ LEFT JOIN users u ON u.id = c.owner_id
+ WHERE u.suspended = 0`).Scan(&collCount)
if err != nil {
log.Error("Unable to fetch collections count: %v", err)
}
@@ -1632,7 +1653,11 @@ func (db *datastore) GetTotalCollections() (collCount int64, err error) {
}
func (db *datastore) GetTotalPosts() (postCount int64, err error) {
- err = db.QueryRow(`SELECT COUNT(*) FROM posts`).Scan(&postCount)
+ err = db.QueryRow(`
+ SELECT COUNT(*)
+ FROM posts p
+ LEFT JOIN users u ON u.id = p.owner_id
+ WHERE u.Suspended = 0`).Scan(&postCount)
if err != nil {
log.Error("Unable to fetch posts count: %v", err)
}
@@ -2341,17 +2366,17 @@ func (db *datastore) GetAllUsers(page uint) (*[]User, error) {
limitStr = fmt.Sprintf("%d, %d", (page-1)*adminUsersPerPage, adminUsersPerPage)
}
- rows, err := db.Query("SELECT id, username, created FROM users ORDER BY created DESC LIMIT " + limitStr)
+ rows, err := db.Query("SELECT id, username, created, suspended FROM users ORDER BY created DESC LIMIT " + limitStr)
if err != nil {
- log.Error("Failed selecting from posts: %v", err)
- return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user posts."}
+ log.Error("Failed selecting from users: %v", err)
+ return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve all users."}
}
defer rows.Close()
users := []User{}
for rows.Next() {
u := User{}
- err = rows.Scan(&u.ID, &u.Username, &u.Created)
+ err = rows.Scan(&u.ID, &u.Username, &u.Created, &u.Suspended)
if err != nil {
log.Error("Failed scanning GetAllUsers() row: %v", err)
break
@@ -2388,6 +2413,14 @@ func (db *datastore) GetUserLastPostTime(id int64) (*time.Time, error) {
return &t, nil
}
+func (db *datastore) SetUserSuspended(id int64, suspend bool) error {
+ _, err := db.Exec("UPDATE users SET suspended = ? WHERE id = ?", suspend, id)
+ if err != nil {
+ return fmt.Errorf("failed to update user suspended status: %v", err)
+ }
+ return nil
+}
+
func (db *datastore) GetCollectionLastPostTime(id int64) (*time.Time, error) {
var t time.Time
err := db.QueryRow("SELECT created FROM posts WHERE collection_id = ? ORDER BY created DESC LIMIT 1", id).Scan(&t)
diff --git a/errors.go b/errors.go
index 0092b7f..fa7304f 100644
--- a/errors.go
+++ b/errors.go
@@ -11,8 +11,9 @@
package writefreely
import (
- "github.com/writeas/impart"
"net/http"
+
+ "github.com/writeas/impart"
)
// Commonly returned HTTP errors
@@ -46,6 +47,8 @@ var (
ErrUserNotFound = impart.HTTPError{http.StatusNotFound, "User doesn't exist."}
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."}
+
+ ErrUserSuspended = impart.HTTPError{http.StatusForbidden, "Account is suspended, contact the administrator."}
)
// Post operation errors
diff --git a/feed.go b/feed.go
index dd82c33..353b1b9 100644
--- a/feed.go
+++ b/feed.go
@@ -12,12 +12,13 @@ package writefreely
import (
"fmt"
+ "net/http"
+ "time"
+
. "github.com/gorilla/feeds"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown"
"github.com/writeas/web-core/log"
- "net/http"
- "time"
)
func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
@@ -34,6 +35,15 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
if err != nil {
return nil
}
+
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("view feed: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrCollectionNotFound
+ }
c.hostName = app.cfg.App.Host
if c.IsPrivate() || c.IsProtected() {
diff --git a/invites.go b/invites.go
index 561255f..93b82b4 100644
--- a/invites.go
+++ b/invites.go
@@ -12,15 +12,16 @@ package writefreely
import (
"database/sql"
+ "html/template"
+ "net/http"
+ "strconv"
+ "time"
+
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/nerds/store"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page"
- "html/template"
- "net/http"
- "strconv"
- "time"
)
type Invite struct {
@@ -77,6 +78,10 @@ func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Re
muVal := r.FormValue("uses")
expVal := r.FormValue("expires")
+ if u.Suspended {
+ return ErrUserSuspended
+ }
+
var err error
var maxUses int
if muVal != "0" {
diff --git a/migrations/migrations.go b/migrations/migrations.go
index 70e4b7b..de3f487 100644
--- a/migrations/migrations.go
+++ b/migrations/migrations.go
@@ -13,6 +13,7 @@ package migrations
import (
"database/sql"
+
"github.com/writeas/web-core/log"
)
@@ -57,6 +58,7 @@ func (m *migration) Migrate(db *datastore) error {
var migrations = []Migration{
New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
New("support dynamic instance pages", supportInstancePages), // V1 -> V2 (v0.9.0)
+ New("support users suspension", supportUserSuspension), // V2 -> V3 ()
}
// CurrentVer returns the current migration version the application is on
diff --git a/migrations/v3.go b/migrations/v3.go
new file mode 100644
index 0000000..c7c00a9
--- /dev/null
+++ b/migrations/v3.go
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2019 A Bunch Tell LLC.
+ *
+ * This file is part of WriteFreely.
+ *
+ * WriteFreely is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, included
+ * in the LICENSE file in this source code package.
+ */
+
+package migrations
+
+func supportUserSuspension(db *datastore) error {
+ t, err := db.Begin()
+
+ _, err = t.Exec(`ALTER TABLE users ADD COLUMN suspended ` + db.typeBool() + ` DEFAULT '0' NOT NULL`)
+ if err != nil {
+ t.Rollback()
+ return err
+ }
+
+ err = t.Commit()
+ if err != nil {
+ t.Rollback()
+ return err
+ }
+
+ return nil
+}
diff --git a/pad.go b/pad.go
index 1545b4f..8a54b76 100644
--- a/pad.go
+++ b/pad.go
@@ -11,12 +11,13 @@
package writefreely
import (
+ "net/http"
+ "strings"
+
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page"
- "net/http"
- "strings"
)
func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
@@ -34,9 +35,10 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
}
appData := &struct {
page.StaticPage
- Post *RawPost
- User *User
- Blogs *[]Collection
+ Post *RawPost
+ User *User
+ Blogs *[]Collection
+ Suspended bool
Editing bool // True if we're modifying an existing post
EditCollection *Collection // Collection of the post we're editing, if any
@@ -51,6 +53,10 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
if err != nil {
log.Error("Unable to get user's blogs for Pad: %v", err)
}
+ appData.Suspended, err = app.db.IsUserSuspended(appData.User.ID)
+ if err != nil {
+ log.Error("Unable to get users suspension status for Pad: %v", err)
+ }
}
padTmpl := app.cfg.App.Editor
@@ -121,12 +127,18 @@ func handleViewMeta(app *App, w http.ResponseWriter, r *http.Request) error {
EditCollection *Collection // Collection of the post we're editing, if any
Flashes []string
NeedsToken bool
+ Suspended bool
}{
StaticPage: pageForReq(app, r),
Post: &RawPost{Font: "norm"},
User: getUserSession(app, r),
}
var err error
+ appData.Suspended, err = app.db.IsUserSuspended(appData.User.ID)
+ if err != nil {
+ log.Error("view meta: get user suspended status: %v", err)
+ return ErrInternalGeneral
+ }
if action == "" && slug == "" {
return ErrPostNotFound
diff --git a/posts.go b/posts.go
index 2f3606f..2d64808 100644
--- a/posts.go
+++ b/posts.go
@@ -380,6 +380,16 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
}
+ suspended, err := app.db.IsUserSuspended(ownerID.Int64)
+ if err != nil {
+ log.Error("view post: get collection owner: %v", err)
+ return ErrInternalGeneral
+ }
+
+ if suspended {
+ return ErrPostNotFound
+ }
+
// Check if post has been unpublished
if content == "" {
gone = true
@@ -496,6 +506,15 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
} else {
userID = app.db.GetUserID(accessToken)
}
+ suspended, err := app.db.IsUserSuspended(userID)
+ if err != nil {
+ log.Error("new post: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrUserSuspended
+ }
+
if userID == -1 {
return ErrNotLoggedIn
}
@@ -508,7 +527,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
var p *SubmittedPost
if reqJSON {
decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&p)
+ err = decoder.Decode(&p)
if err != nil {
log.Error("Couldn't parse new post JSON request: %v\n", err)
return ErrBadJSON
@@ -554,7 +573,6 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
var newPost *PublicPost = &PublicPost{}
var coll *Collection
- var err error
if accessToken != "" {
newPost, err = app.db.CreateOwnedPost(p, accessToken, collAlias, app.cfg.App.Host)
} else {
@@ -662,6 +680,15 @@ func existingPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
}
+ suspended, err := app.db.IsUserSuspended(userID)
+ if err != nil {
+ log.Error("existing post: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrUserSuspended
+ }
+
// Modify post struct
p.ID = postID
@@ -856,11 +883,20 @@ func addPost(app *App, w http.ResponseWriter, r *http.Request) error {
ownerID = u.ID
}
+ suspended, err := app.db.IsUserSuspended(ownerID)
+ if err != nil {
+ log.Error("add post: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrUserSuspended
+ }
+
// Parse claimed posts in format:
// [{"id": "...", "token": "..."}]
var claims *[]ClaimPostRequest
decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&claims)
+ err = decoder.Decode(&claims)
if err != nil {
return ErrBadJSONArray
}
@@ -950,13 +986,22 @@ func pinPost(app *App, w http.ResponseWriter, r *http.Request) error {
userID = u.ID
}
+ suspended, err := app.db.IsUserSuspended(userID)
+ if err != nil {
+ log.Error("pin post: get user: %v", err)
+ return ErrInternalGeneral
+ }
+ if suspended {
+ return ErrUserSuspended
+ }
+
// Parse request
var posts []struct {
ID string `json:"id"`
Position int64 `json:"position"`
}
decoder := json.NewDecoder(r.Body)
- err := decoder.Decode(&posts)
+ err = decoder.Decode(&posts)
if err != nil {
return ErrBadJSONArray
}
@@ -992,6 +1037,7 @@ func pinPost(app *App, w http.ResponseWriter, r *http.Request) error {
func fetchPost(app *App, w http.ResponseWriter, r *http.Request) error {
var collID int64
+ var ownerID int64
var coll *Collection
var err error
vars := mux.Vars(r)
@@ -1007,12 +1053,22 @@ func fetchPost(app *App, w http.ResponseWriter, r *http.Request) error {
return err
}
collID = coll.ID
+ ownerID = coll.OwnerID
}
p, err := app.db.GetPost(vars["post"], collID)
if err != nil {
return err
}
+ suspended, err := app.db.IsUserSuspended(ownerID)
+ if err != nil {
+ log.Error("fetch post: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+
+ if suspended {
+ return ErrPostNotFound
+ }
p.extractData()
@@ -1270,6 +1326,15 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
}
c.hostName = app.cfg.App.Host
+ suspended, err := app.db.IsUserSuspended(c.OwnerID)
+ if err != nil {
+ log.Error("view collection post: get owner: %v", err)
+ return ErrInternalGeneral
+ }
+
+ if suspended {
+ return ErrPostNotFound
+ }
// Check collection permissions
if c.IsPrivate() && (u == nil || u.ID != c.OwnerID) {
return ErrPostNotFound
diff --git a/read.go b/read.go
index 3bc91c7..e7d1e55 100644
--- a/read.go
+++ b/read.go
@@ -13,6 +13,12 @@ package writefreely
import (
"database/sql"
"fmt"
+ "html/template"
+ "math"
+ "net/http"
+ "strconv"
+ "time"
+
. "github.com/gorilla/feeds"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown"
@@ -20,11 +26,6 @@ import (
"github.com/writeas/web-core/log"
"github.com/writeas/web-core/memo"
"github.com/writeas/writefreely/page"
- "html/template"
- "math"
- "net/http"
- "strconv"
- "time"
)
const (
@@ -62,7 +63,8 @@ func (app *App) FetchPublicPosts() (interface{}, error) {
rows, err := app.db.Query(`SELECT p.id, alias, c.title, p.slug, p.title, p.content, p.text_appearance, p.language, p.rtl, p.created, p.updated
FROM collections c
LEFT JOIN posts p ON p.collection_id = c.id
- WHERE c.privacy = 1 AND (p.created >= ` + app.db.dateSub(3, "month") + ` AND p.created <= ` + app.db.now() + ` AND pinned_position IS NULL)
+ LEFT JOIN users u ON u.id = p.owner_id
+ WHERE c.privacy = 1 AND (p.created >= ` + app.db.dateSub(3, "month") + ` AND p.created <= ` + app.db.now() + ` AND pinned_position IS NULL) AND u.suspended = 0
ORDER BY p.created DESC`)
if err != nil {
log.Error("Failed selecting from posts: %v", err)
diff --git a/routes.go b/routes.go
index 724c532..e7014cd 100644
--- a/routes.go
+++ b/routes.go
@@ -11,13 +11,14 @@
package writefreely
import (
+ "net/http"
+ "path/filepath"
+ "strings"
+
"github.com/gorilla/mux"
"github.com/writeas/go-webfinger"
"github.com/writeas/web-core/log"
"github.com/writefreely/go-nodeinfo"
- "net/http"
- "path/filepath"
- "strings"
)
// InitStaticRoutes adds routes for serving static files.
@@ -143,6 +144,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET")
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET")
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET")
+ write.HandleFunc("/admin/user/{username}", handler.Admin(handleAdminToggleUserSuspended)).Methods("POST")
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET")
write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST")
diff --git a/schema.sql b/schema.sql
index b3fae97..3a79736 100644
--- a/schema.sql
+++ b/schema.sql
@@ -225,6 +225,7 @@ CREATE TABLE IF NOT EXISTS `users` (
`password` char(60) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`email` varbinary(255) DEFAULT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `suspended` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
diff --git a/sqlite.sql b/sqlite.sql
index 90989ed..920ffed 100644
--- a/sqlite.sql
+++ b/sqlite.sql
@@ -214,7 +214,8 @@ CREATE TABLE IF NOT EXISTS `users` (
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT DEFAULT NULL,
- created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+ created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ suspended INTEGER NOT NULL DEFAULT 0
);
-- --------------------------------------------------------
diff --git a/templates/edit-meta.tmpl b/templates/edit-meta.tmpl
index 8d96b15..6707e68 100644
--- a/templates/edit-meta.tmpl
+++ b/templates/edit-meta.tmpl
@@ -269,6 +269,10 @@