diff --git a/app.go b/app.go index c2989cb2..24d406ac 100644 --- a/app.go +++ b/app.go @@ -784,6 +784,51 @@ func DoDeleteAccount(apper Apper, username string) error { return nil } +func DoSilenceAccount(apper Apper, username string) error { + // Connect to the database + apper.LoadConfig() + connectToDatabase(apper.App()) + defer shutdown(apper.App()) + + // check user exists + u, err := apper.App().db.GetUserForAuth(username) + if err != nil { + log.Error("%s", err) + os.Exit(1) + } + userID := u.ID + + // do not silence the admin account + // TODO: check for other admins and skip? + if u.IsAdmin() { + log.Error("Can not silence admin account") + os.Exit(1) + } + + // confirm deletion, w/ w/out posts + prompt := promptui.Prompt{ + Templates: &promptui.PromptTemplates{ + Success: "{{ . | bold | faint }}: ", + }, + Label: fmt.Sprintf("Really silence user : %s", username), + IsConfirm: true, + } + _, err = prompt.Run() + if err != nil { + log.Info("Aborted...") + os.Exit(0) + } + + log.Info("Silencing...") + err = apper.App().db.SilenceAccount(userID) + if err != nil { + log.Error("%s", err) + os.Exit(1) + } + log.Info("Success.") + return nil +} + func connectToDatabase(app *App) { log.Info("Connecting to %s database...", app.cfg.Database.Type) diff --git a/cmd/writefreely/main.go b/cmd/writefreely/main.go index cd7ed692..98182cb2 100644 --- a/cmd/writefreely/main.go +++ b/cmd/writefreely/main.go @@ -84,6 +84,11 @@ func main() { Usage: "Delete a user with the given username", Hidden: true, }, + &cli.StringFlag{ + Name: "silence-user", + Usage: "Silence a user with the given username", + Hidden: true, + }, &cli.StringFlag{ Name: "reset-pass", Usage: "Reset the given user's password", @@ -151,6 +156,8 @@ func legacyActions(c *cli.Context) error { return writefreely.CreateUser(app, username, password, false) case c.IsSet("delete-user"): return writefreely.DoDeleteAccount(app, c.String("delete-user")) + case c.IsSet("silence-user"): + return writefreely.DoSilenceAccount(app, c.String("silence-user")) case c.IsSet("reset-pass"): return writefreely.ResetPassword(app, c.String("reset-pass")) } diff --git a/cmd/writefreely/user.go b/cmd/writefreely/user.go index 2efb4b9d..e617e00c 100644 --- a/cmd/writefreely/user.go +++ b/cmd/writefreely/user.go @@ -24,6 +24,7 @@ var ( Subcommands: []*cli.Command{ &cmdAddUser, &cmdDelUser, + &cmdSilenceUser, &cmdResetPass, // TODO: possibly add a user list command }, @@ -50,6 +51,13 @@ var ( Action: delUserAction, } + cmdSilenceUser cli.Command = cli.Command{ + Name: "silence", + Usage: "Silence user", + Aliases: []string{"sil", "s"}, + Action: silenceUserAction, + } + cmdResetPass cli.Command = cli.Command{ Name: "reset-pass", Usage: "Reset user's password", @@ -84,6 +92,17 @@ func delUserAction(c *cli.Context) error { return writefreely.DoDeleteAccount(app, username) } +func silenceUserAction(c *cli.Context) error { + username := "" + if c.NArg() > 0 { + username = c.Args().Get(0) + } else { + return fmt.Errorf("No user passed. Example: writefreely user silence [USER]") + } + app := writefreely.NewApp(c.String("c")) + return writefreely.DoSilenceAccount(app, username) +} + func resetPassAction(c *cli.Context) error { username := "" if c.NArg() > 0 { diff --git a/database.go b/database.go index 8bd5a43f..1b1981d5 100644 --- a/database.go +++ b/database.go @@ -68,6 +68,7 @@ type writestore interface { GetTemporaryAccessToken(userID int64, validSecs int) (string, error) GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error) DeleteAccount(userID int64) error + SilenceAccount(userID int64) error ChangeSettings(app *App, u *User, s *userSettings) error ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error @@ -2404,6 +2405,33 @@ func (db *datastore) DeleteAccount(userID int64) error { return nil } +func (db *datastore) SilenceAccount(userID int64) error { + // Start transaction + t, err := db.Begin() + if err != nil { + log.Error("Unable to begin: %v", err) + return err + } + + // Update user state + _, err = t.Exec("UPDATE users SET status=1 WHERE id=?", userID) + if err != nil { + t.Rollback() + return fmt.Errorf("Unable to update user status: %s", err) + } + + // Commit all changes to the database + err = t.Commit() + if err != nil { + t.Rollback() + log.Error("Unable to commit: %v", err) + return err + } + + // TODO: federate delete actor here too? + return nil +} + func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) { var pub, priv []byte err := db.QueryRow("SELECT public_key, private_key FROM collectionkeys WHERE collection_id = ?", collectionID).Scan(&pub, &priv) diff --git a/go.mod b/go.mod index 54d38bb0..33150e5b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.13.0 github.com/go-ini/ini v1.66.4 - github.com/go-sql-driver/mysql v1.6.0 + github.com/go-sql-driver/mysql v1.7.0 github.com/gorilla/csrf v1.7.1 github.com/gorilla/feeds v1.1.1 github.com/gorilla/mux v1.8.0 diff --git a/go.sum b/go.sum index d9e88856..f2e306aa 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4L github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M= github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= diff --git a/pages/landing.tmpl b/pages/landing.tmpl index 2131b400..a5b2fd91 100644 --- a/pages/landing.tmpl +++ b/pages/landing.tmpl @@ -107,10 +107,15 @@ form dd { {{end}} + {{ else }} + {{ if .UserInvites }} +

Registration is by invitation.

+

Contact us, or an existing user, to get an invite link.

{{ else }}

Registration is currently closed.

You can always sign up on another instance.

{{ end }} + {{ end }} diff --git a/templates/include/footer.tmpl b/templates/include/footer.tmpl index c6f4b875..2d367af8 100644 --- a/templates/include/footer.tmpl +++ b/templates/include/footer.tmpl @@ -26,6 +26,7 @@
  • about
  • {{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}reader{{end}}
  • privacy
  • +
  • Admin contact
  • diff --git a/templates/include/post-render.tmpl b/templates/include/post-render.tmpl index 5b848454..56f6cf28 100644 --- a/templates/include/post-render.tmpl +++ b/templates/include/post-render.tmpl @@ -34,9 +34,7 @@ // Given a set of nodes, run highlighting on them function highlight(nodes) { - for (i=0; i < nodes.length; i++) { - hljs.highlightBlock(nodes[i]); - } + nodes.forEach(node => { hljs.highlightBlock(node); }); } // Given a array of URIs, load them in order @@ -45,11 +43,11 @@ var sc = document.createElement('script'); sc.src = uri; sc.async = false; // critical? - // Set callback on last script + // Set callback on last script if (uris.indexOf(uri) == uris.length-1) { - // Set callback regardless - // so we're sure it will run if last element had error - // (we only know after loading, so we've had load time already) + // Set callback regardless + // so we're sure it will run if last element had error + // (we only know after loading, so we've had load time already) sc.onload = callback; sc.onerror = callback; } document.head.appendChild(sc); @@ -58,11 +56,11 @@ // We don't have to do anything if there are no language blocks if (lb.length > 0) { - // We have blocks to be highlighted, so we load css - var st = document.createElement('link'); - st.rel = "stylesheet"; - st.href = "/css/lib/atom-one-light.min.css"; - document.head.appendChild(st); + // We have blocks to be highlighted, so we load css + var st = document.createElement('link'); + st.rel = "stylesheet"; + st.href = "/css/lib/atom-one-light.min.css"; + document.head.appendChild(st); // Construct set of files to load, in order var jss = [hlbaseUri + "highlight.min.js"]; @@ -70,7 +68,9 @@ for (i=0; i < lb.length; i++) { lang = lb[i].className.replace('language-','').toLowerCase(); // Support the aliases specified above + // and make sure the block has the non-aliased name if (aliasmap[lang]) lang = aliasmap[lang]; + lb[i].className = 'language-' + lang; lurl = hlbaseUri + "highlightjs/" + lang + ".min.js"; if (!jss.includes(lurl)) { jss.push(lurl); @@ -79,9 +79,9 @@ // Load files in order, higlight on last load loadLanguages(jss, () => {highlight(lb)}); } - }); +}); -{{end}} + {{end}} {{define "mathjax"}}