diff --git a/app.lua b/app.lua index b47d82e..9baf2a0 100644 --- a/app.lua +++ b/app.lua @@ -1,5 +1,10 @@ local lapis = require("lapis") local app = lapis.Application() +local constants = require("constants") + +local db = require("lapis.db") +-- sqlite starts without foreign key enforcement +db.query("PRAGMA foreign_keys = ON") local util = require("util") @@ -11,6 +16,7 @@ local function inject_methods(req) req.ntob = function(_, v) return util.ntob(v) end + req.PermissionLevelString = constants.PermissionLevelString end app:before_filter(inject_methods) diff --git a/apps/threads.lua b/apps/threads.lua index b5ebfe4..13aa572 100644 --- a/apps/threads.lua +++ b/apps/threads.lua @@ -109,7 +109,7 @@ app:post("thread", "/:slug", function(self) return {redirect_to = self:url_for("thread", {slug = thread.slug})} end - if util.is_thread_locked(thread) and not user:is_admin() then + if util.is_thread_locked(thread) and not user:is_mod() then return {redirect_to = self:url_for("thread", {slug = thread.slug})} end diff --git a/apps/topics.lua b/apps/topics.lua index 12f6332..c7074d8 100644 --- a/apps/topics.lua +++ b/apps/topics.lua @@ -27,7 +27,7 @@ end) app:get("topic_create", "/create", function(self) local user = util.get_logged_in_user(self) or util.TransientUser - if not user:is_admin() then + if not user:is_mod() then return {status = 403} end @@ -36,7 +36,7 @@ end) app:post("topic_create", "/create", function(self) local user = util.get_logged_in_user(self) or util.TransientUser - if not user:is_admin() then + if not user:is_mod() then return {redirect_to = "all_topics"} end @@ -72,7 +72,7 @@ app:get("topic", "/:slug", function(self) self.thread_create_error = ThreadCreateError.GUEST elseif user:is_guest() then self.thread_create_error = ThreadCreateError.LOGGED_OUT - elseif util.ntob(topic.is_locked) and not user:is_admin() then + elseif util.ntob(topic.is_locked) and not user:is_mod() then self.thread_create_error = ThreadCreateError.TOPIC_LOCKED end @@ -81,7 +81,7 @@ end) app:get("topic_edit", "/:slug/edit", function(self) local user = util.get_logged_in_user_or_transient(self) - if not user:is_admin() then + if not user:is_mod() then return {redirect_to = self:url_for("topic", {slug = self.params.slug})} end local topic = Topics:find({ @@ -96,7 +96,7 @@ end) app:post("topic_edit", "/:slug/edit", function(self) local user = util.get_logged_in_user_or_transient(self) - if not user:is_admin() then + if not user:is_mod() then return {redirect_to = self:url_for("topic", {slug = self.params.slug})} end local topic = Topics:find({ diff --git a/apps/users.lua b/apps/users.lua index bbe7fd0..027a9b6 100644 --- a/apps/users.lua +++ b/apps/users.lua @@ -79,13 +79,59 @@ app:get("user", "/:username", function(self) self.user_is_me = me.id == user.id if user.permission == constants.PermissionLevel.GUEST then - if not (self.user_is_me or me:is_admin()) then + if not (self.user_is_me or me:is_mod()) then return {status = 404} end end return {render = "user.user"} end) +app:post("user_delete", "/:username/delete", function(self) + local me = util.get_logged_in_user(self) + if me == nil then + self.session.flash = {error = "You must be logged in to perform this action."} + return {redirect_to = self:url_for("user_login")} + end + local target_user = Users:find({username = self.params.username}) + if not me:is_mod() then + if me.id ~= target_user.id then + return {redirect_to = self:url_for("user", {username = self.params.username})} + end + + if not authenticate_user(target_user, self.params.password) then + self.session.flash = {error = "The password you entered is incorrect."} + return {redirect_to = self:url_for("user_delete_confirm", {username = me.username})} + end + + util.transfer_and_delete_user(target_user) + self.session.flash = {error = "Your account has been added to the deletion queue."} + return {redirect_to = self:url_for("user_signup")} + else + if target_user.permission >= me.permission then + self.session.flash = {error = "You can not delete another moderator."} + return {redirect_to = self:url_for("user", {username = me.username})} + end + end +end) + +app:get("user_delete_confirm", "/:username/delete_confirm", function(self) + local me = util.get_logged_in_user(self) + if me == nil then + self.session.flash = {error = "You must be logged in to perform this action."} + return {redirect_to = self:url_for("user_login")} + end + local target_user = Users:find({username = self.params.username}) + if me.id ~= target_user.id then + return {redirect_to = self:url_for("user", {username = self.params.username})} + end + if self.session.flash then + self.err = self.session.flash.error + self.session.flash = {} + end + self.user = target_user + return {render = "user.delete_confirm"} +end) + app:post("user_clear_avatar", "/:username/clear_avatar", function(self) local me = util.get_logged_in_user(self) if me == nil then @@ -216,6 +262,10 @@ app:post("user_login", "/login", function(self) self.session.flash = {error = "Invalid username or password"} return {redirect_to = self:url_for("user_login")} end + if user.permission == constants.PermissionLevel.SYSTEM then + self.session.flash = {error = "Invalid username or password"} + return {redirect_to = self:url_for("user_login")} + end if not authenticate_user(user, password) then self.session.flash = {error = "Invalid username or password"} return {redirect_to = self:url_for("user_login")} @@ -300,7 +350,7 @@ app:post("confirm_user", "/confirm_user/:user_id", function (self) if not user then return {status = 403} end - if not user:is_admin() then + if not user:is_mod() then return {status = 403} end local target_user = Users:find(self.params.user_id) @@ -315,4 +365,64 @@ app:post("confirm_user", "/confirm_user/:user_id", function (self) return {redirect_to = self:url_for("user", {username = target_user.username})} end) +app:post("mod_user", "/mod_user/:user_id", function(self) + local user = util.get_logged_in_user(self) + if not user then + return {status = 403} + end + if not user:is_admin() then + return {status = 403} + end + local target_user = Users:find(self.params.user_id) + if not target_user then + return {status = 404} + end + if target_user:is_mod() then + return {status = 404} + end + + target_user:update({permission = constants.PermissionLevel.MODERATOR}) + return {redirect_to = self:url_for("user", {username = target_user.username})} +end) + +app:post("demod_user", "/demod_user/:user_id", function(self) + local user = util.get_logged_in_user(self) + if not user then + return {status = 403} + end + if not user:is_admin() then + return {status = 403} + end + local target_user = Users:find(self.params.user_id) + if not target_user then + return {status = 404} + end + if not target_user:is_mod() then + return {status = 404} + end + + target_user:update({permission = constants.PermissionLevel.USER}) + return {redirect_to = self:url_for("user", {username = target_user.username})} +end) + +app:post("guest_user", "/guest_user/:user_id", function(self) + local user = util.get_logged_in_user(self) + if not user then + return {status = 403} + end + if not user:is_mod() then + return {status = 403} + end + local target_user = Users:find(self.params.user_id) + if not target_user then + return {status = 404} + end + if target_user:is_mod() then + return {status = 404} + end + + target_user:update({permission = constants.PermissionLevel.GUEST}) + return {redirect_to = self:url_for("user", {username = target_user.username})} +end) + return app \ No newline at end of file diff --git a/constants.lua b/constants.lua index 9e04f51..3cf0658 100644 --- a/constants.lua +++ b/constants.lua @@ -3,7 +3,17 @@ local Constants = {} Constants.PermissionLevel = { GUEST = 0, USER = 1, - ADMIN = 2, + MODERATOR = 2, + SYSTEM = 3, + ADMIN = 4, +} + +Constants.PermissionLevelString = { + [Constants.PermissionLevel.GUEST] = "Guest", + [Constants.PermissionLevel.USER] = "User", + [Constants.PermissionLevel.MODERATOR] = "Moderator", + [Constants.PermissionLevel.SYSTEM] = "System", + [Constants.PermissionLevel.ADMIN] = "Administrator", } Constants.BCRYPT_ROUNDS = 10 diff --git a/create_default_admin.lua b/create_default_accounts.lua similarity index 69% rename from create_default_admin.lua rename to create_default_accounts.lua index 950ba6d..aab40ed 100644 --- a/create_default_admin.lua +++ b/create_default_accounts.lua @@ -29,4 +29,20 @@ local function create_admin() print("Admin account created, use \"admin\" as the login and \"" .. password .. "\" as the password. This will only be shown once.") end -create_admin() \ No newline at end of file +local function create_deleted_user() + local username = "DeletedUser" + local root_count = models.Users:count("username = ?", username) + if root_count ~= 0 then + print("deleted user already exists") + return + end + + models.Users:create({ + username = username, + password_hash = "", + permission = constants.PermissionLevel.SYSTEM, + }) +end + +create_admin() +create_deleted_user() diff --git a/models.lua b/models.lua index 1e7bfcf..87f77ad 100644 --- a/models.lua +++ b/models.lua @@ -12,6 +12,14 @@ function Users_mt:is_admin() return self.permission == constants.PermissionLevel.ADMIN end +function Users_mt:is_mod() + return self.permission >= constants.PermissionLevel.MODERATOR +end + +function Users_mt:is_system() + return self.permission == constants.PermissionLevel.SYSTEM +end + function Users_mt:is_logged_in_guest() return self:is_guest() and true end diff --git a/util.lua b/util.lua index 704f22c..09cf576 100644 --- a/util.lua +++ b/util.lua @@ -14,6 +14,9 @@ util.TransientUser = { is_admin = function (self) return false end, + is_mod = function (self) + return false + end, is_guest = function (self) return true end, @@ -116,7 +119,6 @@ function util.create_post(thread_id, user_id, content) local revision = PostHistory:create({ post_id = post.id, - user_id = user_id, content = bb_content, is_initial_revision = true, }) @@ -127,4 +129,16 @@ function util.create_post(thread_id, user_id, content) return post end -return util \ No newline at end of file +function util.transfer_and_delete_user(user) + local deleted_user = Users:find({ + username = "DeletedUser", + }) + -- this needs to be atomic + db.query("BEGIN") + db.query('UPDATE "threads" SET "user_id" = ? WHERE "user_id" = ?', deleted_user.id, user.id) + db.query('UPDATE "posts" SET "user_id" = ? WHERE "user_id" = ?', deleted_user.id, user.id) + user:delete() -- uncomment later + db.query("COMMIT") +end + +return util diff --git a/views/topics/topic.etlua b/views/topics/topic.etlua index 68fb769..f242ccf 100644 --- a/views/topics/topic.etlua +++ b/views/topics/topic.etlua @@ -15,14 +15,14 @@ <% if thread_create_error == ThreadCreateError.OK then %> >New thread <% elseif thread_create_error == ThreadCreateError.GUEST then %> -
Your account is still pending confirmation by an administrator. You are not able to create a new thread or post at this time.
+Your account is still pending confirmation by a moderator. You are not able to create a new thread or post at this time.
<% elseif thread_create_error == ThreadCreateError.LOGGED_OUT then %>Only logged in users can create threads. ">Sign up or ">log in to create a thread.
<% else %>This topic is locked.
<% end %> -<% if user:is_admin() then %> +<% if user:is_mod() then %>After you sign up, an administrator will need to confirm your account before you will be allowed to post.
+After you sign up, a moderator will need to confirm your account before you will be allowed to post.
diff --git a/views/user/user.etlua b/views/user/user.etlua index c2ef76e..fff66e3 100644 --- a/views/user/user.etlua +++ b/views/user/user.etlua @@ -1,22 +1,41 @@ <% if just_logged_in then %> -This user is a guest. They signed up on <%= os.date("%c", user.created_at) %>.
- -<% elseif me:is_admin() then %> -This user signed up on <%= os.date("%c", user.created_at) %> and was confirmed on <%= os.date("%c", user.confirmed_on) %>.
+ +<% if me:is_mod() and not user:is_system() then %> +This user is a guest. They signed up on <%= os.date("%c", user.created_at) %>.
+ + <% else %> <% --[[ user is not guest ]] %> +This user signed up on <%= os.date("%c", user.created_at) %> and was confirmed on <%= os.date("%c", user.confirmed_on) %>.
+ <% if user.id ~= me.id and user.permission < me.permission then %> + + <% end %> + <% if me:is_admin() and not user:is_mod() then %> + + <% elseif me:is_admin() then %> + + <% end %> + <% end %> <% end %>