aha_builder_core 1.0.4 → 1.0.5

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: 67c88ef564275000006548bd40149fb9d8c9c6a28d7b9faf430a8dd6b338147c
4
- data.tar.gz: 10401cac65cfa9f711640911038c12491acaf73b160f340851becdce92edfbb2
3
+ metadata.gz: 8242fcb16eead2a99076077e2722d5815501f5fc3234eebfbf53a37648d79e5d
4
+ data.tar.gz: 854469a275e4e2cc06469e9d3fd967c3039b565c66ca344ac2df39f37daf94d0
5
5
  SHA512:
6
- metadata.gz: f8c526217a495e3e9ec239c4d169eb0848f9bcc6f876ba92f02f114a420f0220313e7960ef1a56b1271c0d270f2906480a5524510e988ecda939eca8491e3f10
7
- data.tar.gz: 1eb5ccd35517e18df03ea87a2c93b4eadea3152d79023f440ee19c9930f600543085c6c6ab2e45fccbfb3ee0df1a1bd14b0223ca11315e8826e2d655f5df5880
6
+ metadata.gz: 51e7277709855e865c53940dfec85081cab5c9c2b0b74c53cfd383a70c5cc01ad4cd725451f0268ae3515d2c0985ec17704ae3fb1d51a4f8204a398f438aa13b
7
+ data.tar.gz: 9385a9e023d76fc58f4dd802cb0dcacd2f773e055f75b549e2be59f11c2832dd9cc357de0c31c13e857ccf85e9645d26c760b1a765e8a61f9f95886f0976b50d
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Aha
4
4
  module Auth
5
- VERSION = "1.0.4"
5
+ VERSION = "1.0.5"
6
6
  end
7
7
  end
