mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-02 10:55:30 -04:00
- basic admin panel and user management
This commit is contained in:
parent
f126dfb430
commit
83f6fc25f0
@ -8,3 +8,6 @@
|
||||
.acard:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.uk-list li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -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
|
||||
|
120
src/mango.cr
120
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
|
||||
|
@ -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
|
||||
|
24
src/views/admin.ecr
Normal file
24
src/views/admin.ecr
Normal file
@ -0,0 +1,24 @@
|
||||
<ul class="uk-list uk-list-large uk-list-divider">
|
||||
<li data-url="/admin/info">Server Info</li>
|
||||
<li data-url="/admin/user">User Managerment</li>
|
||||
<li onclick="scan()">Scan Library Files</li>
|
||||
</ul>
|
||||
|
||||
<hr class="uk-divider-icon">
|
||||
<a class="uk-button uk-button-danger" href="/logout">Log Out</a>
|
||||
|
||||
<% content_for "script" do %>
|
||||
<script>
|
||||
function scan() {
|
||||
alert("scan");
|
||||
}
|
||||
$(function() {
|
||||
$('li').click(function() {
|
||||
url = $(this).attr('data-url');
|
||||
if (url) {
|
||||
$(location).attr('href', url)
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
@ -6,7 +6,6 @@
|
||||
<title>Mango</title>
|
||||
<meta name="description" content="Mango Manga Server">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||
<link rel="stylesheet" href="/css/mango.css" />
|
||||
</head>
|
||||
@ -23,5 +22,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.min.js"></script>
|
||||
|
||||
<%= yield_content "script" %>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -6,7 +6,6 @@
|
||||
<title>Mango</title>
|
||||
<meta name="description" content="Mango Manga Server">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||
<link rel="stylesheet" href="/css/mango.css" />
|
||||
</head>
|
||||
|
67
src/views/user-edit.ecr
Normal file
67
src/views/user-edit.ecr
Normal file
@ -0,0 +1,67 @@
|
||||
<div id="alert"></div>
|
||||
|
||||
<form action="/admin/user/edit" method="post" accept-charset="utf-8">
|
||||
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="form-stacked-text">Username</label>
|
||||
<input class="uk-input" type="text" name="username"
|
||||
<%- if username -%>
|
||||
value=<%= username %>
|
||||
<%- end -%>
|
||||
>
|
||||
</div>
|
||||
<%- if new_user -%>
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="form-stacked-text">Password</label>
|
||||
<input class="uk-input" type="password" name="password">
|
||||
</div>
|
||||
<%- end -%>
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label" for="form-stacked-text">Admin Access</label>
|
||||
<input class="uk-checkbox" type="checkbox" name="admin"
|
||||
<%- if admin == true -%>
|
||||
checked
|
||||
<%- end -%>
|
||||
>
|
||||
</div>
|
||||
|
||||
<%- if !new_user -%>
|
||||
<div>
|
||||
<button class="uk-button uk-button-default" type="button" uk-toggle="target: #change-password">Change Password</button>
|
||||
<div id="change-password" class="uk-margin" hidden>
|
||||
<label class="uk-form-label" for="form-stacked-text">New Password</label>
|
||||
<input class="uk-input" type="password" name="password">
|
||||
</div>
|
||||
</div>
|
||||
<%- end -%>
|
||||
|
||||
<hr class="uk-divider-icon">
|
||||
|
||||
<input type="submit" value="Save" class="uk-button uk-button-primary">
|
||||
</form>
|
||||
|
||||
|
||||
<% content_for "script" do %>
|
||||
<script>
|
||||
$(function(){
|
||||
var target = '/admin/user/edit';
|
||||
<%- if !new_user -%>
|
||||
target += '/<%= username %>';
|
||||
<%- end -%>
|
||||
$('form').attr('action', target);
|
||||
|
||||
function alert(level, text) {
|
||||
hideAlert();
|
||||
var html = '<div class="uk-alert-' + level + '" uk-alert><a class="uk-alert-close" uk-close></a><p>' + text + '</p></div>';
|
||||
$('#alert').append(html);
|
||||
}
|
||||
function hideAlert() {
|
||||
$('#alert').empty();
|
||||
}
|
||||
|
||||
<%- if error -%>
|
||||
alert('danger', '<%= error %>');
|
||||
<%- end -%>
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
34
src/views/user.ecr
Normal file
34
src/views/user.ecr
Normal file
@ -0,0 +1,34 @@
|
||||
<table class="uk-table uk-table-divider">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Admin Access</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%- users.each do |u| -%>
|
||||
<tr>
|
||||
<td><%= u[0] %></td>
|
||||
<td><%= u[1] %></td>
|
||||
<td>
|
||||
<a href="/admin/user/edit?username=<%= u[0] %>&admin=<%= u[1] %>" uk-icon="file-edit"></a>
|
||||
<%- if u[0] != username %>
|
||||
<a href="#" onclick="remove('<%= u[0] %>');return false;" uk-icon="trash"></a>
|
||||
<%- end %>
|
||||
</td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="/admin/user/edit" class="uk-button uk-button-primary">New User</a>
|
||||
|
||||
|
||||
<% content_for "script" do %>
|
||||
<script>
|
||||
function remove(username) {
|
||||
alert(username);
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
Loading…
x
Reference in New Issue
Block a user