diff --git a/shard.lock b/shard.lock index a58d6b6..baa3256 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/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 diff --git a/src/mango.cr b/src/mango.cr index 26149a7..7bd0829 100644 --- a/src/mango.cr +++ b/src/mango.cr @@ -2,31 +2,100 @@ 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 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 "-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 + end + end + end end end -Config.load(config_path).set_current -MangaDex::Downloader.default - -server = Server.new -server.start +CLI.start(ARGV) 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 e6c0ad9..f577557 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 @@ -124,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 @@ -134,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 0d6a993..36a7791 100644 --- a/src/util.cr +++ b/src/util.cr @@ -102,3 +102,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