aha_builder_core 1.0.20 → 1.0.21
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 +22 -1
- data/lib/aha/auth/builder_discovery_service.rb +123 -0
- data/lib/aha/auth/client.rb +5 -2
- data/lib/aha/auth/configuration.rb +10 -3
- data/lib/aha/auth.rb +22 -4
- data/lib/aha/mail/delivery_method.rb +66 -0
- data/lib/aha/mail.rb +19 -0
- data/lib/aha/version.rb +1 -1
- data/lib/aha_builder_core.rb +1 -0
- data/lib/generators/aha_builder_core/email/USAGE +5 -0
- data/lib/generators/aha_builder_core/email/email_generator.rb +41 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2d2cf22937791f0b77aa3dec4abf17939b99877059b17f4a461423ddd34ce50
|
|
4
|
+
data.tar.gz: 7dc28c0a6703d842b2cd499d828fdc77fc0d63301cca1c0ccd9d77f92cafb347
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b04a59fedd4924fa5ce10a2a3c6781cbb29607adaffa9ef2640f3af9c7842b28f6caae196ca5f9f70445d4aeffceeaebd91e89effef124b0ba11d3ddb1a7bc1c
|
|
7
|
+
data.tar.gz: 64f4f61aabc84bacb316f1aaeae4623087b83d5a941060fc2022a74f65d714a6633efd1c761e28f76339c3e7e7c20b4a159a65f0171073a4e8631a4e89aa9251
|
data/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Aha Builder Core Client
|
|
2
2
|
|
|
3
|
-
Ruby client for Aha! Builder core
|
|
3
|
+
Ruby client for Aha! Builder core which provides application services:
|
|
4
|
+
|
|
5
|
+
* Authentication services for login and signup using email/password, social logins (Google, Github, Microsoft), SAML SSO and password reset.
|
|
6
|
+
* Email sending API.
|
|
7
|
+
* User guide content API.
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
@@ -206,3 +210,20 @@ Each page hash contains:
|
|
|
206
210
|
- `position` - Sort order (integer)
|
|
207
211
|
- `children_count` - Number of child pages
|
|
208
212
|
- `content_html` - HTML content (only in `load_page` response)
|
|
213
|
+
|
|
214
|
+
## Email Sending
|
|
215
|
+
|
|
216
|
+
Aha! Builder core provides a mail API that can be used as an ActionMailer delivery method.
|
|
217
|
+
|
|
218
|
+
### ActionMailer Integration
|
|
219
|
+
|
|
220
|
+
Configure your Rails application to send emails through Aha! Builder core:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# config/initializers/action_mailer.rb
|
|
224
|
+
ActionMailer::Base.add_delivery_method :aha_mail, Aha::Mail::DeliveryMethod
|
|
225
|
+
|
|
226
|
+
# config/environments/production.rb
|
|
227
|
+
config.action_mailer.delivery_method = :aha_mail
|
|
228
|
+
```
|
|
229
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aha
|
|
4
|
+
module Auth
|
|
5
|
+
# Discovers BuilderCore configuration via the secure.aha.io discovery endpoint.
|
|
6
|
+
# Resilient to account subdomain changes by resolving the current subdomain from account ID.
|
|
7
|
+
class BuilderDiscoveryService
|
|
8
|
+
CACHE_TTL = 300 # 5 minutes
|
|
9
|
+
|
|
10
|
+
def initialize(configuration)
|
|
11
|
+
@configuration = configuration
|
|
12
|
+
@cache = Concurrent::Atom.new({})
|
|
13
|
+
@refreshing = Concurrent::AtomicBoolean.new(false)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Discover the server URL for the account.
|
|
17
|
+
# @param force [Boolean] Force refresh, bypassing cache
|
|
18
|
+
# @return [Hash] Discovery data with :server_url
|
|
19
|
+
def discover(force: false)
|
|
20
|
+
@cache.reset({}) if force
|
|
21
|
+
|
|
22
|
+
cached = @cache.value
|
|
23
|
+
|
|
24
|
+
# Return cached data if present, serving stale data while refreshing in the background if past threshold
|
|
25
|
+
if cached[:data]
|
|
26
|
+
schedule_background_refresh(cached) if should_refresh?(cached)
|
|
27
|
+
return cached[:data]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Cache expired or empty - fetch synchronously with fallback
|
|
31
|
+
fetch_with_fallback
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def should_refresh?(cached)
|
|
37
|
+
return true unless cached[:fetched_at]
|
|
38
|
+
|
|
39
|
+
Time.current > cached[:fetched_at] + CACHE_TTL
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def schedule_background_refresh(cached)
|
|
43
|
+
return unless @refreshing.make_true
|
|
44
|
+
|
|
45
|
+
Concurrent::Future.execute do
|
|
46
|
+
data = fetch_discovery
|
|
47
|
+
@cache.reset(data: data, fetched_at: Time.current)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
logger.warn { "[BuilderDiscovery] Background refresh failed: #{e.message}" }
|
|
50
|
+
ensure
|
|
51
|
+
@refreshing.make_false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def fetch_with_fallback
|
|
56
|
+
@refreshing.make_true
|
|
57
|
+
data = fetch_discovery
|
|
58
|
+
@refreshing.make_false
|
|
59
|
+
@cache.reset(data: data, fetched_at: Time.current)
|
|
60
|
+
data
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
logger.warn { "[BuilderDiscovery] Fetch failed: #{e.message}" }
|
|
63
|
+
|
|
64
|
+
fallback_data = build_fallback_data
|
|
65
|
+
if fallback_data
|
|
66
|
+
logger.warn { "[BuilderDiscovery] Using fallback from AHA_CORE_SERVER_URL" }
|
|
67
|
+
@cache.reset(data: fallback_data, fetched_at: Time.current)
|
|
68
|
+
return fallback_data
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_fallback_data
|
|
75
|
+
server_url = ENV.fetch("AHA_CORE_SERVER_URL", nil)
|
|
76
|
+
return nil if server_url.to_s.empty?
|
|
77
|
+
|
|
78
|
+
{ server_url: server_url }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fetch_discovery
|
|
82
|
+
account_id = @configuration.account_id
|
|
83
|
+
raise ConfigurationError, "account_id is required for discovery" if account_id.to_s.empty?
|
|
84
|
+
|
|
85
|
+
url = "#{@configuration.core_discovery_url}/.well-known/core-discovery/#{account_id}"
|
|
86
|
+
logger.debug { "[BuilderDiscovery] Fetching #{url}" }
|
|
87
|
+
response = http_client.get(url)
|
|
88
|
+
logger.debug { "[BuilderDiscovery] Response status: #{response.status}, body: #{response.body}" }
|
|
89
|
+
handle_response(response)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def logger
|
|
93
|
+
@logger ||= defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def http_client
|
|
97
|
+
@http_client ||= Faraday.new do |conn|
|
|
98
|
+
conn.request :retry, max: 2, interval: 0.5, backoff_factor: 2
|
|
99
|
+
conn.options.timeout = @configuration.timeout
|
|
100
|
+
conn.adapter Faraday.default_adapter
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def handle_response(response)
|
|
105
|
+
case response.status
|
|
106
|
+
when 200
|
|
107
|
+
parsed = JSON.parse(response.body)
|
|
108
|
+
domain = parsed["domain"]
|
|
109
|
+
base_domain = @configuration.core_discovery_url.sub(%r{\Ahttps?://secure\.}, "")
|
|
110
|
+
{ server_url: "https://#{domain}.#{base_domain}/api/core" }
|
|
111
|
+
when 400
|
|
112
|
+
raise BadRequestError.new("Invalid account ID", status: response.status)
|
|
113
|
+
when 404
|
|
114
|
+
raise NotFoundError.new("Account not found", status: response.status)
|
|
115
|
+
else
|
|
116
|
+
raise ApiError.new("Discovery failed (status: #{response.status})", status: response.status)
|
|
117
|
+
end
|
|
118
|
+
rescue Faraday::Error => e
|
|
119
|
+
raise NetworkError.new("Discovery network error", original_error: e)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/aha/auth/client.rb
CHANGED
|
@@ -8,8 +8,11 @@ module Aha
|
|
|
8
8
|
class Client
|
|
9
9
|
ALGORITHM = "RS256"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# @param configuration [Configuration] The client configuration
|
|
12
|
+
# @param server_url [String, nil] Optional server URL override (used for discovery)
|
|
13
|
+
def initialize(configuration, server_url: nil)
|
|
12
14
|
@configuration = configuration
|
|
15
|
+
@server_url = server_url || configuration.server_url
|
|
13
16
|
@token_cache = TokenCache.new(ttl: configuration.jwks_cache_ttl)
|
|
14
17
|
end
|
|
15
18
|
|
|
@@ -194,7 +197,7 @@ module Aha
|
|
|
194
197
|
end
|
|
195
198
|
|
|
196
199
|
def http_client
|
|
197
|
-
@http_client ||= Faraday.new(url: @
|
|
200
|
+
@http_client ||= Faraday.new(url: @server_url) do |conn|
|
|
198
201
|
if @configuration.enable_logging && @configuration.logger
|
|
199
202
|
conn.response :logger, @configuration.logger, bodies: true
|
|
200
203
|
end
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
module Aha
|
|
4
4
|
module Auth
|
|
5
5
|
class Configuration
|
|
6
|
-
#
|
|
6
|
+
# Account ID for discovery-based URL resolution
|
|
7
|
+
attr_accessor :account_id
|
|
8
|
+
|
|
9
|
+
# Base URL for builder discovery endpoint (e.g., https://secure.aha.io)
|
|
10
|
+
attr_accessor :core_discovery_url
|
|
11
|
+
|
|
12
|
+
# The base URL of the BuilderCore auth server (discovered or explicit)
|
|
7
13
|
attr_accessor :server_url
|
|
8
14
|
|
|
9
15
|
# API key for server-to-server authentication
|
|
@@ -29,7 +35,8 @@ module Aha
|
|
|
29
35
|
attr_accessor :logger
|
|
30
36
|
|
|
31
37
|
def initialize
|
|
32
|
-
@
|
|
38
|
+
@account_id = ENV.fetch("AHA_ACCOUNT_ID", nil)
|
|
39
|
+
@core_discovery_url = ENV.fetch("AHA_SECURE_URL", "https://secure.aha.io")
|
|
33
40
|
@api_key = ENV.fetch("AHA_CORE_API_KEY", nil)
|
|
34
41
|
@client_id = ENV.fetch("APPLICATION_ID", nil)
|
|
35
42
|
@jwks_cache_ttl = 3600 # 1 hour
|
|
@@ -40,7 +47,7 @@ module Aha
|
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def validate!
|
|
43
|
-
raise ConfigurationError, "
|
|
50
|
+
raise ConfigurationError, "account_id is required" if account_id.nil? || account_id.to_s.empty?
|
|
44
51
|
raise ConfigurationError, "client_id is required" if client_id.nil? || client_id.to_s.empty?
|
|
45
52
|
end
|
|
46
53
|
end
|
data/lib/aha/auth.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "version"
|
|
|
11
11
|
require_relative "auth/errors"
|
|
12
12
|
require_relative "auth/configuration"
|
|
13
13
|
require_relative "auth/token_cache"
|
|
14
|
+
require_relative "auth/builder_discovery_service"
|
|
14
15
|
require_relative "auth/session"
|
|
15
16
|
require_relative "auth/user"
|
|
16
17
|
require_relative "auth/client"
|
|
@@ -20,6 +21,7 @@ require_relative "auth/sessions_resource"
|
|
|
20
21
|
module Aha
|
|
21
22
|
module Auth
|
|
22
23
|
CONFIGURATION = Concurrent::Atom.new(Configuration.new)
|
|
24
|
+
DISCOVERY_SERVICE = Concurrent::Atom.new(nil)
|
|
23
25
|
CLIENT = Concurrent::Atom.new(nil)
|
|
24
26
|
USERS_RESOURCE = Concurrent::Atom.new(nil)
|
|
25
27
|
SESSIONS_RESOURCE = Concurrent::Atom.new(nil)
|
|
@@ -35,11 +37,27 @@ module Aha
|
|
|
35
37
|
|
|
36
38
|
def reset_configuration!
|
|
37
39
|
CONFIGURATION.reset(Configuration.new)
|
|
40
|
+
DISCOVERY_SERVICE.reset(nil)
|
|
38
41
|
CLIENT.reset(nil)
|
|
39
42
|
USERS_RESOURCE.reset(nil)
|
|
40
43
|
SESSIONS_RESOURCE.reset(nil)
|
|
41
44
|
end
|
|
42
45
|
|
|
46
|
+
# Access the discovery service for resolving server URLs
|
|
47
|
+
#
|
|
48
|
+
# @return [BuilderDiscoveryService]
|
|
49
|
+
def discovery_service
|
|
50
|
+
DISCOVERY_SERVICE.compare_and_set(nil, BuilderDiscoveryService.new(configuration))
|
|
51
|
+
DISCOVERY_SERVICE.value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get the effective server URL (discovered or configured)
|
|
55
|
+
#
|
|
56
|
+
# @return [String] The server URL to use
|
|
57
|
+
def server_url
|
|
58
|
+
discovery_service.discover[:server_url]
|
|
59
|
+
end
|
|
60
|
+
|
|
43
61
|
# Generate login URL for redirecting users to the auth server
|
|
44
62
|
#
|
|
45
63
|
# @param cookies [Hash] Cookies hash to store the nonce for CSRF verification
|
|
@@ -73,7 +91,7 @@ module Aha
|
|
|
73
91
|
}
|
|
74
92
|
|
|
75
93
|
query = URI.encode_www_form(params)
|
|
76
|
-
"#{
|
|
94
|
+
"#{server_url}/auth_ui/start?#{query}"
|
|
77
95
|
end
|
|
78
96
|
|
|
79
97
|
# Exchange an authorization code for tokens
|
|
@@ -84,12 +102,12 @@ module Aha
|
|
|
84
102
|
def authenticate_with_code(code:, cookies:)
|
|
85
103
|
# Split the code and nonce if present
|
|
86
104
|
actual_code, nonce = code.split(".", 2)
|
|
87
|
-
|
|
105
|
+
|
|
88
106
|
raise "CSRF verification failed: unable to verify nonce" unless nonce
|
|
89
107
|
|
|
90
108
|
cookie_nonce = cookies[:auth_nonce]
|
|
91
109
|
cookies.delete(:auth_nonce)
|
|
92
|
-
raise "CSRF verification failed: nonce missing in cookie" if cookie_nonce.blank?
|
|
110
|
+
raise "CSRF verification failed: nonce missing in cookie" if cookie_nonce.blank?
|
|
93
111
|
raise "CSRF verification failed: nonce mismatch" if cookie_nonce != nonce
|
|
94
112
|
|
|
95
113
|
client.authenticate_with_code(code: actual_code)
|
|
@@ -139,7 +157,7 @@ module Aha
|
|
|
139
157
|
private
|
|
140
158
|
|
|
141
159
|
def client
|
|
142
|
-
CLIENT.compare_and_set(nil, Client.new(configuration))
|
|
160
|
+
CLIENT.compare_and_set(nil, Client.new(configuration, server_url: server_url))
|
|
143
161
|
CLIENT.value
|
|
144
162
|
end
|
|
145
163
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aha
|
|
4
|
+
module Mail
|
|
5
|
+
class DeliveryMethod
|
|
6
|
+
attr_accessor :settings
|
|
7
|
+
|
|
8
|
+
def initialize(settings = {})
|
|
9
|
+
@settings = settings
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def deliver!(mail)
|
|
13
|
+
# Extract mail attributes
|
|
14
|
+
mail_params = {
|
|
15
|
+
from: format_addresses(mail.from).first,
|
|
16
|
+
to: format_addresses(mail.to),
|
|
17
|
+
cc: format_addresses(mail.cc),
|
|
18
|
+
bcc: format_addresses(mail.bcc),
|
|
19
|
+
reply_to: format_addresses(mail.reply_to),
|
|
20
|
+
subject: mail.subject,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Determine content type and body
|
|
24
|
+
if mail.multipart?
|
|
25
|
+
# For multipart messages, prefer HTML over plain text
|
|
26
|
+
html_part = mail.html_part
|
|
27
|
+
text_part = mail.text_part
|
|
28
|
+
|
|
29
|
+
if html_part
|
|
30
|
+
mail_params[:body] = html_part.body.decoded
|
|
31
|
+
mail_params[:content_type] = "text/html"
|
|
32
|
+
elsif text_part
|
|
33
|
+
mail_params[:body] = text_part.body.decoded
|
|
34
|
+
mail_params[:content_type] = "text/plain"
|
|
35
|
+
else
|
|
36
|
+
mail_params[:body] = mail.body.decoded
|
|
37
|
+
mail_params[:content_type] = mail.content_type
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
mail_params[:body] = mail.body.decoded
|
|
41
|
+
mail_params[:content_type] = mail.content_type || "text/plain"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Send via the Aha::Mail API
|
|
45
|
+
Aha::Mail.send_mail(mail_params)
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
# ActionMailer expects delivery methods to raise exceptions on failure
|
|
48
|
+
raise "Failed to deliver mail via Aha::Mail: #{e.message}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def format_addresses(addresses)
|
|
54
|
+
return [] if addresses.nil?
|
|
55
|
+
|
|
56
|
+
Array(addresses).map do |address|
|
|
57
|
+
if address.is_a?(::Mail::Address)
|
|
58
|
+
address.address
|
|
59
|
+
else
|
|
60
|
+
address
|
|
61
|
+
end
|
|
62
|
+
end.compact
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/aha/mail.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "mail/delivery_method"
|
|
4
|
+
|
|
5
|
+
module Aha
|
|
6
|
+
module Mail
|
|
7
|
+
class << self
|
|
8
|
+
def send_mail(mail_params)
|
|
9
|
+
client.send(:post, "/api/core/mail/send", mail: mail_params)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def client
|
|
15
|
+
Aha::Auth.send(:client)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/aha/version.rb
CHANGED
data/lib/aha_builder_core.rb
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module AhaBuilderCore
|
|
6
|
+
class EmailGenerator < Rails::Generators::Base
|
|
7
|
+
ACTION_MAILER_INITIALIZER_PATH = "config/initializers/action_mailer.rb"
|
|
8
|
+
DELIVERY_METHOD_REGISTRATION_LINE = "ActionMailer::Base.add_delivery_method :aha_mail, Aha::Mail::DeliveryMethod"
|
|
9
|
+
DELIVERY_METHOD_LINE = "config.action_mailer.delivery_method = :aha_mail"
|
|
10
|
+
|
|
11
|
+
def configure_action_mailer_initializer
|
|
12
|
+
if File.exist?(ACTION_MAILER_INITIALIZER_PATH)
|
|
13
|
+
ensure_line_present(ACTION_MAILER_INITIALIZER_PATH, DELIVERY_METHOD_REGISTRATION_LINE)
|
|
14
|
+
else
|
|
15
|
+
create_file ACTION_MAILER_INITIALIZER_PATH, <<~RUBY
|
|
16
|
+
# frozen_string_literal: true
|
|
17
|
+
|
|
18
|
+
#{DELIVERY_METHOD_REGISTRATION_LINE}
|
|
19
|
+
RUBY
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configure_environment_delivery_methods
|
|
24
|
+
environment "#{DELIVERY_METHOD_LINE}\n", env: %w[development production]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def display_instructions
|
|
28
|
+
say "\nActionMailer configured to use Aha::Mail delivery!", :green
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def ensure_line_present(path, line)
|
|
34
|
+
contents = File.read(path)
|
|
35
|
+
return if contents.include?(line)
|
|
36
|
+
|
|
37
|
+
separator = contents.end_with?("\n") ? "" : "\n"
|
|
38
|
+
append_to_file path, "#{separator}#{line}\n"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aha_builder_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aha! Labs Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -115,6 +115,7 @@ files:
|
|
|
115
115
|
- LICENSE
|
|
116
116
|
- README.md
|
|
117
117
|
- lib/aha/auth.rb
|
|
118
|
+
- lib/aha/auth/builder_discovery_service.rb
|
|
118
119
|
- lib/aha/auth/client.rb
|
|
119
120
|
- lib/aha/auth/configuration.rb
|
|
120
121
|
- lib/aha/auth/errors.rb
|
|
@@ -123,6 +124,8 @@ files:
|
|
|
123
124
|
- lib/aha/auth/token_cache.rb
|
|
124
125
|
- lib/aha/auth/user.rb
|
|
125
126
|
- lib/aha/auth/users_resource.rb
|
|
127
|
+
- lib/aha/mail.rb
|
|
128
|
+
- lib/aha/mail/delivery_method.rb
|
|
126
129
|
- lib/aha/user_guide.rb
|
|
127
130
|
- lib/aha/version.rb
|
|
128
131
|
- lib/aha_builder_core.rb
|
|
@@ -136,6 +139,8 @@ files:
|
|
|
136
139
|
- lib/generators/aha_builder_core/blob_storage/USAGE
|
|
137
140
|
- lib/generators/aha_builder_core/blob_storage/blob_storage_generator.rb
|
|
138
141
|
- lib/generators/aha_builder_core/blob_storage/templates/storage.yml.tt
|
|
142
|
+
- lib/generators/aha_builder_core/email/USAGE
|
|
143
|
+
- lib/generators/aha_builder_core/email/email_generator.rb
|
|
139
144
|
homepage: https://www.aha.io
|
|
140
145
|
licenses:
|
|
141
146
|
- MIT
|