mirror of
https://github.com/hkalexling/Mango.git
synced 2025-08-03 03:15:31 -04:00
- basic admin panel and user management
This commit is contained in:
parent
f126dfb430
commit
83f6fc25f0
@ -8,3 +8,6 @@
|
|||||||
.acard:hover {
|
.acard:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
.uk-list li {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
@ -13,13 +13,17 @@ class AuthHandler < Kemal::Handler
|
|||||||
def call(env)
|
def call(env)
|
||||||
return call_next(env) if exclude_match?(env)
|
return call_next(env) if exclude_match?(env)
|
||||||
|
|
||||||
env.request.cookies.each do |c|
|
cookie = env.request.cookies.find { |c| c.name == "token" }
|
||||||
next if c.name != "token"
|
if cookie.nil? || ! @storage.verify_token cookie.value
|
||||||
if @storage.verify_token c.value
|
return env.redirect "/login"
|
||||||
return call_next env
|
end
|
||||||
|
|
||||||
|
if env.request.path.starts_with? "/admin"
|
||||||
|
unless storage.verify_admin cookie.value
|
||||||
|
env.response.status_code = 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
env.redirect "/login"
|
call_next env
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
110
src/mango.cr
110
src/mango.cr
@ -17,6 +17,17 @@ macro send_img(env, img)
|
|||||||
send_file {{env}}, {{img}}.data, {{img}}.mime
|
send_file {{env}}, {{img}}.data, {{img}}.mime
|
||||||
end
|
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|
|
get "/" do |env|
|
||||||
titles = library.titles
|
titles = library.titles
|
||||||
@ -32,6 +43,84 @@ get "/book/:title" do |env|
|
|||||||
layout "title"
|
layout "title"
|
||||||
end
|
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|
|
get "/reader/:title/:entry" do |env|
|
||||||
# We should save the reading progress, and ask the user if she wants to
|
# 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
|
# 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"
|
render "src/views/login.ecr"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
post "/login" do |env|
|
post "/login" do |env|
|
||||||
|
begin
|
||||||
username = env.params.body["username"]
|
username = env.params.body["username"]
|
||||||
password = env.params.body["password"]
|
password = env.params.body["password"]
|
||||||
token = storage.verify_user username, password
|
token = storage.verify_user username, password
|
||||||
if token.nil?
|
raise "" if token.nil?
|
||||||
env.redirect "/login"
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
cookie = HTTP::Cookie.new "token", token
|
cookie = HTTP::Cookie.new "token", token
|
||||||
env.response.cookies << cookie
|
env.response.cookies << cookie
|
||||||
env.redirect "/"
|
env.redirect "/"
|
||||||
|
rescue
|
||||||
|
env.redirect "/login"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/page/:title/:entry/:page" do |env|
|
get "/api/page/:title/:entry/:page" do |env|
|
||||||
@ -128,7 +229,6 @@ get "/api/book" do |env|
|
|||||||
library.to_json
|
library.to_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
add_handler AuthHandler.new storage
|
add_handler AuthHandler.new storage
|
||||||
|
|
||||||
Kemal.config.port = config.port
|
Kemal.config.port = config.port
|
||||||
|
@ -70,4 +70,62 @@ class Storage
|
|||||||
end
|
end
|
||||||
end
|
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
|
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>
|
<title>Mango</title>
|
||||||
<meta name="description" content="Mango Manga Server">
|
<meta name="description" content="Mango Manga Server">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||||
<link rel="stylesheet" href="/css/mango.css" />
|
<link rel="stylesheet" href="/css/mango.css" />
|
||||||
</head>
|
</head>
|
||||||
@ -23,5 +22,7 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
|
<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.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/js/uikit-icons.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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
<title>Mango</title>
|
<title>Mango</title>
|
||||||
<meta name="description" content="Mango Manga Server">
|
<meta name="description" content="Mango Manga Server">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<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="https://cdn.jsdelivr.net/npm/uikit@3.3.1/dist/css/uikit.min.css" />
|
||||||
<link rel="stylesheet" href="/css/mango.css" />
|
<link rel="stylesheet" href="/css/mango.css" />
|
||||||
</head>
|
</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