aha_builder_core 1.0.1 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cec0f75d63cf8393eb2c6130cefa3ff8f06c041536a2fc8062ca229c13647697
4
- data.tar.gz: a8759f979fe6256c4627fe0bca73c4fe2dca1e0ca1f6874b03b145bfa46241f0
3
+ metadata.gz: 3bfa1fb612c68e9997d4058381ef9c4b6b8d7eb61ddcbb52800fac41ace7fcd0
4
+ data.tar.gz: e673fd8edc82b0ae0f1a7c32ecf82bdd6c8368ebe2b8b626dc4e5d1cf6d6b696
5
5
  SHA512:
6
- metadata.gz: f4e44c3f8cf0c1eb89f179ad97562e913f86e4621eba409b7868ff1cf26f24aff42810951430ea0e88a1e22c61aac7991baf9c37fc38472c1f2d6b34ef384220
7
- data.tar.gz: 39a1ea75024a765fa7f74ed6b223d0de67d3aa5fb336dad99d644f42516cd8a3514e81535f7ebf0a4c4fe07395c3239bbea2b3f2f8c39522929879d0729c7f75
6
+ metadata.gz: a2b43eeff2fbea68a9b7b908097ca88bf858504035671808c8dac2716b0621d37219c06f92aaed8d9be7eb6571da665a1f8fed95f52cf02f3d02ba4151c29898
7
+ data.tar.gz: 927e0f48d3a1d2e4cb492ccefa7cb3bc377468ac7c57b3896abcaaa449a3b85a42cad99392927f0733127b28a7fe612d0f365c55060993b3c545c8c193048606
data/README.md CHANGED
@@ -12,20 +12,7 @@ gem "aha_builder_core", path: "engines/builder_core/client"
12
12
 
13
13
  ## Configuration
14
14
 
15
- The configuration is usually read from the environment automatically so there is no need for custom configuration in most cases.
16
-
17
- ```ruby
18
- require "aha_builder_core"
19
-
20
- Aha::Auth.configure do |config|
21
- config.server_url = "https://secure.aha.io" # Auth server URL
22
- config.client_id = "your-client-id" # ID of the builder application
23
- config.api_key = "your-api-key" # For server-to-server auth
24
- config.jwks_cache_ttl = 3600 # JWKS cache duration (default: 1 hour)
25
- config.refresh_threshold = 120 # Seconds before expiry to refresh (default: 2 min)
26
- config.timeout = 30 # HTTP timeout in seconds
27
- end
28
- ```
15
+ No configuration is necessary and no environment variables are necessary.
29
16
 
30
17
  ## Authentication Flow
31
18
 
@@ -38,11 +25,12 @@ Redirect users to the authentication and signup UI:
38
25
  ```ruby
39
26
  url = Aha::Auth.login_url(
40
27
  state: { return_to: "/" }.to_json,
41
- redirect_uri: "#{ENV['APPLICATION_URL']}/callback"
42
28
  )
43
29
  redirect_to url
44
30
  ```
45
31
 
32
+ Redirecting to the login_url must be done with a full page load. It will not work using XHR.
33
+
46
34
  ### Exchange Authorization Code
47
35
 
48
36
  In the `/callback` action callback, you must exchange the code for a session token and refresh token:
@@ -54,11 +42,11 @@ result = Aha::Auth.authenticate_with_code(code: params[:code])
54
42
  # refresh_token: "...",
55
43
  # expires_at: Time,
56
44
  # user: {
57
- # "id" => "user-uuid",
58
- # "first_name" => "Jane",
59
- # "last_name" => "Doe",
60
- # "email" => "jane.doe@example.com",
61
- # "email_verified" => true
45
+ # id: "user-uuid",
46
+ # first_name: "Jane",
47
+ # last_name: "Doe",
48
+ # email: "jane.doe@example.com",
49
+ # email_verified: true
62
50
  # }
63
51
  # }
