better_auth-rails 0.1.2 → 0.2.1
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/CHANGELOG.md +8 -1
- data/README.md +140 -46
- data/lib/better_auth/rails/active_record_adapter.rb +308 -0
- data/lib/better_auth/rails/configuration.rb +53 -0
- data/lib/better_auth/rails/controller_helpers.rb +50 -0
- data/lib/better_auth/rails/migration.rb +113 -0
- data/lib/better_auth/rails/mounted_app.rb +36 -0
- data/lib/better_auth/rails/railtie.rb +17 -0
- data/lib/better_auth/rails/routing.rb +22 -0
- data/lib/better_auth/rails/version.rb +1 -1
- data/lib/better_auth/rails.rb +25 -2
- data/lib/generators/better_auth/install/install_generator.rb +34 -0
- data/lib/generators/better_auth/install/templates/initializer.rb.tt +50 -0
- data/lib/generators/better_auth/migration/migration_generator.rb +40 -0
- data/lib/tasks/better_auth.rake +17 -0
- metadata +48 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc3d5bd26fdfd519d462f2634d4187d3a52a7334864cd2fcddf43c22b88c0975
|
|
4
|
+
data.tar.gz: 9b3f4ff8abd83a3264706fbb6e8406835168d781f6a247eba896f4736b4041a6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78edf35b13082e9ab5ab0a964978dcf9fce608abcee9c080268a9868e1e7e9a24ce73033e36c3547e7b5eaacbbdf1573b695c95e2ac3805ce04f9048291f7e24
|
|
7
|
+
data.tar.gz: c9a6a504de5d9560d4223ce47a8c4f9b2b3c331b890f061bac1ae56d9ef7a9a0e30a57d4f7c683aff2a32d9869fe2dc307686aac72e453e4dd7412de070a5272
|
data/CHANGELOG.md
CHANGED
|
@@ -7,13 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.1] - 2026-04-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed Active Record adapter value lookup so falsey values are preserved across symbol, string, and storage-key variants.
|
|
15
|
+
- Fixed Rails migration generation for JSON and array-like schema fields.
|
|
16
|
+
|
|
10
17
|
## [0.1.2] - 2026-03-22
|
|
11
18
|
|
|
12
19
|
### Fixed
|
|
13
20
|
|
|
14
21
|
- Fixed gemspec files list to use `Dir.glob` instead of `git ls-files` for better CI compatibility
|
|
15
22
|
- Fixed dependency constraints for railties and activesupport (now `>= 6.0, < 9`)
|
|
16
|
-
- Fixed better_auth_rails compatibility gem dependency version
|
|
23
|
+
- Fixed `better_auth_rails` compatibility gem dependency version
|
|
17
24
|
|
|
18
25
|
## [0.1.1] - 2026-03-17
|
|
19
26
|
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Rails adapter for Better Auth Ruby. Provides seamless integration with Ruby on R
|
|
|
7
7
|
Add this line to your application's Gemfile:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
gem
|
|
10
|
+
gem "better_auth-rails"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
### Defensive alias package
|
|
@@ -29,87 +29,181 @@ bundle install
|
|
|
29
29
|
Add to your `config/application.rb`:
|
|
30
30
|
|
|
31
31
|
```ruby
|
|
32
|
-
require
|
|
32
|
+
require "better_auth/rails"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Compatibility require is also supported:
|
|
36
36
|
|
|
37
37
|
```ruby
|
|
38
|
-
require
|
|
38
|
+
require "better_auth_rails"
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Or in your Gemfile:
|
|
42
42
|
|
|
43
43
|
```ruby
|
|
44
|
-
gem
|
|
44
|
+
gem "better_auth-rails", require: "better_auth/rails"
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
###
|
|
47
|
+
### Initializer And Migration
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Create the default initializer and base migration:
|
|
50
50
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
include BetterAuth::Rails::ControllerHelpers
|
|
54
|
-
end
|
|
51
|
+
```bash
|
|
52
|
+
bin/rails generate better_auth:install
|
|
55
53
|
```
|
|
56
54
|
|
|
57
|
-
|
|
55
|
+
The same install path is available as a Rails task:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bin/rails better_auth:init
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
To generate only the base migration:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bin/rails generate better_auth:migration
|
|
65
|
+
bin/rails better_auth:generate:migration
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The generators skip an existing `config/initializers/better_auth.rb` or existing `*_create_better_auth_tables.rb` migration instead of overwriting them.
|
|
69
|
+
|
|
70
|
+
### Configuration
|
|
71
|
+
|
|
72
|
+
The install generator creates `config/initializers/better_auth.rb`:
|
|
58
73
|
|
|
59
74
|
```ruby
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
BetterAuth::Rails.configure do |config|
|
|
76
|
+
config.secret =
|
|
77
|
+
Rails.application.credentials.dig(:better_auth, :secret) ||
|
|
78
|
+
Rails.application.credentials.secret_key_base ||
|
|
79
|
+
Rails.application.secret_key_base
|
|
80
|
+
|
|
81
|
+
config.base_url = ENV["BETTER_AUTH_URL"]
|
|
82
|
+
config.base_path = "/api/auth"
|
|
83
|
+
config.database = ->(options) { BetterAuth::Rails::ActiveRecordAdapter.new(options) }
|
|
84
|
+
config.trusted_origins = [
|
|
85
|
+
ENV["BETTER_AUTH_URL"]
|
|
86
|
+
].compact
|
|
87
|
+
|
|
88
|
+
config.session = {
|
|
89
|
+
cookie_cache: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
max_age: 5 * 60,
|
|
92
|
+
strategy: "jwe"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
config.advanced = {
|
|
97
|
+
ip_address: {
|
|
98
|
+
ip_address_headers: ["x-forwarded-for"],
|
|
99
|
+
disable_ip_tracking: false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
103
|
+
config.experimental = {
|
|
104
|
+
joins: false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
config.social_providers = {
|
|
108
|
+
# github: BetterAuth::SocialProviders.github(
|
|
109
|
+
# client_id: ENV.fetch("GITHUB_CLIENT_ID"),
|
|
110
|
+
# client_secret: ENV.fetch("GITHUB_CLIENT_SECRET")
|
|
111
|
+
# )
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
config.plugins = []
|
|
115
|
+
config.hooks = {
|
|
116
|
+
before: [],
|
|
117
|
+
after: []
|
|
118
|
+
}
|
|
71
119
|
end
|
|
72
120
|
```
|
|
73
121
|
|
|
74
|
-
|
|
122
|
+
Rails configuration is a thin option builder for the core Rack auth object. The same option concepts are available in core Ruby through `BetterAuth.auth(...)`; Rails places them in `config/initializers/better_auth.rb` so applications can rely on credentials, ActiveRecord, and Rails environment configuration.
|
|
75
123
|
|
|
76
|
-
|
|
77
|
-
- `authenticate_user!` - Redirects to login if not authenticated
|
|
78
|
-
- `user_signed_in?` - Returns true if user is authenticated
|
|
79
|
-
- `sign_in(user)` - Signs in a user
|
|
80
|
-
- `sign_out` - Signs out the current user
|
|
124
|
+
The ActiveRecord adapter uses whichever database adapter the Rails app is already configured with, including PostgreSQL and MySQL.
|
|
81
125
|
|
|
82
|
-
###
|
|
126
|
+
### JavaScript Client
|
|
127
|
+
|
|
128
|
+
Ruby Better Auth exposes the same HTTP route surface. Frontend apps should use the upstream Better Auth JavaScript client and point it at the Ruby server:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { createAuthClient } from "better-auth/client";
|
|
132
|
+
|
|
133
|
+
export const authClient = createAuthClient({
|
|
134
|
+
baseURL: "http://localhost:3000",
|
|
135
|
+
basePath: "/api/auth",
|
|
136
|
+
});
|
|
137
|
+
```
|
|
83
138
|
|
|
84
|
-
|
|
139
|
+
Plugin schemas are included in generated migrations through the same configuration:
|
|
85
140
|
|
|
86
141
|
```ruby
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
config.session_options = {
|
|
94
|
-
url: ENV['REDIS_URL']
|
|
95
|
-
}
|
|
142
|
+
require "better_auth/api_key"
|
|
143
|
+
|
|
144
|
+
BetterAuth::Rails.configure do |config|
|
|
145
|
+
config.plugins = [
|
|
146
|
+
BetterAuth::Plugins.api_key
|
|
147
|
+
]
|
|
96
148
|
end
|
|
149
|
+
|
|
150
|
+
# Then regenerate before migrating if this is a new app:
|
|
151
|
+
# bin/rails generate better_auth:migration
|
|
97
152
|
```
|
|
98
153
|
|
|
99
154
|
### Routes
|
|
100
155
|
|
|
101
|
-
Mount the Better Auth
|
|
156
|
+
Mount the Better Auth Rack app in your routes:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
Rails.application.routes.draw do
|
|
160
|
+
better_auth
|
|
161
|
+
end
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
By default this mounts at `/api/auth`. Rails mounts the core Rack auth app through a small wrapper so Better Auth still sees the full auth path after Rails moves the mount prefix into `SCRIPT_NAME`. To customize the path:
|
|
102
165
|
|
|
103
166
|
```ruby
|
|
104
167
|
Rails.application.routes.draw do
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# Your routes...
|
|
168
|
+
better_auth at: "/auth"
|
|
108
169
|
end
|
|
109
170
|
```
|
|
110
171
|
|
|
172
|
+
The Better Auth core router handles internal routes such as `/callback/:providerId`.
|
|
173
|
+
|
|
174
|
+
### Controller Helpers
|
|
175
|
+
|
|
176
|
+
Include the controller helpers in your ApplicationController:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class ApplicationController < ActionController::Base
|
|
180
|
+
include BetterAuth::Rails::ControllerHelpers
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Now you have access to authentication methods:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
class PostsController < ApplicationController
|
|
188
|
+
before_action :require_authentication
|
|
189
|
+
|
|
190
|
+
def index
|
|
191
|
+
@user = current_user
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Available Methods
|
|
197
|
+
|
|
198
|
+
- `current_session` - Returns the current Better Auth session hash
|
|
199
|
+
- `current_user` - Returns the current Better Auth user hash
|
|
200
|
+
- `authenticated?` - Returns true when a user is present
|
|
201
|
+
- `require_authentication` - Halts with `head :unauthorized` and returns `false` when no user is present
|
|
202
|
+
|
|
111
203
|
## Development
|
|
112
204
|
|
|
205
|
+
Full documentation is being adapted in the root [`docs/`](/Users/sebastiansala/projects/better-auth/docs/README.md) app. The Rails guide lives at `docs/content/docs/integrations/rails.mdx`; pages with a Ruby port warning still contain upstream TypeScript examples for reference.
|
|
206
|
+
|
|
113
207
|
### Setup
|
|
114
208
|
|
|
115
209
|
```bash
|
|
@@ -125,13 +219,13 @@ bundle install
|
|
|
125
219
|
|
|
126
220
|
```bash
|
|
127
221
|
# Run all tests
|
|
128
|
-
bundle exec rspec
|
|
222
|
+
rbenv exec bundle exec rspec
|
|
129
223
|
|
|
130
224
|
# Run with coverage
|
|
131
|
-
COVERAGE=true bundle exec rspec
|
|
225
|
+
COVERAGE=true rbenv exec bundle exec rspec
|
|
132
226
|
|
|
133
227
|
# Run specific test
|
|
134
|
-
bundle exec rspec spec/better_auth/rails/controller_helpers_spec.rb
|
|
228
|
+
rbenv exec bundle exec rspec spec/better_auth/rails/controller_helpers_spec.rb
|
|
135
229
|
```
|
|
136
230
|
|
|
137
231
|
### Code Style
|
|
@@ -140,10 +234,10 @@ We use StandardRB for linting:
|
|
|
140
234
|
|
|
141
235
|
```bash
|
|
142
236
|
# Check style
|
|
143
|
-
bundle exec standardrb
|
|
237
|
+
RUBOCOP_CACHE_ROOT=/private/var/folders/7x/jrsz946d2w73n42fb1_ff5000000gn/T/rubocop_cache_rails rbenv exec bundle exec standardrb
|
|
144
238
|
|
|
145
239
|
# Auto-fix issues
|
|
146
|
-
bundle exec standardrb --fix
|
|
240
|
+
RUBOCOP_CACHE_ROOT=/private/var/folders/7x/jrsz946d2w73n42fb1_ff5000000gn/T/rubocop_cache_rails rbenv exec bundle exec standardrb --fix
|
|
147
241
|
```
|
|
148
242
|
|
|
149
243
|
## Contributing
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
class ActiveRecordAdapter < BetterAuth::Adapters::Base
|
|
6
|
+
begin
|
|
7
|
+
require "active_record" unless defined?(::ActiveRecord)
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# ActiveRecord is required only when the adapter is instantiated in a Rails app.
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if defined?(::ActiveRecord::Base)
|
|
13
|
+
class ApplicationRecord < ::ActiveRecord::Base
|
|
14
|
+
self.abstract_class = true
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
class ApplicationRecord
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :connection
|
|
22
|
+
|
|
23
|
+
def initialize(options, connection: nil)
|
|
24
|
+
super(options)
|
|
25
|
+
@connection = connection || ::ActiveRecord::Base
|
|
26
|
+
@models = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create(model:, data:, force_allow_id: false)
|
|
30
|
+
model = model.to_s
|
|
31
|
+
input = transform_input(model, data, "create", force_allow_id)
|
|
32
|
+
record = model_class(model).create!(physical_attributes(model, input))
|
|
33
|
+
normalize_record(model, record)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_one(model:, where: [], select: nil, join: nil)
|
|
37
|
+
find_many(model: model, where: where, select: select, join: join, limit: 1).first
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def find_many(model:, where: [], sort_by: nil, limit: nil, offset: nil, select: nil, join: nil)
|
|
41
|
+
model = model.to_s
|
|
42
|
+
relation = relation_for(model, where: where, sort_by: sort_by, limit: limit, offset: offset, select: select, join: join)
|
|
43
|
+
records = relation.map { |record| normalize_record(model, record, join: join) }
|
|
44
|
+
collection_join?(model, join) ? aggregate_collection_joins(records) : records
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update(model:, where:, update:)
|
|
48
|
+
model = model.to_s
|
|
49
|
+
record = relation_for(model, where: where).first
|
|
50
|
+
return nil unless record
|
|
51
|
+
|
|
52
|
+
record.update!(physical_attributes(model, transform_input(model, update, "update", true)))
|
|
53
|
+
normalize_record(model, record)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update_many(model:, where:, update:, returning: false)
|
|
57
|
+
model = model.to_s
|
|
58
|
+
attributes = physical_attributes(model, transform_input(model, update, "update", true))
|
|
59
|
+
relation = relation_for(model, where: where)
|
|
60
|
+
if returning
|
|
61
|
+
relation.map do |record|
|
|
62
|
+
record.update!(attributes)
|
|
63
|
+
normalize_record(model, record)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
relation.update_all(attributes)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def delete(model:, where:)
|
|
71
|
+
model = model.to_s
|
|
72
|
+
record = relation_for(model, where: where).first
|
|
73
|
+
record&.destroy!
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def delete_many(model:, where:)
|
|
78
|
+
relation_for(model.to_s, where: where).delete_all
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def count(model:, where: nil)
|
|
82
|
+
relation_for(model.to_s, where: where || []).count
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transaction
|
|
86
|
+
connection.connection.transaction { yield self }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def model_class(model)
|
|
92
|
+
model = model.to_s
|
|
93
|
+
return @models[model] if @models.key?(model)
|
|
94
|
+
|
|
95
|
+
klass = Class.new(ApplicationRecord)
|
|
96
|
+
model_namespace.const_set(class_name_for(model), klass)
|
|
97
|
+
klass.table_name = table_for(model) if klass.respond_to?(:table_name=)
|
|
98
|
+
klass.primary_key = storage_field(model, "id") if klass.respond_to?(:primary_key=)
|
|
99
|
+
@models[model] = klass
|
|
100
|
+
define_join_associations(model, klass)
|
|
101
|
+
klass
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def relation_for(model, where:, sort_by: nil, limit: nil, offset: nil, select: nil, join: nil)
|
|
105
|
+
relation = model_class(model).all
|
|
106
|
+
relation = apply_where(model, relation, where || [])
|
|
107
|
+
relation = apply_select(model, relation, select) if select
|
|
108
|
+
relation = apply_join_includes(model, relation, join) if join
|
|
109
|
+
relation = apply_order(model, relation, sort_by) if sort_by
|
|
110
|
+
relation = relation.limit(Integer(limit)) if limit
|
|
111
|
+
relation = relation.offset(Integer(offset)) if offset
|
|
112
|
+
relation
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def apply_where(model, relation, where)
|
|
116
|
+
Array(where).reduce(relation) do |scope, clause|
|
|
117
|
+
field = storage_key(fetch_key(clause, :field))
|
|
118
|
+
column = storage_field(model, field)
|
|
119
|
+
operator = (fetch_key(clause, :operator) || "eq").to_s
|
|
120
|
+
value = fetch_key(clause, :value)
|
|
121
|
+
apply_operator(scope, column, operator, value)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def apply_operator(scope, column, operator, value)
|
|
126
|
+
case operator
|
|
127
|
+
when "in" then scope.where(column => Array(value))
|
|
128
|
+
when "not_in" then scope.where.not(column => Array(value))
|
|
129
|
+
when "ne" then scope.where.not(column => value)
|
|
130
|
+
when "gt" then scope.where("#{column} > ?", value)
|
|
131
|
+
when "gte" then scope.where("#{column} >= ?", value)
|
|
132
|
+
when "lt" then scope.where("#{column} < ?", value)
|
|
133
|
+
when "lte" then scope.where("#{column} <= ?", value)
|
|
134
|
+
when "contains" then scope.where("#{column} LIKE ?", "%#{value}%")
|
|
135
|
+
when "starts_with" then scope.where("#{column} LIKE ?", "#{value}%")
|
|
136
|
+
when "ends_with" then scope.where("#{column} LIKE ?", "%#{value}")
|
|
137
|
+
else scope.where(column => value)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def apply_select(model, relation, select)
|
|
142
|
+
columns = Array(select).map { |field| storage_field(model, storage_key(field)) }
|
|
143
|
+
relation.select(*columns)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def apply_order(model, relation, sort_by)
|
|
147
|
+
field = storage_key(fetch_key(sort_by, :field))
|
|
148
|
+
direction = (fetch_key(sort_by, :direction).to_s.downcase == "desc") ? :desc : :asc
|
|
149
|
+
relation.order(storage_field(model, field) => direction)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def transform_input(model, data, action, force_allow_id)
|
|
153
|
+
fields = schema_for(model).fetch(:fields)
|
|
154
|
+
input = stringify_keys(data)
|
|
155
|
+
output = {}
|
|
156
|
+
fields.each do |field, attributes|
|
|
157
|
+
next if field == "id" && input.key?(field) && !force_allow_id
|
|
158
|
+
|
|
159
|
+
value_provided = input.key?(field)
|
|
160
|
+
value = input[field]
|
|
161
|
+
if !value_provided && action == "create" && attributes.key?(:default_value)
|
|
162
|
+
value = resolve_default(attributes[:default_value])
|
|
163
|
+
value_provided = true
|
|
164
|
+
elsif !value_provided && action == "update" && attributes[:on_update]
|
|
165
|
+
value = resolve_default(attributes[:on_update])
|
|
166
|
+
value_provided = true
|
|
167
|
+
end
|
|
168
|
+
output[field] = value if value_provided
|
|
169
|
+
end
|
|
170
|
+
output["id"] = SecureRandom.urlsafe_base64(16) if action == "create" && !output.key?("id")
|
|
171
|
+
output
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def physical_attributes(model, logical)
|
|
175
|
+
logical.each_with_object({}) do |(field, value), attributes|
|
|
176
|
+
attributes[storage_field(model, field)] = value
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_record(model, record, join: nil)
|
|
181
|
+
return nil unless record
|
|
182
|
+
|
|
183
|
+
attributes = record.respond_to?(:attributes) ? record.attributes : record
|
|
184
|
+
normalized = schema_for(model).fetch(:fields).each_with_object({}) do |(field, config), output|
|
|
185
|
+
column = config[:field_name] || physical_name(field)
|
|
186
|
+
output[field] = attributes[column] if attributes.key?(column)
|
|
187
|
+
end
|
|
188
|
+
attach_joins(model, normalized, record, join)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def attach_joins(model, normalized, record, join)
|
|
192
|
+
return normalized unless join
|
|
193
|
+
|
|
194
|
+
join.each_key do |join_model|
|
|
195
|
+
join_model = join_model.to_s
|
|
196
|
+
definition = join_definition(model, join_model)
|
|
197
|
+
next unless definition
|
|
198
|
+
|
|
199
|
+
association = definition.fetch(:association)
|
|
200
|
+
next unless record.respond_to?(association)
|
|
201
|
+
|
|
202
|
+
joined = record.public_send(association)
|
|
203
|
+
normalized[join_model] = if definition[:collection]
|
|
204
|
+
Array(joined).map { |joined_record| normalize_record(join_model, joined_record) }
|
|
205
|
+
else
|
|
206
|
+
normalize_record(join_model, joined)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
normalized
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def apply_join_includes(model, relation, join)
|
|
213
|
+
associations = join.filter_map do |join_model, _enabled|
|
|
214
|
+
join_definition(model, join_model.to_s)&.fetch(:association)
|
|
215
|
+
end
|
|
216
|
+
return relation if associations.empty? || !relation.respond_to?(:includes)
|
|
217
|
+
|
|
218
|
+
relation.includes(*associations)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def define_join_associations(model, klass)
|
|
222
|
+
case model
|
|
223
|
+
when "session", "account"
|
|
224
|
+
return unless klass.respond_to?(:belongs_to)
|
|
225
|
+
|
|
226
|
+
klass.belongs_to(
|
|
227
|
+
:user,
|
|
228
|
+
class_name: model_class("user").name,
|
|
229
|
+
foreign_key: storage_field(model, "userId"),
|
|
230
|
+
primary_key: storage_field("user", "id"),
|
|
231
|
+
optional: true
|
|
232
|
+
)
|
|
233
|
+
when "user"
|
|
234
|
+
return unless klass.respond_to?(:has_many)
|
|
235
|
+
|
|
236
|
+
klass.has_many(
|
|
237
|
+
:accounts,
|
|
238
|
+
class_name: model_class("account").name,
|
|
239
|
+
foreign_key: storage_field("account", "userId"),
|
|
240
|
+
primary_key: storage_field("user", "id")
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def join_definition(model, join_model)
|
|
246
|
+
case [model.to_s, join_model.to_s]
|
|
247
|
+
when ["session", "user"], ["account", "user"]
|
|
248
|
+
{association: :user, collection: false}
|
|
249
|
+
when ["user", "account"]
|
|
250
|
+
{association: :accounts, collection: true}
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def model_namespace
|
|
255
|
+
@model_namespace ||= BetterAuth::Rails.const_set("ActiveRecordAdapterModels#{object_id}", Module.new)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def class_name_for(model)
|
|
259
|
+
physical_name(model).split("_").map(&:capitalize).join
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def collection_join?(model, join)
|
|
263
|
+
model == "user" && join&.keys&.any? { |join_model| join_model.to_s == "account" }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def aggregate_collection_joins(records)
|
|
267
|
+
records
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def table_for(model)
|
|
271
|
+
schema_for(model).fetch(:model_name)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def schema_for(model)
|
|
275
|
+
BetterAuth::Schema.auth_tables(options).fetch(model.to_s)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def storage_field(model, field)
|
|
279
|
+
schema_for(model).fetch(:fields).fetch(field.to_s).fetch(:field_name, physical_name(field))
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def stringify_keys(value)
|
|
283
|
+
return {} unless value.respond_to?(:each)
|
|
284
|
+
|
|
285
|
+
value.each_with_object({}) { |(key, object), result| result[storage_key(key)] = object }
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def fetch_key(hash, key)
|
|
289
|
+
[key, key.to_s, storage_key(key), storage_key(key).to_sym].each do |candidate|
|
|
290
|
+
return hash[candidate] if hash.key?(candidate)
|
|
291
|
+
end
|
|
292
|
+
nil
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def storage_key(value)
|
|
296
|
+
BetterAuth::Schema.send(:storage_key, value)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def physical_name(value)
|
|
300
|
+
BetterAuth::Schema.send(:physical_name, value)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def resolve_default(value)
|
|
304
|
+
value.respond_to?(:call) ? value.call : value
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
class Configuration
|
|
6
|
+
AUTH_OPTION_NAMES = %i[
|
|
7
|
+
app_name
|
|
8
|
+
base_url
|
|
9
|
+
base_path
|
|
10
|
+
secret
|
|
11
|
+
database
|
|
12
|
+
plugins
|
|
13
|
+
trusted_origins
|
|
14
|
+
rate_limit
|
|
15
|
+
session
|
|
16
|
+
account
|
|
17
|
+
user
|
|
18
|
+
verification
|
|
19
|
+
advanced
|
|
20
|
+
email_and_password
|
|
21
|
+
password_hasher
|
|
22
|
+
email_verification
|
|
23
|
+
social_providers
|
|
24
|
+
experimental
|
|
25
|
+
secondary_storage
|
|
26
|
+
database_hooks
|
|
27
|
+
hooks
|
|
28
|
+
on_api_error
|
|
29
|
+
disabled_paths
|
|
30
|
+
logger
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
attr_accessor(*AUTH_OPTION_NAMES)
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
@base_path = BetterAuth::Configuration::DEFAULT_BASE_PATH
|
|
37
|
+
@plugins = []
|
|
38
|
+
@trusted_origins = []
|
|
39
|
+
@database = ->(options) { ActiveRecordAdapter.new(options) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_auth_options
|
|
43
|
+
AUTH_OPTION_NAMES.each_with_object({}) do |name, options|
|
|
44
|
+
value = public_send(name)
|
|
45
|
+
next if value.nil?
|
|
46
|
+
next if value.respond_to?(:empty?) && value.empty?
|
|
47
|
+
|
|
48
|
+
options[name] = value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
module ControllerHelpers
|
|
6
|
+
def current_session
|
|
7
|
+
data = better_auth_session_data
|
|
8
|
+
data&.fetch(:session, nil) || data&.fetch("session", nil)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def current_user
|
|
12
|
+
data = better_auth_session_data
|
|
13
|
+
data&.fetch(:user, nil) || data&.fetch("user", nil)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def authenticated?
|
|
17
|
+
!current_user.nil?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def require_authentication
|
|
21
|
+
return true if authenticated?
|
|
22
|
+
|
|
23
|
+
head(:unauthorized) if respond_to?(:head)
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def better_auth_session_data
|
|
30
|
+
return request.env["better_auth.session"] if request.env.key?("better_auth.session")
|
|
31
|
+
|
|
32
|
+
request.env["better_auth.session"] = resolve_better_auth_session
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_better_auth_session
|
|
36
|
+
context = BetterAuth::Endpoint::Context.new(
|
|
37
|
+
path: request.path,
|
|
38
|
+
method: request.request_method,
|
|
39
|
+
query: request.query_parameters,
|
|
40
|
+
body: {},
|
|
41
|
+
params: {},
|
|
42
|
+
headers: {"cookie" => request.get_header("HTTP_COOKIE")},
|
|
43
|
+
context: BetterAuth::Rails.auth.context,
|
|
44
|
+
request: request
|
|
45
|
+
)
|
|
46
|
+
BetterAuth::Session.find_current(context, disable_refresh: true)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
module Migration
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def render(options, migration_version: nil)
|
|
9
|
+
migration_version ||= self.migration_version
|
|
10
|
+
tables = BetterAuth::Schema.auth_tables(options)
|
|
11
|
+
lines = [
|
|
12
|
+
"# frozen_string_literal: true",
|
|
13
|
+
"",
|
|
14
|
+
"class CreateBetterAuthTables < ActiveRecord::Migration[#{migration_version}]",
|
|
15
|
+
" def change"
|
|
16
|
+
]
|
|
17
|
+
tables.each_value { |table| lines.concat(create_table_lines(table)) }
|
|
18
|
+
tables.each_value { |table| lines.concat(primary_key_lines(table)) }
|
|
19
|
+
tables.each_value { |table| lines.concat(index_lines(table)) }
|
|
20
|
+
tables.each_value { |table| lines.concat(foreign_key_lines(table, options)) }
|
|
21
|
+
lines.concat([" end", "end", ""])
|
|
22
|
+
lines.join("\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def migration_version
|
|
26
|
+
return ::ActiveRecord::Migration.current_version if defined?(::ActiveRecord::Migration)
|
|
27
|
+
|
|
28
|
+
"7.0"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_table_lines(table)
|
|
32
|
+
table_name = table.fetch(:model_name)
|
|
33
|
+
lines = ["", " create_table :#{table_name}, id: false do |t|"]
|
|
34
|
+
table.fetch(:fields).each do |logical_field, attributes|
|
|
35
|
+
lines << column_line(logical_field, attributes)
|
|
36
|
+
end
|
|
37
|
+
lines << " end"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def column_line(logical_field, attributes)
|
|
41
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
42
|
+
parts = ["t.#{rails_type(attributes)} :#{column}"]
|
|
43
|
+
parts << "null: false" if attributes[:required]
|
|
44
|
+
default = default_value(attributes)
|
|
45
|
+
parts << "default: #{default}" unless default.nil?
|
|
46
|
+
" #{parts.join(", ")}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def index_lines(table)
|
|
50
|
+
table_name = table.fetch(:model_name)
|
|
51
|
+
table.fetch(:fields).filter_map do |logical_field, attributes|
|
|
52
|
+
next unless attributes[:unique] || attributes[:index]
|
|
53
|
+
|
|
54
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
55
|
+
unique = attributes[:unique] ? ", unique: true" : ""
|
|
56
|
+
" add_index :#{table_name}, :#{column}#{unique}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def primary_key_lines(table)
|
|
61
|
+
table_name = table.fetch(:model_name)
|
|
62
|
+
return [] unless table.fetch(:fields).key?("id")
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
%( execute "ALTER TABLE \#{quote_table_name(:#{table_name})} ADD PRIMARY KEY (\#{quote_column_name(:id)})")
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def foreign_key_lines(table, options)
|
|
70
|
+
table_name = table.fetch(:model_name)
|
|
71
|
+
table.fetch(:fields).filter_map do |logical_field, attributes|
|
|
72
|
+
reference = attributes[:references]
|
|
73
|
+
next unless reference
|
|
74
|
+
|
|
75
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
76
|
+
target = foreign_key_target(reference.fetch(:model), options)
|
|
77
|
+
on_delete = reference[:on_delete] ? ", on_delete: :#{reference[:on_delete]}" : ""
|
|
78
|
+
" add_foreign_key :#{table_name}, :#{target}, column: :#{column}#{on_delete}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def rails_type(attributes)
|
|
83
|
+
case attributes[:type]
|
|
84
|
+
when "boolean" then "boolean"
|
|
85
|
+
when "date" then "datetime"
|
|
86
|
+
when "number" then attributes[:bigint] ? "bigint" : "integer"
|
|
87
|
+
when "json", "string[]", "number[]" then "json"
|
|
88
|
+
else "string"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_value(attributes)
|
|
93
|
+
default = attributes[:default_value]
|
|
94
|
+
return if default.respond_to?(:call)
|
|
95
|
+
|
|
96
|
+
case default
|
|
97
|
+
when true then "true"
|
|
98
|
+
when false then "false"
|
|
99
|
+
when Numeric then default.to_s
|
|
100
|
+
when String then default.inspect
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def physical_name(value)
|
|
105
|
+
BetterAuth::Schema.send(:physical_name, value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def foreign_key_target(model, options)
|
|
109
|
+
BetterAuth::Schema.auth_tables(options).fetch(model.to_s, nil)&.fetch(:model_name) || model
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
class MountedApp
|
|
6
|
+
def initialize(auth, mount_path:)
|
|
7
|
+
@auth = auth
|
|
8
|
+
@mount_path = normalize_path(mount_path)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
@auth.call(env.merge("PATH_INFO" => mounted_path_info(env)))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def mounted_path_info(env)
|
|
18
|
+
path_info = normalize_path(env["PATH_INFO"])
|
|
19
|
+
script_name = normalize_path(env["SCRIPT_NAME"])
|
|
20
|
+
prefix = (script_name == "/") ? @mount_path : script_name
|
|
21
|
+
|
|
22
|
+
return path_info if path_info == prefix || path_info.start_with?("#{prefix}/")
|
|
23
|
+
|
|
24
|
+
normalize_path("#{prefix}/#{path_info.delete_prefix("/")}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def normalize_path(path)
|
|
28
|
+
normalized = path.to_s
|
|
29
|
+
normalized = "/#{normalized}" unless normalized.start_with?("/")
|
|
30
|
+
normalized = normalized.squeeze("/")
|
|
31
|
+
normalized = normalized.delete_suffix("/") unless normalized == "/"
|
|
32
|
+
normalized.empty? ? "/" : normalized
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
class Railtie < ::Rails::Railtie
|
|
6
|
+
initializer "better_auth_rails.routes" do
|
|
7
|
+
ActiveSupport.on_load(:action_dispatch_routing) do
|
|
8
|
+
include BetterAuth::Rails::Routing
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rake_tasks do
|
|
13
|
+
load File.expand_path("../../tasks/better_auth.rake", __dir__)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Rails
|
|
5
|
+
module Routing
|
|
6
|
+
def better_auth(auth: nil, at: BetterAuth::Configuration::DEFAULT_BASE_PATH)
|
|
7
|
+
mount_path = normalize_better_auth_mount_path(at)
|
|
8
|
+
auth ||= BetterAuth::Rails.auth(base_path: mount_path)
|
|
9
|
+
mount BetterAuth::Rails::MountedApp.new(auth, mount_path: mount_path), at: mount_path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def normalize_better_auth_mount_path(path)
|
|
15
|
+
normalized = path.to_s
|
|
16
|
+
normalized = "/#{normalized}" unless normalized.start_with?("/")
|
|
17
|
+
normalized = normalized.squeeze("/")
|
|
18
|
+
(normalized == "/") ? normalized : normalized.delete_suffix("/")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/better_auth/rails.rb
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "better_auth"
|
|
3
4
|
require_relative "rails/version"
|
|
5
|
+
require_relative "rails/configuration"
|
|
6
|
+
require_relative "rails/migration"
|
|
7
|
+
require_relative "rails/active_record_adapter"
|
|
8
|
+
require_relative "rails/mounted_app"
|
|
9
|
+
require_relative "rails/routing"
|
|
10
|
+
require_relative "rails/controller_helpers"
|
|
11
|
+
require_relative "rails/railtie" if defined?(::Rails::Railtie)
|
|
4
12
|
|
|
5
13
|
module BetterAuth
|
|
6
14
|
module Rails
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
class << self
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
yield configuration
|
|
22
|
+
@auth = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def auth(overrides = nil)
|
|
26
|
+
options = configuration.to_auth_options
|
|
27
|
+
return @auth ||= BetterAuth.auth(options) if overrides.nil? || overrides.empty?
|
|
28
|
+
|
|
29
|
+
BetterAuth.auth(options.merge(overrides))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
9
32
|
end
|
|
10
33
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "better_auth/rails"
|
|
5
|
+
require "generators/better_auth/migration/migration_generator"
|
|
6
|
+
|
|
7
|
+
module BetterAuth
|
|
8
|
+
module Generators
|
|
9
|
+
class InstallGenerator < ::Rails::Generators::Base
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
class_option :database, type: :string, default: "active_record"
|
|
12
|
+
|
|
13
|
+
def create_initializer
|
|
14
|
+
initializer = "config/initializers/better_auth.rb"
|
|
15
|
+
if File.exist?(destination_path(initializer))
|
|
16
|
+
say_status :skip, "#{initializer} already exists"
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
template "initializer.rb.tt", initializer
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create_migration
|
|
24
|
+
MigrationGenerator.start([], destination_root: destination_root)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def destination_path(path)
|
|
30
|
+
File.join(destination_root, path)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
BetterAuth::Rails.configure do |config|
|
|
4
|
+
config.secret =
|
|
5
|
+
Rails.application.credentials.dig(:better_auth, :secret) ||
|
|
6
|
+
Rails.application.credentials.secret_key_base ||
|
|
7
|
+
Rails.application.secret_key_base
|
|
8
|
+
|
|
9
|
+
config.base_url = ENV["BETTER_AUTH_URL"]
|
|
10
|
+
config.base_path = "/api/auth"
|
|
11
|
+
config.database = ->(options) { BetterAuth::Rails::ActiveRecordAdapter.new(options) }
|
|
12
|
+
config.trusted_origins = [
|
|
13
|
+
ENV["BETTER_AUTH_URL"]
|
|
14
|
+
].compact
|
|
15
|
+
|
|
16
|
+
config.session = {
|
|
17
|
+
cookie_cache: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
max_age: 5 * 60,
|
|
20
|
+
strategy: "jwe"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
config.advanced = {
|
|
25
|
+
ip_address: {
|
|
26
|
+
ip_address_headers: ["x-forwarded-for"],
|
|
27
|
+
disable_ip_tracking: false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
config.experimental = {
|
|
32
|
+
joins: false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
config.social_providers = {
|
|
36
|
+
# github: BetterAuth::SocialProviders.github(
|
|
37
|
+
# client_id: ENV.fetch("GITHUB_CLIENT_ID"),
|
|
38
|
+
# client_secret: ENV.fetch("GITHUB_CLIENT_SECRET")
|
|
39
|
+
# )
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Add Better Auth plugins here.
|
|
43
|
+
config.plugins = []
|
|
44
|
+
|
|
45
|
+
# Add Better Auth hooks here. Auth decisions still run through the core gem.
|
|
46
|
+
config.hooks = {
|
|
47
|
+
before: [],
|
|
48
|
+
after: []
|
|
49
|
+
}
|
|
50
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "better_auth/rails"
|
|
5
|
+
|
|
6
|
+
module BetterAuth
|
|
7
|
+
module Generators
|
|
8
|
+
class MigrationGenerator < ::Rails::Generators::Base
|
|
9
|
+
def create_migration
|
|
10
|
+
if existing_migration?
|
|
11
|
+
say_status :skip, "db/migrate/*_create_better_auth_tables.rb already exists"
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
create_file migration_path, BetterAuth::Rails::Migration.render(generator_config)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def existing_migration?
|
|
21
|
+
Dir[File.join(destination_root, "db/migrate/*_create_better_auth_tables.rb")].any?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def migration_path
|
|
25
|
+
File.join("db/migrate", "#{timestamp}_create_better_auth_tables.rb")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def timestamp
|
|
29
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def generator_config
|
|
33
|
+
options = BetterAuth::Rails.configuration.to_auth_options
|
|
34
|
+
options[:secret] ||= BetterAuth::Configuration::DEFAULT_SECRET
|
|
35
|
+
options[:database] ||= :memory
|
|
36
|
+
BetterAuth::Configuration.new(options)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :better_auth do
|
|
4
|
+
desc "Create the Better Auth initializer and base migration"
|
|
5
|
+
task :init do
|
|
6
|
+
require "generators/better_auth/install/install_generator"
|
|
7
|
+
BetterAuth::Generators::InstallGenerator.start([])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
namespace :generate do
|
|
11
|
+
desc "Create the Better Auth base migration"
|
|
12
|
+
task :migration do
|
|
13
|
+
require "generators/better_auth/migration/migration_generator"
|
|
14
|
+
BetterAuth::Generators::MigrationGenerator.start([])
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: better_auth
|
|
@@ -64,6 +63,26 @@ dependencies:
|
|
|
64
63
|
- - "<"
|
|
65
64
|
- !ruby/object:Gem::Version
|
|
66
65
|
version: '9'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: activerecord
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '6.0'
|
|
73
|
+
- - "<"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '9'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '6.0'
|
|
83
|
+
- - "<"
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '9'
|
|
67
86
|
- !ruby/object:Gem::Dependency
|
|
68
87
|
name: bundler
|
|
69
88
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -134,6 +153,20 @@ dependencies:
|
|
|
134
153
|
- - "~>"
|
|
135
154
|
- !ruby/object:Gem::Version
|
|
136
155
|
version: '0.22'
|
|
156
|
+
- !ruby/object:Gem::Dependency
|
|
157
|
+
name: pg
|
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
|
159
|
+
requirements:
|
|
160
|
+
- - "~>"
|
|
161
|
+
- !ruby/object:Gem::Version
|
|
162
|
+
version: '1.5'
|
|
163
|
+
type: :development
|
|
164
|
+
prerelease: false
|
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - "~>"
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: '1.5'
|
|
137
170
|
description: Rails integration for Better Auth Ruby. Provides middleware, controller
|
|
138
171
|
helpers, and generators.
|
|
139
172
|
email:
|
|
@@ -146,8 +179,19 @@ files:
|
|
|
146
179
|
- LICENSE.md
|
|
147
180
|
- README.md
|
|
148
181
|
- lib/better_auth/rails.rb
|
|
182
|
+
- lib/better_auth/rails/active_record_adapter.rb
|
|
183
|
+
- lib/better_auth/rails/configuration.rb
|
|
184
|
+
- lib/better_auth/rails/controller_helpers.rb
|
|
185
|
+
- lib/better_auth/rails/migration.rb
|
|
186
|
+
- lib/better_auth/rails/mounted_app.rb
|
|
187
|
+
- lib/better_auth/rails/railtie.rb
|
|
188
|
+
- lib/better_auth/rails/routing.rb
|
|
149
189
|
- lib/better_auth/rails/version.rb
|
|
150
190
|
- lib/better_auth_rails.rb
|
|
191
|
+
- lib/generators/better_auth/install/install_generator.rb
|
|
192
|
+
- lib/generators/better_auth/install/templates/initializer.rb.tt
|
|
193
|
+
- lib/generators/better_auth/migration/migration_generator.rb
|
|
194
|
+
- lib/tasks/better_auth.rake
|
|
151
195
|
homepage: https://github.com/sebasxsala/better-auth
|
|
152
196
|
licenses:
|
|
153
197
|
- MIT
|
|
@@ -156,7 +200,6 @@ metadata:
|
|
|
156
200
|
source_code_uri: https://github.com/sebasxsala/better-auth
|
|
157
201
|
changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-rails/CHANGELOG.md
|
|
158
202
|
bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
|
|
159
|
-
post_install_message:
|
|
160
203
|
rdoc_options: []
|
|
161
204
|
require_paths:
|
|
162
205
|
- lib
|
|
@@ -171,8 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
171
214
|
- !ruby/object:Gem::Version
|
|
172
215
|
version: '0'
|
|
173
216
|
requirements: []
|
|
174
|
-
rubygems_version: 3.
|
|
175
|
-
signing_key:
|
|
217
|
+
rubygems_version: 3.6.9
|
|
176
218
|
specification_version: 4
|
|
177
219
|
summary: Rails adapter for Better Auth
|
|
178
220
|
test_files: []
|