leanweb 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/leanweb +21 -16
- data/contrib/lib/hawese.rb +62 -0
- data/contrib/lib/lean_mail.rb +125 -0
- data/contrib/lib/leanweb/controller_mixins/render_with_layout.rb +170 -0
- data/contrib/lib/rack/session/json_file.rb +84 -0
- data/lib/leanweb/app.rb +18 -30
- data/lib/leanweb/controller.rb +56 -54
- data/lib/leanweb/helpers.rb +34 -0
- data/lib/leanweb/route.rb +90 -52
- data/lib/leanweb/version.rb +13 -0
- data/lib/leanweb.rb +13 -7
- metadata +118 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4e80c5813858b838b39fea56bb96b2fb28f7749d22f02ab04694662a8595d96
|
4
|
+
data.tar.gz: c2fdc8ebf92d647516db11352e5a6cb7bc36f1489dfabe69c095e7090f1b7970
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bed8458a93a2f0139609f17fe3402f69065182607b6b6664cc0c6d90ab3815f4dca7cd08c165dab17e5448d9c3f7d3c44ef8e1f28468cd3e4c42a66b096c8b8
|
7
|
+
data.tar.gz: 320c619aedd70c9bd0ed5e698ef456e293d3cf754bbe3ca939a7669b45ad117a371089db15737edf0c5c271ad638b84784e6ee3a730093a82a16843828a1074c
|
data/bin/leanweb
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
|
4
4
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
5
5
|
#
|
6
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
7
|
-
# General Public License version
|
8
|
-
# should have received a copy of this
|
9
|
-
# see <https://
|
6
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
7
|
+
# General Public License version 3 with permissions for compatibility with the
|
8
|
+
# Hacktivista General Public License. You should have received a copy of this
|
9
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
10
10
|
|
11
11
|
require 'fileutils'
|
12
12
|
require 'leanweb'
|
@@ -22,6 +22,9 @@ files = [
|
|
22
22
|
source 'https://rubygems.org'
|
23
23
|
|
24
24
|
gem 'leanweb', '~> #{LeanWeb::VERSION}'
|
25
|
+
gem 'haml', '~> 5.2.2'
|
26
|
+
gem 'rake'
|
27
|
+
gem 'puma'
|
25
28
|
RUBY
|
26
29
|
}, {
|
27
30
|
filename: 'config.ru',
|
@@ -29,21 +32,21 @@ files = [
|
|
29
32
|
# frozen_string_literal: true
|
30
33
|
|
31
34
|
require 'leanweb'
|
35
|
+
require_relative 'routes'
|
32
36
|
|
33
37
|
if ENV['RACK_ENV'] == 'development'
|
34
38
|
use(Rack::Reloader, 0)
|
35
|
-
use(Rack::Static, urls: [''], root: 'public', cascade: true)
|
39
|
+
use(Rack::Static, urls: ['/assets'], root: 'public', cascade: true)
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
|
-
run app
|
42
|
+
run LeanWeb::App.new(ROUTES)
|
40
43
|
RUBY
|
41
44
|
}, {
|
42
45
|
filename: 'routes.rb',
|
43
46
|
content: <<~RUBY
|
44
47
|
# frozen_string_literal: true
|
45
48
|
|
46
|
-
[{ path: '/' }]
|
49
|
+
ROUTES = [{ path: '/' }].freeze
|
47
50
|
RUBY
|
48
51
|
}, {
|
49
52
|
filename: 'Rakefile',
|
@@ -52,23 +55,24 @@ files = [
|
|
52
55
|
|
53
56
|
require 'leanweb'
|
54
57
|
|
55
|
-
task default: %w[
|
58
|
+
task default: %w[build_static]
|
56
59
|
|
57
|
-
task :
|
58
|
-
|
60
|
+
task :build_static do
|
61
|
+
require_relative 'routes'
|
62
|
+
LeanWeb::App.new(ROUTES).build_static
|
59
63
|
end
|
60
64
|
RUBY
|
61
65
|
}, {
|
62
|
-
filename: 'src/controllers/
|
66
|
+
filename: 'src/controllers/main_controller.rb',
|
63
67
|
content: <<~RUBY
|
64
68
|
# frozen_string_literal: true
|
65
69
|
|
66
70
|
require 'leanweb'
|
67
71
|
|
68
|
-
# Main controller is the default controller
|
72
|
+
# Main controller is the default controller.
|
69
73
|
class MainController < LeanWeb::Controller
|
70
74
|
def index_get
|
71
|
-
|
75
|
+
render_response 'index.haml'
|
72
76
|
end
|
73
77
|
end
|
74
78
|
RUBY
|
@@ -96,9 +100,10 @@ begin
|
|
96
100
|
path = ARGV[1] || '.'
|
97
101
|
FileUtils.mkdir_p(path)
|
98
102
|
FileUtils.cd(path)
|
99
|
-
FileUtils.mkdir_p(['public', 'src/controllers', 'src/views'])
|
100
|
-
files.each
|
103
|
+
FileUtils.mkdir_p(['public/assets', 'src/controllers', 'src/views'])
|
104
|
+
files.each{ |file| File.write(file[:filename], file[:content]) }
|
101
105
|
`git init`
|
106
|
+
`bundle install`
|
102
107
|
puts("Project '#{File.basename(Dir.getwd)}' successfully created.")
|
103
108
|
rescue # rubocop:disable Style/RescueStandardError
|
104
109
|
puts('Woops! Something went wrong.')
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'json'
|
11
|
+
require 'net/http'
|
12
|
+
require 'openssl'
|
13
|
+
require 'tilt'
|
14
|
+
require 'uri'
|
15
|
+
|
16
|
+
# Hawese client.
|
17
|
+
#
|
18
|
+
# **Environment variables**
|
19
|
+
#
|
20
|
+
# - HAWESE_RETURN_URL defaults to `LEANWEB_ENDPOINT/checkout`.
|
21
|
+
# - HAWESE_ENDPOINT
|
22
|
+
# - HAWESE_ORIGIN
|
23
|
+
class Hawese
|
24
|
+
class << self
|
25
|
+
ENDPOINT = ENV.fetch('HAWESE_ENDPOINT')
|
26
|
+
RETURN_URL = ENV.fetch('HAWESE_RETURN_URL') do
|
27
|
+
"#{ENV.fetch('LEANWEB_ENDPOINT')}/checkout"
|
28
|
+
end
|
29
|
+
ORIGIN = ENV.fetch('HAWESE_ORIGIN')
|
30
|
+
|
31
|
+
def payment_methods
|
32
|
+
uri = URI("#{ENDPOINT}/gateways/payment-methods/purchase")
|
33
|
+
JSON.parse(Net::HTTP.get(uri))
|
34
|
+
end
|
35
|
+
|
36
|
+
def gateway_schema(gateway, query_params, schema = 'purchase')
|
37
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/schemas/#{schema}")
|
38
|
+
uri.query = URI.encode_www_form(query_params)
|
39
|
+
JSON.parse(Net::HTTP.get(uri))
|
40
|
+
end
|
41
|
+
|
42
|
+
def purchase(gateway, fields)
|
43
|
+
fields[:origin] = ORIGIN
|
44
|
+
uri = URI("#{ENDPOINT}/gateways/#{gateway}/purchase")
|
45
|
+
uri.query = URI.encode_www_form(return_url: RETURN_URL)
|
46
|
+
response = Net::HTTP.post(
|
47
|
+
uri,
|
48
|
+
fields.to_json,
|
49
|
+
'Content-Type' => 'application/json'
|
50
|
+
)
|
51
|
+
JSON.parse(response.body)
|
52
|
+
end
|
53
|
+
|
54
|
+
def purchase_from_schema(gateway, schema)
|
55
|
+
fields = {}
|
56
|
+
schema['properties'].each do |property, contents|
|
57
|
+
fields[property] = contents['default'] if contents.include?('default')
|
58
|
+
end
|
59
|
+
purchase(gateway, fields)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'net/smtp'
|
11
|
+
require 'securerandom'
|
12
|
+
require 'socket'
|
13
|
+
require 'time'
|
14
|
+
|
15
|
+
# Send an email with SMTP easily.
|
16
|
+
#
|
17
|
+
# Environment variables:
|
18
|
+
#
|
19
|
+
# - SMTP_HOST: Where to connect to.
|
20
|
+
# - SMTP_PORT: Port, optional (default: 25).
|
21
|
+
# - SMTP_SECURITY: `tls` or `starttls`, optional (default: `nil`).
|
22
|
+
# - SMTP_USER: User, optional.
|
23
|
+
# - SMTP_PASSWORD: Password, optional.
|
24
|
+
# - SMTP_FROM: In the format `Name <user@mail>` or `user@mail`.
|
25
|
+
#
|
26
|
+
# @example Single address
|
27
|
+
# LeanMail.deliver('to@mail', 'subject', 'body')
|
28
|
+
#
|
29
|
+
# @example Multiple addresses
|
30
|
+
# LeanMail.deliver(['to@mail', 'to2@mail'], 'subject', 'body')
|
31
|
+
module LeanMail
|
32
|
+
# RFC 2821 message data representation.
|
33
|
+
class Data
|
34
|
+
attr_reader :from, :to, :subject, :message
|
35
|
+
|
36
|
+
def initialize(from, to, subject, message)
|
37
|
+
@from = from
|
38
|
+
@to = to
|
39
|
+
@subject = subject
|
40
|
+
@message = message
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
<<~MAIL
|
45
|
+
From: #{@from}
|
46
|
+
To: #{to.instance_of?(Array) ? to.join(', ') : to}
|
47
|
+
Subject: #{subject}
|
48
|
+
Date: #{Time.new.rfc2822}
|
49
|
+
Message-Id: <#{SecureRandom.uuid}@#{Socket.gethostname}>
|
50
|
+
|
51
|
+
#{@message}
|
52
|
+
MAIL
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Email deliverer.
|
57
|
+
class Deliver
|
58
|
+
attr_reader :data
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@user = ENV.fetch('SMTP_USER', nil)
|
62
|
+
@password = ENV.fetch('SMTP_PASSWORD', nil)
|
63
|
+
@from = ENV.fetch('SMTP_FROM')
|
64
|
+
|
65
|
+
@host = ENV.fetch('SMTP_HOST')
|
66
|
+
@port = ENV.fetch('SMTP_PORT', 25)
|
67
|
+
@security = ENV.fetch('SMTP_SECURITY', nil)
|
68
|
+
|
69
|
+
initialize_smtp
|
70
|
+
end
|
71
|
+
|
72
|
+
# Send email.
|
73
|
+
#
|
74
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
75
|
+
# @param subject [String]
|
76
|
+
# @param body [String]
|
77
|
+
# @return [Deliver]
|
78
|
+
def call(to, subject, body)
|
79
|
+
@data = Data.new(@from, to, subject, body)
|
80
|
+
|
81
|
+
@smtp.start(Socket.gethostname, @user, @password, :plain) do |smtp|
|
82
|
+
smtp.send_message(@data.to_s, extract_addr(@from), extract_addrs(to))
|
83
|
+
end
|
84
|
+
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def initialize_smtp
|
91
|
+
@smtp = Net::SMTP.new(@host, @port)
|
92
|
+
maybe_enable_security(@security)
|
93
|
+
end
|
94
|
+
|
95
|
+
def maybe_enable_security(security)
|
96
|
+
case security
|
97
|
+
when 'tls' then @smtp.enable_tls
|
98
|
+
when 'starttls' then @smtp.enable_starttls
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def extract_addr(str)
|
103
|
+
match = str.match(%r{<([^/]+)>})
|
104
|
+
return match[1] if match
|
105
|
+
|
106
|
+
str
|
107
|
+
end
|
108
|
+
|
109
|
+
def extract_addrs(to)
|
110
|
+
return to.map{ |addr| extract_addr(addr) } if to.instance_of?(Array)
|
111
|
+
|
112
|
+
extract_addr(to)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Deliver email.
|
117
|
+
#
|
118
|
+
# @param to [Array, String] In the format `Name <user@mail>` or `user@mail`.
|
119
|
+
# @param subject [String]
|
120
|
+
# @param body [String]
|
121
|
+
# @return [Deliver]
|
122
|
+
def self.deliver(to, subject, body)
|
123
|
+
LeanMail::Deliver.new.call(to, subject, body)
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
|
11
|
+
module LeanWeb
|
12
|
+
module ControllerMixins
|
13
|
+
# {render_with_layout} for {Controller} with ERB, HAML, Tilt::EmacsOrg and
|
14
|
+
# Redcarpet Markdown support.
|
15
|
+
module RenderWithLayout
|
16
|
+
# Render a response with a layout.
|
17
|
+
#
|
18
|
+
# Depending on the `path` file contents and options `@content_for` might
|
19
|
+
# be filled. These contents must be inserted on the `head` or `body` of
|
20
|
+
# your layout file.
|
21
|
+
#
|
22
|
+
# All paths used on parameters can be relative to {VIEW_PATH} or absolute.
|
23
|
+
#
|
24
|
+
# @param path [String] Path to file to rendered.
|
25
|
+
# @param title [String] `@content_for[:title]` content.
|
26
|
+
# @param head [Hash] Things to be inserted on `@content_for[:head]`.
|
27
|
+
# @option head [String|Array] :js Javascript file path(s).
|
28
|
+
# @option head [String|Array] :jsm Javascript module file path (s).
|
29
|
+
# @option head [String|Array] :css CSS file path(s).
|
30
|
+
# @option head [String|Array] :raw Raw headers in HTML format.
|
31
|
+
# @param layout [String] Layout file path.
|
32
|
+
# @param sub_layout [String] Layout file to be inserted within layout.
|
33
|
+
# @param options [Hash] Options hash, to be given to the template engine
|
34
|
+
# based on file extension: `Tilt::EmacsOrg` for `.org` and `Redcarpet`
|
35
|
+
# for `.md`.
|
36
|
+
# @option options [String] :setupfile For `.org`.
|
37
|
+
# @option options [true] :toc For `.md`. To fill `@content_for[:toc]`
|
38
|
+
# render with option `:with_toc_data`.
|
39
|
+
# @yield On dynamic templates (such as Haml and ERB) you can yield.
|
40
|
+
def render_with_layout( # rubocop:disable Metrics/ParameterLists
|
41
|
+
path,
|
42
|
+
title: nil,
|
43
|
+
head: nil,
|
44
|
+
layout: 'layout.haml',
|
45
|
+
sub_layout: nil,
|
46
|
+
options: {},
|
47
|
+
&block
|
48
|
+
)
|
49
|
+
@content_for[:title] = title unless title.nil?
|
50
|
+
content = render_by_extension(path, sub_layout, options, &block)
|
51
|
+
prepare_head(head) unless head.nil?
|
52
|
+
render_response(layout, 'text/html'){ content }
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# @param path [String]
|
58
|
+
# @param layout [String]
|
59
|
+
# @param options [Hash] Check `Tilt::EmacsOrg` options.
|
60
|
+
# @option options [String] :setupfile can be absolute or relative to
|
61
|
+
# {VIEW_PATH}.
|
62
|
+
def render_org(path, layout: nil, options: {})
|
63
|
+
options[:setupfile] = "#{VIEW_PATH}/#{options[:setupfile]}" \
|
64
|
+
if options.include?(:setupfile) && options[:setupfile][0] != '/'
|
65
|
+
|
66
|
+
org_template = create_template(path, options)
|
67
|
+
|
68
|
+
@content_for[:title] = org_template.title \
|
69
|
+
unless @content_for.include?(:title)
|
70
|
+
|
71
|
+
if layout
|
72
|
+
create_template(layout).render(self){ org_template.render(self) }
|
73
|
+
else
|
74
|
+
org_template.render(self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param path [String]
|
79
|
+
# @param layout [String]
|
80
|
+
# @param options [Hash] Check `RedCarpet` `render_options`.
|
81
|
+
# @option options [true] :toc To place an HTML based table of contents on
|
82
|
+
# `@content_for[:toc]` and render with option `:with_toc_data`.
|
83
|
+
def render_markdown(path, layout: nil, options: {})
|
84
|
+
maybe_render_markdown_toc!(path, options)
|
85
|
+
markdown_template = create_template(path, options)
|
86
|
+
if layout
|
87
|
+
create_template(layout).render(self){ markdown_template.render(self) }
|
88
|
+
else
|
89
|
+
markdown_template.render(self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# If `options[:toc]` is set, deletes options `:toc` and adds
|
94
|
+
# `:with_toc_data`. Also adds `@content_for[:toc]`.
|
95
|
+
def maybe_render_markdown_toc!(path, options)
|
96
|
+
return unless options.include?(:toc)
|
97
|
+
|
98
|
+
require('redcarpet')
|
99
|
+
@content_for[:toc] = create_template(
|
100
|
+
path, { renderer: Redcarpet::Render::HTML_TOC }
|
101
|
+
).render
|
102
|
+
|
103
|
+
options.delete(:toc)
|
104
|
+
options[:with_toc_data] = true
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param path [String]
|
108
|
+
# @param layout [String]
|
109
|
+
# @param options [Hash] Check `Haml` options.
|
110
|
+
# @yield If block given.
|
111
|
+
def render_other(path, layout: nil, options: {})
|
112
|
+
template = create_template(path, options)
|
113
|
+
if layout
|
114
|
+
create_template(layout).render(self) do
|
115
|
+
template.render(self){ yield if block_given? }
|
116
|
+
end
|
117
|
+
else
|
118
|
+
template.render(self){ yield if block_given? }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def render_by_extension(path, layout, options, &block)
|
123
|
+
case File.extname(path)
|
124
|
+
when '.org'
|
125
|
+
render_org(path, layout: layout, options: options)
|
126
|
+
when '.md'
|
127
|
+
render_markdown(path, layout: layout, options: options)
|
128
|
+
else
|
129
|
+
render_other(path, layout: layout, options: options, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def prepare_head(js: nil, jsm: nil, css: nil, raw: nil)
|
134
|
+
head = String.new
|
135
|
+
head << prepare_head_js(js) unless js.nil?
|
136
|
+
head << prepare_head_js(jsm, 'module') unless jsm.nil?
|
137
|
+
head << prepare_head_css(css) unless css.nil?
|
138
|
+
head << prepare_head_raw(raw) unless raw.nil?
|
139
|
+
@content_for[:head] = head
|
140
|
+
end
|
141
|
+
|
142
|
+
def prepare_head_js(js, type = 'application/javascript')
|
143
|
+
head = String.new
|
144
|
+
js = [js] if js.instance_of?(String)
|
145
|
+
js.each do |src|
|
146
|
+
head << "<script type='#{type}' src='#{src}'"
|
147
|
+
head << ' defer' if type != 'module'
|
148
|
+
head << "></script>\n"
|
149
|
+
end
|
150
|
+
head
|
151
|
+
end
|
152
|
+
|
153
|
+
def prepare_head_css(css)
|
154
|
+
head = String.new
|
155
|
+
css = [css] if css.instance_of?(String)
|
156
|
+
css.each do |src|
|
157
|
+
head << "<link rel='stylesheet' type='text/css' href='#{src}'>\n"
|
158
|
+
end
|
159
|
+
head
|
160
|
+
end
|
161
|
+
|
162
|
+
def prepare_head_raw(raw)
|
163
|
+
head = String.new
|
164
|
+
raw = [raw] if raw.instance_of?(String)
|
165
|
+
raw.each{ |src| head << "#{src}\n" }
|
166
|
+
head
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
|
+
#
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
|
+
|
10
|
+
require 'date'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'json'
|
13
|
+
require 'rack/session/abstract/id'
|
14
|
+
|
15
|
+
module Rack
|
16
|
+
module Session
|
17
|
+
# A JSON File based session storage.
|
18
|
+
class JsonFile < Abstract::PersistedSecure
|
19
|
+
# Cleans session files that have not been used.
|
20
|
+
# @param to [Time|Date] Time from which file has not been accessed.
|
21
|
+
# Defaults to 00:00 hrs 1 month ago.
|
22
|
+
# @param dir [String] Directory from which to clean files. Defaults to
|
23
|
+
# `/tmp/rack.session`.
|
24
|
+
def self.clean(
|
25
|
+
to = Date.today.prev_month,
|
26
|
+
dir = "#{Dir.tmpdir}/rack.session"
|
27
|
+
)
|
28
|
+
to = to.to_time if to.instance_of?(Date)
|
29
|
+
Dir["#{dir}/*"].each do |file|
|
30
|
+
FileUtils.rm(file, force: true) if ::File.atime(file) < to
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param options [Hash] Accepts a :session_dir path which defaults to
|
35
|
+
# `/tmp/rack.session`.
|
36
|
+
def initialize(app, options = {})
|
37
|
+
super(
|
38
|
+
app,
|
39
|
+
{
|
40
|
+
secure: ENV['RACK_ENV'] != 'development', # HTTPS unless in dev env
|
41
|
+
same_site: 'Strict' # don't allow cross-site sessions
|
42
|
+
}.merge!(options)
|
43
|
+
)
|
44
|
+
@session_dir = "#{Dir.tmpdir}/#{@key}" || @default_options[:session_dir]
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Array] [self, Hash].
|
48
|
+
def find_session(_req, sid)
|
49
|
+
sid = generate_sid if sid.nil?
|
50
|
+
|
51
|
+
[sid, JSON.parse(::File.read("#{@session_dir}/#{sid}"))]
|
52
|
+
rescue Errno::ENOENT
|
53
|
+
[generate_sid, {}]
|
54
|
+
rescue JSON::ParserError
|
55
|
+
[sid, {}]
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param sid [String] Only known ids are allowed, to avoid session
|
59
|
+
# fixation attacks.
|
60
|
+
# @return [self|false] session_id or false.
|
61
|
+
def write_session(_req, sid, session, _options)
|
62
|
+
return false unless ::File.exist?("#{@session_dir}/#{sid}")
|
63
|
+
|
64
|
+
::File.open("#{@session_dir}/#{sid}", 'w', 0o600) do |f|
|
65
|
+
f << JSON.generate(session)
|
66
|
+
end
|
67
|
+
sid
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [self|nil] new session id or nil if options[:drop].
|
71
|
+
def delete_session(_req, sid, options)
|
72
|
+
FileUtils.rm("#{@session_dir}/#{sid}", force: true)
|
73
|
+
options&.include?(:drop) ? nil : generate_sid
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_sid(*)
|
77
|
+
sid = super
|
78
|
+
FileUtils.mkdir_p(@session_dir, mode: 0o700)
|
79
|
+
::File.new("#{@session_dir}/#{sid}", 'w', 0o600).close
|
80
|
+
sid
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/leanweb/app.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
# Copyright 2022 Felix Freeman <libsys@hacktivista.org>
|
4
4
|
#
|
5
|
-
# This file is part of "LeanWeb" and licensed under the terms of the
|
6
|
-
# General Public License version
|
7
|
-
# should have received a copy of this
|
8
|
-
# see <https://
|
5
|
+
# This file is part of "LeanWeb" and licensed under the terms of the GNU Affero
|
6
|
+
# General Public License version 3 with permissions for compatibility with the
|
7
|
+
# Hacktivista General Public License. You should have received a copy of this
|
8
|
+
# license along with the software. If not, see <https://gnu.org/licenses/>.
|
9
9
|
|
10
10
|
require 'rack'
|
11
11
|
|
@@ -20,19 +20,15 @@ module LeanWeb
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Entry point for dynamic routes (Rack).
|
23
|
+
#
|
23
24
|
# @param env [Hash] `env` for Rack.
|
24
25
|
def call(env)
|
25
26
|
request = Rack::Request.new(env)
|
26
|
-
route =
|
27
|
-
|
28
|
-
|
29
|
-
rescue TypeError
|
30
|
-
r[:path] == request.path
|
31
|
-
end
|
32
|
-
end
|
33
|
-
return [404, {}, ['Not found']] unless route_exists?(route)
|
27
|
+
route = find_route(request)
|
28
|
+
route = Route.new(**route) unless route.nil?
|
29
|
+
return [404, {}, ['Not found']] unless route&.available?
|
34
30
|
|
35
|
-
|
31
|
+
route.respond(request)
|
36
32
|
end
|
37
33
|
|
38
34
|
# Build static routes to files.
|
@@ -42,31 +38,23 @@ module LeanWeb
|
|
42
38
|
next unless route.static
|
43
39
|
|
44
40
|
begin
|
45
|
-
route.static.each
|
41
|
+
route.static.each{ |seed| route.build(route.seed_path(seed)) }
|
46
42
|
rescue NoMethodError
|
47
43
|
route.build
|
48
44
|
end
|
49
45
|
end
|
50
46
|
end
|
51
47
|
|
52
|
-
# Initialize by evaluating the routes file.
|
53
|
-
# Do this here so users don't freak out for using eval and rubocop is happy
|
54
|
-
# on client side.
|
55
|
-
# @param file [String] Routes file.
|
56
|
-
def self.init(file = 'routes.rb')
|
57
|
-
new(eval(File.read(file))) # rubocop:disable Security/Eval
|
58
|
-
end
|
59
|
-
|
60
48
|
protected
|
61
49
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
50
|
+
def find_route(request)
|
51
|
+
@routes.find do |r|
|
52
|
+
(r[:method] || 'GET') == request.request_method && begin
|
53
|
+
r[:path] =~ request.path
|
54
|
+
rescue TypeError
|
55
|
+
r[:path] == request.path
|
56
|
+
end
|
57
|
+
end
|
70
58
|
end
|
71
59
|
end
|
72
60
|
end
|