64
52
  ```
@@ -68,8 +56,8 @@ result = Aha::Auth.authenticate_with_code(code: params[:code])
68
56
  The `user` object returned contains the authenticated user's profile information from the Builder Core system:
69
57
 
70
58
  - `id`: The unique identifier for the user in Builder Core
71
- - `first_name`: User's first name
72
- - `last_name`: User's last name
59
+ - `first_name`: User's first name. first_name is optional and may not be present.
60
+ - `last_name`: User's last name. last_name is optional and may not be present.
73
61
  - `email`: User's email address
74
62
  - `email_verified`: Boolean indicating if the email has been verified
75
63
 
@@ -86,10 +74,10 @@ local_user = User.find_or_initialize_by(auth_identifier: result[:user]["id"])
86
74
 
87
75
  # Update local user attributes
88
76
  local_user.update!(
89
- email: result[:user]["email"],
90
- first_name: result[:user]["first_name"],
91
- last_name: result[:user]["last_name"],
92
- email_verified: result[:user]["email_verified"]
77
+ email: result[:user][:email],
78
+ first_name: result[:user][:first_name],
79
+ last_name: result[:user][:last_name],
80
+ email_verified: result[:user][:email_verified]
93
81
  )
94
82
 
95
83
  # Store tokens in session or database
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
3
5
  module Aha
4
6
  module Auth
5
7
  # HTTP client for communicating with the BuilderCore auth server
@@ -47,14 +49,16 @@ module Aha
47
49
  # @param refresh_token [String, nil] Optional refresh token for automatic refresh
48
50
  # @return [Session] Session validation result
49
51
  def validate_session(session_token, refresh_token: nil)
50
- claims = decode_and_verify_token(session_token)
51
- return Session.invalid unless claims
52
+ begin
53
+ claims = decode_and_verify_token(session_token)
54
+ return Session.from_claims(claims) if claims
55
+ rescue ExpiredTokenError
56
+ # Token has expired, attempt refresh
57
+ end
52
58
 
53
- # Check if token is about to expire and we have a refresh token
54
- exp = claims["exp"]
55
- if exp && refresh_token && should_refresh?(exp)
59
+ if refresh_token
56
60
  begin
57
- tokens = refresh_tokens(refresh_token: refresh_token)
61
+ tokens = authenticate_with_refresh_token(refresh_token: refresh_token)
58
62
  new_claims = decode_and_verify_token(tokens[:session_token])
59
63
 
60
64
  return Session.from_claims(
@@ -64,13 +68,11 @@ module Aha
64
68
  new_refresh_token: tokens[:refresh_token]
65
69
  )
66
70
  rescue Error
67
- puts "Token refresh failed"
68
- # Refresh failed, return session with original token if still valid
69
- return Session.invalid if Time.at(exp).utc < Time.now.utc
71
+ return Session.invalid
70
72
  end
71
73
  end
72
74
 
73
- Session.from_claims(claims)
75
+ Session.invalid
74
76
  rescue InvalidTokenError, ExpiredTokenError
75
77
  Session.invalid
76
78
  end
@@ -80,7 +82,7 @@ module Aha
80
82
  # @param session_token [String] The session token to revoke
81
83
  # @return [Boolean] true if successful
82
84
  def logout(session_token:)
83
- get("/api/core/auth/logout", headers: { "Authorization" => "Bearer #{session_token}" })
85
+ get("/api/core/auth_ui/logout", headers: { "Authorization" => "Bearer #{session_token}" })
84
86
  true
85
87
  rescue ApiError
86
88
  false
@@ -90,8 +92,7 @@ module Aha
90
92
  #
91
93
  # @return [Hash] The JWKS response
92
94
  def fetch_jwks
93
- response = http_client.get("/api/core/auth/jwks/#{@configuration.client_id}")
94
- handle_response(response)
95
+ get("/api/core/auth/jwks/#{@configuration.client_id}")
95
96
  end
96
97
 
97
98
  private
@@ -125,17 +126,13 @@ module Aha
125
126
  raise InvalidTokenError, "Invalid token: #{e.message}"
126
127
  end
127
128
 
128
- def should_refresh?(exp)
129
- Time.at(exp).utc - Time.now.utc <= @configuration.refresh_threshold
130
- end
131
-
132
129
  def parse_token_response(response)
133
130
  {
134
131
  session_token: response["session_token"],
135
132
  refresh_token: response["refresh_token"],
136
133
  expires_at: response["expires_at"] ? Time.iso8601(response["expires_at"]) : nil,
137
134
  user: response["user"]
138
- }
135
+ }.with_indifferent_access
139
136
  end
140
137
 
141
138
  def get(path, headers: {})
@@ -22,7 +22,7 @@ module Aha
22
22
  attr_accessor :timeout
23
23
 
24
24
  def initialize
25
- @server_url = ENV.fetch("AHA_CORE_SERVER_URL", nil) || "https://secure.aha.io/api/core"
25
+ @server_url = ENV.fetch("AHA_CORE_SERVER_URL", "https://secure.aha.io/api/core")
26
26
  @api_key = ENV.fetch("AHA_CORE_API_KEY", nil)
27
27
  @client_id = ENV.fetch("APPLICATION_ID", nil)
28
28
  @jwks_cache_ttl = 3600 # 1 hour
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+
3
5
  module Aha
4
6
  module Auth
5
7
  # API resource for session management operations
data/lib/aha/auth/user.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+
3
5
  module Aha
4
6
  module Auth
5
7
  # Represents a user from the auth server
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aha
4
4
  module Auth
5
- VERSION = "1.0.1"
5
+ VERSION = "1.0.3"
6
6
  end
7
7
  end
data/lib/aha/auth.rb CHANGED
@@ -41,15 +41,13 @@ module Aha
41
41
  # Generate login URL for redirecting users to the auth server
42
42
  #
43
43
  # @param state [String] Optional state parameter to pass through the auth flow
44
- # @param redirect_uri [String] Optional redirect URI after authentication
45
44
  # @return [String] The login URL
46
- def login_url(state: nil, redirect_uri: nil)
45
+ def login_url(state: nil)
47
46
  params = { client_id: configuration.client_id }
48
47
  params[:state] = state if state
49
- params[:redirect_uri] = redirect_uri if redirect_uri
50
48
 
51
49
  query = URI.encode_www_form(params)
52
- "#{configuration.server_url}/auth/start?#{query}"
50
+ "#{configuration.server_url}/auth_ui/start?#{query}"
53
51
  end
54
52
 
55
53
  # Exchange an authorization code for tokens
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aha_builder_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
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: 2025-12-21 00:00:00.000000000 Z
11
+ date: 2026-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: concurrent-ruby
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -111,12 +125,6 @@ files:
111
125
  - lib/aha/auth/users_resource.rb
112
126
  - lib/aha/auth/version.rb
113
127
  - lib/aha_builder_core.rb
114
- - lib/generators/aha_builder_core/authentication/USAGE
115
- - lib/generators/aha_builder_core/authentication/authentication_generator.rb
116
- - lib/generators/aha_builder_core/authentication/templates/authentication.rb.erb
117
- - lib/generators/aha_builder_core/authentication/templates/current.rb.erb
118
- - lib/generators/aha_builder_core/authentication/templates/sessions_controller.rb.erb
119
- - lib/generators/aha_builder_core/authentication/templates/user.rb.erb
120
128
  homepage: https://www.aha.io
121
129
  licenses:
122
130
  - MIT
@@ -1,21 +0,0 @@
1
- Description:
2
- Generates authentication setup for Aha! Builder Core client
3
-
4
- This generator creates:
5
- - SessionsController with new and callback actions for authentication flow
6
- - User model for storing authenticated user information
7
- - Current model for thread-isolated current user access
8
- - Authentication concern to be included in ApplicationController
9
-
10
- Example:
11
- bin/rails generate aha_builder_core:authentication
12
-
13
- This will create:
14
- app/controllers/sessions_controller.rb
15
- app/controllers/concerns/authentication.rb
16
- app/models/user.rb
17
- app/models/current.rb
18
-
19
- And add routes:
20
- get "login", to: "sessions#new", as: :new_session
21
- get "callback", to: "sessions#callback", as: :session_callback
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/generators"
4
-
5
- module AhaBuilderCore
6
- module Generators
7
- class AuthenticationGenerator < Rails::Generators::Base
8
- source_root File.expand_path("templates", __dir__)
9
-
10
- def create_sessions_controller
11
- template "sessions_controller.rb.erb", "app/controllers/sessions_controller.rb"
12
- end
13
-
14
- def create_user_model
15
- template "user.rb.erb", "app/models/user.rb"
16
- end
17
-
18
- def create_current_model
19
- template "current.rb.erb", "app/models/current.rb"
20
- end
21
-
22
- def create_authentication_concern
23
- template "authentication.rb.erb", "app/controllers/concerns/authentication.rb"
24
- end
25
-
26
- def add_routes
27
- route <<~RUBY
28
- get "login", to: "sessions#new", as: :new_session
29
- get "callback", to: "sessions#callback", as: :session_callback
30
- RUBY
31
- end
32
-
33
- def display_instructions
34
- say "\nAuthentication setup complete!", :green
35
- say "\nNext steps:"
36
- say "1. Configure your environment variables:"
37
- say " - APPLICATION_URL: Your application's base URL"
38
- say " - AHA_AUTH_SERVER_URL: The authentication server URL"
39
- say " - AHA_AUTH_CLIENT_ID: Your client ID"
40
- say " - AHA_AUTH_API_KEY: Your API key (if using server-to-server operations)"
41
- say "\n2. Run migrations to create the users table:"
42
- say " rails generate migration CreateUsers auth_identifier:string:index email:string first_name:string last_name:string email_verified:boolean"
43
- say " rails db:migrate"
44
- say "\n3. Include authentication in ApplicationController:"
45
- say " include Authentication"
46
- say "\n4. Protect routes with:"
47
- say " before_action :authenticate"
48
- end
49
-
50
- private
51
-
52
- def application_name
53
- Rails.application.class.module_parent.name
54
- end
55
- end
56
- end
57
- end
@@ -1,48 +0,0 @@
1
- module Authentication
2
- extend ActiveSupport::Concern
3
-
4
- included do
5
- before_action :authenticate
6
- helper_method :current_user, :logged_in?
7
- end
8
-
9
- private
10
-
11
- def authenticate
12
- if session[:session_token].present?
13
- session_result = Aha::Auth.validate_session(
14
- session[:session_token],
15
- refresh_token: session[:refresh_token]
16
- )
17
-
18
- if session_result.valid?
19
- # If tokens were refreshed, update stored tokens
20
- if session_result.refreshed?
21
- session[:session_token] = session_result.new_session_token
22
- session[:refresh_token] = session_result.new_refresh_token
23
- end
24
-
25
- # Set current user
26
- @current_user = User.find_by(id: session[:user_id])
27
- Current.user = @current_user
28
- else
29
- reset_session
30
- redirect_to new_session_path, alert: "Your session has expired. Please log in again."
31
- end
32
- else
33
- redirect_to new_session_path
34
- end
35
- rescue Aha::Auth::ApiError => e
36
- Rails.logger.error "Session validation failed: #{e.message}"
37
- reset_session
38
- redirect_to new_session_path, alert: "Authentication error. Please log in again."
39
- end
40
-
41
- def current_user
42
- @current_user ||= Current.user
43
- end
44
-
45
- def logged_in?
46
- current_user.present?
47
- end
48
- end
@@ -1,4 +0,0 @@
1
- # Provides a thread-isolated attributes singleton for current user
2
- class Current < ActiveSupport::CurrentAttributes
3
- attribute :user
4
- end
@@ -1,44 +0,0 @@
1
- class SessionsController < ApplicationController
2
- skip_before_action :authenticate, only: [:new, :callback]
3
-
4
- def new
5
- # Start authentication by redirecting to the auth URL
6
- redirect_to Aha::Auth.login_url(
7
- state: { return_to: params[:return_to] || root_path }.to_json,
8
- redirect_uri: "#{ENV['APPLICATION_URL']}/callback"
9
- ), allow_other_host: true
10
- end
11
-
12
- def callback
13
- # Handle the callback from the authentication server
14
- if params[:code].present?
15
- result = Aha::Auth.authenticate_with_code(code: params[:code])
16
-
17
- # Find or create a local user record linked to Builder Core user
18
- user = User.find_or_initialize_by(auth_identifier: result[:user]["id"])
19
-
20
- # Update local user attributes
21
- user.update!(
22
- email: result[:user]["email"],
23
- first_name: result[:user]["first_name"],
24
- last_name: result[:user]["last_name"],
25
- email_verified: result[:user]["email_verified"]
26
- )
27
-
28
- # Store tokens in session
29
- session[:session_token] = result[:session_token]
30
- session[:refresh_token] = result[:refresh_token]
31
- session[:user_id] = user.id
32
-
33
- # Parse state to get return_to path
34
- state = JSON.parse(params[:state]) rescue {}
35
- redirect_to state["return_to"] || root_path
36
- else
37
- # Authentication failed or was cancelled
38
- redirect_to new_session_path, alert: "Authentication failed. Please try again."
39
- end
40
- rescue Aha::Auth::ApiError => e
41
- Rails.logger.error "Authentication failed: #{e.message}"
42
- redirect_to new_session_path, alert: "Authentication failed. Please try again."
43
- end
44
- end
@@ -1,8 +0,0 @@
1
- class User < ApplicationRecord
2
- validates :auth_identifier, presence: true, uniqueness: true
3
- validates :email, presence: true, uniqueness: true
4
-
5
- def name
6
- "#{first_name} #{last_name}".strip.presence || email
7
- end
8
- end