Update authentication method
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -29,6 +29,8 @@ gem "gpx"
|
||||
gem "gnuplot"
|
||||
gem "matrix"
|
||||
|
||||
gem "rack-session"
|
||||
|
||||
gem "ruby-readability", require: "readability"
|
||||
gem "down"
|
||||
gem "httparty"
|
||||
|
@@ -330,6 +330,8 @@ GEM
|
||||
que (2.3.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8)
|
||||
rack-session (1.0.1)
|
||||
rack (< 3)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rainbow (3.1.1)
|
||||
@@ -515,6 +517,7 @@ DEPENDENCIES
|
||||
pinboard!
|
||||
puma
|
||||
que
|
||||
rack-session
|
||||
rack-test
|
||||
rake
|
||||
redcarpet
|
||||
|
7
Rakefile
7
Rakefile
@@ -51,6 +51,13 @@ namespace :blog do
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
task :create_user, [:email] => ["blog:load_environment"] do |t, args|
|
||||
require "hanami/prepare"
|
||||
|
||||
user_repo = Admin::Container["repos.user_repo"]
|
||||
user_repo.create(id: SecureRandom.uuid, email: args[:email])
|
||||
end
|
||||
end
|
||||
|
||||
namespace :tailwind do
|
||||
|
11
app/relations/login_tokens.rb
Normal file
11
app/relations/login_tokens.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Adamantium
|
||||
module Relations
|
||||
class LoginTokens < ROM::Relation[:sql]
|
||||
schema :login_tokens, infer: true
|
||||
|
||||
auto_struct(true)
|
||||
end
|
||||
end
|
||||
end
|
11
app/relations/users.rb
Normal file
11
app/relations/users.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Adamantium
|
||||
module Relations
|
||||
class Users < ROM::Relation[:sql]
|
||||
schema :users, infer: true
|
||||
|
||||
auto_struct(true)
|
||||
end
|
||||
end
|
||||
end
|
@@ -4,4 +4,12 @@ require "hanami/boot"
|
||||
|
||||
use Rack::Static, urls: ["/assets", "/media"], root: "public"
|
||||
|
||||
raise StandardError.new("No secret key") unless ENV["SESSION_SECRET"]
|
||||
|
||||
use Rack::Session::Cookie,
|
||||
:domain => URI.parse(ENV["MICROPUB_SITE_URL"]).host,
|
||||
:path => '/',
|
||||
:expire_after => 3600*24,
|
||||
:secret => ENV["SESSION_SECRET"]
|
||||
|
||||
run Hanami.app
|
||||
|
@@ -1,13 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "hanami/middleware/body_parser"
|
||||
# require_relative "../slices/admin/config/routes"
|
||||
# require_relative "authenticated_admin_action"
|
||||
|
||||
module Adamantium
|
||||
class Routes < Hanami::Routes
|
||||
use Hanami::Middleware::BodyParser, [:form, :json]
|
||||
# use Adamantium::Middleware::ProcessParams
|
||||
|
||||
slice :micropub, at: "/micropub"
|
||||
|
||||
|
@@ -44,6 +44,7 @@ module Adamantium
|
||||
setting :from_email, default: nil
|
||||
setting :dayone_email, default: nil
|
||||
|
||||
setting :smtp_server, default: nil
|
||||
setting :smtp_password, default: nil
|
||||
setting :smtp_username, default: nil
|
||||
# Micropub endpoints
|
||||
|
10
db/migrate/20231118054424_create_users.rb
Normal file
10
db/migrate/20231118054424_create_users.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ROM::SQL.migration do
|
||||
change do
|
||||
create_table :users do
|
||||
uuid :id, primary_key: true
|
||||
column :email, :text, null: false, unique: true
|
||||
end
|
||||
end
|
||||
end
|
12
db/migrate/20231118054707_create_login_token.rb
Normal file
12
db/migrate/20231118054707_create_login_token.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ROM::SQL.migration do
|
||||
change do
|
||||
create_table :login_tokens do
|
||||
primary_key :id
|
||||
column :user_id, :uuid, null: false
|
||||
column :token, :uuid, null: false
|
||||
column :created_at, :timestamptz, default: Sequel.lit("now()")
|
||||
end
|
||||
end
|
||||
end
|
17
lib/adamantium/middleware/authenticate.rb
Normal file
17
lib/adamantium/middleware/authenticate.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Adamantium
|
||||
module Middleware
|
||||
class Authenticate
|
||||
def initialize(app, auth_proc)
|
||||
@app = app
|
||||
@auth_proc = auth_proc
|
||||
end
|
||||
|
||||
def call(env)
|
||||
session = env["rack.session"]
|
||||
return [403, {'Content-Type' => 'text/html'}, ["Unauthorized | <a href=\"/admin/login\">Login</>"]] unless @auth_proc.call(session[:user_id])
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,14 +0,0 @@
|
||||
module Adamantium
|
||||
module Middleware
|
||||
class ProcessParams
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# NOOP for now.
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/admin/actions/sessions/create.rb
Normal file
15
slices/admin/actions/sessions/create.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Admin
|
||||
module Actions
|
||||
module Sessions
|
||||
class Create < Action
|
||||
include Deps["commands.sessions.create"]
|
||||
|
||||
def handle(req, res)
|
||||
create.(email: req.params[:email])
|
||||
|
||||
res.redirect_to "/admin"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/admin/actions/sessions/new.rb
Normal file
13
slices/admin/actions/sessions/new.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Admin
|
||||
module Actions
|
||||
module Sessions
|
||||
class New < Action
|
||||
include Deps["views.sessions.new"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
slices/admin/actions/sessions/validate.rb
Normal file
18
slices/admin/actions/sessions/validate.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Admin
|
||||
module Actions
|
||||
module Sessions
|
||||
class Validate < Action
|
||||
include Deps["commands.sessions.validate"]
|
||||
|
||||
def handle(req, res)
|
||||
user_id = validate.(token: req.params[:token])
|
||||
session = req.env["rack.session"]
|
||||
|
||||
session[:user_id] = user_id
|
||||
|
||||
res.redirect_to "/admin"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
slices/admin/commands/sessions/create.rb
Normal file
48
slices/admin/commands/sessions/create.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require "mail"
|
||||
|
||||
module Admin
|
||||
module Commands
|
||||
module Sessions
|
||||
class Create
|
||||
include Deps[
|
||||
"repos.login_tokens_repo",
|
||||
"repos.user_repo"
|
||||
]
|
||||
|
||||
def call(email:)
|
||||
app_settings = Admin::Container["settings"]
|
||||
user = user_repo.by_email(email: email)
|
||||
|
||||
return unless user
|
||||
|
||||
login_tokens_repo.delete_all
|
||||
|
||||
token = login_tokens_repo.create(user_id: user.id, token: SecureRandom.uuid)
|
||||
|
||||
Mail.defaults do
|
||||
delivery_method :smtp, {
|
||||
address: app_settings.smtp_server,
|
||||
port: 587,
|
||||
authentication: "plain",
|
||||
openssl_verify_mode: "peer",
|
||||
enable_starttls_auto: true
|
||||
}
|
||||
end
|
||||
|
||||
Mail.delivery_method.settings[:user_name] = app_settings.smtp_username
|
||||
Mail.delivery_method.settings[:password] = app_settings.smtp_password
|
||||
|
||||
mail = Mail.new do
|
||||
subject "Login to #{app_settings.site_name}"
|
||||
body "#{app_settings.micropub_site_url}/admin/login/#{token.token}"
|
||||
end
|
||||
|
||||
mail[:to] = email
|
||||
mail[:from] = app_settings.from_email
|
||||
|
||||
mail.deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
slices/admin/commands/sessions/validate.rb
Normal file
17
slices/admin/commands/sessions/validate.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Admin
|
||||
module Commands
|
||||
module Sessions
|
||||
class Validate
|
||||
include Deps["repos.login_tokens_repo"]
|
||||
|
||||
def call(token:)
|
||||
user_id = login_tokens_repo.by_token(token: token).user_id
|
||||
if user_id
|
||||
login_tokens_repo.delete_all
|
||||
user_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,16 +1,12 @@
|
||||
require "adamantium/middleware/authenticate"
|
||||
|
||||
module Adamantium
|
||||
class AuthenticatedAdminAction
|
||||
def self.call(action:)
|
||||
auth_proc = -> (id) { Admin::Container["repos.user_repo"].exists(id) }
|
||||
action_proc = ->(env) { Admin::Container["actions.#{action}"].call(env) }
|
||||
|
||||
if Hanami.app.settings.basic_auth_username && Hanami.app.settings.basic_auth_password
|
||||
Rack::Auth::Basic.new(action_proc) do |username, password|
|
||||
username == Hanami.app.settings.basic_auth_username &&
|
||||
password == Hanami.app.settings.basic_auth_password
|
||||
end
|
||||
else
|
||||
Rack::Auth::Basic.new(action_proc) { |_username, _password| true }
|
||||
end
|
||||
Adamantium::Middleware::Authenticate.new(action_proc, auth_proc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@@ -11,6 +11,10 @@ module Admin
|
||||
|
||||
get "/", to: Auth.call(action: "index")
|
||||
|
||||
get "/login", to: "sessions.new"
|
||||
get "/login/:token", to: "sessions.validate"
|
||||
post "/sessions/create", to: "sessions.create"
|
||||
|
||||
get "/pages", to: Auth.call(action: "pages.index")
|
||||
get "/pages/new", to: Auth.call(action: "pages.new")
|
||||
get "/pages/:slug/edit", to: Auth.call(action: "pages.edit")
|
||||
|
15
slices/admin/repos/login_tokens_repo.rb
Normal file
15
slices/admin/repos/login_tokens_repo.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Admin
|
||||
module Repos
|
||||
class LoginTokensRepo < Adamantium::Repo[:login_tokens]
|
||||
commands :create
|
||||
|
||||
def by_token(token:)
|
||||
login_tokens.where(token: token).one
|
||||
end
|
||||
|
||||
def delete_all
|
||||
login_tokens.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
slices/admin/repos/user_repo.rb
Normal file
17
slices/admin/repos/user_repo.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Admin
|
||||
module Repos
|
||||
class UserRepo < Adamantium::Repo[:users]
|
||||
commands :create
|
||||
|
||||
def exists(id)
|
||||
!!users
|
||||
.where(id: id)
|
||||
.one
|
||||
end
|
||||
|
||||
def by_email(email:)
|
||||
users.where(email: email).one
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
24
slices/admin/templates/layouts/minimal.html.slim
Normal file
24
slices/admin/templates/layouts/minimal.html.slim
Normal file
@@ -0,0 +1,24 @@
|
||||
html
|
||||
head
|
||||
meta charest="utf-8"
|
||||
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0"
|
||||
|
||||
meta name="theme-color" content="rgb(37, 99, 235)"
|
||||
|
||||
title Admin // Daniel Nitsikopoulos
|
||||
|
||||
= stylesheet_tag "app"
|
||||
link rel="icon" type="image/x-icon" href="/assets/favicon.ico"
|
||||
|
||||
body class="bg-white dark:bg-black selection:bg-blue-100 selection:text-blue-900 dark:selection:bg-blue-600 dark:selection:text-blue-100"
|
||||
main class="pb-8 px-4 pt-4 md:pt-8"
|
||||
header class="mb-12 max-w-screen-md mx-auto"
|
||||
div class="flex items-center mb-8 md:mb-12 text-lg md:text-xl text-gray-400 dark:text-gray-600"
|
||||
div class="flex-none mx-auto md:flex-auto md:mx-0"
|
||||
div class="h-card flex items-center"
|
||||
img class="u-photo w-6 h6 md:w-10 md:h-10 rounded-full mr-1.5" src="/assets/memoji.png"
|
||||
a href="/" rel="me" class="u-url u-uid"
|
||||
h1 class="p-name uppercase text-sm md:text-sm text-gray-400 dark:text-gray-400" = Hanami.app.settings.site_name
|
||||
|
||||
== yield
|
7
slices/admin/templates/sessions/new.html.slim
Normal file
7
slices/admin/templates/sessions/new.html.slim
Normal file
@@ -0,0 +1,7 @@
|
||||
div class="max-w-prose mx-auto prose dark:prose-invert"
|
||||
form action="/admin/sessions/create" method="POST"
|
||||
div class="mb-2"
|
||||
label for="email" class="mr-2" Email
|
||||
input type="email" id="email" name="email" class="border"
|
||||
div class="mb-2"
|
||||
button Login
|
9
slices/admin/views/sessions/new.rb
Normal file
9
slices/admin/views/sessions/new.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Admin
|
||||
module Views
|
||||
module Sessions
|
||||
class New < Admin::View
|
||||
config.layout = "minimal"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user