mirror of
https://github.com/hkalexling/Mango.git
synced 2026-01-24 00:03:14 -05:00
Compare commits
17 Commits
feature/pl
...
fix/saniti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebe2c8efed | ||
|
|
d1de8b7a4e | ||
|
|
7ae0577e4e | ||
|
|
e9b1bccbc9 | ||
|
|
293fb84e1d | ||
|
|
9c07944390 | ||
|
|
173d69eb26 | ||
|
|
21d8d0e8a7 | ||
|
|
61e85dd49f | ||
|
|
c778364ca2 | ||
|
|
7ecdb1c0dd | ||
|
|
a5a7396edd | ||
|
|
461398d219 | ||
|
|
0d52544617 | ||
|
|
c3736d222c | ||
|
|
2091053221 | ||
|
|
703e6d076b |
@@ -50,7 +50,7 @@ shards:
|
||||
|
||||
koa:
|
||||
git: https://github.com/hkalexling/koa.git
|
||||
version: 0.8.0
|
||||
version: 0.9.0
|
||||
|
||||
mg:
|
||||
git: https://github.com/hkalexling/mg.git
|
||||
@@ -68,6 +68,10 @@ shards:
|
||||
git: https://github.com/luislavena/radix.git
|
||||
version: 0.4.1
|
||||
|
||||
sanitize:
|
||||
git: https://github.com/hkalexling/sanitize.git
|
||||
version: 0.1.0+git.commit.e09520e972d0d9b70b71bb003e6831f7c2c59dce
|
||||
|
||||
sqlite3:
|
||||
git: https://github.com/crystal-lang/crystal-sqlite3.git
|
||||
version: 0.18.0
|
||||
|
||||
@@ -42,3 +42,5 @@ dependencies:
|
||||
branch: master
|
||||
mg:
|
||||
github: hkalexling/mg
|
||||
sanitize:
|
||||
github: hkalexling/sanitize
|
||||
|
||||
@@ -6,6 +6,7 @@ class AuthHandler < Kemal::Handler
|
||||
# Some of the code is copied form kemalcr/kemal-basic-auth on GitHub
|
||||
|
||||
BASIC = "Basic"
|
||||
BEARER = "Bearer"
|
||||
AUTH = "Authorization"
|
||||
AUTH_MESSAGE = "Could not verify your access level for that URL.\n" \
|
||||
"You have to login with proper credentials"
|
||||
@@ -18,8 +19,14 @@ class AuthHandler < Kemal::Handler
|
||||
end
|
||||
|
||||
def require_auth(env)
|
||||
env.session.string "callback", env.request.path
|
||||
redirect env, "/login"
|
||||
if request_path_startswith env, ["/api"]
|
||||
# Do not redirect API requests
|
||||
env.response.status_code = 401
|
||||
send_text env, "Unauthorized"
|
||||
else
|
||||
env.session.string "callback", env.request.path
|
||||
redirect env, "/login"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_token(env)
|
||||
@@ -35,13 +42,18 @@ class AuthHandler < Kemal::Handler
|
||||
def validate_auth_header(env)
|
||||
if env.request.headers[AUTH]?
|
||||
if value = env.request.headers[AUTH]
|
||||
if value.size > 0 && value.starts_with?(BASIC)
|
||||
if value.starts_with? BASIC
|
||||
token = verify_user value
|
||||
return false if token.nil?
|
||||
|
||||
env.session.string "token", token
|
||||
return true
|
||||
end
|
||||
if value.starts_with? BEARER
|
||||
session_id = value.split(" ")[1]
|
||||
token = Kemal::Session.get(session_id).try &.string? "token"
|
||||
return !token.nil? && Storage.default.verify_token token
|
||||
end
|
||||
end
|
||||
end
|
||||
false
|
||||
@@ -54,6 +66,10 @@ class AuthHandler < Kemal::Handler
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# OPTIONS requests do not require authentication
|
||||
if env.request.method === "OPTIONS"
|
||||
return call_next(env)
|
||||
end
|
||||
# Skip all authentication if requesting /login, /logout, /api/login,
|
||||
# or a static file
|
||||
if request_path_startswith(env, ["/login", "/logout", "/api/login"]) ||
|
||||
@@ -62,8 +78,8 @@ class AuthHandler < Kemal::Handler
|
||||
end
|
||||
|
||||
# Check user is logged in
|
||||
if validate_token env
|
||||
# Skip if the request has a valid token
|
||||
if validate_token(env) || validate_auth_header(env)
|
||||
# Skip if the request has a valid token (either from cookies or header)
|
||||
elsif Config.current.disable_login
|
||||
# Check default username if login is disabled
|
||||
unless Storage.default.username_exists Config.current.default_username
|
||||
|
||||
8
src/handlers/cors_handler.cr
Normal file
8
src/handlers/cors_handler.cr
Normal file
@@ -0,0 +1,8 @@
|
||||
class CORSHandler < Kemal::Handler
|
||||
def call(env)
|
||||
if request_path_startswith env, ["/api"]
|
||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
end
|
||||
call_next env
|
||||
end
|
||||
end
|
||||
@@ -55,9 +55,12 @@ class Entry
|
||||
def build_json(*, slim = false)
|
||||
JSON.build do |json|
|
||||
json.object do
|
||||
{% for str in ["zip_path", "title", "size", "id"] %}
|
||||
{% for str in %w(zip_path title size id) %}
|
||||
json.field {{str}}, @{{str.id}}
|
||||
{% end %}
|
||||
if err_msg
|
||||
json.field "err_msg", err_msg
|
||||
end
|
||||
json.field "title_id", @book.id
|
||||
json.field "title_title", @book.title
|
||||
json.field "sort_title", sort_title
|
||||
|
||||
@@ -139,14 +139,31 @@ class Library
|
||||
titles.flat_map &.deep_entries
|
||||
end
|
||||
|
||||
def build_json(*, slim = false, depth = -1)
|
||||
def build_json(*, slim = false, depth = -1, sort_context = nil,
|
||||
percentage = false)
|
||||
_titles = if sort_context
|
||||
sorted_titles sort_context[:username],
|
||||
sort_context[:opt]
|
||||
else
|
||||
self.titles
|
||||
end
|
||||
JSON.build do |json|
|
||||
json.object do
|
||||
json.field "dir", @dir
|
||||
json.field "titles" do
|
||||
json.array do
|
||||
self.titles.each do |title|
|
||||
json.raw title.build_json(slim: slim, depth: depth)
|
||||
_titles.each do |title|
|
||||
json.raw title.build_json(slim: slim, depth: depth,
|
||||
sort_context: sort_context, percentage: percentage)
|
||||
end
|
||||
end
|
||||
end
|
||||
if percentage && sort_context
|
||||
json.field "title_percentages" do
|
||||
json.array do
|
||||
_titles.each do |title|
|
||||
json.number title.load_percentage sort_context[:username]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -202,7 +202,21 @@ class Title
|
||||
alias SortContext = NamedTuple(username: String, opt: SortOptions)
|
||||
|
||||
def build_json(*, slim = false, depth = -1,
|
||||
sort_context : SortContext? = nil)
|
||||
sort_context : SortContext? = nil,
|
||||
percentage = false)
|
||||
_titles = if sort_context
|
||||
sorted_titles sort_context[:username],
|
||||
sort_context[:opt]
|
||||
else
|
||||
self.titles
|
||||
end
|
||||
_entries = if sort_context
|
||||
sorted_entries sort_context[:username],
|
||||
sort_context[:opt]
|
||||
else
|
||||
@entries
|
||||
end
|
||||
|
||||
JSON.build do |json|
|
||||
json.object do
|
||||
{% for str in ["dir", "title", "id"] %}
|
||||
@@ -218,25 +232,39 @@ class Title
|
||||
unless depth == 0
|
||||
json.field "titles" do
|
||||
json.array do
|
||||
self.titles.each do |title|
|
||||
_titles.each do |title|
|
||||
json.raw title.build_json(slim: slim,
|
||||
depth: depth > 0 ? depth - 1 : depth)
|
||||
depth: depth > 0 ? depth - 1 : depth,
|
||||
sort_context: sort_context, percentage: percentage)
|
||||
end
|
||||
end
|
||||
end
|
||||
json.field "entries" do
|
||||
json.array do
|
||||
_entries = if sort_context
|
||||
sorted_entries sort_context[:username],
|
||||
sort_context[:opt]
|
||||
else
|
||||
@entries
|
||||
end
|
||||
_entries.each do |entry|
|
||||
json.raw entry.build_json(slim: slim)
|
||||
end
|
||||
end
|
||||
end
|
||||
if percentage && sort_context
|
||||
json.field "title_percentages" do
|
||||
json.array do
|
||||
_titles.each do |t|
|
||||
json.number t.load_percentage sort_context[:username]
|
||||
end
|
||||
end
|
||||
end
|
||||
json.field "entry_percentages" do
|
||||
json.array do
|
||||
load_percentage_for_all_entries(
|
||||
sort_context[:username],
|
||||
sort_context[:opt]
|
||||
).each do |p|
|
||||
json.number p.nan? ? 0 : p
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
json.field "parents" do
|
||||
json.array do
|
||||
|
||||
@@ -55,6 +55,13 @@ class SortOptions
|
||||
def to_tuple
|
||||
{@method.to_s.underscore, ascend}
|
||||
end
|
||||
|
||||
def to_json
|
||||
{
|
||||
"method" => method.to_s.underscore,
|
||||
"ascend" => ascend,
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
struct Image
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require "sanitize"
|
||||
|
||||
struct AdminRouter
|
||||
def initialize
|
||||
get "/admin" do |env|
|
||||
@@ -14,13 +16,13 @@ struct AdminRouter
|
||||
end
|
||||
|
||||
get "/admin/user/edit" do |env|
|
||||
username = env.params.query["username"]?
|
||||
sanitizer = Sanitize::Policy::Text.new
|
||||
username = env.params.query["username"]?.try { |s| sanitizer.process s }
|
||||
admin = env.params.query["admin"]?
|
||||
if admin
|
||||
admin = admin == "true"
|
||||
end
|
||||
error = env.params.query["error"]?
|
||||
current_user = get_username env
|
||||
error = env.params.query["error"]?.try { |s| sanitizer.process s }
|
||||
new_user = username.nil? && admin.nil?
|
||||
layout "user-edit"
|
||||
end
|
||||
|
||||
@@ -47,7 +47,12 @@ struct APIRouter
|
||||
"mtime" => Int64,
|
||||
"entries" => ["entry"],
|
||||
"titles" => ["title"],
|
||||
"parents" => [String],
|
||||
"parents" => [{
|
||||
"title" => String,
|
||||
"id" => String,
|
||||
}],
|
||||
"title_percentages" => [Float64?],
|
||||
"entry_percentages" => [Float64?],
|
||||
}.merge(s %w(dir title id display_name cover_url)),
|
||||
desc: "A manga title (a collection of entries and sub-titles)"
|
||||
|
||||
@@ -80,6 +85,12 @@ struct APIRouter
|
||||
"username" => String,
|
||||
"password" => String,
|
||||
}
|
||||
Koa.response 200, schema: {
|
||||
"success" => Bool,
|
||||
"error" => String?,
|
||||
"session_id" => String?,
|
||||
"is_admin" => Bool?,
|
||||
}
|
||||
Koa.tag "users"
|
||||
post "/api/login" do |env|
|
||||
begin
|
||||
@@ -88,11 +99,18 @@ struct APIRouter
|
||||
token = Storage.default.verify_user(username, password).not_nil!
|
||||
|
||||
env.session.string "token", token
|
||||
"Authenticated"
|
||||
send_json env, {
|
||||
"success" => true,
|
||||
"session_id" => env.session.id,
|
||||
"is_admin" => Storage.default.username_is_admin username,
|
||||
}.to_json
|
||||
rescue e
|
||||
Logger.error e
|
||||
env.response.status_code = 403
|
||||
e.message
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,7 +149,7 @@ struct APIRouter
|
||||
rescue e
|
||||
Logger.error e
|
||||
env.response.status_code = 500
|
||||
e.message
|
||||
send_text env, e.message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -168,11 +186,13 @@ struct APIRouter
|
||||
rescue e
|
||||
Logger.error e
|
||||
env.response.status_code = 500
|
||||
e.message
|
||||
send_text env, e.message
|
||||
end
|
||||
end
|
||||
|
||||
Koa.describe "Returns the book with title `tid`", <<-MD
|
||||
The entries and titles will be sorted by the default sorting method for the logged-in user.
|
||||
- Supply the `percentage` query parameter to include the reading progress
|
||||
- Supply the `slim` query parameter to strip away "display_name", "cover_url", and "mtime" from the returned object to speed up the loading time
|
||||
- Supply the `depth` query parameter to control the depth of nested titles to return.
|
||||
- When `depth` is 1, returns the top-level titles and sub-titles/entries one level in them
|
||||
@@ -183,8 +203,7 @@ struct APIRouter
|
||||
Koa.path "tid", desc: "Title ID"
|
||||
Koa.query "slim"
|
||||
Koa.query "depth"
|
||||
Koa.query "sort", desc: "Sorting option for entries. Can be one of 'auto', 'title', 'progress', 'time_added' and 'time_modified'"
|
||||
Koa.query "ascend", desc: "Sorting direction for entries. Set to 0 for the descending order. Doesn't work without specifying 'sort'"
|
||||
Koa.query "percentage"
|
||||
Koa.response 200, schema: "title"
|
||||
Koa.response 404, "Title not found"
|
||||
Koa.tag "library"
|
||||
@@ -192,29 +211,104 @@ struct APIRouter
|
||||
begin
|
||||
username = get_username env
|
||||
|
||||
sort_opt = SortOptions.new
|
||||
get_sort_opt
|
||||
|
||||
tid = env.params.url["tid"]
|
||||
title = Library.default.get_title tid
|
||||
raise "Title ID `#{tid}` not found" if title.nil?
|
||||
|
||||
sort_opt = SortOptions.from_info_json title.dir, username
|
||||
|
||||
slim = !env.params.query["slim"]?.nil?
|
||||
depth = env.params.query["depth"]?.try(&.to_i?) || -1
|
||||
percentage = !env.params.query["percentage"]?.nil?
|
||||
|
||||
send_json env, title.build_json(slim: slim, depth: depth,
|
||||
sort_context: {username: username,
|
||||
opt: sort_opt})
|
||||
opt: sort_opt}, percentage: percentage)
|
||||
rescue e
|
||||
Logger.error e
|
||||
env.response.status_code = 404
|
||||
e.message
|
||||
send_text env, e.message
|
||||
end
|
||||
end
|
||||
|
||||
Koa.describe "Returns the sorting option of a title or the library", <<-MD
|
||||
- If the query parameter `tid` is supplied, returns the sorting option of the title identified by the `tid`.
|
||||
- If the query parameter `tid` is missing, returns the sorting option of the library.
|
||||
MD
|
||||
Koa.query "tid"
|
||||
Koa.response 200, schema: {
|
||||
"method" => String?,
|
||||
"ascend" => Bool?,
|
||||
"error" => String?,
|
||||
}
|
||||
Koa.tag "library"
|
||||
get "/api/sort_opt" do |env|
|
||||
username = get_username env
|
||||
|
||||
tid = env.params.query["tid"]?
|
||||
dir = if tid
|
||||
(Library.default.get_title tid).not_nil!.dir
|
||||
else
|
||||
Library.default.dir
|
||||
end
|
||||
sort_opt = SortOptions.from_info_json dir, username
|
||||
send_json env, sort_opt.to_json
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Updates the sorting option of a title or the library", <<-MD
|
||||
- When the `tid` field is supplied in the body, updates the sorting option of the title identified by the `tid`.
|
||||
- When the `tid` field is missing in the body, updates the sorting option of the library.
|
||||
MD
|
||||
Koa.body schema: {
|
||||
"tid" => String?,
|
||||
"method" => String,
|
||||
"ascend" => Bool,
|
||||
}
|
||||
Koa.response 200, schema: {
|
||||
"success" => Bool,
|
||||
"error" => String?,
|
||||
}
|
||||
Koa.tag "library"
|
||||
put "/api/sort_opt" do |env|
|
||||
username = get_username env
|
||||
|
||||
tid = env.params.json["tid"]?.try &.as String
|
||||
dir = if tid
|
||||
(Library.default.get_title tid).not_nil!.dir
|
||||
else
|
||||
Library.default.dir
|
||||
end
|
||||
|
||||
method = env.params.json["sort"].as String
|
||||
ascend = env.params.json["ascend"].as Bool
|
||||
sort_opt = SortOptions.new method, ascend
|
||||
|
||||
TitleInfo.new dir do |info|
|
||||
info.sort_by[username] = sort_opt.to_tuple
|
||||
info.save
|
||||
end
|
||||
send_json env, {
|
||||
"success" => true,
|
||||
}.to_json
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Returns the entire library with all titles and entries", <<-MD
|
||||
The titles will be sorted by the default sorting method for the logged-in user.
|
||||
- Supply the `slim` query parameter to strip away "display_name", "cover_url", and "mtime" from the returned object to speed up the loading time
|
||||
- Supply the `dpeth` query parameter to control the depth of nested titles to return.
|
||||
- Supply the `percentage` query parameter to include the reading progress
|
||||
- When `depth` is 1, returns the requested title and sub-titles/entries one level in it
|
||||
- When `depth` is 0, returns the requested title without its sub-titles/entries
|
||||
- When `depth` is N, returns the requested title and sub-titles/entries N levels in it
|
||||
@@ -222,16 +316,162 @@ struct APIRouter
|
||||
MD
|
||||
Koa.query "slim"
|
||||
Koa.query "depth"
|
||||
Koa.query "percentage"
|
||||
Koa.response 200, schema: {
|
||||
"dir" => String,
|
||||
"titles" => ["title"],
|
||||
"dir" => String,
|
||||
"titles" => ["title"],
|
||||
"title_percentage" => [Float64?],
|
||||
}
|
||||
Koa.tag "library"
|
||||
get "/api/library" do |env|
|
||||
username = get_username env
|
||||
|
||||
sort_opt = SortOptions.from_info_json Library.default.dir, username
|
||||
|
||||
slim = !env.params.query["slim"]?.nil?
|
||||
depth = env.params.query["depth"]?.try(&.to_i?) || -1
|
||||
percentage = !env.params.query["percentage"]?.nil?
|
||||
|
||||
send_json env, Library.default.build_json(slim: slim, depth: depth)
|
||||
send_json env, Library.default.build_json(slim: slim, depth: depth,
|
||||
sort_context: {username: username,
|
||||
opt: sort_opt}, percentage: percentage)
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Returns the continue reading entries"
|
||||
Koa.response 200, schema: {
|
||||
"success" => Bool,
|
||||
"error" => String?,
|
||||
"entries" => ["entry"],
|
||||
"entry_percentages" => [Float64],
|
||||
}
|
||||
Koa.tag "library"
|
||||
get "/api/library/continue_reading" do |env|
|
||||
username = get_username env
|
||||
cr_entries = Library.default.get_continue_reading_entries username
|
||||
|
||||
json = JSON.build do |j|
|
||||
j.object do
|
||||
j.field "success" do
|
||||
j.bool true
|
||||
end
|
||||
j.field "entries" do
|
||||
j.array do
|
||||
cr_entries.each do |e|
|
||||
j.raw e[:entry].build_json
|
||||
end
|
||||
end
|
||||
end
|
||||
j.field "entry_percentages" do
|
||||
j.array do
|
||||
cr_entries.each do |e|
|
||||
j.number e[:percentage]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
send_json env, json
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Returns the start reading titles"
|
||||
Koa.response 200, schema: {
|
||||
"success" => Bool,
|
||||
"error" => String?,
|
||||
"titles" => ["title"],
|
||||
}
|
||||
Koa.tag "library"
|
||||
get "/api/library/start_reading" do |env|
|
||||
username = get_username env
|
||||
titles = Library.default.get_start_reading_titles username
|
||||
|
||||
json = JSON.build do |j|
|
||||
j.object do
|
||||
j.field "success" do
|
||||
j.bool true
|
||||
end
|
||||
j.field "titles" do
|
||||
j.array do
|
||||
titles.each do |t|
|
||||
j.raw t.build_json depth: 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
send_json env, json
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Returns the recently added items"
|
||||
Koa.response 200, schema: {
|
||||
"success" => Bool,
|
||||
"error" => String?,
|
||||
"items" => [{
|
||||
"item" => "title | entry",
|
||||
"percentage" => Float64,
|
||||
"count" => Int32,
|
||||
}],
|
||||
}
|
||||
Koa.tag "library"
|
||||
get "/api/library/recently_added" do |env|
|
||||
username = get_username env
|
||||
ra_entries = Library.default.get_recently_added_entries username
|
||||
|
||||
json = JSON.build do |j|
|
||||
j.object do
|
||||
j.field "success" do
|
||||
j.bool true
|
||||
end
|
||||
j.field "items" do
|
||||
j.array do
|
||||
ra_entries.each do |e|
|
||||
j.object do
|
||||
j.field "item" do
|
||||
if e[:grouped_count] === 1
|
||||
j.raw e[:entry].build_json
|
||||
else
|
||||
j.raw e[:entry].book.build_json depth: 0
|
||||
end
|
||||
end
|
||||
j.field "percentage" do
|
||||
j.number e[:percentage]
|
||||
end
|
||||
j.field "count" do
|
||||
j.number e[:grouped_count]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
send_json env, json
|
||||
rescue e
|
||||
Logger.error e
|
||||
send_json env, {
|
||||
"success" => false,
|
||||
"error" => e.message,
|
||||
}.to_json
|
||||
end
|
||||
|
||||
Koa.describe "Triggers a library scan"
|
||||
@@ -267,6 +507,7 @@ struct APIRouter
|
||||
spawn do
|
||||
Library.default.generate_thumbnails
|
||||
end
|
||||
send_text env, ""
|
||||
end
|
||||
|
||||
Koa.describe "Deletes a user with `username`"
|
||||
@@ -901,7 +1142,7 @@ struct APIRouter
|
||||
e_tag = "W/#{file_hash}"
|
||||
if e_tag == prev_e_tag
|
||||
env.response.status_code = 304
|
||||
""
|
||||
send_text env, ""
|
||||
else
|
||||
sizes = entry.page_dimensions
|
||||
env.response.headers["ETag"] = e_tag
|
||||
@@ -935,6 +1176,7 @@ struct APIRouter
|
||||
rescue e
|
||||
Logger.error e
|
||||
env.response.status_code = 404
|
||||
send_text env, e.message
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -23,7 +23,17 @@ class Server
|
||||
AdminRouter.new
|
||||
ReaderRouter.new
|
||||
APIRouter.new
|
||||
OPDSRouter.new
|
||||
|
||||
{% for path in %w(/api/* /uploads/* /img/*) %}
|
||||
options {{path}} do |env|
|
||||
cors
|
||||
halt env
|
||||
end
|
||||
{% end %}
|
||||
|
||||
static_headers do |response|
|
||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
||||
end
|
||||
|
||||
Kemal.config.logging = false
|
||||
add_handler LogHandler.new
|
||||
|
||||
@@ -39,13 +39,28 @@ macro send_error_page(msg)
|
||||
end
|
||||
|
||||
macro send_img(env, img)
|
||||
cors
|
||||
send_file {{env}}, {{img}}.data, {{img}}.mime
|
||||
end
|
||||
|
||||
def get_token_from_auth_header(env) : String?
|
||||
value = env.request.headers["Authorization"]
|
||||
if value && value.starts_with? "Bearer"
|
||||
session_id = value.split(" ")[1]
|
||||
return Kemal::Session.get(session_id).try &.string? "token"
|
||||
end
|
||||
end
|
||||
|
||||
macro get_username(env)
|
||||
begin
|
||||
token = env.session.string "token"
|
||||
(Storage.default.verify_token token).not_nil!
|
||||
# Check if we can get the session id from the cookie
|
||||
token = env.session.string? "token"
|
||||
if token.nil?
|
||||
# If not, check if we can get the session id from the auth header
|
||||
token = get_token_from_auth_header env
|
||||
end
|
||||
# If we still don't have a token, we handle it in `resuce` with `not_nil!`
|
||||
(Storage.default.verify_token token.not_nil!).not_nil!
|
||||
rescue e
|
||||
if Config.current.disable_login
|
||||
Config.current.default_username
|
||||
@@ -57,12 +72,29 @@ macro get_username(env)
|
||||
end
|
||||
end
|
||||
|
||||
macro cors
|
||||
env.response.headers["Access-Control-Allow-Methods"] = "HEAD,GET,PUT,POST," \
|
||||
"DELETE,OPTIONS"
|
||||
env.response.headers["Access-Control-Allow-Headers"] = "X-Requested-With," \
|
||||
"X-HTTP-Method-Override, Content-Type, Cache-Control, Accept," \
|
||||
"Authorization"
|
||||
env.response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
end
|
||||
|
||||
def send_json(env, json)
|
||||
cors
|
||||
env.response.content_type = "application/json"
|
||||
env.response.print json
|
||||
end
|
||||
|
||||
def send_text(env, text)
|
||||
cors
|
||||
env.response.content_type = "text/plain"
|
||||
env.response.print text
|
||||
end
|
||||
|
||||
def send_attachment(env, path)
|
||||
cors
|
||||
send_file env, path, filename: File.basename(path), disposition: "attachment"
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user