rhales 0.4.0 → 0.5.3
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/.github/renovate.json5 +52 -0
- data/.github/workflows/ci.yml +123 -0
- data/.github/workflows/claude-code-review.yml +69 -0
- data/.github/workflows/claude.yml +49 -0
- data/.github/workflows/code-smells.yml +146 -0
- data/.github/workflows/ruby-lint.yml +78 -0
- data/.github/workflows/yardoc.yml +126 -0
- data/.gitignore +55 -0
- data/.pr_agent.toml +63 -0
- data/.pre-commit-config.yaml +89 -0
- data/.prettierignore +8 -0
- data/.prettierrc +38 -0
- data/.reek.yml +98 -0
- data/.rubocop.yml +428 -0
- data/.serena/.gitignore +3 -0
- data/.yardopts +56 -0
- data/CHANGELOG.md +44 -0
- data/CLAUDE.md +1 -1
- data/Gemfile +29 -0
- data/Gemfile.lock +189 -0
- data/README.md +686 -868
- data/Rakefile +46 -0
- data/debug_context.rb +25 -0
- data/demo/rhales-roda-demo/.gitignore +7 -0
- data/demo/rhales-roda-demo/Gemfile +32 -0
- data/demo/rhales-roda-demo/Gemfile.lock +151 -0
- data/demo/rhales-roda-demo/MAIL.md +405 -0
- data/demo/rhales-roda-demo/README.md +376 -0
- data/demo/rhales-roda-demo/RODA-TEMPLATE-ENGINES.md +192 -0
- data/demo/rhales-roda-demo/Rakefile +49 -0
- data/demo/rhales-roda-demo/app.rb +325 -0
- data/demo/rhales-roda-demo/bin/rackup +26 -0
- data/demo/rhales-roda-demo/config.ru +13 -0
- data/demo/rhales-roda-demo/db/migrate/001_create_rodauth_tables.rb +266 -0
- data/demo/rhales-roda-demo/db/migrate/002_create_rodauth_password_tables.rb +79 -0
- data/demo/rhales-roda-demo/db/migrate/003_add_admin_account.rb +68 -0
- data/demo/rhales-roda-demo/templates/change_login.rue +31 -0
- data/demo/rhales-roda-demo/templates/change_password.rue +36 -0
- data/demo/rhales-roda-demo/templates/close_account.rue +31 -0
- data/demo/rhales-roda-demo/templates/create_account.rue +40 -0
- data/demo/rhales-roda-demo/templates/dashboard.rue +150 -0
- data/demo/rhales-roda-demo/templates/home.rue +78 -0
- data/demo/rhales-roda-demo/templates/layouts/main.rue +168 -0
- data/demo/rhales-roda-demo/templates/login.rue +65 -0
- data/demo/rhales-roda-demo/templates/logout.rue +25 -0
- data/demo/rhales-roda-demo/templates/reset_password.rue +26 -0
- data/demo/rhales-roda-demo/templates/verify_account.rue +27 -0
- data/demo/rhales-roda-demo/test_full_output.rb +27 -0
- data/demo/rhales-roda-demo/test_simple.rb +24 -0
- data/docs/.gitignore +9 -0
- data/docs/architecture/data-flow.md +499 -0
- data/examples/dashboard-with-charts.rue +271 -0
- data/examples/form-with-validation.rue +180 -0
- data/examples/simple-page.rue +61 -0
- data/examples/vue.rue +136 -0
- data/generate-json-schemas.ts +158 -0
- data/json_schemer_migration_summary.md +172 -0
- data/lib/rhales/adapters/base_auth.rb +2 -0
- data/lib/rhales/adapters/base_request.rb +2 -0
- data/lib/rhales/adapters/base_session.rb +2 -0
- data/lib/rhales/adapters.rb +7 -0
- data/lib/rhales/configuration.rb +47 -0
- data/lib/rhales/core/context.rb +354 -0
- data/lib/rhales/{rue_document.rb → core/rue_document.rb} +56 -38
- data/lib/rhales/{template_engine.rb → core/template_engine.rb} +66 -59
- data/lib/rhales/{view.rb → core/view.rb} +112 -135
- data/lib/rhales/{view_composition.rb → core/view_composition.rb} +78 -8
- data/lib/rhales/core.rb +9 -0
- data/lib/rhales/errors/hydration_collision_error.rb +2 -0
- data/lib/rhales/errors.rb +2 -0
- data/lib/rhales/{earliest_injection_detector.rb → hydration/earliest_injection_detector.rb} +4 -0
- data/lib/rhales/hydration/hydration_data_aggregator.rb +487 -0
- data/lib/rhales/{hydration_endpoint.rb → hydration/hydration_endpoint.rb} +16 -12
- data/lib/rhales/{hydration_injector.rb → hydration/hydration_injector.rb} +4 -0
- data/lib/rhales/{hydration_registry.rb → hydration/hydration_registry.rb} +2 -0
- data/lib/rhales/hydration/hydrator.rb +102 -0
- data/lib/rhales/{link_based_injection_detector.rb → hydration/link_based_injection_detector.rb} +4 -0
- data/lib/rhales/{mount_point_detector.rb → hydration/mount_point_detector.rb} +4 -0
- data/lib/rhales/{safe_injection_validator.rb → hydration/safe_injection_validator.rb} +4 -0
- data/lib/rhales/hydration.rb +13 -0
- data/lib/rhales/{refinements → integrations/refinements}/require_refinements.rb +3 -1
- data/lib/rhales/{tilt.rb → integrations/tilt.rb} +22 -15
- data/lib/rhales/integrations.rb +6 -0
- data/lib/rhales/middleware/json_responder.rb +191 -0
- data/lib/rhales/middleware/schema_validator.rb +300 -0
- data/lib/rhales/middleware.rb +6 -0
- data/lib/rhales/parsers/handlebars_parser.rb +2 -0
- data/lib/rhales/parsers/rue_format_parser.rb +9 -7
- data/lib/rhales/parsers.rb +9 -0
- data/lib/rhales/{csp.rb → security/csp.rb} +27 -3
- data/lib/rhales/utils/json_serializer.rb +114 -0
- data/lib/rhales/utils/logging_helpers.rb +75 -0
- data/lib/rhales/utils/schema_extractor.rb +132 -0
- data/lib/rhales/utils/schema_generator.rb +194 -0
- data/lib/rhales/utils.rb +40 -0
- data/lib/rhales/version.rb +3 -1
- data/lib/rhales.rb +41 -24
- data/lib/tasks/rhales_schema.rake +197 -0
- data/package.json +10 -0
- data/pnpm-lock.yaml +345 -0
- data/pnpm-workspace.yaml +2 -0
- data/proofs/error_handling.rb +79 -0
- data/proofs/expanded_object_inheritance.rb +82 -0
- data/proofs/partial_context_scoping_fix.rb +168 -0
- data/proofs/ui_context_partial_inheritance.rb +236 -0
- data/rhales.gemspec +14 -6
- data/schema_vs_data_comparison.md +254 -0
- data/test_direct_access.rb +36 -0
- metadata +141 -23
- data/CLAUDE.locale.txt +0 -7
- data/lib/rhales/context.rb +0 -239
- data/lib/rhales/hydration_data_aggregator.rb +0 -221
- data/lib/rhales/hydrator.rb +0 -141
- data/lib/rhales/parsers/handlebars-grammar-review.txt +0 -39
data/Rakefile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Rakefile
|
|
2
|
+
|
|
3
|
+
$:.unshift(File.expand_path('lib', __dir__))
|
|
4
|
+
|
|
5
|
+
require 'bundler/gem_tasks'
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
7
|
+
|
|
8
|
+
# Load task files
|
|
9
|
+
Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
|
|
10
|
+
|
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
12
|
+
|
|
13
|
+
task default: :spec
|
|
14
|
+
|
|
15
|
+
# Rhales specific tasks
|
|
16
|
+
namespace :rhales do
|
|
17
|
+
desc 'Run Rhales tests only'
|
|
18
|
+
task :test do
|
|
19
|
+
system('bundle exec rspec spec/rhales/')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'Generate Rhales documentation'
|
|
23
|
+
task :docs do
|
|
24
|
+
system('bundle exec yard doc lib/rhales/')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'Validate Rhales templates in examples'
|
|
28
|
+
task :validate do
|
|
29
|
+
require 'rhales'
|
|
30
|
+
|
|
31
|
+
examples_dir = File.join(__dir__, 'examples', 'templates')
|
|
32
|
+
if Dir.exist?(examples_dir)
|
|
33
|
+
Dir.glob(File.join(examples_dir, '**', '*.rue')).each do |file|
|
|
34
|
+
puts "Validating #{file}..."
|
|
35
|
+
begin
|
|
36
|
+
Rhales::RueDocument.parse_file(file)
|
|
37
|
+
puts ' ✓ Valid'
|
|
38
|
+
rescue StandardError => ex
|
|
39
|
+
puts " ✗ Error: #{ex.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
puts "No examples directory found at #{examples_dir}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/debug_context.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/rhales'
|
|
4
|
+
|
|
5
|
+
# Test props with both symbols and strings
|
|
6
|
+
props = {
|
|
7
|
+
'greeting' => 'Hello World',
|
|
8
|
+
'user' => { 'name' => 'John Doe', 'role' => 'admin' },
|
|
9
|
+
'authenticated' => true,
|
|
10
|
+
'items' => [
|
|
11
|
+
{ 'name' => 'Item 1', 'active' => true },
|
|
12
|
+
{ 'name' => 'Item 2', 'active' => false }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
context = Rhales::Context.minimal(props: props)
|
|
17
|
+
|
|
18
|
+
puts "=== Context Variables ==="
|
|
19
|
+
puts "greeting: #{context.get('greeting')}"
|
|
20
|
+
puts "user.name: #{context.get('user.name')}"
|
|
21
|
+
puts "items.0.name: #{context.get('items.0.name')}"
|
|
22
|
+
puts "items.0.active: #{context.get('items.0.active')}"
|
|
23
|
+
puts "items.1.name: #{context.get('items.1.name')}"
|
|
24
|
+
puts "items.1.active: #{context.get('items.1.active')}"
|
|
25
|
+
puts "========================="
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
source 'https://rubygems.org'
|
|
2
|
+
|
|
3
|
+
# Web framework
|
|
4
|
+
gem 'puma', '~> 6.4'
|
|
5
|
+
gem 'rack-session', '~> 2.0'
|
|
6
|
+
gem 'rackup'
|
|
7
|
+
gem 'roda', '~> 3.84'
|
|
8
|
+
|
|
9
|
+
# Authentication
|
|
10
|
+
gem 'bcrypt', '~> 3.1'
|
|
11
|
+
gem 'rodauth', '~> 2.36'
|
|
12
|
+
gem 'rotp'
|
|
13
|
+
gem 'rqrcode'
|
|
14
|
+
|
|
15
|
+
# Database
|
|
16
|
+
gem 'sequel', '~> 5.85'
|
|
17
|
+
gem 'sqlite3', '~> 2.0'
|
|
18
|
+
|
|
19
|
+
# Templates and utilities
|
|
20
|
+
gem 'logger'
|
|
21
|
+
gem 'mail'
|
|
22
|
+
gem 'rake'
|
|
23
|
+
gem 'rhales', path: '../..' # Using local gem
|
|
24
|
+
gem 'rspec'
|
|
25
|
+
gem 'tilt'
|
|
26
|
+
|
|
27
|
+
# Development
|
|
28
|
+
group :development do
|
|
29
|
+
gem 'pry'
|
|
30
|
+
gem 'pry-byebug'
|
|
31
|
+
gem 'rerun'
|
|
32
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ../..
|
|
3
|
+
specs:
|
|
4
|
+
rhales (0.5.1)
|
|
5
|
+
json_schemer (~> 2.3)
|
|
6
|
+
logger
|
|
7
|
+
tilt (~> 2)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
base64 (0.3.0)
|
|
13
|
+
bcrypt (3.1.20)
|
|
14
|
+
bigdecimal (3.2.2)
|
|
15
|
+
byebug (12.0.0)
|
|
16
|
+
chunky_png (1.4.0)
|
|
17
|
+
coderay (1.1.3)
|
|
18
|
+
date (3.4.1)
|
|
19
|
+
diff-lcs (1.6.2)
|
|
20
|
+
ffi (1.17.2-aarch64-linux-gnu)
|
|
21
|
+
ffi (1.17.2-aarch64-linux-musl)
|
|
22
|
+
ffi (1.17.2-arm-linux-gnu)
|
|
23
|
+
ffi (1.17.2-arm-linux-musl)
|
|
24
|
+
ffi (1.17.2-arm64-darwin)
|
|
25
|
+
ffi (1.17.2-x86-linux-gnu)
|
|
26
|
+
ffi (1.17.2-x86-linux-musl)
|
|
27
|
+
ffi (1.17.2-x86_64-darwin)
|
|
28
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
|
29
|
+
ffi (1.17.2-x86_64-linux-musl)
|
|
30
|
+
hana (1.3.7)
|
|
31
|
+
json_schemer (2.4.0)
|
|
32
|
+
bigdecimal
|
|
33
|
+
hana (~> 1.3)
|
|
34
|
+
regexp_parser (~> 2.0)
|
|
35
|
+
simpleidn (~> 0.2)
|
|
36
|
+
listen (3.9.0)
|
|
37
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
38
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
|
39
|
+
logger (1.7.0)
|
|
40
|
+
mail (2.8.1)
|
|
41
|
+
mini_mime (>= 0.1.1)
|
|
42
|
+
net-imap
|
|
43
|
+
net-pop
|
|
44
|
+
net-smtp
|
|
45
|
+
method_source (1.1.0)
|
|
46
|
+
mini_mime (1.1.5)
|
|
47
|
+
net-imap (0.5.9)
|
|
48
|
+
date
|
|
49
|
+
net-protocol
|
|
50
|
+
net-pop (0.1.2)
|
|
51
|
+
net-protocol
|
|
52
|
+
net-protocol (0.2.2)
|
|
53
|
+
timeout
|
|
54
|
+
net-smtp (0.5.1)
|
|
55
|
+
net-protocol
|
|
56
|
+
nio4r (2.7.4)
|
|
57
|
+
pry (0.15.2)
|
|
58
|
+
coderay (~> 1.1)
|
|
59
|
+
method_source (~> 1.0)
|
|
60
|
+
pry-byebug (3.11.0)
|
|
61
|
+
byebug (~> 12.0)
|
|
62
|
+
pry (>= 0.13, < 0.16)
|
|
63
|
+
puma (6.6.0)
|
|
64
|
+
nio4r (~> 2.0)
|
|
65
|
+
rack (3.1.16)
|
|
66
|
+
rack-session (2.1.1)
|
|
67
|
+
base64 (>= 0.1.0)
|
|
68
|
+
rack (>= 3.0.0)
|
|
69
|
+
rackup (2.2.1)
|
|
70
|
+
rack (>= 3)
|
|
71
|
+
rake (13.3.0)
|
|
72
|
+
rb-fsevent (0.11.2)
|
|
73
|
+
rb-inotify (0.11.1)
|
|
74
|
+
ffi (~> 1.0)
|
|
75
|
+
regexp_parser (2.11.3)
|
|
76
|
+
rerun (0.14.0)
|
|
77
|
+
listen (~> 3.0)
|
|
78
|
+
roda (3.93.0)
|
|
79
|
+
rack
|
|
80
|
+
rodauth (2.39.0)
|
|
81
|
+
roda (>= 2.6.0)
|
|
82
|
+
sequel (>= 4)
|
|
83
|
+
rotp (6.3.0)
|
|
84
|
+
rqrcode (3.1.0)
|
|
85
|
+
chunky_png (~> 1.0)
|
|
86
|
+
rqrcode_core (~> 2.0)
|
|
87
|
+
rqrcode_core (2.0.0)
|
|
88
|
+
rspec (3.13.1)
|
|
89
|
+
rspec-core (~> 3.13.0)
|
|
90
|
+
rspec-expectations (~> 3.13.0)
|
|
91
|
+
rspec-mocks (~> 3.13.0)
|
|
92
|
+
rspec-core (3.13.5)
|
|
93
|
+
rspec-support (~> 3.13.0)
|
|
94
|
+
rspec-expectations (3.13.5)
|
|
95
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
96
|
+
rspec-support (~> 3.13.0)
|
|
97
|
+
rspec-mocks (3.13.5)
|
|
98
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
99
|
+
rspec-support (~> 3.13.0)
|
|
100
|
+
rspec-support (3.13.4)
|
|
101
|
+
sequel (5.94.0)
|
|
102
|
+
bigdecimal
|
|
103
|
+
simpleidn (0.2.3)
|
|
104
|
+
sqlite3 (2.7.1-aarch64-linux-gnu)
|
|
105
|
+
sqlite3 (2.7.1-aarch64-linux-musl)
|
|
106
|
+
sqlite3 (2.7.1-arm-linux-gnu)
|
|
107
|
+
sqlite3 (2.7.1-arm-linux-musl)
|
|
108
|
+
sqlite3 (2.7.1-arm64-darwin)
|
|
109
|
+
sqlite3 (2.7.1-x86-linux-gnu)
|
|
110
|
+
sqlite3 (2.7.1-x86-linux-musl)
|
|
111
|
+
sqlite3 (2.7.1-x86_64-darwin)
|
|
112
|
+
sqlite3 (2.7.1-x86_64-linux-gnu)
|
|
113
|
+
sqlite3 (2.7.1-x86_64-linux-musl)
|
|
114
|
+
tilt (2.3.0)
|
|
115
|
+
timeout (0.4.3)
|
|
116
|
+
|
|
117
|
+
PLATFORMS
|
|
118
|
+
aarch64-linux-gnu
|
|
119
|
+
aarch64-linux-musl
|
|
120
|
+
arm-linux-gnu
|
|
121
|
+
arm-linux-musl
|
|
122
|
+
arm64-darwin
|
|
123
|
+
x86-linux-gnu
|
|
124
|
+
x86-linux-musl
|
|
125
|
+
x86_64-darwin
|
|
126
|
+
x86_64-linux-gnu
|
|
127
|
+
x86_64-linux-musl
|
|
128
|
+
|
|
129
|
+
DEPENDENCIES
|
|
130
|
+
bcrypt (~> 3.1)
|
|
131
|
+
logger
|
|
132
|
+
mail
|
|
133
|
+
pry
|
|
134
|
+
pry-byebug
|
|
135
|
+
puma (~> 6.4)
|
|
136
|
+
rack-session (~> 2.0)
|
|
137
|
+
rackup
|
|
138
|
+
rake
|
|
139
|
+
rerun
|
|
140
|
+
rhales!
|
|
141
|
+
roda (~> 3.84)
|
|
142
|
+
rodauth (~> 2.36)
|
|
143
|
+
rotp
|
|
144
|
+
rqrcode
|
|
145
|
+
rspec
|
|
146
|
+
sequel (~> 5.85)
|
|
147
|
+
sqlite3 (~> 2.0)
|
|
148
|
+
tilt
|
|
149
|
+
|
|
150
|
+
BUNDLED WITH
|
|
151
|
+
2.6.6
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# Mail Configuration in Rodauth
|
|
2
|
+
|
|
3
|
+
This document explains how to configure SMTP and mail settings in Rodauth for sending authentication emails.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Rodauth uses the **Mail gem** for email functionality. Email configuration happens at two levels:
|
|
8
|
+
|
|
9
|
+
1. **Global Mail Configuration** - SMTP settings and delivery method
|
|
10
|
+
2. **Rodauth Email Configuration** - Email addresses, subjects, and custom logic
|
|
11
|
+
|
|
12
|
+
## Global Mail Configuration (SMTP Settings)
|
|
13
|
+
|
|
14
|
+
Configure SMTP **before** loading Rodauth:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
require 'mail'
|
|
18
|
+
|
|
19
|
+
# SMTP configuration
|
|
20
|
+
Mail.defaults do
|
|
21
|
+
delivery_method :smtp, {
|
|
22
|
+
address: 'smtp.gmail.com',
|
|
23
|
+
port: 587,
|
|
24
|
+
domain: 'yoursite.com',
|
|
25
|
+
user_name: 'your-email@gmail.com',
|
|
26
|
+
password: 'your-app-password',
|
|
27
|
+
authentication: 'plain',
|
|
28
|
+
enable_starttls_auto: true
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Then configure Rodauth
|
|
33
|
+
plugin :rodauth do
|
|
34
|
+
enable :login, :reset_password, :verify_account
|
|
35
|
+
# ... other config
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Rodauth Email Configuration
|
|
40
|
+
|
|
41
|
+
Within your Rodauth configuration block:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
plugin :rodauth do
|
|
45
|
+
enable :reset_password, :verify_account
|
|
46
|
+
|
|
47
|
+
# Basic email settings
|
|
48
|
+
email_from 'noreply@yoursite.com'
|
|
49
|
+
email_subject_prefix '[YourSite] '
|
|
50
|
+
|
|
51
|
+
# Customize recipient (defaults to account email)
|
|
52
|
+
email_to do
|
|
53
|
+
account[:email] # or custom logic
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Override email sending for custom delivery
|
|
57
|
+
send_email do |email|
|
|
58
|
+
# Custom delivery logic
|
|
59
|
+
email.deliver!
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Common SMTP Providers
|
|
65
|
+
|
|
66
|
+
### Gmail
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
Mail.defaults do
|
|
70
|
+
delivery_method :smtp, {
|
|
71
|
+
address: 'smtp.gmail.com',
|
|
72
|
+
port: 587,
|
|
73
|
+
user_name: 'your-email@gmail.com',
|
|
74
|
+
password: 'your-app-password', # Use app password, not account password
|
|
75
|
+
authentication: 'plain',
|
|
76
|
+
enable_starttls_auto: true
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Note**: For Gmail, you need to use an App Password, not your regular Gmail password.
|
|
82
|
+
|
|
83
|
+
### SendGrid
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
Mail.defaults do
|
|
87
|
+
delivery_method :smtp, {
|
|
88
|
+
address: 'smtp.sendgrid.net',
|
|
89
|
+
port: 587,
|
|
90
|
+
user_name: 'apikey',
|
|
91
|
+
password: 'your-sendgrid-api-key',
|
|
92
|
+
authentication: 'plain',
|
|
93
|
+
enable_starttls_auto: true
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Mailgun
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
Mail.defaults do
|
|
102
|
+
delivery_method :smtp, {
|
|
103
|
+
address: 'smtp.mailgun.org',
|
|
104
|
+
port: 587,
|
|
105
|
+
user_name: 'postmaster@your-domain.mailgun.org',
|
|
106
|
+
password: 'your-mailgun-password',
|
|
107
|
+
authentication: 'plain',
|
|
108
|
+
enable_starttls_auto: true
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Amazon SES
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
Mail.defaults do
|
|
117
|
+
delivery_method :smtp, {
|
|
118
|
+
address: 'email-smtp.us-east-1.amazonaws.com',
|
|
119
|
+
port: 587,
|
|
120
|
+
user_name: 'your-ses-smtp-username',
|
|
121
|
+
password: 'your-ses-smtp-password',
|
|
122
|
+
authentication: 'plain',
|
|
123
|
+
enable_starttls_auto: true
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Environment-Based Configuration
|
|
129
|
+
|
|
130
|
+
Configure different delivery methods per environment:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
case ENV['RACK_ENV']
|
|
134
|
+
when 'production'
|
|
135
|
+
Mail.defaults do
|
|
136
|
+
delivery_method :smtp, {
|
|
137
|
+
address: ENV['SMTP_ADDRESS'],
|
|
138
|
+
port: ENV['SMTP_PORT'],
|
|
139
|
+
user_name: ENV['SMTP_USERNAME'],
|
|
140
|
+
password: ENV['SMTP_PASSWORD'],
|
|
141
|
+
authentication: 'plain',
|
|
142
|
+
enable_starttls_auto: true
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
when 'development'
|
|
146
|
+
Mail.defaults do
|
|
147
|
+
delivery_method :file, location: 'tmp/mails'
|
|
148
|
+
end
|
|
149
|
+
when 'test'
|
|
150
|
+
Mail.defaults do
|
|
151
|
+
delivery_method :test
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Custom Email Templates
|
|
157
|
+
|
|
158
|
+
Override email content by defining methods in your Rodauth configuration:
|
|
159
|
+
|
|
160
|
+
### Custom Reset Password Email
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
plugin :rodauth do
|
|
164
|
+
enable :reset_password
|
|
165
|
+
|
|
166
|
+
# Custom email subject
|
|
167
|
+
reset_password_email_subject do
|
|
168
|
+
"Reset your #{domain} password"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Custom email body
|
|
172
|
+
reset_password_email_body do
|
|
173
|
+
<<~EMAIL
|
|
174
|
+
Hello,
|
|
175
|
+
|
|
176
|
+
Someone has requested a password reset for your account.
|
|
177
|
+
|
|
178
|
+
Click here to reset: #{reset_password_email_link}
|
|
179
|
+
|
|
180
|
+
If you didn't request this, please ignore this email.
|
|
181
|
+
|
|
182
|
+
Thanks,
|
|
183
|
+
The #{domain} Team
|
|
184
|
+
EMAIL
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Custom Verification Email
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
plugin :rodauth do
|
|
193
|
+
enable :verify_account
|
|
194
|
+
|
|
195
|
+
verify_account_email_subject do
|
|
196
|
+
"Please verify your #{domain} account"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
verify_account_email_body do
|
|
200
|
+
<<~EMAIL
|
|
201
|
+
Welcome to #{domain}!
|
|
202
|
+
|
|
203
|
+
Please verify your account by clicking this link:
|
|
204
|
+
#{verify_account_email_link}
|
|
205
|
+
|
|
206
|
+
Thanks for signing up!
|
|
207
|
+
EMAIL
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Email Configuration Methods
|
|
213
|
+
|
|
214
|
+
Key configuration methods available in Rodauth:
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
plugin :rodauth do
|
|
218
|
+
# Sender email address (defaults to "webmaster@#{domain}")
|
|
219
|
+
email_from 'noreply@yoursite.com'
|
|
220
|
+
|
|
221
|
+
# Subject prefix for all emails
|
|
222
|
+
email_subject_prefix '[YourSite] '
|
|
223
|
+
|
|
224
|
+
# Customize recipient email address
|
|
225
|
+
email_to do
|
|
226
|
+
account[:email] # or custom logic
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Custom email creation
|
|
230
|
+
create_email do |subject, body|
|
|
231
|
+
mail = Mail.new
|
|
232
|
+
mail.from = email_from
|
|
233
|
+
mail.to = email_to
|
|
234
|
+
mail.subject = "#{email_subject_prefix}#{subject}"
|
|
235
|
+
mail.body = body
|
|
236
|
+
# Add custom headers, attachments, etc.
|
|
237
|
+
mail
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Custom email sending
|
|
241
|
+
send_email do |email|
|
|
242
|
+
# Add logging, error handling, etc.
|
|
243
|
+
puts "Sending email to #{email.to}: #{email.subject}"
|
|
244
|
+
email.deliver!
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Alternative Delivery Methods
|
|
250
|
+
|
|
251
|
+
### File Delivery (Development)
|
|
252
|
+
|
|
253
|
+
Saves emails to files instead of sending them:
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
Mail.defaults do
|
|
257
|
+
delivery_method :file, location: 'tmp/mails'
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Test Delivery
|
|
262
|
+
|
|
263
|
+
Captures emails in memory for testing:
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
Mail.defaults do
|
|
267
|
+
delivery_method :test
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Access sent emails in tests
|
|
271
|
+
Mail::TestMailer.deliveries.last
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Sendmail
|
|
275
|
+
|
|
276
|
+
Use local sendmail binary:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
Mail.defaults do
|
|
280
|
+
delivery_method :sendmail
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Email Features
|
|
285
|
+
|
|
286
|
+
Features that send emails and can be customized:
|
|
287
|
+
|
|
288
|
+
- **reset_password** - Password reset emails
|
|
289
|
+
- **verify_account** - Account verification emails
|
|
290
|
+
- **verify_login_change** - Login change verification emails
|
|
291
|
+
- **change_password_notify** - Password change notifications
|
|
292
|
+
- **lockout** - Account lockout notifications
|
|
293
|
+
- **email_auth** - Passwordless email authentication
|
|
294
|
+
- **otp_lockout_email** - OTP lockout notifications
|
|
295
|
+
- **otp_modify_email** - OTP setup/disable notifications
|
|
296
|
+
- **webauthn_modify_email** - WebAuthn setup/removal notifications
|
|
297
|
+
|
|
298
|
+
## Security Considerations
|
|
299
|
+
|
|
300
|
+
### HMAC Protection
|
|
301
|
+
|
|
302
|
+
Enable HMAC protection for email tokens:
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
plugin :rodauth do
|
|
306
|
+
# Set an HMAC secret for token security
|
|
307
|
+
hmac_secret 'your-secret-key-here'
|
|
308
|
+
|
|
309
|
+
# Disable raw token acceptance (recommended for production)
|
|
310
|
+
allow_raw_email_token? false
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Rate Limiting
|
|
315
|
+
|
|
316
|
+
Built-in rate limiting prevents email spam:
|
|
317
|
+
|
|
318
|
+
```ruby
|
|
319
|
+
plugin :rodauth do
|
|
320
|
+
enable :reset_password
|
|
321
|
+
|
|
322
|
+
# Don't resend reset email within 5 minutes
|
|
323
|
+
reset_password_skip_resend_email_within 300
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Testing Email
|
|
328
|
+
|
|
329
|
+
In your test suite:
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
# Configure test delivery
|
|
333
|
+
Mail.defaults do
|
|
334
|
+
delivery_method :test
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# In tests, check sent emails
|
|
338
|
+
def test_password_reset_email
|
|
339
|
+
post '/reset-password', email: 'user@example.com'
|
|
340
|
+
|
|
341
|
+
email = Mail::TestMailer.deliveries.last
|
|
342
|
+
assert_includes email.to, 'user@example.com'
|
|
343
|
+
assert_includes email.subject, 'Reset Password'
|
|
344
|
+
assert_includes email.body.to_s, 'reset'
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Clear deliveries between tests
|
|
348
|
+
def setup
|
|
349
|
+
Mail::TestMailer.deliveries.clear
|
|
350
|
+
end
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Troubleshooting
|
|
354
|
+
|
|
355
|
+
### Common Issues
|
|
356
|
+
|
|
357
|
+
1. **Authentication failures**: Ensure you're using the correct credentials and authentication method
|
|
358
|
+
2. **Port blocked**: Try different ports (25, 465, 587, 2525)
|
|
359
|
+
3. **Gmail App Passwords**: Regular Gmail passwords won't work with SMTP
|
|
360
|
+
4. **Development emails not visible**: Check your delivery method and file location
|
|
361
|
+
|
|
362
|
+
### Debug Logging
|
|
363
|
+
|
|
364
|
+
Enable Mail gem logging:
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
Mail.defaults do
|
|
368
|
+
delivery_method :smtp, {
|
|
369
|
+
# ... SMTP settings
|
|
370
|
+
openssl_verify_mode: 'none',
|
|
371
|
+
enable_starttls_auto: true
|
|
372
|
+
}
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Enable logging
|
|
376
|
+
Mail.defaults do
|
|
377
|
+
retriever_method :imap, {
|
|
378
|
+
# ... settings
|
|
379
|
+
}
|
|
380
|
+
end
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Testing SMTP Configuration
|
|
384
|
+
|
|
385
|
+
Test your SMTP settings independently:
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
require 'mail'
|
|
389
|
+
|
|
390
|
+
Mail.defaults do
|
|
391
|
+
delivery_method :smtp, {
|
|
392
|
+
# your SMTP settings
|
|
393
|
+
}
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
mail = Mail.new do
|
|
397
|
+
from 'test@yoursite.com'
|
|
398
|
+
to 'recipient@example.com'
|
|
399
|
+
subject 'Test email'
|
|
400
|
+
body 'This is a test email'
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
mail.deliver!
|
|
404
|
+
puts "Email sent successfully!"
|
|
405
|
+
```
|