Skip to content

Commit

Permalink
Update authentication method
Browse files Browse the repository at this point in the history
  • Loading branch information
dNitza committed Nov 18, 2023
1 parent 9d9ffc9 commit 3b3ad66
Show file tree
Hide file tree
Showing 24 changed files with 273 additions and 25 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Expand Up @@ -29,6 +29,8 @@ gem "gpx"
gem "gnuplot"
gem "matrix"

gem "rack-session"

gem "ruby-readability", require: "readability"
gem "down"
gem "httparty"
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Expand Up @@ -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)
Expand Down Expand Up @@ -515,6 +517,7 @@ DEPENDENCIES
pinboard!
puma
que
rack-session
rack-test
rake
redcarpet
Expand Down
7 changes: 7 additions & 0 deletions Rakefile
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions app/relations/login_tokens.rb
@@ -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 changes: 11 additions & 0 deletions app/relations/users.rb
@@ -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
8 changes: 8 additions & 0 deletions config.ru
Expand Up @@ -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
3 changes: 0 additions & 3 deletions config/routes.rb
@@ -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"

Expand Down
1 change: 1 addition & 0 deletions config/settings.rb
Expand Up @@ -44,6 +44,7 @@ class Settings < Hanami::Settings
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
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20231118054424_create_users.rb
@@ -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 changes: 12 additions & 0 deletions db/migrate/20231118054707_create_login_token.rb
@@ -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 changes: 17 additions & 0 deletions lib/adamantium/middleware/authenticate.rb
@@ -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
14 changes: 0 additions & 14 deletions lib/adamantium/middleware/process_params.rb

This file was deleted.

15 changes: 15 additions & 0 deletions slices/admin/actions/sessions/create.rb
@@ -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 changes: 13 additions & 0 deletions slices/admin/actions/sessions/new.rb
@@ -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 changes: 18 additions & 0 deletions slices/admin/actions/sessions/validate.rb
@@ -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 changes: 48 additions & 0 deletions slices/admin/commands/sessions/create.rb
@@ -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 changes: 17 additions & 0 deletions slices/admin/commands/sessions/validate.rb
@@ -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
12 changes: 4 additions & 8 deletions slices/admin/config/authenticated_admin_action.rb
@@ -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
4 changes: 4 additions & 0 deletions slices/admin/config/routes.rb
Expand Up @@ -11,6 +11,10 @@ class Routes < Hanami::Routes

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")
Expand Down
15 changes: 15 additions & 0 deletions slices/admin/repos/login_tokens_repo.rb
@@ -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 changes: 17 additions & 0 deletions slices/admin/repos/user_repo.rb
@@ -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 changes: 24 additions & 0 deletions slices/admin/templates/layouts/minimal.html.slim
@@ -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 changes: 7 additions & 0 deletions slices/admin/templates/sessions/new.html.slim
@@ -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 changes: 9 additions & 0 deletions slices/admin/views/sessions/new.rb
@@ -0,0 +1,9 @@
module Admin
module Views
module Sessions
class New < Admin::View
config.layout = "minimal"
end
end
end
end

0 comments on commit 3b3ad66

Please sign in to comment.