letter_thief 0.2.0 → 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/README.md +1 -1
- data/app/controllers/letter_thief/email_messages_controller.rb +23 -0
- data/app/models/letter_thief/email_message.rb +4 -2
- data/app/models/letter_thief/email_search.rb +1 -1
- data/app/views/letter_thief/email_messages/_email_message.html.erb +25 -0
- data/app/views/letter_thief/email_messages/index.html.erb +25 -15
- data/app/views/letter_thief/email_messages/show.html.erb +17 -9
- data/config/routes.rb +3 -1
- data/lib/letter_thief/engine.rb +3 -3
- data/lib/letter_thief/observer.rb +40 -0
- data/lib/letter_thief/version.rb +1 -1
- data/lib/letter_thief.rb +5 -0
- metadata +4 -3
- data/lib/letter_thief/interceptor.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3b860cc3ad45c5e200e2cb4b5d9610fd6a2e8438131de9b24dc2c8c902390f5
|
4
|
+
data.tar.gz: d5ce85e83a08a78010c79bfd3ca77f14f3d3c3f8e3632458bd0eb0ffb0886f53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f698e01a0984e8d8f4404892831ae71275d5fd1ae35077a5c0b58ce1cb7ab1bd1eacf2d9a567a47d09ad4ea017ab042ae5ab9b0aefbadf473e224a656b043c8e
|
7
|
+
data.tar.gz: 5f817d2ddbd8260fe90b9aab6c9c7433361df14f0944e45ff3fb1c0751b0d4e931482e9f4cccba2f49bdaa13b43036ca6ce3162280c513c4f1df7fb5174e5461
|
data/README.md
CHANGED
@@ -52,7 +52,7 @@ Mount the engine in your routes, protecting it.
|
|
52
52
|
The code below might be different depending on how you authenticate your users.
|
53
53
|
|
54
54
|
```ruby
|
55
|
-
authenticate :user, ->(user) {
|
55
|
+
authenticate :user, ->(user) { user&.administrator? } do
|
56
56
|
mount LetterThief::Engine => "/letter_thief"
|
57
57
|
end
|
58
58
|
```
|
@@ -4,6 +4,9 @@ module LetterThief
|
|
4
4
|
class EmailMessagesController < ApplicationController
|
5
5
|
layout "letter_thief/application"
|
6
6
|
|
7
|
+
before_action :turn_off_csp_nonce_generation
|
8
|
+
before_action :set_email, only: [:show, :destroy]
|
9
|
+
|
7
10
|
content_security_policy do |policy|
|
8
11
|
policy.style_src :self, :https, :unsafe_inline
|
9
12
|
end
|
@@ -15,7 +18,27 @@ module LetterThief
|
|
15
18
|
end
|
16
19
|
|
17
20
|
def show
|
21
|
+
end
|
22
|
+
|
23
|
+
def destroy
|
24
|
+
redirect_to email_messages_path if @email.destroy
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy_all
|
28
|
+
EmailMessage.destroy_all
|
29
|
+
redirect_to email_messages_path, notice: "All email messages have been deleted"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def set_email
|
18
35
|
@email = EmailMessage.find(params[:id])
|
19
36
|
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def turn_off_csp_nonce_generation
|
41
|
+
request.content_security_policy_nonce_directives = []
|
42
|
+
end
|
20
43
|
end
|
21
44
|
end
|
@@ -4,8 +4,10 @@ module LetterThief
|
|
4
4
|
|
5
5
|
connects_to(**LetterThief.connects_to) if LetterThief.connects_to
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
if LetterThief.activestorage_available?
|
8
|
+
has_many_attached :attachments
|
9
|
+
has_one_attached :raw_email
|
10
|
+
end
|
9
11
|
|
10
12
|
unless ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
|
11
13
|
serialize :to, coder: JSON, type: Array
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%# locals:(email_message:) %>
|
2
|
+
<tr>
|
3
|
+
<td>
|
4
|
+
<%= email_message.from&.join(", ") %>
|
5
|
+
</td>
|
6
|
+
<td>
|
7
|
+
<%= email_message.to&.join(", ") %>
|
8
|
+
</td>
|
9
|
+
<td>
|
10
|
+
<%= link_to email_message.subject.presence || "(no subject)", email_message_path(email_message) %>
|
11
|
+
</td>
|
12
|
+
<td>
|
13
|
+
<%= email_message.intercepted_at.strftime("%Y-%m-%d %H:%M") %>
|
14
|
+
</td>
|
15
|
+
<td>
|
16
|
+
<% if @email.respond_to?(:raw_email) %>
|
17
|
+
<%= number_to_human_size(email_message.raw_email.byte_size) %>
|
18
|
+
<% else %>
|
19
|
+
-
|
20
|
+
<% end %>
|
21
|
+
</td>
|
22
|
+
<td>
|
23
|
+
<%= button_to 'Delete', email_message_path(email_message), method: :delete, class: 'outline contrast' %>
|
24
|
+
</td>
|
25
|
+
</tr>
|
@@ -1,9 +1,15 @@
|
|
1
1
|
<% content_for :title, "Letter Thief" %>
|
2
2
|
|
3
3
|
<h1>📬 Intercepted Emails (Outbox)</h1>
|
4
|
+
|
4
5
|
<p>
|
5
|
-
|
6
|
+
<% if LetterThief.activestorage_available? %>
|
7
|
+
Currently occupying <%= number_to_human_size(LetterThief.used_activestorage_space)%>
|
8
|
+
<% else %>
|
9
|
+
Attachments are not saved. In order to save the attachments you need to setup ActiveStorage.
|
10
|
+
<% end %>
|
6
11
|
</p>
|
12
|
+
|
7
13
|
<form method="get" style="margin-bottom: 1rem;">
|
8
14
|
<fieldset class="grid">
|
9
15
|
<label>
|
@@ -35,7 +41,6 @@
|
|
35
41
|
</fieldset>
|
36
42
|
</form>
|
37
43
|
|
38
|
-
|
39
44
|
<% if @search.total_count > 0 %>
|
40
45
|
<p><%= pluralize(@search.total_count, "email") %> found</p>
|
41
46
|
|
@@ -47,22 +52,11 @@
|
|
47
52
|
<th>Subject</th>
|
48
53
|
<th>Time</th>
|
49
54
|
<th>Size</th>
|
55
|
+
<th></th>
|
50
56
|
</tr>
|
51
57
|
</thead>
|
52
58
|
<tbody>
|
53
|
-
|
54
|
-
<tr>
|
55
|
-
<td><%= email.from&.join(", ") %></td>
|
56
|
-
<td><%= email.to&.join(", ") %></td>
|
57
|
-
<td>
|
58
|
-
<a href="<%= email_message_path(email) %>">
|
59
|
-
<%= email.subject.presence || "(no subject)" %>
|
60
|
-
</a>
|
61
|
-
</td>
|
62
|
-
<td><%= email.intercepted_at.strftime("%Y-%m-%d %H:%M") %></td>
|
63
|
-
<td><%= number_to_human_size(email.raw_email.byte_size) %></td>
|
64
|
-
</tr>
|
65
|
-
<% end %>
|
59
|
+
<%= render @search.results %>
|
66
60
|
</tbody>
|
67
61
|
</table>
|
68
62
|
|
@@ -93,6 +87,22 @@
|
|
93
87
|
<% end %>
|
94
88
|
</ul>
|
95
89
|
</nav>
|
90
|
+
|
91
|
+
<%= button_to "Delete All Messages",
|
92
|
+
destroy_all_email_messages_path,
|
93
|
+
method: :delete,
|
94
|
+
class: "secondary outline",
|
95
|
+
onclick: "return confirmDeleteAll(event)" %>
|
96
|
+
|
97
|
+
<script>
|
98
|
+
function confirmDeleteAll(event) {
|
99
|
+
event.preventDefault();
|
100
|
+
if (window.confirm('Are you sure you want to delete all messages? This action cannot be undone.')) {
|
101
|
+
event.target.closest('form').submit();
|
102
|
+
}
|
103
|
+
return false;
|
104
|
+
}
|
105
|
+
</script>
|
96
106
|
<% else %>
|
97
107
|
<p>No emails found.</p>
|
98
108
|
<% end %>
|
@@ -33,21 +33,29 @@
|
|
33
33
|
<dt>BCC:</dt>
|
34
34
|
<dd><%= @email.bcc.join(", ") %></dd>
|
35
35
|
<% end %>
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
<% if @email.respond_to?(:attachments) %>
|
37
|
+
<% if @email.attachments.any? %>
|
38
|
+
<dt>Attachments:</dt>
|
39
|
+
<dd>
|
40
|
+
<% @email.attachments.each do |file| %>
|
41
|
+
<%= link_to file.filename, main_app.rails_blob_path(file, disposition: "attachment") %>
|
42
|
+
<% end %>
|
43
|
+
</dd>
|
42
44
|
<% end %>
|
43
|
-
</dd>
|
44
45
|
<% end %>
|
45
46
|
</dl>
|
46
|
-
|
47
|
-
|
47
|
+
|
48
|
+
<% if @email.respond_to?(:raw_email) %>
|
49
|
+
<% if @email.raw_email.attached? %>
|
50
|
+
<%= link_to "Download", main_app.rails_blob_path(@email.raw_email, disposition: "attachment") %>
|
51
|
+
<% end %>
|
48
52
|
<% end %>
|
49
53
|
</section>
|
50
54
|
|
55
|
+
<% if !LetterThief.activestorage_available? %>
|
56
|
+
<p>Attachments are not saved. In order to save the attachments you need to setup ActiveStorage.</p>
|
57
|
+
<% end %>
|
58
|
+
|
51
59
|
<hr>
|
52
60
|
|
53
61
|
<% if @email.multipart? %>
|
data/config/routes.rb
CHANGED
data/lib/letter_thief/engine.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
require "letter_thief/
|
1
|
+
require "letter_thief/observer"
|
2
2
|
require "letter_thief/delivery_method"
|
3
3
|
|
4
4
|
module LetterThief
|
5
5
|
class Engine < ::Rails::Engine
|
6
6
|
isolate_namespace LetterThief
|
7
7
|
|
8
|
-
initializer "letter_thief.
|
8
|
+
initializer "letter_thief.add_observer" do
|
9
9
|
ActiveSupport.on_load(:action_mailer) do
|
10
|
-
ActionMailer::Base.
|
10
|
+
ActionMailer::Base.register_observer(LetterThief::Observer)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LetterThief
|
2
|
+
class Observer
|
3
|
+
def self.delivered_email(mail)
|
4
|
+
string_io = StringIO.new(mail.to_s)
|
5
|
+
email = EmailMessage.create!(
|
6
|
+
to: mail.to,
|
7
|
+
from: mail.from,
|
8
|
+
sender: mail.sender,
|
9
|
+
cc: mail.cc,
|
10
|
+
bcc: mail.bcc,
|
11
|
+
subject: mail.subject,
|
12
|
+
body_text: mail.text_part&.decoded || mail.body.decoded,
|
13
|
+
body_html: mail.html_part&.decoded,
|
14
|
+
headers: mail.header.to_s,
|
15
|
+
content_type: mail.content_type,
|
16
|
+
intercepted_at: Time.current
|
17
|
+
)
|
18
|
+
|
19
|
+
if LetterThief.activestorage_available?
|
20
|
+
Array(mail.attachments).each do |attachment|
|
21
|
+
ar_attachment = email.attachments.attach(
|
22
|
+
io: StringIO.new(attachment.body.decoded),
|
23
|
+
filename: attachment.filename,
|
24
|
+
content_type: attachment.mime_type
|
25
|
+
).last
|
26
|
+
ar_attachment.blob.metadata["cid"] = attachment.cid
|
27
|
+
ar_attachment.blob.save!
|
28
|
+
end
|
29
|
+
|
30
|
+
email.raw_email.attach(
|
31
|
+
io: string_io,
|
32
|
+
filename: "message-#{email.id}.eml",
|
33
|
+
content_type: "message/rfc822"
|
34
|
+
)
|
35
|
+
end
|
36
|
+
rescue => e
|
37
|
+
Rails.logger.error("[LetterThief] Failed to store observed email: #{e.message}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/letter_thief/version.rb
CHANGED
data/lib/letter_thief.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: letter_thief
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Rodi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- app/models/letter_thief/email_message.rb
|
42
42
|
- app/models/letter_thief/email_search.rb
|
43
43
|
- app/views/layouts/letter_thief/application.html.erb
|
44
|
+
- app/views/letter_thief/email_messages/_email_message.html.erb
|
44
45
|
- app/views/letter_thief/email_messages/index.html.erb
|
45
46
|
- app/views/letter_thief/email_messages/show.html.erb
|
46
47
|
- config/routes.rb
|
@@ -49,7 +50,7 @@ files:
|
|
49
50
|
- lib/letter_thief.rb
|
50
51
|
- lib/letter_thief/delivery_method.rb
|
51
52
|
- lib/letter_thief/engine.rb
|
52
|
-
- lib/letter_thief/
|
53
|
+
- lib/letter_thief/observer.rb
|
53
54
|
- lib/letter_thief/version.rb
|
54
55
|
- lib/tasks/letter_thief_tasks.rake
|
55
56
|
homepage: https://github.com/coorasse/letter_thief
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module LetterThief
|
2
|
-
class Interceptor
|
3
|
-
def self.delivering_email(mail)
|
4
|
-
string_io = StringIO.new(mail.to_s)
|
5
|
-
email = EmailMessage.create!(
|
6
|
-
to: mail.to,
|
7
|
-
from: mail.from,
|
8
|
-
sender: mail.sender,
|
9
|
-
cc: mail.cc,
|
10
|
-
bcc: mail.bcc,
|
11
|
-
subject: mail.subject,
|
12
|
-
body_text: mail.text_part&.decoded || mail.body.decoded,
|
13
|
-
body_html: mail.html_part&.decoded,
|
14
|
-
headers: mail.header.to_s,
|
15
|
-
content_type: mail.content_type,
|
16
|
-
intercepted_at: Time.current
|
17
|
-
)
|
18
|
-
|
19
|
-
Array(mail.attachments).each do |attachment|
|
20
|
-
ar_attachment = email.attachments.attach(
|
21
|
-
io: StringIO.new(attachment.body.decoded),
|
22
|
-
filename: attachment.filename,
|
23
|
-
content_type: attachment.mime_type
|
24
|
-
).last
|
25
|
-
ar_attachment.blob.metadata["cid"] = attachment.cid
|
26
|
-
ar_attachment.blob.save!
|
27
|
-
end
|
28
|
-
|
29
|
-
email.raw_email.attach(
|
30
|
-
io: string_io,
|
31
|
-
filename: "message-#{email.id}.eml",
|
32
|
-
content_type: "message/rfc822"
|
33
|
-
)
|
34
|
-
rescue => e
|
35
|
-
Rails.logger.error("[LetterThief] Failed to store intercepted email: #{e.message}")
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|