@@ -0,0 +1,27 @@
1
+ Description:
2
+ Generates authentication scaffolding for Aha Builder Core integration.
3
+ Creates User model, migration, SessionsController, Authentication concern,
4
+ Current model, and routes.
5
+
6
+ Example:
7
+ bin/rails generate aha_builder_core:auth
8
+
9
+ This will create:
10
+ app/models/user.rb
11
+ app/models/current.rb
12
+ app/controllers/sessions_controller.rb
13
+ app/controllers/concerns/authentication.rb
14
+ db/migrate/XXXXXXXXXXXXXX_create_users.rb
15
+
16
+ And add routes:
17
+ get "login" => "sessions#new"
18
+ get "callback" => "sessions#callback"
19
+ delete "logout" => "sessions#logout"
20
+
21
+ You can add custom attributes to the User model:
22
+ bin/rails generate aha_builder_core:auth company:string role:string
23
+
24
+ This adds company and role columns to the users table migration.
25
+
26
+ Options:
27
+ --skip-migration Skip generating the database migration
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
+
6
+ module AhaBuilderCore
7
+ class AuthGenerator < Rails::Generators::Base
8
+ include ActiveRecord::Generators::Migration
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ argument :attributes, type: :array, default: [], banner: "field[:type] field[:type]"
13
+
14
+ class_option :skip_migration, type: :boolean, default: false, desc: "Skip migration generation"
15
+
16
+ def generate_migration_file
17
+ return if options[:skip_migration]
18
+
19
+ migration_template "migration.rb.tt", File.join(db_migrate_path, "create_users.rb")
20
+ end
21
+
22
+ def create_user_model
23
+ template "user.rb.tt", "app/models/user.rb"
24
+ end
25
+
26
+ def create_current_model
27
+ template "current.rb.tt", "app/models/current.rb"
28
+ end
29
+
30
+ def create_sessions_controller
31
+ template "sessions_controller.rb.tt", "app/controllers/sessions_controller.rb"
32
+ end
33
+
34
+ def create_authentication_concern
35
+ template "authentication.rb.tt", "app/controllers/concerns/authentication.rb"
36
+ end
37
+
38
+ def add_routes
39
+ route <<~RUBY
40
+ get "login", to: "sessions#new", as: :new_session
41
+ get "callback", to: "sessions#callback", as: :session_callback
42
+ delete "logout", to: "sessions#logout", as: :logout
43
+ RUBY
44
+ end
45
+
46
+ def add_inertia_auth_share
47
+ return unless File.exist?("app/controllers/inertia_controller.rb")
48
+
49
+ inject_into_file "app/controllers/inertia_controller.rb",
50
+ after: "inertia_share flash: -> { flash.to_hash }\n" do
51
+ <<-RUBY
52
+ inertia_share auth: -> {
53
+ {
54
+ user: current_user&.as_json(only: %i[id email first_name last_name])
55
+ }
56
+ }
57
+ RUBY
58
+ end
59
+ end
60
+
61
+ def add_authentication_to_application_controller
62
+ inject_into_file "app/controllers/application_controller.rb",
63
+ after: "class ApplicationController < ActionController::Base\n" do
64
+ " include Authentication\n"
65
+ end
66
+ end
67
+
68
+ def add_typescript_types
69
+ types_file = "app/frontend/types/index.ts"
70
+ return unless File.exist?(types_file)
71
+
72
+ gsub_file types_file,
73
+ /export interface Flash \{\n alert\?: string\n notice\?: string\n\}\n\nexport interface SharedData \{\n flash: Flash\n \[key: string\]: unknown\n\}/,
74
+ <<~TYPESCRIPT.chomp
75
+ export interface Flash {
76
+ alert?: string
77
+ notice?: string
78
+ }
79
+
80
+ export interface AuthUser {
81
+ id: number
82
+ email: string
83
+ first_name: string | null
84
+ last_name: string | null
85
+ }
86
+
87
+ export interface SharedData {
88
+ flash: Flash
89
+ auth: {
90
+ user: AuthUser | null
91
+ }
92
+ [key: string]: unknown
93
+ }
94
+ TYPESCRIPT
95
+ end
96
+
97
+ def generate_js_routes
98
+ say "\nRegenerating js-routes file...", :green
99
+ rails_command "js:routes"
100
+ end
101
+
102
+ def display_instructions
103
+ say "\nAha Auth setup complete!", :green
104
+ end
105
+
106
+ private
107
+
108
+ def auth_attributes
109
+ %w[
110
+ auth_identifier:string
111
+ email:string
112
+ first_name:string
113
+ last_name:string
114
+ email_verified:boolean
115
+ ]
116
+ end
117
+
118
+ def all_attributes
119
+ @all_attributes ||= (auth_attributes + attributes.map(&:to_s)).map do |attr|
120
+ Rails::Generators::GeneratedAttribute.parse(attr)
121
+ end
122
+ end
123
+
124
+ def custom_attributes
125
+ @custom_attributes ||= attributes.map do |attr|
126
+ Rails::Generators::GeneratedAttribute.parse(attr.to_s)
127
+ end
128
+ end
129
+
130
+ def migration_class_name
131
+ "CreateUsers"
132
+ end
133
+
134
+ def table_name
135
+ "users"
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,57 @@
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
+ return unless session[:session_token].present?
13
+
14
+ session_result = Aha::Auth.validate_session(
15
+ session[:session_token],
16
+ refresh_token: session[:refresh_token]
17
+ )
18
+
19
+ if session_result.valid?
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
+ @current_user = User.find_by(id: session[:user_id])
26
+ Current.user = @current_user
27
+ else
28
+ clear_session
29
+ redirect_to login_path, alert: "Your session has expired. Please log in again."
30
+ end
31
+ rescue Aha::Auth::ApiError => e
32
+ Rails.logger.error "Session validation failed: #{e.message}"
33
+ clear_session
34
+ redirect_to login_path, alert: "Authentication error. Please log in again."
35
+ end
36
+
37
+ def current_user
38
+ @current_user ||= Current.user
39
+ end
40
+
41
+ def logged_in?
42
+ current_user.present?
43
+ end
44
+
45
+ def require_authentication
46
+ return if logged_in?
47
+
48
+ redirect_to login_path(return_to: request.fullpath), alert: "You must be logged in to access this page."
49
+ end
50
+
51
+ def clear_session
52
+ session.delete(:session_token)
53
+ session.delete(:refresh_token)
54
+ session.delete(:user_id)
55
+ @current_user = nil
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ class Current < ActiveSupport::CurrentAttributes
2
+ attribute :user
3
+ end
@@ -0,0 +1,21 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :auth_identifier, null: false
5
+ t.string :email, null: false
6
+ t.string :first_name
7
+ t.string :last_name
8
+ t.boolean :email_verified, default: false, null: false
9
+ <% custom_attributes.each do |attribute| -%>
10
+ <% unless attribute.virtual? -%>
11
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
12
+ <% end -%>
13
+ <% end -%>
14
+
15
+ t.timestamps
16
+ end
17
+
18
+ add_index :users, :auth_identifier, unique: true
19
+ add_index :users, :email, unique: true
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ class SessionsController < ApplicationController
2
+ skip_before_action :authenticate, only: [ :new, :callback ]
3
+
4
+ def new
5
+ redirect_to Aha::Auth.login_url(
6
+ state: { return_to: params[:return_to] || root_path }.to_json,
7
+ session:
8
+ ), allow_other_host: true
9
+ end
10
+
11
+ def callback
12
+ if params[:code].present?
13
+ result = Aha::Auth.authenticate_with_code(code: params[:code], session:)
14
+
15
+ user = User.find_or_initialize_by(auth_identifier: result[:user]["id"])
16
+
17
+ user.update!(
18
+ email: result[:user]["email"],
19
+ first_name: result[:user]["first_name"],
20
+ last_name: result[:user]["last_name"],
21
+ email_verified: result[:user]["email_verified"]
22
+ )
23
+
24
+ session[:session_token] = result[:session_token]
25
+ session[:refresh_token] = result[:refresh_token]
26
+ session[:user_id] = user.id
27
+
28
+ state = JSON.parse(params[:state]) rescue {}
29
+ redirect_to state["return_to"] || root_path
30
+ else
31
+ redirect_to new_session_path, alert: "Authentication failed. Please try again."
32
+ end
33
+ rescue Aha::Auth::ApiError => e
34
+ Rails.logger.error "Authentication failed: #{e.message}"
35
+ redirect_to new_session_path, alert: "Authentication failed. Please try again."
36
+ end
37
+
38
+ def logout
39
+ if session[:session_token]
40
+ begin
41
+ Aha::Auth.logout(session_token: session[:session_token])
42
+ rescue => e
43
+ Rails.logger.error "Logout error: #{e.message}"
44
+ end
45
+ end
46
+
47
+ clear_session
48
+ redirect_to root_path, notice: "Successfully signed out."
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ class User < ApplicationRecord
2
+ validates :auth_identifier, presence: true, uniqueness: true
3
+ validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
4
+
5
+ def name
6
+ "#{first_name} #{last_name}".strip.presence || email
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ Description:
2
+ Configures Active Storage to use Aha blob storage service.
3
+
4
+ This generator sets up S3-compatible blob storage for file uploads
5
+ using environment variables provided by the container orchestrator.
6
+
7
+ Example:
8
+ bin/rails generate aha_builder_core:blob_storage
9
+
10
+ This will:
11
+ - Run active_storage:install to create required migrations
12
+ - Add aws-sdk-s3 gem to Gemfile
13
+ - Update config/storage.yml with blob storage configuration
14
+ - Set config.active_storage.service = :blob in development.rb
15
+ - Set config.active_storage.service = :blob in production.rb
16
+
17
+ Prerequisites:
18
+ The container must have blob storage attached via the SetupBlobStorageTool
19
+ which sets the required BLOB_UPLOADS_S3_* environment variables.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module AhaBuilderCore
6
+ class BlobStorageGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ def install_active_storage
10
+ run "bin/rails active_storage:install"
11
+ end
12
+
13
+ def add_aws_sdk_gem
14
+ gem "aws-sdk-s3", require: false unless gem_exists?("aws-sdk-s3")
15
+ end
16
+
17
+ def update_storage_yml
18
+ template "storage.yml.tt", "config/storage.yml", force: true
19
+ end
20
+
21
+ def update_development_environment
22
+ gsub_file "config/environments/development.rb",
23
+ /config\.active_storage\.service\s*=\s*:\w+/,
24
+ "config.active_storage.service = :blob"
25
+ end
26
+
27
+ def update_production_environment
28
+ gsub_file "config/environments/production.rb",
29
+ /config\.active_storage\.service\s*=\s*:\w+/,
30
+ "config.active_storage.service = :blob"
31
+ end
32
+
33
+ def display_instructions
34
+ say "\nBlob storage configured!", :green
35
+ end
36
+
37
+ private
38
+
39
+ def gem_exists?(gem_name)
40
+ File.read("Gemfile").include?(gem_name)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ test:
2
+ service: Disk
3
+ root: <%%= Rails.root.join("tmp/storage") %>
4
+
5
+ local:
6
+ service: Disk
7
+ root: <%%= Rails.root.join("storage") %>
8
+
9
+ blob:
10
+ service: S3
11
+ endpoint: <%%= ENV.fetch('BLOB_UPLOADS_S3_ENDPOINT') %>
12
+ access_key_id: <%%= ENV.fetch('BLOB_UPLOADS_S3_ACCESS_KEY_ID') %>
13
+ secret_access_key: <%%= ENV.fetch('BLOB_UPLOADS_S3_SECRET_ACCESS_KEY') %>
14
+ region: <%%= ENV.fetch('BLOB_UPLOADS_S3_REGION', 'us-east-1') %>
15
+ bucket: <%%= ENV.fetch('BLOB_UPLOADS_S3_BUCKET') %>
16
+ force_path_style: true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aha_builder_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aha! Labs Inc.
@@ -125,6 +125,16 @@ files:
125
125
  - lib/aha/auth/users_resource.rb
126
126
  - lib/aha/auth/version.rb
127
127
  - lib/aha_builder_core.rb
128
+ - lib/generators/aha_builder_core/auth/USAGE
129
+ - lib/generators/aha_builder_core/auth/auth_generator.rb
130
+ - lib/generators/aha_builder_core/auth/templates/authentication.rb.tt
131
+ - lib/generators/aha_builder_core/auth/templates/current.rb.tt
132
+ - lib/generators/aha_builder_core/auth/templates/migration.rb.tt
133
+ - lib/generators/aha_builder_core/auth/templates/sessions_controller.rb.tt
134
+ - lib/generators/aha_builder_core/auth/templates/user.rb.tt
135
+ - lib/generators/aha_builder_core/blob_storage/USAGE
136
+ - lib/generators/aha_builder_core/blob_storage/blob_storage_generator.rb
137
+ - lib/generators/aha_builder_core/blob_storage/templates/storage.yml.tt
128
138
  homepage: https://www.aha.io
129
139
  licenses:
130
140
  - MIT