aha_builder_core 1.0.16 → 1.0.18

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 040e466bb6704c03036a10c3b1242abe7180212e6c34319ff6377d090f1f5373
4
- data.tar.gz: 8fae9ac629308b1f7ecf306f3f5fee5b362f181fab5dd2a2f75502e0ed22dccf
3
+ metadata.gz: 30124379c106a7a57827a0d4ef2a0b32d60d4805cb9e791ce52f7813fd95d002
4
+ data.tar.gz: 1c189283a64adcde157dd6fee5fe641f7fd87ddbe9599f866028bfe4281a985f
5
5
  SHA512:
6
- metadata.gz: 16711e2de018b08fdedcb47a92d5e1a6a3ea3862d88d786c8ce609d76de9f620293760cbb74bb7cff68d243133ee0ad6041020c02e98ee7d6210d68ea234b7a3
7
- data.tar.gz: 883be935bafe88f56091079f1b38cdcc3104b115d3f1d3e633bb78d501e9c0b9d7df9b3010416162343b7c943623eb124645bbf2282dc8336b5be2c691308534
6
+ metadata.gz: d5b490c19d61cc5bcef3987b68bcada8e177393b382546e35d4c11c34647fb5c5897801cc5efbb89b60b0b18b828845a122a4e8faeb817d1c5f0e01f1e010c71
7
+ data.tar.gz: 48a9e14a55b43af0f588038a7ff72caec4cf894511fa8b522b3d0da20f410887f9a83ed56d6c3a0745a6848e903aef8ef1c284163412adecc3ac49f0a354847a
data/README.md CHANGED
@@ -1,10 +1,6 @@
1
1
  # Aha Builder Core Client
2
2
 
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.
3
+ Ruby client for Aha! Builder core authentication services which provides login and signup using email/password, social logins (Google, Github, Microsoft), SAML SSO and password reset.
8
4
 
9
5
  ## Installation
10
6
 
@@ -118,10 +114,15 @@ end
118
114
 
119
115
  ### Logout
120
116
 
117
+ Implement logout by making a Ruby call to the library.
118
+
121
119
  ```ruby
122
120
  Aha::Auth.logout(session_token: session_token)
123
121
  ```
124
122
 
123
+ After logout the user should be redirected back to the login page. The front-end
124
+ should not use XHR for the logout request since it can't handle a redirect.
125
+
125
126
  ## User Management
126
127
 
127
128
  Server-to-server operations (requires `api_key`):
@@ -205,20 +206,3 @@ Each page hash contains:
205
206
  - `position` - Sort order (integer)
206
207
  - `children_count` - Number of child pages
207
208
  - `content_html` - HTML content (only in `load_page` response)
208
-
209
- ## Email Sending
210
-
211
- Aha! Builder core provides a mail API that can be used as an ActionMailer delivery method.
212
-
213
- ### ActionMailer Integration
214
-
215
- Configure your Rails application to send emails through Aha! Builder core:
216
-
217
- ```ruby
218
- # config/initializers/action_mailer.rb
219
- ActionMailer::Base.add_delivery_method :aha_mail, Aha::Mail::DeliveryMethod
220
-
221
- # config/environments/production.rb
222
- config.action_mailer.delivery_method = :aha_mail
223
- ```
224
-
@@ -49,27 +49,27 @@ module Aha
49
49
  # @param refresh_token [String, nil] Optional refresh token for automatic refresh
50
50
  # @return [Session] Session validation result
51
51
  def validate_session(session_token, refresh_token: nil)
52
+ claims = nil
53
+
52
54
  begin
53
55
  claims = decode_and_verify_token(session_token)
54
- return Session.from_claims(claims) if claims
55
56
  rescue ExpiredTokenError
56
57
  # Token has expired, attempt refresh
57
58
  end
58
59
 
59
- if refresh_token
60
- begin
61
- tokens = authenticate_with_refresh_token(refresh_token: refresh_token)
62
- new_claims = decode_and_verify_token(tokens[:session_token])
63
-
64
- return Session.from_claims(
65
- new_claims || claims,
66
- refreshed: true,
67
- new_session_token: tokens[:session_token],
68
- new_refresh_token: tokens[:refresh_token]
69
- )
70
- rescue Error
71
- return Session.invalid
60
+ if claims
61
+ # Valid session - check if we should proactively refresh based on lifetime
62
+ if refresh_token && token_needs_refresh?(claims)
63
+ # If refresh fails, we still want to return the existing session as valid
64
+ # until it fully expires, so we don't disrupt active users
65
+ return attempt_refresh(refresh_token) || Session.from_claims(claims)
72
66
  end
