mailbox-kit 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +104 -0
- data/app/controllers/mailbox_kit/management/mailboxes_controller.rb +171 -0
- data/app/controllers/mailbox_kit/management/styles_controller.rb +11 -0
- data/app/views/layouts/mailbox_kit/management.html.erb +29 -0
- data/app/views/mailbox_kit/management/mailboxes/index.html.erb +65 -0
- data/app/views/mailbox_kit/management/mailboxes/message.html.erb +34 -0
- data/app/views/mailbox_kit/management/mailboxes/show.html.erb +86 -0
- data/docs/integration.md +283 -0
- data/docs/upgrading.md +60 -0
- data/lib/generators/mailbox_kit/install/install_generator.rb +24 -0
- data/lib/generators/mailbox_kit/install/templates/create_mailbox_kit_mailboxes.rb +51 -0
- data/lib/generators/mailbox_kit/install/templates/create_mailbox_kit_receiving_domains.rb +16 -0
- data/lib/generators/mailbox_kit/upgrade/templates/allow_provider_neutral_receiving_domains.rb +9 -0
- data/lib/generators/mailbox_kit/upgrade/templates/index_mailbox_kit_inbound_messages.rb +12 -0
- data/lib/generators/mailbox_kit/upgrade/upgrade_generator.rb +23 -0
- data/lib/mailbox-kit.rb +11 -0
- data/lib/mailbox_kit/active_record/base.rb +58 -0
- data/lib/mailbox_kit/address_syntax.rb +19 -0
- data/lib/mailbox_kit/error.rb +14 -0
- data/lib/mailbox_kit/inbound_email.rb +56 -0
- data/lib/mailbox_kit/mailboxes/configuration.rb +19 -0
- data/lib/mailbox_kit/mailboxes/inbound_retention.rb +16 -0
- data/lib/mailbox_kit/mailboxes/models.rb +152 -0
- data/lib/mailbox_kit/mailboxes/service.rb +312 -0
- data/lib/mailbox_kit/mailboxes.rb +9 -0
- data/lib/mailbox_kit/management/adapter.rb +29 -0
- data/lib/mailbox_kit/management/configuration.rb +15 -0
- data/lib/mailbox_kit/management/engine.rb +13 -0
- data/lib/mailbox_kit/management/management.css +68 -0
- data/lib/mailbox_kit/management/routes.rb +14 -0
- data/lib/mailbox_kit/management.rb +6 -0
- data/lib/mailbox_kit/railtie.rb +21 -0
- data/lib/mailbox_kit/tenancy.rb +76 -0
- data/lib/mailbox_kit/tenant_job_context.rb +88 -0
- data/lib/mailbox_kit/version.rb +3 -0
- metadata +81 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "mailbox_kit/error"
|
|
2
|
+
|
|
3
|
+
module MailboxKit
|
|
4
|
+
# An adapter boundary: the host owns tenant discovery and connection switching.
|
|
5
|
+
# Configure once, before requiring any optional Active Record models.
|
|
6
|
+
module Tenancy
|
|
7
|
+
CONTEXT_KEY = :cloudflare_email_tenant_key
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def configure(base_class:, switch:, current:)
|
|
11
|
+
raise ConfigurationError, "configure tenancy before loading Mailbox Kit models" if @models_loaded
|
|
12
|
+
raise ConfigurationError, "tenancy is already configured" if enabled?
|
|
13
|
+
unless base_class.is_a?(Class) && base_class.respond_to?(:abstract_class?) && base_class.abstract_class?
|
|
14
|
+
raise ConfigurationError, "base_class must be an abstract Active Record class"
|
|
15
|
+
end
|
|
16
|
+
unless switch.respond_to?(:call) && current.respond_to?(:call)
|
|
17
|
+
raise ConfigurationError, "switch and current must be callable"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@base_class, @switch, @current = base_class, switch, current
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enabled?
|
|
25
|
+
!@switch.nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def model_base(default)
|
|
29
|
+
@models_loaded = true
|
|
30
|
+
@base_class || default
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def current_key
|
|
34
|
+
Thread.current[CONTEXT_KEY]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Only trusted host integration code should use this to capture new work.
|
|
38
|
+
# Model access and persisted job deserialization still require explicit
|
|
39
|
+
# gem context; the adapter must not invent a default tenant here.
|
|
40
|
+
def host_current_key
|
|
41
|
+
return unless enabled?
|
|
42
|
+
key = @current.call
|
|
43
|
+
normalize_key(key) unless key.nil?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def normalize_key(key)
|
|
47
|
+
unless key.is_a?(String) && !key.empty? && key == key.strip && !key.match?(/[[:cntrl:]]/)
|
|
48
|
+
raise ConfigurationError, "tenant key must be a nonempty string without surrounding whitespace or control characters"
|
|
49
|
+
end
|
|
50
|
+
key.dup.freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def require_context!
|
|
54
|
+
key = current_key
|
|
55
|
+
raise ConfigurationError, "an explicit Mailbox Kit tenant context is required" unless key
|
|
56
|
+
if enabled? && @current.call != key
|
|
57
|
+
raise ConfigurationError, "host database tenant does not match Mailbox Kit tenant context"
|
|
58
|
+
end
|
|
59
|
+
key
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with(key)
|
|
63
|
+
key = normalize_key(key)
|
|
64
|
+
previous = current_key
|
|
65
|
+
run = proc do
|
|
66
|
+
Thread.current[CONTEXT_KEY] = key
|
|
67
|
+
require_context!
|
|
68
|
+
yield
|
|
69
|
+
ensure
|
|
70
|
+
Thread.current[CONTEXT_KEY] = previous
|
|
71
|
+
end
|
|
72
|
+
enabled? ? @switch.call(key, &run) : run.call
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require "mailbox_kit/tenancy"
|
|
2
|
+
|
|
3
|
+
module MailboxKit
|
|
4
|
+
# Opt in with `prepend MailboxKit::TenantJobContext` on jobs whose
|
|
5
|
+
# arguments or work reference tenant records. Queue payloads must be trusted.
|
|
6
|
+
module TenantJobContext
|
|
7
|
+
PAYLOAD_KEY = "cloudflare_email_tenant_key".freeze
|
|
8
|
+
|
|
9
|
+
def serialize
|
|
10
|
+
key = cloudflare_email_job_tenant_key
|
|
11
|
+
return super unless key
|
|
12
|
+
Tenancy.with(key) do
|
|
13
|
+
payload = super
|
|
14
|
+
verify_cloudflare_email_host_tenant!(key, payload["tenant"])
|
|
15
|
+
payload.merge(PAYLOAD_KEY => key)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def deserialize(job_data)
|
|
20
|
+
# Bind before super: host job integrations may deserialize model arguments
|
|
21
|
+
# eagerly. Never infer a missing tenant from the worker's ambient context.
|
|
22
|
+
if !job_data.key?(PAYLOAD_KEY) && cloudflare_email_optional_job_context?
|
|
23
|
+
@cloudflare_email_job_tenant_key = nil
|
|
24
|
+
return super
|
|
25
|
+
end
|
|
26
|
+
@cloudflare_email_job_tenant_key = Tenancy.normalize_key(job_data[PAYLOAD_KEY])
|
|
27
|
+
verify_cloudflare_email_host_tenant!(@cloudflare_email_job_tenant_key, job_data["tenant"])
|
|
28
|
+
Tenancy.with(@cloudflare_email_job_tenant_key) { super }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def perform_now
|
|
32
|
+
# Active Job resolves GlobalIDs before around_perform, so that callback
|
|
33
|
+
# cannot safely implement database tenant selection.
|
|
34
|
+
key = cloudflare_email_job_tenant_key
|
|
35
|
+
return super unless key
|
|
36
|
+
if defined?(::ActiveRecord::Tenanted::Job) && respond_to?(:tenant)
|
|
37
|
+
verify_cloudflare_email_host_tenant!(key, tenant)
|
|
38
|
+
end
|
|
39
|
+
Tenancy.with(key) { super }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.install_framework_jobs!
|
|
43
|
+
if defined?(::ActionMailbox)
|
|
44
|
+
%i[RoutingJob IncinerationJob].each do |name|
|
|
45
|
+
next unless ::ActionMailbox.const_defined?(name)
|
|
46
|
+
klass = ::ActionMailbox.const_get(name)
|
|
47
|
+
klass.define_singleton_method(:cloudflare_email_optional_job_context?) { !Tenancy.enabled? }
|
|
48
|
+
klass.prepend(self) unless klass.ancestors.include?(self)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
if defined?(::ActiveStorage)
|
|
52
|
+
%i[BaseJob AnalyzeJob PurgeJob MirrorJob TransformJob PreviewImageJob].each do |name|
|
|
53
|
+
next unless ::ActiveStorage.const_defined?(name)
|
|
54
|
+
klass = ::ActiveStorage.const_get(name)
|
|
55
|
+
klass.define_singleton_method(:cloudflare_email_optional_job_context?) { !Tenancy.enabled? }
|
|
56
|
+
klass.prepend(self) unless klass.ancestors.include?(self)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def cloudflare_email_job_tenant_key
|
|
64
|
+
# Once serialized/deserialized, retries retain their original tenant even
|
|
65
|
+
# if re-enqueued from a different tenant or outside a tenant context.
|
|
66
|
+
return @cloudflare_email_job_tenant_key if instance_variable_defined?(:@cloudflare_email_job_tenant_key)
|
|
67
|
+
return nil if cloudflare_email_optional_job_context? && !Tenancy.current_key
|
|
68
|
+
# Native uploads/mailbox work may originate in the host's own with_tenant
|
|
69
|
+
# block. Framework jobs can capture that trusted context on first use.
|
|
70
|
+
# deserialize never takes this path for missing persisted metadata.
|
|
71
|
+
if !Tenancy.current_key && self.class.respond_to?(:cloudflare_email_optional_job_context?)
|
|
72
|
+
key = Tenancy.host_current_key
|
|
73
|
+
return @cloudflare_email_job_tenant_key = key if key
|
|
74
|
+
end
|
|
75
|
+
@cloudflare_email_job_tenant_key = Tenancy.require_context!
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def cloudflare_email_optional_job_context?
|
|
79
|
+
self.class.respond_to?(:cloudflare_email_optional_job_context?) && self.class.cloudflare_email_optional_job_context?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def verify_cloudflare_email_host_tenant!(key, host_key)
|
|
83
|
+
if host_key && host_key != key
|
|
84
|
+
raise ConfigurationError, "job host tenant conflicts with Mailbox Kit tenant context"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mailbox-kit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cole
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Provider-neutral mailbox identities, aliases, memberships, retention,
|
|
13
|
+
optional tenancy, and a server-rendered management engine built on Rails Action
|
|
14
|
+
Mailbox.
|
|
15
|
+
email:
|
|
16
|
+
- cole@dscribeai.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- app/controllers/mailbox_kit/management/mailboxes_controller.rb
|
|
24
|
+
- app/controllers/mailbox_kit/management/styles_controller.rb
|
|
25
|
+
- app/views/layouts/mailbox_kit/management.html.erb
|
|
26
|
+
- app/views/mailbox_kit/management/mailboxes/index.html.erb
|
|
27
|
+
- app/views/mailbox_kit/management/mailboxes/message.html.erb
|
|
28
|
+
- app/views/mailbox_kit/management/mailboxes/show.html.erb
|
|
29
|
+
- docs/integration.md
|
|
30
|
+
- docs/upgrading.md
|
|
31
|
+
- lib/generators/mailbox_kit/install/install_generator.rb
|
|
32
|
+
- lib/generators/mailbox_kit/install/templates/create_mailbox_kit_mailboxes.rb
|
|
33
|
+
- lib/generators/mailbox_kit/install/templates/create_mailbox_kit_receiving_domains.rb
|
|
34
|
+
- lib/generators/mailbox_kit/upgrade/templates/allow_provider_neutral_receiving_domains.rb
|
|
35
|
+
- lib/generators/mailbox_kit/upgrade/templates/index_mailbox_kit_inbound_messages.rb
|
|
36
|
+
- lib/generators/mailbox_kit/upgrade/upgrade_generator.rb
|
|
37
|
+
- lib/mailbox-kit.rb
|
|
38
|
+
- lib/mailbox_kit/active_record/base.rb
|
|
39
|
+
- lib/mailbox_kit/address_syntax.rb
|
|
40
|
+
- lib/mailbox_kit/error.rb
|
|
41
|
+
- lib/mailbox_kit/inbound_email.rb
|
|
42
|
+
- lib/mailbox_kit/mailboxes.rb
|
|
43
|
+
- lib/mailbox_kit/mailboxes/configuration.rb
|
|
44
|
+
- lib/mailbox_kit/mailboxes/inbound_retention.rb
|
|
45
|
+
- lib/mailbox_kit/mailboxes/models.rb
|
|
46
|
+
- lib/mailbox_kit/mailboxes/service.rb
|
|
47
|
+
- lib/mailbox_kit/management.rb
|
|
48
|
+
- lib/mailbox_kit/management/adapter.rb
|
|
49
|
+
- lib/mailbox_kit/management/configuration.rb
|
|
50
|
+
- lib/mailbox_kit/management/engine.rb
|
|
51
|
+
- lib/mailbox_kit/management/management.css
|
|
52
|
+
- lib/mailbox_kit/management/routes.rb
|
|
53
|
+
- lib/mailbox_kit/railtie.rb
|
|
54
|
+
- lib/mailbox_kit/tenancy.rb
|
|
55
|
+
- lib/mailbox_kit/tenant_job_context.rb
|
|
56
|
+
- lib/mailbox_kit/version.rb
|
|
57
|
+
homepage: https://github.com/cole-robertson/cloudflare-email
|
|
58
|
+
licenses:
|
|
59
|
+
- MIT
|
|
60
|
+
metadata:
|
|
61
|
+
source_code_uri: https://github.com/cole-robertson/cloudflare-email/tree/main/mailbox-kit
|
|
62
|
+
changelog_uri: https://github.com/cole-robertson/cloudflare-email/blob/main/CHANGELOG.md
|
|
63
|
+
bug_tracker_uri: https://github.com/cole-robertson/cloudflare-email/issues
|
|
64
|
+
rdoc_options: []
|
|
65
|
+
require_paths:
|
|
66
|
+
- lib
|
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '3.2'
|
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
requirements: []
|
|
78
|
+
rubygems_version: 3.6.9
|
|
79
|
+
specification_version: 4
|
|
80
|
+
summary: Persistent inboxes and management UI built on Rails Action Mailbox.
|
|
81
|
+
test_files: []
|