From 83f6fc25f00824d23437ac2e58b5397a89723ff8 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Thu, 13 Feb 2020 04:36:59 +0000 Subject: [PATCH] - basic admin panel and user management --- public/css/mango.css | 3 + src/auth_handler.cr | 14 +++-- src/mango.cr | 120 ++++++++++++++++++++++++++++++++++++---- src/storage.cr | 58 +++++++++++++++++++ src/views/admin.ecr | 24 ++++++++ src/views/layout.ecr | 3 +- src/views/reader.ecr | 1 - src/views/user-edit.ecr | 67 ++++++++++++++++++++++ src/views/user.ecr | 34 ++++++++++++ 9 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 src/views/admin.ecr create mode 100644 src/views/user-edit.ecr create mode 100644 src/views/user.ecr diff --git a/public/css/mango.css b/public/css/mango.css index e601b57..d7393bb 100644 --- a/public/css/mango.css +++ b/public/css/mango.css @@ -8,3 +8,6 @@ .acard:hover { text-decoration: none; } +.uk-list li { + cursor: pointer; +} diff --git a/src/auth_handler.cr b/src/auth_handler.cr index f026e80..4224dd5 100644 --- a/src/auth_handler.cr +++ b/src/auth_handler.cr @@ -13,13 +13,17 @@ class AuthHandler < Kemal::Handler def call(env) return call_next(env) if exclude_match?(env) - env.request.cookies.each do |c| - next if c.name != "token" - if @storage.verify_token c.value - return call_next env + cookie = env.request.cookies.find { |c| c.name == "token" } + if cookie.nil? || ! @storage.verify_token cookie.value + return env.redirect "/login" + end + + if env.request.path.starts_with? "/admin" + unless storage.verify_admin cookie.value + env.response.status_code = 401 end end - env.redirect "/login" + call_next env end end diff --git a/src/mango.cr b/src/mango.cr index 0d9ea07..8cffbc6 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -17,6 +17,17 @@ macro send_img(env, img) send_file {{env}}, {{img}}.data, {{img}}.mime end +macro get_username(env) + cookie = {{env}}.request.cookies.find { |c| c.name == "token" } + next if cookie.nil? + storage.verify_token cookie.value +end + +def hash_to_query(hash) + hash.map { |k, v| "#{k}=#{v}" } + .join("&") +end + get "/" do |env| titles = library.titles @@ -32,6 +43,84 @@ get "/book/:title" do |env| layout "title" end +get "/admin" do |env| + layout "admin" +end + +get "/admin/user" do |env| + users = storage.list_users + username = get_username env + layout "user" +end + +get "/admin/user/edit" do |env| + username = env.params.query["username"]? + admin = env.params.query["admin"]? + if admin + admin = admin == "true" + end + error = env.params.query["error"]? + current_user = get_username env + new_user = username.nil? && admin.nil? + layout "user-edit" +end + +post "/admin/user/edit" do |env| + # creating new user + begin + username = env.params.body["username"] + password = env.params.body["password"] + # if `admin` is unchecked, the body hash would not contain `admin` + admin = !env.params.body["admin"]?.nil? + + if username.size < 3 + raise "Username should contain at least 3 characters" + end + if password.size < 6 + raise "Password should contain at least 6 characters" + end + + storage.new_user username, password, admin + + env.redirect "/admin/user" + rescue e + puts e.message + redirect_url = URI.new \ + path: "/admin/user/edit",\ + query: hash_to_query({"error" => e.message}) + env.redirect redirect_url.to_s + end +end + +post "/admin/user/edit/:original_username" do |env| + # editing existing user + begin + username = env.params.body["username"] + password = env.params.body["password"] + # if `admin` is unchecked, the body hash would not contain `admin` + admin = !env.params.body["admin"]?.nil? + original_username = env.params.url["original_username"] + + if username.size < 3 + raise "Username should contain at least 3 characters" + end + if password.size != 0 && password.size < 6 + raise "Password should contain at least 6 characters" + end + + storage.update_user original_username, username, password, admin + + env.redirect "/admin/user" + rescue e + puts e.message + redirect_url = URI.new \ + path: "/admin/user/edit",\ + query: hash_to_query({"username" => original_username, \ + "admin" => admin, "error" => e.message}) + env.redirect redirect_url.to_s + end +end + get "/reader/:title/:entry" do |env| # We should save the reading progress, and ask the user if she wants to # start over or resume. For now we just start from page 0 @@ -72,18 +161,30 @@ get "/login" do |env| render "src/views/login.ecr" end -post "/login" do |env| - username = env.params.body["username"] - password = env.params.body["password"] - token = storage.verify_user username, password - if token.nil? +get "/logout" do |env| + begin + cookie = env.request.cookies.find { |c| c.name == "token" } + raise "" if cookie.nil? + storage.logout cookie.value + rescue + ensure env.redirect "/login" - next end +end - cookie = HTTP::Cookie.new "token", token - env.response.cookies << cookie - env.redirect "/" +post "/login" do |env| + begin + username = env.params.body["username"] + password = env.params.body["password"] + token = storage.verify_user username, password + raise "" if token.nil? + + cookie = HTTP::Cookie.new "token", token + env.response.cookies << cookie + env.redirect "/" + rescue + env.redirect "/login" + end end get "/api/page/:title/:entry/:page" do |env| @@ -128,7 +229,6 @@ get "/api/book" do |env| library.to_json end - add_handler AuthHandler.new storage Kemal.config.port = config.port diff --git a/src/storage.cr b/src/storage.cr index bfe6712..35c063b 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -70,4 +70,62 @@ class Storage end end end + + def verify_admin(token) + DB.open "sqlite3://#{@path}" do |db| + begin + return db.query_one "select admin from users where " \ + "token = (?)", token, as: Bool + rescue e : SQLite3::Exception | DB::Error + return false + end + end + end + + def list_users() + results = Array(Tuple(String, Bool)).new + DB.open "sqlite3://#{@path}" do |db| + db.query "select username, admin from users" do |rs| + rs.each do + results << {rs.read(String), rs.read(Bool)} + end + end + end + results + end + + def new_user(username, password, admin) + admin = (admin ? 1 : 0) + DB.open "sqlite3://#{@path}" do |db| + hash = hash_password password + db.exec "insert into users values (?, ?, ?, ?)", + username, hash, "", admin + end + end + + def update_user(original_username, username, password, admin) + admin = (admin ? 1 : 0) + DB.open "sqlite3://#{@path}" do |db| + if password.size == 0 + db.exec "update users set username = (?), admin = (?) "\ + "where username = (?)",\ + username, admin, original_username + else + hash = hash_password password + db.exec "update users set username = (?), admin = (?),"\ + "password = (?) where username = (?)",\ + username, admin, hash, original_username + end + end + end + + def logout(token) + DB.open "sqlite3://#{@path}" do |db| + begin + db.exec "update users set token = (?) where token = (?)", \ + "", token + rescue + end + end + end end diff --git a/src/views/admin.ecr b/src/views/admin.ecr new file mode 100644 index 0000000..0b5cc64 --- /dev/null +++ b/src/views/admin.ecr @@ -0,0 +1,24 @@ + + +
+Log Out + +<% content_for "script" do %> + +<% end %> diff --git a/src/views/layout.ecr b/src/views/layout.ecr index 118b141..e13cdee 100644 --- a/src/views/layout.ecr +++ b/src/views/layout.ecr @@ -6,7 +6,6 @@ Mango - @@ -23,5 +22,7 @@ + + <%= yield_content "script" %> diff --git a/src/views/reader.ecr b/src/views/reader.ecr index 4f43962..2608b0a 100644 --- a/src/views/reader.ecr +++ b/src/views/reader.ecr @@ -6,7 +6,6 @@ Mango - diff --git a/src/views/user-edit.ecr b/src/views/user-edit.ecr new file mode 100644 index 0000000..0d168b5 --- /dev/null +++ b/src/views/user-edit.ecr @@ -0,0 +1,67 @@ +
+ +
+ +
+ + + value=<%= username %> + <%- end -%> + > +
+ <%- if new_user -%> +
+ + +
+ <%- end -%> +
+ + + checked + <%- end -%> + > +
+ + <%- if !new_user -%> +
+ + +
+ <%- end -%> + +
+ + +
+ + +<% content_for "script" do %> + +<% end %> diff --git a/src/views/user.ecr b/src/views/user.ecr new file mode 100644 index 0000000..a322b9e --- /dev/null +++ b/src/views/user.ecr @@ -0,0 +1,34 @@ + + + + + + + + + + <%- users.each do |u| -%> + + + + + + <%- end -%> + +
UsernameAdmin AccessActions
<%= u[0] %><%= u[1] %> + + <%- if u[0] != username %> + + <%- end %> +
+ +New User + + +<% content_for "script" do %> + +<% end %>