67
+
68
+ return Session.from_claims(claims)
69
+ end
70
+
71
+ if refresh_token
72
+ return attempt_refresh(refresh_token) || Session.invalid
73
73
  end
74
74
 
75
75
  Session.invalid
@@ -101,6 +101,36 @@ module Aha
101
101
 
102
102
  private
103
103
 
104
+ def attempt_refresh(refresh_token)
105
+ tokens = authenticate_with_refresh_token(refresh_token: refresh_token)
106
+ new_claims = decode_and_verify_token(tokens[:session_token])
107
+ Session.from_claims(
108
+ new_claims,
109
+ refreshed: true,
110
+ new_session_token: tokens[:session_token],
111
+ new_refresh_token: tokens[:refresh_token]
112
+ )
113
+ rescue Error => e
114
+ Rails.logger.error("Session refresh failed: #{e.message}")
115
+ nil
116
+ end
117
+
118
+ # Determine if a token is approaching expiration and should be proactively refreshed
119
+ #
120
+ # @param claims [Hash] The decoded JWT claims
121
+ # @return [Boolean] true if the token should be refreshed
122
+ def token_needs_refresh?(claims)
123
+ iat = claims["iat"]
124
+ exp = claims["exp"]
125
+ return false unless iat && exp
126
+
127
+ lifetime = exp - iat
128
+ return false if lifetime <= 0
129
+
130
+ elapsed = Time.now.to_i - iat
131
+ elapsed.to_f / lifetime >= @configuration.refresh_lifetime_fraction
132
+ end
133
+
104
134
  def decode_and_verify_token(token)
105
135
  # First decode without verification to get the header
106
136
  header = JWT.decode(token, nil, false).last
@@ -15,8 +15,9 @@ module Aha
15
15
  # How long to cache JWKS keys (default: 1 hour)
16
16
  attr_accessor :jwks_cache_ttl
17
17
 
18
- # Number of seconds before token expiry to trigger refresh (default: 120)
19
- attr_accessor :refresh_threshold
18
+ # Fraction of token lifetime after which to attempt proactive refresh (default: 0.5)
19
+ # e.g. at 0.5, a 15-minute token triggers refresh after 7.5 minutes
20
+ attr_accessor :refresh_lifetime_fraction
20
21
 
21
22
  # HTTP timeout in seconds (default: 30)
22
23
  attr_accessor :timeout
@@ -30,9 +31,9 @@ module Aha
30
31
  def initialize
31
32
  @server_url = ENV.fetch("AHA_CORE_SERVER_URL", "https://secure.aha.io/api/core")
32
33
  @api_key = ENV.fetch("AHA_CORE_API_KEY", nil)
33
- @client_id = ENV.fetch("APPLICATION_ID", nil)
34
+ @client_id = "#{ENV.fetch("APPLICATION_ID", nil)}.#{Rails.env.development? ? 'dev' : 'prod'}"
34
35
  @jwks_cache_ttl = 3600 # 1 hour
35
- @refresh_threshold = 120 # 2 minutes
36
+ @refresh_lifetime_fraction = 0.5
36
37
  @timeout = 30
37
38
  @enable_logging = false
38
39
  @logger = Rails.logger
data/lib/aha/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aha
4
- VERSION = "1.0.16"
4
+ VERSION = "1.0.18"
5
5
  end
@@ -2,4 +2,3 @@
2
2
 
3
3
  require_relative "aha/auth"
4
4
  require_relative "aha/user_guide"
5
- require_relative "aha/mail"
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.16
4
+ version: 1.0.18
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-02-14 00:00:00.000000000 Z
11
+ date: 2026-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -123,8 +123,6 @@ files:
123
123
  - lib/aha/auth/token_cache.rb
124
124
  - lib/aha/auth/user.rb
125
125
  - lib/aha/auth/users_resource.rb
126
- - lib/aha/mail.rb
127
- - lib/aha/mail/delivery_method.rb
128
126
  - lib/aha/user_guide.rb
129
127
  - lib/aha/version.rb
130
128
  - lib/aha_builder_core.rb
@@ -151,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
149
  requirements:
152
150
  - - ">="
153
151
  - !ruby/object:Gem::Version
154
- version: 3.3.0
152
+ version: 3.2.0
155
153
  required_rubygems_version: !ruby/object:Gem::Requirement
156
154
  requirements:
157
155
  - - ">="
@@ -1,66 +0,0 @@
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.to_s
59
- else
60
- address
61
- end
62
- end.compact
63
- end
64
- end
65
- end
66
- end
data/lib/aha/mail.rb DELETED
@@ -1,19 +0,0 @@
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