better_auth-hanami 0.8.0 → 0.10.0
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 +7 -0
- data/README.md +18 -5
- data/lib/better_auth/hanami/action_helpers.rb +64 -7
- data/lib/better_auth/hanami/configuration.rb +3 -2
- data/lib/better_auth/hanami/generators/install_generator.rb +12 -4
- data/lib/better_auth/hanami/generators/migration_generator.rb +14 -2
- data/lib/better_auth/hanami/migration.rb +152 -12
- data/lib/better_auth/hanami/routing.rb +8 -1
- data/lib/better_auth/hanami/sequel_adapter.rb +167 -39
- data/lib/better_auth/hanami/version.rb +1 -1
- data/lib/better_auth/hanami.rb +11 -3
- data/lib/tasks/better_auth.rake +7 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b23720fd336f4026363d337c886f73c6ab60369d3e3f8679d25b989712caf74d
|
|
4
|
+
data.tar.gz: 809a0912aaafdce964e6622c05f7fdc77892165524901f7e56970a7e34abac99
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ddad200e1d991c7b81a746828854b65359b01545f0e161a342ce8a1a7cfdf8ae1f82fd7ec1703d58ce728e10ec1b71defbcb2049508627eb0241988a1a256945
|
|
7
|
+
data.tar.gz: '038a9aaa4497e56a9fcf64ca88d650b3f750a75256c0d3a08b505dfbe58894d7a2d17d54ecd1cbce8a709c7b3d5187daee3b8912b41c59d7b3c1b6d09c790a04'
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.0] - 2026-05-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Preserved migration foreign keys and improved generated migration output.
|
|
15
|
+
- Hardened Hanami routing, helpers, rate limits, rake tasks, and Sequel adapter behavior.
|
|
16
|
+
|
|
10
17
|
## [0.7.0] - 2026-05-05
|
|
11
18
|
|
|
12
19
|
### Fixed
|
data/README.md
CHANGED
|
@@ -40,11 +40,15 @@ bin/hanami db migrate
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
When you add plugins that introduce schema tables or fields, regenerate both
|
|
43
|
-
the migration and the app query objects
|
|
43
|
+
the migration and the app query objects. If the base migration already exists
|
|
44
|
+
and Hanami can connect to the current Sequel database, the migration generator
|
|
45
|
+
creates a new incremental update migration for missing plugin tables, additional
|
|
46
|
+
fields, and indexes:
|
|
44
47
|
|
|
45
48
|
```bash
|
|
46
49
|
bundle exec rake better_auth:generate:migration
|
|
47
50
|
bundle exec rake better_auth:generate:relations
|
|
51
|
+
bundle exec rake better_auth:doctor
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
## Configuration
|
|
@@ -58,14 +62,17 @@ Hanami.app.register_provider(:better_auth) do
|
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
start do
|
|
65
|
+
better_auth_url = target["settings"].better_auth_url.to_s
|
|
66
|
+
raise "better_auth_url must be configured" if better_auth_url.empty?
|
|
67
|
+
|
|
61
68
|
BetterAuth::Hanami.configure do |config|
|
|
62
69
|
config.secret = target["settings"].better_auth_secret
|
|
63
|
-
config.base_url =
|
|
70
|
+
config.base_url = better_auth_url
|
|
64
71
|
config.base_path = "/api/auth"
|
|
65
72
|
config.database = ->(options) {
|
|
66
73
|
BetterAuth::Hanami::SequelAdapter.from_container(target, options)
|
|
67
74
|
}
|
|
68
|
-
config.trusted_origins = [
|
|
75
|
+
config.trusted_origins = [better_auth_url]
|
|
69
76
|
config.email_and_password = {enabled: true}
|
|
70
77
|
config.plugins = []
|
|
71
78
|
end
|
|
@@ -84,8 +91,10 @@ CORS middleware in your Hanami app as well so preflight requests and
|
|
|
84
91
|
policy. For the shared Rack/CORS/CSRF boundary, see
|
|
85
92
|
[`host-app-responsibilities.md`](../../.docs/features/host-app-responsibilities.md).
|
|
86
93
|
|
|
87
|
-
Do not rely on a Hanami-only empty `trusted_origins` list
|
|
88
|
-
deny-all-origin policy; set
|
|
94
|
+
Do not rely on a Hanami-only empty `trusted_origins` list or inferred request
|
|
95
|
+
hosts as a strict deny-all-origin policy; set a canonical deployment URL in app
|
|
96
|
+
settings. The generated provider raises when `better_auth_url` is blank so auth
|
|
97
|
+
URLs and origin checks are not derived from an untrusted `Host` header. Keep
|
|
89
98
|
`BetterAuth::Hanami::MountedApp` behavior aligned with Hanami's router instead
|
|
90
99
|
of copying Rails mount internals without integration tests. Be cautious with
|
|
91
100
|
relation or inflector overrides generated for an app, because overwriting
|
|
@@ -174,6 +183,10 @@ BetterAuth::Hanami.configure do |config|
|
|
|
174
183
|
end
|
|
175
184
|
```
|
|
176
185
|
|
|
186
|
+
When no Hanami `db.gateway` is available, the adapter still falls back to
|
|
187
|
+
memory storage in development and tests with a warning. In production it raises
|
|
188
|
+
instead, unless you intentionally set `config.allow_memory_fallback = true`.
|
|
189
|
+
|
|
177
190
|
## Limitations
|
|
178
191
|
|
|
179
192
|
- Supports Hanami 2.3+ only. Better Auth core depends on Rack 3, and Hanami 2.3 is the first Hanami line that allows Rack 3.
|
|
@@ -20,6 +20,7 @@ module BetterAuth
|
|
|
20
20
|
def require_authentication(request, response)
|
|
21
21
|
return true if authenticated?(request)
|
|
22
22
|
|
|
23
|
+
apply_better_auth_session_headers(request, response)
|
|
23
24
|
response.status = 401 if response.respond_to?(:status=)
|
|
24
25
|
false
|
|
25
26
|
end
|
|
@@ -36,18 +37,28 @@ module BetterAuth
|
|
|
36
37
|
def resolve_better_auth_session(request)
|
|
37
38
|
auth = BetterAuth::Hanami.auth
|
|
38
39
|
auth.context.prepare_for_request!(request) if auth.context.respond_to?(:prepare_for_request!)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
path:
|
|
42
|
-
method:
|
|
43
|
-
query:
|
|
40
|
+
endpoint = auth.api.endpoints.fetch(:get_session)
|
|
41
|
+
endpoint_context = BetterAuth::Endpoint::Context.new(
|
|
42
|
+
path: endpoint.path,
|
|
43
|
+
method: "GET",
|
|
44
|
+
query: {"disableRefresh" => "true"},
|
|
44
45
|
body: {},
|
|
45
46
|
params: {},
|
|
46
|
-
headers:
|
|
47
|
+
headers: request_headers(request),
|
|
47
48
|
context: auth.context,
|
|
48
49
|
request: request
|
|
49
50
|
)
|
|
50
|
-
|
|
51
|
+
result = auth.api.execute(endpoint, endpoint_context)
|
|
52
|
+
request_env(request)["better_auth.session_headers"] = result.headers || {}
|
|
53
|
+
session = result.response
|
|
54
|
+
return nil unless session
|
|
55
|
+
return nil if session.is_a?(BetterAuth::APIError)
|
|
56
|
+
|
|
57
|
+
{session: session["session"] || session[:session], user: session["user"] || session[:user]}
|
|
58
|
+
rescue BetterAuth::APIError
|
|
59
|
+
nil
|
|
60
|
+
ensure
|
|
61
|
+
auth.context.clear_runtime! if defined?(auth) && auth.context.respond_to?(:clear_runtime!)
|
|
51
62
|
end
|
|
52
63
|
|
|
53
64
|
def request_env(request)
|
|
@@ -72,6 +83,52 @@ module BetterAuth
|
|
|
72
83
|
headers = request.respond_to?(:headers) ? request.headers : {}
|
|
73
84
|
headers["cookie"] || headers["Cookie"]
|
|
74
85
|
end
|
|
86
|
+
|
|
87
|
+
def request_authorization(request)
|
|
88
|
+
return request.get_header("HTTP_AUTHORIZATION") if request.respond_to?(:get_header)
|
|
89
|
+
|
|
90
|
+
headers = request.respond_to?(:headers) ? request.headers : {}
|
|
91
|
+
headers["authorization"] || headers["Authorization"]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def request_headers(request)
|
|
95
|
+
headers = headers_from_env(request_env(request))
|
|
96
|
+
cookie = request_cookie(request)
|
|
97
|
+
authorization = request_authorization(request)
|
|
98
|
+
headers["cookie"] = cookie if cookie
|
|
99
|
+
headers["authorization"] = authorization if authorization
|
|
100
|
+
if request.respond_to?(:headers)
|
|
101
|
+
request.headers.each { |key, value| headers[key.to_s] ||= value }
|
|
102
|
+
end
|
|
103
|
+
headers
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def headers_from_env(env)
|
|
107
|
+
env.each_with_object({}) do |(key, value), headers|
|
|
108
|
+
case key
|
|
109
|
+
when "CONTENT_TYPE"
|
|
110
|
+
headers["content-type"] = value if value
|
|
111
|
+
when "CONTENT_LENGTH"
|
|
112
|
+
headers["content-length"] = value if value
|
|
113
|
+
else
|
|
114
|
+
next unless key.start_with?("HTTP_")
|
|
115
|
+
|
|
116
|
+
headers[key.delete_prefix("HTTP_").downcase.tr("_", "-")] = value
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def apply_better_auth_session_headers(request, response)
|
|
122
|
+
headers = request_env(request)["better_auth.session_headers"]
|
|
123
|
+
return unless headers && headers["set-cookie"]
|
|
124
|
+
|
|
125
|
+
if response.respond_to?(:headers) && response.headers.respond_to?(:[]=)
|
|
126
|
+
existing = response.headers["set-cookie"]
|
|
127
|
+
response.headers["set-cookie"] = [existing, headers["set-cookie"]].compact.join("\n")
|
|
128
|
+
elsif response.respond_to?(:[]=)
|
|
129
|
+
response["set-cookie"] = headers["set-cookie"]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
75
132
|
end
|
|
76
133
|
end
|
|
77
134
|
end
|
|
@@ -30,13 +30,14 @@ module BetterAuth
|
|
|
30
30
|
logger
|
|
31
31
|
].freeze
|
|
32
32
|
|
|
33
|
-
attr_accessor(*AUTH_OPTION_NAMES)
|
|
33
|
+
attr_accessor(*AUTH_OPTION_NAMES, :allow_memory_fallback)
|
|
34
34
|
|
|
35
35
|
def initialize
|
|
36
36
|
@base_path = BetterAuth::Configuration::DEFAULT_BASE_PATH
|
|
37
37
|
@plugins = []
|
|
38
38
|
@trusted_origins = []
|
|
39
|
-
@
|
|
39
|
+
@allow_memory_fallback = false
|
|
40
|
+
@database = ->(options) { SequelAdapter.from_hanami(options, allow_memory_fallback: allow_memory_fallback) }
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def to_auth_options
|
|
@@ -65,11 +65,16 @@ module BetterAuth
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
content = File.read(path)
|
|
68
|
-
|
|
68
|
+
required_url_setting = "setting :better_auth_url, constructor: Types::String.constrained(min_size: 1)"
|
|
69
|
+
content = content.gsub("setting :better_auth_url, constructor: Types::String.optional", required_url_setting)
|
|
70
|
+
if content.include?("setting :better_auth_secret")
|
|
71
|
+
File.write(path, content)
|
|
72
|
+
return
|
|
73
|
+
end
|
|
69
74
|
|
|
70
75
|
insertion = [
|
|
71
76
|
" setting :better_auth_secret, constructor: Types::String.constrained(min_size: 32)",
|
|
72
|
-
"
|
|
77
|
+
" #{required_url_setting}"
|
|
73
78
|
].join("\n")
|
|
74
79
|
content = content.sub(/(class[ \t]+Settings[ \t]*<[ \t]*Hanami::Settings[ \t]*\n)/, "\\1#{insertion}\n")
|
|
75
80
|
File.write(path, content)
|
|
@@ -96,14 +101,17 @@ module BetterAuth
|
|
|
96
101
|
end
|
|
97
102
|
|
|
98
103
|
start do
|
|
104
|
+
better_auth_url = target["settings"].better_auth_url.to_s
|
|
105
|
+
raise "better_auth_url must be configured" if better_auth_url.empty?
|
|
106
|
+
|
|
99
107
|
BetterAuth::Hanami.configure do |config|
|
|
100
108
|
config.secret = target["settings"].better_auth_secret
|
|
101
|
-
config.base_url =
|
|
109
|
+
config.base_url = better_auth_url
|
|
102
110
|
config.base_path = "/api/auth"
|
|
103
111
|
config.database = ->(options) {
|
|
104
112
|
BetterAuth::Hanami::SequelAdapter.from_container(target, options)
|
|
105
113
|
}
|
|
106
|
-
config.trusted_origins = [
|
|
114
|
+
config.trusted_origins = [better_auth_url]
|
|
107
115
|
config.email_and_password = {enabled: true}
|
|
108
116
|
config.plugins = []
|
|
109
117
|
end
|
|
@@ -15,7 +15,7 @@ module BetterAuth
|
|
|
15
15
|
|
|
16
16
|
def run(force: nil)
|
|
17
17
|
force = @force if force.nil?
|
|
18
|
-
return
|
|
18
|
+
return incremental_migration_path_if_needed if existing_migration_path && !force
|
|
19
19
|
|
|
20
20
|
path = existing_migration_path || migration_path
|
|
21
21
|
FileUtils.mkdir_p(File.dirname(path))
|
|
@@ -35,8 +35,20 @@ module BetterAuth
|
|
|
35
35
|
@migration_path ||= File.join(destination_root, "config/db/migrate", "#{timestamp}_create_better_auth_tables.rb")
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def incremental_migration_path_if_needed
|
|
39
|
+
plan = BetterAuth::Hanami::Migration.plan_pending(generator_config)
|
|
40
|
+
return existing_migration_path if plan.empty?
|
|
41
|
+
|
|
42
|
+
path = File.join(destination_root, "config/db/migrate", "#{timestamp}_update_better_auth_tables.rb")
|
|
43
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
44
|
+
File.write(path, BetterAuth::Hanami::Migration.render_pending(plan))
|
|
45
|
+
path
|
|
46
|
+
rescue BetterAuth::SQLMigration::UnsupportedAdapterError
|
|
47
|
+
existing_migration_path
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
def timestamp
|
|
39
|
-
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
51
|
+
@timestamp ||= Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
40
52
|
end
|
|
41
53
|
|
|
42
54
|
def generator_config
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "better_auth/sql_migration"
|
|
4
|
+
|
|
3
5
|
module BetterAuth
|
|
4
6
|
module Hanami
|
|
5
7
|
module Migration
|
|
@@ -21,6 +23,88 @@ module BetterAuth
|
|
|
21
23
|
lines.join("\n")
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
def render_pending(plan)
|
|
27
|
+
created_tables = plan.to_create.map(&:table_name).to_set
|
|
28
|
+
lines = [
|
|
29
|
+
"# frozen_string_literal: true",
|
|
30
|
+
"",
|
|
31
|
+
"require \"date\"",
|
|
32
|
+
"require \"rom-sql\"",
|
|
33
|
+
"",
|
|
34
|
+
"ROM::SQL.migration do",
|
|
35
|
+
" change do"
|
|
36
|
+
]
|
|
37
|
+
plan.to_create.each { |change| lines.concat(create_table_lines(change.table, plan.tables)) }
|
|
38
|
+
plan.to_add.each { |change| lines.concat(alter_table_lines(change, plan.tables)) }
|
|
39
|
+
plan.to_index.reject { |change| created_tables.include?(change.table_name) }.each do |change|
|
|
40
|
+
lines.concat(alter_table_index_lines(change))
|
|
41
|
+
end
|
|
42
|
+
lines.concat([" end", "end", ""])
|
|
43
|
+
lines.join("\n")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def plan_pending(options)
|
|
47
|
+
config = BetterAuth::SQLMigration.configuration_for(options)
|
|
48
|
+
if config.database == :memory
|
|
49
|
+
raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection"
|
|
50
|
+
end
|
|
51
|
+
if default_hanami_database?(config.database) && !(defined?(::Hanami) && ::Hanami.respond_to?(:app))
|
|
52
|
+
raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection"
|
|
53
|
+
end
|
|
54
|
+
auth = BetterAuth.auth(config.to_h)
|
|
55
|
+
adapter = auth.context.adapter
|
|
56
|
+
connection = adapter.connection if adapter.respond_to?(:connection)
|
|
57
|
+
raise BetterAuth::SQLMigration::UnsupportedAdapterError, "Better Auth Hanami incremental migrations require a Sequel connection" unless connection
|
|
58
|
+
|
|
59
|
+
BetterAuth::SQLMigration.plan_from_existing(
|
|
60
|
+
config,
|
|
61
|
+
existing: current_schema(connection),
|
|
62
|
+
dialect: sequel_dialect(connection)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def sequel_dialect(connection)
|
|
67
|
+
type = connection.respond_to?(:database_type) ? connection.database_type.to_s : ""
|
|
68
|
+
case type
|
|
69
|
+
when /postgres/
|
|
70
|
+
:postgres
|
|
71
|
+
when /mysql/
|
|
72
|
+
:mysql
|
|
73
|
+
when /sqlite/
|
|
74
|
+
:sqlite
|
|
75
|
+
when /mssql|sqlserver|sql_server/
|
|
76
|
+
:mssql
|
|
77
|
+
else
|
|
78
|
+
:postgres
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def current_schema(connection)
|
|
83
|
+
connection.tables.each_with_object({}) do |table_name, schema|
|
|
84
|
+
columns = connection.schema(table_name).each_with_object({}) do |entry, result|
|
|
85
|
+
column, metadata = entry
|
|
86
|
+
result[column.to_s] = (metadata[:db_type] || metadata[:type]).to_s
|
|
87
|
+
end
|
|
88
|
+
indexes = {names: Set.new, columns: Set.new, unique_columns: Set.new}
|
|
89
|
+
connection.indexes(table_name).each do |name, metadata|
|
|
90
|
+
indexes[:names] << name.to_s
|
|
91
|
+
Array(metadata[:columns]).each do |column|
|
|
92
|
+
column = column.to_s
|
|
93
|
+
indexes[:columns] << column
|
|
94
|
+
indexes[:unique_columns] << column if metadata[:unique]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
schema[table_name.to_s] = {name: table_name.to_s, columns: columns, indexes: indexes}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def default_hanami_database?(database)
|
|
102
|
+
return false unless database.respond_to?(:source_location)
|
|
103
|
+
|
|
104
|
+
path, = database.source_location
|
|
105
|
+
path.to_s.end_with?("better_auth/hanami/configuration.rb")
|
|
106
|
+
end
|
|
107
|
+
|
|
24
108
|
def create_table_lines(table, options)
|
|
25
109
|
table_name = table.fetch(:model_name)
|
|
26
110
|
lines = ["", " create_table :#{table_name} do"]
|
|
@@ -37,16 +121,9 @@ module BetterAuth
|
|
|
37
121
|
end
|
|
38
122
|
|
|
39
123
|
def column_line(logical_field, attributes, options)
|
|
40
|
-
|
|
41
|
-
reference = attributes[:references]
|
|
42
|
-
if reference
|
|
43
|
-
target = foreign_key_target(reference.fetch(:model), options)
|
|
44
|
-
parts = ["foreign_key :#{column}, :#{target}", "type: #{hanami_type(attributes)}"]
|
|
45
|
-
parts << "null: false" if attributes[:required]
|
|
46
|
-
parts << "on_delete: :#{reference[:on_delete]}" if reference[:on_delete]
|
|
47
|
-
return " #{parts.join(", ")}"
|
|
48
|
-
end
|
|
124
|
+
return foreign_key_line("foreign_key", logical_field, attributes, options) if attributes[:references]
|
|
49
125
|
|
|
126
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
50
127
|
parts = ["column :#{column}", hanami_type(attributes)]
|
|
51
128
|
parts << "null: false" if attributes[:required]
|
|
52
129
|
default = default_value(attributes)
|
|
@@ -62,6 +139,36 @@ module BetterAuth
|
|
|
62
139
|
" index :#{column}#{unique}"
|
|
63
140
|
end
|
|
64
141
|
|
|
142
|
+
def alter_table_lines(change, tables)
|
|
143
|
+
lines = ["", " alter_table :#{change.table_name} do"]
|
|
144
|
+
change.fields.each do |logical_field, attributes|
|
|
145
|
+
lines << add_column_line(logical_field, attributes, tables)
|
|
146
|
+
end
|
|
147
|
+
lines << " end"
|
|
148
|
+
lines
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def add_column_line(logical_field, attributes, tables)
|
|
152
|
+
return foreign_key_line("add_foreign_key", logical_field, attributes, tables) if attributes[:references]
|
|
153
|
+
|
|
154
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
155
|
+
parts = ["add_column :#{column}", hanami_type(attributes)]
|
|
156
|
+
parts << "null: false" if attributes[:required]
|
|
157
|
+
default = default_value(attributes)
|
|
158
|
+
parts << "default: #{default}" unless default.nil?
|
|
159
|
+
" #{parts.join(", ")}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def alter_table_index_lines(change)
|
|
163
|
+
unique = change.unique ? ", unique: true" : ""
|
|
164
|
+
[
|
|
165
|
+
"",
|
|
166
|
+
" alter_table :#{change.table_name} do",
|
|
167
|
+
" add_index :#{change.field_name}#{unique}",
|
|
168
|
+
" end"
|
|
169
|
+
]
|
|
170
|
+
end
|
|
171
|
+
|
|
65
172
|
def hanami_type(attributes)
|
|
66
173
|
case attributes[:type]
|
|
67
174
|
when "boolean" then "TrueClass"
|
|
@@ -84,9 +191,42 @@ module BetterAuth
|
|
|
84
191
|
end
|
|
85
192
|
end
|
|
86
193
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
194
|
+
def foreign_key_line(command, logical_field, attributes, options_or_tables)
|
|
195
|
+
column = attributes[:field_name] || physical_name(logical_field)
|
|
196
|
+
reference = attributes.fetch(:references)
|
|
197
|
+
target, target_key = foreign_key_target(reference, options_or_tables)
|
|
198
|
+
parts = ["#{command} :#{column}, :#{target}", "type: #{hanami_type(attributes)}"]
|
|
199
|
+
parts << "null: false" if attributes[:required]
|
|
200
|
+
parts << "key: :#{target_key}" unless target_key == "id"
|
|
201
|
+
parts << "on_delete: :#{reference[:on_delete]}" if reference[:on_delete]
|
|
202
|
+
" #{parts.join(", ")}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def foreign_key_target(reference, options_or_tables)
|
|
206
|
+
tables = auth_tables_for(options_or_tables)
|
|
207
|
+
model = reference.fetch(:model).to_s
|
|
208
|
+
table = tables.fetch(model, nil) || tables.each_value.find { |candidate| candidate.fetch(:model_name).to_s == model }
|
|
209
|
+
target = table&.fetch(:model_name) || model
|
|
210
|
+
[target, foreign_key_target_field(reference, table)]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def foreign_key_target_field(reference, table)
|
|
214
|
+
field = reference.fetch(:field).to_s
|
|
215
|
+
return physical_name(field) unless table
|
|
216
|
+
|
|
217
|
+
attributes = table.fetch(:fields).fetch(field, nil)
|
|
218
|
+
return attributes[:field_name] || physical_name(field) if attributes
|
|
219
|
+
return field if table.fetch(:fields).each_value.any? { |data| data[:field_name].to_s == field }
|
|
220
|
+
|
|
221
|
+
physical_name(field)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def auth_tables_for(options_or_tables)
|
|
225
|
+
if options_or_tables.is_a?(Hash) && options_or_tables.values.all? { |value| value.is_a?(Hash) && value.key?(:fields) && value.key?(:model_name) }
|
|
226
|
+
options_or_tables
|
|
227
|
+
else
|
|
228
|
+
BetterAuth::Schema.auth_tables(options_or_tables)
|
|
229
|
+
end
|
|
90
230
|
end
|
|
91
231
|
|
|
92
232
|
def physical_name(value)
|
|
@@ -11,7 +11,7 @@ module BetterAuth
|
|
|
11
11
|
|
|
12
12
|
def better_auth(auth: nil, at: BetterAuth::Configuration::DEFAULT_BASE_PATH)
|
|
13
13
|
mount_path = normalize_better_auth_mount_path(at)
|
|
14
|
-
auth ||=
|
|
14
|
+
auth ||= auth_for_better_auth_mount(mount_path)
|
|
15
15
|
app = BetterAuth::Hanami::MountedApp.new(auth, mount_path: mount_path)
|
|
16
16
|
|
|
17
17
|
HTTP_METHODS.each do |method_name|
|
|
@@ -29,6 +29,13 @@ module BetterAuth
|
|
|
29
29
|
normalized = normalized.delete_suffix("/") unless normalized == "/"
|
|
30
30
|
normalized.empty? ? "/" : normalized
|
|
31
31
|
end
|
|
32
|
+
|
|
33
|
+
def auth_for_better_auth_mount(mount_path)
|
|
34
|
+
configured_path = normalize_better_auth_mount_path(BetterAuth::Hanami.configuration.base_path)
|
|
35
|
+
return BetterAuth::Hanami.auth if mount_path == configured_path
|
|
36
|
+
|
|
37
|
+
BetterAuth::Hanami.auth(base_path: mount_path)
|
|
38
|
+
end
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
end
|
|
@@ -10,24 +10,26 @@ module BetterAuth
|
|
|
10
10
|
class SequelAdapter < BetterAuth::Adapters::Base
|
|
11
11
|
include BetterAuth::Adapters::JoinSupport
|
|
12
12
|
|
|
13
|
+
WHERE_OPERATORS = %w[eq ne gt gte lt lte in not_in contains starts_with ends_with].freeze
|
|
14
|
+
|
|
13
15
|
attr_reader :connection
|
|
14
16
|
|
|
15
|
-
def self.from_hanami(options, container: nil)
|
|
17
|
+
def self.from_hanami(options, container: nil, allow_memory_fallback: false)
|
|
16
18
|
if container.nil? && defined?(::Hanami) && ::Hanami.respond_to?(:app)
|
|
17
19
|
container = ::Hanami.app
|
|
18
20
|
end
|
|
19
|
-
return memory_fallback(options) unless container
|
|
21
|
+
return memory_fallback(options, allow_memory_fallback: allow_memory_fallback) unless container
|
|
20
22
|
|
|
21
|
-
from_container(container, options)
|
|
23
|
+
from_container(container, options, allow_memory_fallback: allow_memory_fallback)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def self.from_container(container, options)
|
|
26
|
+
def self.from_container(container, options, allow_memory_fallback: false)
|
|
25
27
|
gateway = if container.respond_to?(:key?) && container.key?("db.gateway")
|
|
26
28
|
container["db.gateway"]
|
|
27
29
|
elsif container.respond_to?(:[]) && safe_fetch(container, "db.gateway")
|
|
28
30
|
container["db.gateway"]
|
|
29
31
|
end
|
|
30
|
-
return memory_fallback(options) unless gateway
|
|
32
|
+
return memory_fallback(options, allow_memory_fallback: allow_memory_fallback) unless gateway
|
|
31
33
|
|
|
32
34
|
connection = gateway.respond_to?(:connection) ? gateway.connection : gateway
|
|
33
35
|
new(options, connection: connection)
|
|
@@ -39,7 +41,11 @@ module BetterAuth
|
|
|
39
41
|
nil
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def self.memory_fallback(options)
|
|
44
|
+
def self.memory_fallback(options, allow_memory_fallback: false)
|
|
45
|
+
if options.respond_to?(:production?) && options.production? && !allow_memory_fallback
|
|
46
|
+
raise Error, "Hanami db.gateway is required in production. Set config.allow_memory_fallback = true to use volatile memory storage intentionally."
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
Kernel.warn(
|
|
44
50
|
"[better_auth-hanami] SequelAdapter: using BetterAuth::Adapters::Memory " \
|
|
45
51
|
"(no Hanami container or db.gateway). Persisted auth data will not survive process restart."
|
|
@@ -56,7 +62,8 @@ module BetterAuth
|
|
|
56
62
|
model = model.to_s
|
|
57
63
|
input = transform_input(model, data, "create", force_allow_id)
|
|
58
64
|
table_dataset(model).insert(physical_attributes(model, input))
|
|
59
|
-
|
|
65
|
+
lookup = create_lookup(model, input)
|
|
66
|
+
lookup ? find_one(model: model, where: [lookup]) : input
|
|
60
67
|
end
|
|
61
68
|
|
|
62
69
|
def find_one(model:, where: [], select: nil, join: nil)
|
|
@@ -67,32 +74,41 @@ module BetterAuth
|
|
|
67
74
|
model = model.to_s
|
|
68
75
|
dataset = table_dataset(model)
|
|
69
76
|
dataset = apply_where(model, dataset, where || [])
|
|
70
|
-
|
|
77
|
+
join_config = normalized_join(model, join)
|
|
78
|
+
requested_select = select ? Array(select).map { |field| storage_key(field) } : nil
|
|
79
|
+
effective_select = select_fields_for_join(requested_select, join_config)
|
|
80
|
+
dataset = apply_select(model, dataset, effective_select) if effective_select
|
|
71
81
|
dataset = apply_order(model, dataset, sort_by) if sort_by
|
|
72
|
-
dataset = dataset.limit(
|
|
73
|
-
dataset = dataset.offset(
|
|
82
|
+
dataset = dataset.limit(coerce_pagination(limit, "limit")) if limit
|
|
83
|
+
dataset = dataset.offset(coerce_pagination(offset, "offset")) if offset
|
|
74
84
|
|
|
75
85
|
records = dataset.all.map { |row| normalize_record(model, row) }
|
|
76
|
-
attach_joins(model, records,
|
|
86
|
+
records = attach_joins(model, records, join_config)
|
|
87
|
+
trim_unrequested_select_fields(records, requested_select, join_config) if requested_select
|
|
88
|
+
records
|
|
77
89
|
end
|
|
78
90
|
|
|
79
91
|
def update(model:, where:, update:)
|
|
80
92
|
model = model.to_s
|
|
81
|
-
existing = find_one(model: model, where: where
|
|
93
|
+
existing = find_one(model: model, where: where)
|
|
82
94
|
return nil unless existing
|
|
83
95
|
|
|
84
96
|
update_many(model: model, where: where, update: update)
|
|
85
|
-
|
|
97
|
+
lookup = record_lookup(model, existing)
|
|
98
|
+
lookup ? find_one(model: model, where: [lookup]) : find_one(model: model, where: where)
|
|
86
99
|
end
|
|
87
100
|
|
|
88
101
|
def update_many(model:, where:, update:, returning: false)
|
|
89
102
|
model = model.to_s
|
|
90
|
-
existing = returning ? find_many(model: model, where: where
|
|
103
|
+
existing = returning ? find_many(model: model, where: where) : []
|
|
91
104
|
attributes = physical_attributes(model, transform_input(model, update, "update", true))
|
|
92
105
|
apply_where(model, table_dataset(model), where || []).update(attributes)
|
|
93
106
|
return unless returning
|
|
94
107
|
|
|
95
|
-
existing.map
|
|
108
|
+
existing.map do |record|
|
|
109
|
+
lookup = record_lookup(model, record)
|
|
110
|
+
lookup ? find_one(model: model, where: [lookup]) : record
|
|
111
|
+
end
|
|
96
112
|
end
|
|
97
113
|
|
|
98
114
|
def delete(model:, where:)
|
|
@@ -136,22 +152,34 @@ module BetterAuth
|
|
|
136
152
|
column = storage_field(model, field)
|
|
137
153
|
identifier = Sequel[column.to_sym]
|
|
138
154
|
operator = (fetch_key(clause, :operator) || "eq").to_s
|
|
155
|
+
raise APIError.new("BAD_REQUEST", message: "Invalid operator #{operator}") unless WHERE_OPERATORS.include?(operator)
|
|
156
|
+
|
|
157
|
+
mode = (fetch_key(clause, :mode) || "sensitive").to_s
|
|
139
158
|
attributes = schema_for(model).fetch(:fields).fetch(field)
|
|
140
159
|
raw_value = fetch_key(clause, :value)
|
|
141
160
|
value = coerce_where_value(raw_value, attributes)
|
|
161
|
+
insensitive = insensitive_string_mode?(mode, attributes)
|
|
162
|
+
comparable = insensitive ? Sequel.function(:lower, identifier) : identifier
|
|
163
|
+
compare_value = insensitive ? downcase_where_value(value) : value
|
|
142
164
|
|
|
143
165
|
case operator
|
|
144
|
-
when "in"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
when "
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
when "
|
|
153
|
-
when "
|
|
154
|
-
|
|
166
|
+
when "in"
|
|
167
|
+
values = Array(raw_value).map { |entry| coerce_where_value(entry, attributes) }
|
|
168
|
+
values = downcase_where_value(values) if insensitive
|
|
169
|
+
insensitive ? Sequel.expr(comparable => values) : {column.to_sym => values}
|
|
170
|
+
when "not_in"
|
|
171
|
+
values = Array(raw_value).map { |entry| coerce_where_value(entry, attributes) }
|
|
172
|
+
values = downcase_where_value(values) if insensitive
|
|
173
|
+
Sequel.~(insensitive ? Sequel.expr(comparable => values) : {column.to_sym => values})
|
|
174
|
+
when "ne" then insensitive ? Sequel.~(Sequel.expr(comparable => compare_value)) : Sequel.~(column.to_sym => value)
|
|
175
|
+
when "gt" then comparable > compare_value
|
|
176
|
+
when "gte" then comparable >= compare_value
|
|
177
|
+
when "lt" then comparable < compare_value
|
|
178
|
+
when "lte" then comparable <= compare_value
|
|
179
|
+
when "contains" then Sequel.like(comparable, "%#{escape_like(compare_value)}%", escape: "\\")
|
|
180
|
+
when "starts_with" then Sequel.like(comparable, "#{escape_like(compare_value)}%", escape: "\\")
|
|
181
|
+
when "ends_with" then Sequel.like(comparable, "%#{escape_like(compare_value)}", escape: "\\")
|
|
182
|
+
else insensitive ? Sequel.expr(comparable => compare_value) : {column.to_sym => value}
|
|
155
183
|
end
|
|
156
184
|
end
|
|
157
185
|
|
|
@@ -165,27 +193,50 @@ module BetterAuth
|
|
|
165
193
|
dataset.order(direction)
|
|
166
194
|
end
|
|
167
195
|
|
|
168
|
-
def attach_joins(
|
|
169
|
-
return records
|
|
196
|
+
def attach_joins(_model, records, join_config)
|
|
197
|
+
return records if join_config.empty? || records.empty?
|
|
170
198
|
|
|
171
|
-
join_config = normalized_join(model, join)
|
|
172
199
|
records.each do |record|
|
|
173
200
|
join_config.each do |join_model, config|
|
|
174
|
-
record[join_model] =
|
|
201
|
+
record[join_model] = one_to_one_join?(config) ? nil : []
|
|
175
202
|
end
|
|
176
203
|
end
|
|
204
|
+
join_config.each do |join_model, config|
|
|
205
|
+
attach_join(model_records: records, join_model: join_model, config: config)
|
|
206
|
+
end
|
|
177
207
|
records
|
|
178
208
|
end
|
|
179
209
|
|
|
210
|
+
def attach_join(model_records:, join_model:, config:)
|
|
211
|
+
values = model_records.map { |record| record[config.fetch(:from)] }.compact.uniq
|
|
212
|
+
return if values.empty?
|
|
213
|
+
|
|
214
|
+
joined = if one_to_one_join?(config)
|
|
215
|
+
find_many(model: join_model, where: [{field: config.fetch(:to), operator: "in", value: values}])
|
|
216
|
+
else
|
|
217
|
+
values.flat_map do |value|
|
|
218
|
+
find_many(model: join_model, where: [{field: config.fetch(:to), value: value}], limit: join_limit(config))
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
grouped = joined.group_by { |record| record[config.fetch(:to)] }
|
|
222
|
+
model_records.each do |record|
|
|
223
|
+
records = grouped.fetch(record[config.fetch(:from)], [])
|
|
224
|
+
record[join_model] = if one_to_one_join?(config)
|
|
225
|
+
records.first
|
|
226
|
+
else
|
|
227
|
+
records.first(join_limit(config))
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
180
232
|
def joined_records(record, join_model, config)
|
|
181
233
|
local_value = record[config.fetch(:from)]
|
|
182
234
|
where = [{field: config.fetch(:to), value: local_value}]
|
|
183
|
-
|
|
184
235
|
if one_to_one_join?(config)
|
|
185
236
|
find_one(model: join_model, where: where)
|
|
186
237
|
else
|
|
187
238
|
records = find_many(model: join_model, where: where)
|
|
188
|
-
|
|
239
|
+
records.first(join_limit(config))
|
|
189
240
|
end
|
|
190
241
|
end
|
|
191
242
|
|
|
@@ -213,9 +264,9 @@ module BetterAuth
|
|
|
213
264
|
reference = attributes.fetch(:references)
|
|
214
265
|
if forward_join
|
|
215
266
|
unique = attributes[:unique] == true
|
|
216
|
-
{from: reference.fetch(:field).to_s, to: foreign_key, relation: unique ? "one-to-one" : "one-to-many", unique: unique}
|
|
267
|
+
{from: reference.fetch(:field).to_s, to: foreign_key, relation: unique ? "one-to-one" : "one-to-many", unique: unique, limit: unique ? 1 : default_find_many_limit}
|
|
217
268
|
else
|
|
218
|
-
{from: foreign_key, to: reference.fetch(:field).to_s, relation: "one-to-one", unique: true}
|
|
269
|
+
{from: foreign_key, to: reference.fetch(:field).to_s, relation: "one-to-one", unique: true, limit: 1}
|
|
219
270
|
end
|
|
220
271
|
end
|
|
221
272
|
|
|
@@ -233,7 +284,7 @@ module BetterAuth
|
|
|
233
284
|
raise APIError.new("BAD_REQUEST", message: "#{field} is not allowed to be set")
|
|
234
285
|
end
|
|
235
286
|
|
|
236
|
-
if
|
|
287
|
+
if action == "create" && attributes.key?(:default_value) && (!value_provided || (attributes[:required] && value.nil?))
|
|
237
288
|
value = resolve_default(attributes[:default_value])
|
|
238
289
|
value_provided = true
|
|
239
290
|
elsif !value_provided && action == "update" && attributes[:on_update]
|
|
@@ -246,10 +297,30 @@ module BetterAuth
|
|
|
246
297
|
output[field] = coerce_value(value, attributes) if value_provided
|
|
247
298
|
end
|
|
248
299
|
|
|
249
|
-
output["id"] = generated_id if action == "create" && !output.key?("id")
|
|
300
|
+
output["id"] = generated_id if action == "create" && !output.key?("id") && fields.key?("id")
|
|
250
301
|
output
|
|
251
302
|
end
|
|
252
303
|
|
|
304
|
+
def create_lookup(model, input)
|
|
305
|
+
fields = schema_for(model).fetch(:fields)
|
|
306
|
+
return {field: "id", value: input.fetch("id")} if fields.key?("id") && input.key?("id")
|
|
307
|
+
|
|
308
|
+
unique_field = fields.find { |field, attributes| attributes[:unique] && input.key?(field) }
|
|
309
|
+
return {field: unique_field.first, value: input.fetch(unique_field.first)} if unique_field
|
|
310
|
+
|
|
311
|
+
nil
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def record_lookup(model, record)
|
|
315
|
+
fields = schema_for(model).fetch(:fields)
|
|
316
|
+
return {field: "id", value: record.fetch("id")} if fields.key?("id") && record.key?("id")
|
|
317
|
+
|
|
318
|
+
unique_field = fields.find { |field, attributes| attributes[:unique] && record.key?(field) }
|
|
319
|
+
return {field: unique_field.first, value: record.fetch(unique_field.first)} if unique_field
|
|
320
|
+
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
|
|
253
324
|
def physical_attributes(model, logical)
|
|
254
325
|
logical.each_with_object({}) do |(field, value), attributes|
|
|
255
326
|
attributes[storage_field(model, field).to_sym] = value
|
|
@@ -271,10 +342,16 @@ module BetterAuth
|
|
|
271
342
|
|
|
272
343
|
def schema_for(model)
|
|
273
344
|
BetterAuth::Schema.auth_tables(options).fetch(model.to_s)
|
|
345
|
+
rescue KeyError
|
|
346
|
+
raise APIError.new("BAD_REQUEST", message: "Invalid model #{model}")
|
|
274
347
|
end
|
|
275
348
|
|
|
276
349
|
def storage_field(model, field)
|
|
277
|
-
schema_for(model).fetch(:fields)
|
|
350
|
+
fields = schema_for(model).fetch(:fields)
|
|
351
|
+
attributes = fields[field.to_s]
|
|
352
|
+
raise APIError.new("BAD_REQUEST", message: "Invalid field #{field} for model #{model}") unless attributes
|
|
353
|
+
|
|
354
|
+
attributes.fetch(:field_name, physical_name(field))
|
|
278
355
|
end
|
|
279
356
|
|
|
280
357
|
def generated_id
|
|
@@ -291,7 +368,7 @@ module BetterAuth
|
|
|
291
368
|
|
|
292
369
|
def coerce_value(value, attributes)
|
|
293
370
|
return value if value.nil?
|
|
294
|
-
return
|
|
371
|
+
return parse_time_value(value) if attributes[:type] == "date" && value.is_a?(String)
|
|
295
372
|
return JSON.generate(value) if json_like?(attributes) && !value.is_a?(String)
|
|
296
373
|
|
|
297
374
|
value
|
|
@@ -316,7 +393,7 @@ module BetterAuth
|
|
|
316
393
|
when "number"
|
|
317
394
|
return coerce_number(value)
|
|
318
395
|
when "date"
|
|
319
|
-
return
|
|
396
|
+
return parse_time_value(value) if value.is_a?(String)
|
|
320
397
|
end
|
|
321
398
|
|
|
322
399
|
coerce_value(value, attributes)
|
|
@@ -348,6 +425,57 @@ module BetterAuth
|
|
|
348
425
|
value
|
|
349
426
|
end
|
|
350
427
|
|
|
428
|
+
def coerce_pagination(value, label)
|
|
429
|
+
Integer(value).tap do |integer|
|
|
430
|
+
raise ArgumentError if integer.negative?
|
|
431
|
+
end
|
|
432
|
+
rescue ArgumentError, TypeError
|
|
433
|
+
raise APIError.new("BAD_REQUEST", message: "Invalid #{label}")
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def select_fields_for_join(select, join_config)
|
|
437
|
+
return select unless select && !join_config.empty?
|
|
438
|
+
|
|
439
|
+
join_config.each_value.with_object(select.dup) do |config, fields|
|
|
440
|
+
from = storage_key(config.fetch(:from))
|
|
441
|
+
fields << from unless fields.include?(from)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def trim_unrequested_select_fields(records, requested_select, join_config)
|
|
446
|
+
hidden = join_config.each_value.map { |config| storage_key(config.fetch(:from)) } - requested_select
|
|
447
|
+
records.each { |record| hidden.each { |field| record.delete(field) } }
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def insensitive_string_mode?(mode, attributes)
|
|
451
|
+
mode == "insensitive" && attributes[:type].to_s == "string"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def downcase_where_value(value)
|
|
455
|
+
return value.map { |entry| downcase_where_value(entry) } if value.is_a?(Array)
|
|
456
|
+
return value.downcase if value.is_a?(String)
|
|
457
|
+
|
|
458
|
+
value
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def default_find_many_limit
|
|
462
|
+
database_options = options.advanced[:database] || {}
|
|
463
|
+
coerce_pagination(
|
|
464
|
+
database_options[:default_find_many_limit] || database_options[:defaultFindManyLimit] || 100,
|
|
465
|
+
"limit"
|
|
466
|
+
)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def join_limit(config)
|
|
470
|
+
coerce_pagination(config[:limit] || default_find_many_limit, "limit")
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def parse_time_value(value)
|
|
474
|
+
Time.parse(value)
|
|
475
|
+
rescue ArgumentError
|
|
476
|
+
raise APIError.new("BAD_REQUEST", message: "Invalid date")
|
|
477
|
+
end
|
|
478
|
+
|
|
351
479
|
def escape_like(value)
|
|
352
480
|
value.to_s.gsub(/[\\%_]/) { |match| "\\#{match}" }
|
|
353
481
|
end
|
data/lib/better_auth/hanami.rb
CHANGED
|
@@ -20,16 +20,24 @@ module BetterAuth
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def configure
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
auth_mutex.synchronize do
|
|
24
|
+
yield configuration
|
|
25
|
+
@auth = nil
|
|
26
|
+
end
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def auth(overrides = nil)
|
|
28
30
|
options = configuration.to_auth_options
|
|
29
|
-
return @auth ||= BetterAuth.auth(options) if overrides.nil? || overrides.empty?
|
|
31
|
+
return auth_mutex.synchronize { @auth ||= BetterAuth.auth(options) } if overrides.nil? || overrides.empty?
|
|
30
32
|
|
|
31
33
|
BetterAuth.auth(options.merge(overrides))
|
|
32
34
|
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def auth_mutex
|
|
39
|
+
@auth_mutex ||= Mutex.new
|
|
40
|
+
end
|
|
33
41
|
end
|
|
34
42
|
end
|
|
35
43
|
end
|
data/lib/tasks/better_auth.rake
CHANGED
|
@@ -19,4 +19,11 @@ namespace :better_auth do
|
|
|
19
19
|
BetterAuth::Hanami::Generators::RelationGenerator.new.run
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
desc "Check Better Auth configuration and schema health"
|
|
24
|
+
task :doctor do
|
|
25
|
+
config = BetterAuth::Configuration.new(BetterAuth::Hanami.configuration.to_auth_options)
|
|
26
|
+
exit_code = BetterAuth::Doctor.print(BetterAuth::Doctor.check(config), stdout: $stdout, stderr: $stderr)
|
|
27
|
+
abort if exit_code != 0
|
|
28
|
+
end
|
|
22
29
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth-hanami
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -206,14 +206,14 @@ files:
|
|
|
206
206
|
- lib/better_auth/hanami/version.rb
|
|
207
207
|
- lib/better_auth_hanami.rb
|
|
208
208
|
- lib/tasks/better_auth.rake
|
|
209
|
-
homepage: https://github.com/sebasxsala/better-auth
|
|
209
|
+
homepage: https://github.com/sebasxsala/better-auth-rb
|
|
210
210
|
licenses:
|
|
211
211
|
- MIT
|
|
212
212
|
metadata:
|
|
213
|
-
homepage_uri: https://github.com/sebasxsala/better-auth
|
|
214
|
-
source_code_uri: https://github.com/sebasxsala/better-auth
|
|
215
|
-
changelog_uri: https://github.com/sebasxsala/better-auth/blob/main/packages/better_auth-hanami/CHANGELOG.md
|
|
216
|
-
bug_tracker_uri: https://github.com/sebasxsala/better-auth/issues
|
|
213
|
+
homepage_uri: https://github.com/sebasxsala/better-auth-rb
|
|
214
|
+
source_code_uri: https://github.com/sebasxsala/better-auth-rb
|
|
215
|
+
changelog_uri: https://github.com/sebasxsala/better-auth-rb/blob/main/packages/better_auth-hanami/CHANGELOG.md
|
|
216
|
+
bug_tracker_uri: https://github.com/sebasxsala/better-auth-rb/issues
|
|
217
217
|
rdoc_options: []
|
|
218
218
|
require_paths:
|
|
219
219
|
- lib
|