From 651bd17612ece4856e71e61df4b6b70f8a2940b6 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sat, 30 May 2020 15:14:39 +0000 Subject: [PATCH 1/5] Rewrite option parsing using clim and add the `admin` subcommand --- shard.lock | 6 +++- shard.yml | 2 ++ src/mango.cr | 87 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/shard.lock b/shard.lock index 74ad3e1..e955617 100644 --- a/shard.lock +++ b/shard.lock @@ -2,7 +2,7 @@ version: 1.0 shards: ameba: github: crystal-ameba/ameba - version: 0.12.0 + version: 0.12.1 archive: github: hkalexling/archive.cr @@ -12,6 +12,10 @@ shards: github: schovi/baked_file_system version: 0.9.8 + clim: + github: at-grandpa/clim + version: 0.12.0 + db: github: crystal-lang/crystal-db version: 0.9.0 diff --git a/shard.yml b/shard.yml index e7c9e2d..7ec37f0 100644 --- a/shard.yml +++ b/shard.yml @@ -23,3 +23,5 @@ dependencies: github: hkalexling/archive.cr ameba: github: crystal-ameba/ameba + clim: + github: at-grandpa/clim diff --git a/src/mango.cr b/src/mango.cr index 26149a7..53a281c 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -2,31 +2,76 @@ require "./config" require "./server" require "./mangadex/*" require "option_parser" +require "clim" -VERSION = "0.4.0" +MANGO_VERSION = "0.4.0" -config_path = nil +macro common_option + option "-c PATH", "--config=PATH", type: String, + desc: "Path to the config file" +end -OptionParser.parse do |parser| - parser.banner = "Mango e-manga server/reader. Version #{VERSION}\n" +macro throw(msg) + puts "ERROR: #{{{msg}}}" + puts + puts "Please see the `--help`." + exit 1 +end - parser.on "-v", "--version", "Show version" do - puts "Version #{VERSION}" - exit - end - parser.on "-h", "--help", "Show help" do - puts parser - exit - end - parser.on "-c PATH", "--config=PATH", - "Path to the config file. " \ - "Default is `~/.config/mango/config.yml`" do |path| - config_path = path +class CLI < Clim + main do + desc "Mango - Manga Server and Web Reader. Version #{MANGO_VERSION}" + usage "mango [sub_command] [options]" + help short: "-h" + version "Version #{MANGO_VERSION}", short: "-v" + common_option + run do |opts| + Config.load(opts.config).set_current + MangaDex::Downloader.default + + server = Server.new + server.start + end + + sub "admin" do + desc "Run admin tools" + usage "mango admin [tool]" + help short: "-h" + run do |opts| + puts opts.help_string + end + sub "user" do + desc "User management tool" + usage "mango admin user [arguments] [options]" + help short: "-h" + argument "action", type: String, + desc: "Action to make. Can be add/delete/update", required: true + argument "username", type: String, + desc: "Username to update or delete" + option "-u USERNAME", "--username=USERNAME", type: String, + desc: "Username" + option "-p PASSWORD", "--password=PASSWORD", type: String, + desc: "Password" + option "--admin", desc: "Admin flag", type: Bool, default: false + common_option + run do |opts, args| + Config.load(opts.config).set_current + + case args.action + when "add" + throw "Options `-u` and `-p` required." if opts.username.nil? || + opts.password.nil? + when "delete" + throw "Argument `username` required." if args.username.nil? + when "update" + throw "Argument `username` required." if args.username.nil? + else + throw "Unknown action \"#{args.action}\"." + end + end + end + end end end -Config.load(config_path).set_current -MangaDex::Downloader.default - -server = Server.new -server.start +CLI.start(ARGV) From 8bbbe650f1ed334789445c5458183fa804b52950 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:25:15 +0000 Subject: [PATCH 2/5] Allow skipping initial user creation --- src/storage.cr | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/storage.cr b/src/storage.cr index e6c0ad9..82341a8 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -22,7 +22,7 @@ class Storage @@default.not_nil! end - def initialize(db_path : String? = nil) + def initialize(db_path : String? = nil, init_user = true) @path = db_path || Config.current.db_path dir = File.dirname @path unless Dir.exists? dir @@ -51,12 +51,15 @@ class Storage Logger.debug "Creating DB file at #{@path}" db.exec "create unique index username_idx on users (username)" db.exec "create unique index token_idx on users (token)" - random_pw = random_str - hash = hash_password random_pw - db.exec "insert into users values (?, ?, ?, ?)", - "admin", hash, nil, 1 - Logger.log "Initial user created. You can log in with " \ - "#{{"username" => "admin", "password" => random_pw}}" + + if init_user + random_pw = random_str + hash = hash_password random_pw + db.exec "insert into users values (?, ?, ?, ?)", + "admin", hash, nil, 1 + Logger.log "Initial user created. You can log in with " \ + "#{{"username" => "admin", "password" => random_pw}}" + end end end end From b724b4d5089d5486f42050af4d22eb5a48f8dbd2 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:26:20 +0000 Subject: [PATCH 3/5] Move username/password validation to `Storage` class --- src/routes/admin.cr | 31 ------------------------------- src/storage.cr | 6 +++++- src/util.cr | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/routes/admin.cr b/src/routes/admin.cr index 1fbd978..c2f8fa9 100644 --- a/src/routes/admin.cr +++ b/src/routes/admin.cr @@ -32,20 +32,6 @@ class AdminRouter < Router # would not contain `admin` admin = !env.params.body["admin"]?.nil? - if username.size < 3 - raise "Username should contain at least 3 characters" - end - if (username =~ /^[A-Za-z0-9_]+$/).nil? - raise "Username should contain alphanumeric characters " \ - "and underscores only" - end - if password.size < 6 - raise "Password should contain at least 6 characters" - end - if (password =~ /^[[:ascii:]]+$/).nil? - raise "password should contain ASCII characters only" - end - @context.storage.new_user username, password, admin redirect env, "/admin/user" @@ -65,23 +51,6 @@ class AdminRouter < Router 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 (username =~ /^[A-Za-z0-9_]+$/).nil? - raise "Username should contain alphanumeric characters " \ - "and underscores only" - end - - if password.size != 0 - if password.size < 6 - raise "Password should contain at least 6 characters" - end - if (password =~ /^[[:ascii:]]+$/).nil? - raise "password should contain ASCII characters only" - end - end - @context.storage.update_user \ original_username, username, password, admin diff --git a/src/storage.cr b/src/storage.cr index 82341a8..f577557 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -127,6 +127,8 @@ class Storage end def new_user(username, password, admin) + validate_username username + validate_password password admin = (admin ? 1 : 0) DB.open "sqlite3://#{@path}" do |db| hash = hash_password password @@ -137,8 +139,10 @@ class Storage def update_user(original_username, username, password, admin) admin = (admin ? 1 : 0) + validate_username username + validate_password password unless password.empty? DB.open "sqlite3://#{@path}" do |db| - if password.size == 0 + if password.empty? db.exec "update users set username = (?), admin = (?) " \ "where username = (?)", username, admin, original_username diff --git a/src/util.cr b/src/util.cr index cfdd2ea..767f79e 100644 --- a/src/util.cr +++ b/src/util.cr @@ -101,3 +101,22 @@ def redirect(env, path) base = Config.current.base_url env.redirect File.join base, path end + +def validate_username(username) + if username.size < 3 + raise "Username should contain at least 3 characters" + end + if (username =~ /^[A-Za-z0-9_]+$/).nil? + raise "Username should contain alphanumeric characters " \ + "and underscores only" + end +end + +def validate_password(password) + if password.size < 6 + raise "Password should contain at least 6 characters" + end + if (password =~ /^[[:ascii:]]+$/).nil? + raise "password should contain ASCII characters only" + end +end From d72d635c6804702681e6f6905a79a52b9ca80b9a Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 14:30:45 +0000 Subject: [PATCH 4/5] Add `admin/user` sub-command --- src/mango.cr | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/mango.cr b/src/mango.cr index 53a281c..7bd0829 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -45,26 +45,50 @@ class CLI < Clim usage "mango admin user [arguments] [options]" help short: "-h" argument "action", type: String, - desc: "Action to make. Can be add/delete/update", required: true + desc: "Action to perform. Can be add/delete/update/list" argument "username", type: String, desc: "Username to update or delete" option "-u USERNAME", "--username=USERNAME", type: String, desc: "Username" option "-p PASSWORD", "--password=PASSWORD", type: String, desc: "Password" - option "--admin", desc: "Admin flag", type: Bool, default: false + option "-a", "--admin", desc: "Admin flag", type: Bool, default: false common_option run do |opts, args| Config.load(opts.config).set_current + storage = Storage.new nil, false case args.action when "add" throw "Options `-u` and `-p` required." if opts.username.nil? || opts.password.nil? + storage.new_user opts.username.not_nil!, + opts.password.not_nil!, opts.admin when "delete" throw "Argument `username` required." if args.username.nil? + storage.delete_user args.username when "update" throw "Argument `username` required." if args.username.nil? + username = opts.username || args.username + password = opts.password || "" + storage.update_user args.username, username.not_nil!, + password.not_nil!, opts.admin + when "list" + users = storage.list_users + name_length = users.map(&.[0].size).max + l_cell_width = ["username".size, name_length].max + r_cell_width = "admin access".size + header = " #{"username".ljust l_cell_width} | admin access " + puts "-" * header.size + puts header + puts "-" * header.size + users.each do |name, admin| + puts " #{name.ljust l_cell_width} | " \ + "#{admin.to_s.ljust r_cell_width} " + end + puts "-" * header.size + when nil + puts opts.help_string else throw "Unknown action \"#{args.action}\"." end From 27dab3c989968e84ce7bf299f57c42c7a87f9597 Mon Sep 17 00:00:00 2001 From: Alex Ling Date: Sun, 31 May 2020 15:26:11 +0000 Subject: [PATCH 5/5] Disable initial user creation in spec --- spec/spec_helper.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index a8bc5ab..578ae42 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -45,7 +45,7 @@ end def with_storage with_default_config do temp_db = get_tempfile "mango-test-db" - storage = Storage.new temp_db.path + storage = Storage.new temp_db.path, false clear = yield storage, temp_db.path if clear == true temp_db.delete