better_auth-hanami 0.6.2 → 0.8.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 +37 -0
- data/lib/better_auth/hanami/action_helpers.rb +4 -1
- data/lib/better_auth/hanami/generators/install_generator.rb +21 -3
- data/lib/better_auth/hanami/generators/migration_generator.rb +11 -8
- data/lib/better_auth/hanami/mounted_app.rb +5 -0
- data/lib/better_auth/hanami/sequel_adapter.rb +106 -16
- data/lib/better_auth/hanami/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38b660cd7b5342ffeb1604a0b1c31ec6b0258016e496f6910764d0c166242edb
|
|
4
|
+
data.tar.gz: b8857da84de1c4d1ca206892aeaf438fd633ee729290074e2e68872930964428
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40d10b9a1922dc8b80c90362c124f4513b98e6cb0cd1895eae17cafb07fa30d5ec6473f19b3ce828ecf3db22181472c20edcbf6d98d888889f2c389c789b3cec
|
|
7
|
+
data.tar.gz: 9c44539d3fbaee60870d67bb2e9fa79ef75b7d3d9cc1cc48cc1979fc256347fe282b29cb24eed9689d1c7850bd728a0b2208fbb7d3d7752d72f21626b9cc8a65
|
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.7.0] - 2026-05-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Aligned Hanami route mounting, action helpers, install generator, and migration generator behavior with the shared Rack and schema semantics.
|
|
15
|
+
- Hardened the Sequel adapter for upstream-shaped filtering, joins, falsey values, and limit behavior.
|
|
16
|
+
|
|
10
17
|
## [0.1.1] - 2026-04-29
|
|
11
18
|
|
|
12
19
|
### Fixed
|
data/README.md
CHANGED
|
@@ -65,6 +65,7 @@ Hanami.app.register_provider(:better_auth) do
|
|
|
65
65
|
config.database = ->(options) {
|
|
66
66
|
BetterAuth::Hanami::SequelAdapter.from_container(target, options)
|
|
67
67
|
}
|
|
68
|
+
config.trusted_origins = [target["settings"].better_auth_url].compact
|
|
68
69
|
config.email_and_password = {enabled: true}
|
|
69
70
|
config.plugins = []
|
|
70
71
|
end
|
|
@@ -76,6 +77,37 @@ Hanami.app.register_provider(:better_auth) do
|
|
|
76
77
|
end
|
|
77
78
|
```
|
|
78
79
|
|
|
80
|
+
`trusted_origins` controls Better Auth origin and redirect URL validation. If
|
|
81
|
+
your browser client calls the auth endpoints from another origin, configure Rack
|
|
82
|
+
CORS middleware in your Hanami app as well so preflight requests and
|
|
83
|
+
`Access-Control-*` response headers match your frontend origin and credentials
|
|
84
|
+
policy. For the shared Rack/CORS/CSRF boundary, see
|
|
85
|
+
[`host-app-responsibilities.md`](../../.docs/features/host-app-responsibilities.md).
|
|
86
|
+
|
|
87
|
+
Do not rely on a Hanami-only empty `trusted_origins` list as a strict
|
|
88
|
+
deny-all-origin policy; set real deployment URLs in app settings. Keep
|
|
89
|
+
`BetterAuth::Hanami::MountedApp` behavior aligned with Hanami's router instead
|
|
90
|
+
of copying Rails mount internals without integration tests. Be cautious with
|
|
91
|
+
relation or inflector overrides generated for an app, because overwriting
|
|
92
|
+
application-specific Hanami relations can be destructive.
|
|
93
|
+
|
|
94
|
+
## Regenerating Migrations
|
|
95
|
+
|
|
96
|
+
The migration generator skips an existing `*_create_better_auth_tables.rb` file
|
|
97
|
+
by default so user-edited migrations are not overwritten. To intentionally
|
|
98
|
+
regenerate the base migration for a new app or after changing plugin schemas,
|
|
99
|
+
call the Ruby API with `force: true`:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
BetterAuth::Hanami::Generators::MigrationGenerator.new.run(force: true)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The generated rake task keeps the non-overwriting behavior:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
bundle exec rake better_auth:generate:migration
|
|
109
|
+
```
|
|
110
|
+
|
|
79
111
|
## Routes
|
|
80
112
|
|
|
81
113
|
The generated `config/routes.rb` includes:
|
|
@@ -98,6 +130,11 @@ By default this mounts Better Auth at `/api/auth`. Customize the path:
|
|
|
98
130
|
better_auth at: "/auth"
|
|
99
131
|
```
|
|
100
132
|
|
|
133
|
+
`BetterAuth::Hanami::MountedApp` expects `PATH_INFO` in the shape produced by
|
|
134
|
+
Hanami's router; see `spec/better_auth/hanami/routing_spec.rb`. Custom Rack
|
|
135
|
+
stacks with different `SCRIPT_NAME` conventions may need application-level path
|
|
136
|
+
rewriting.
|
|
137
|
+
|
|
101
138
|
## Action Helpers
|
|
102
139
|
|
|
103
140
|
Include helpers in your base action:
|
|
@@ -34,6 +34,9 @@ module BetterAuth
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def resolve_better_auth_session(request)
|
|
37
|
+
auth = BetterAuth::Hanami.auth
|
|
38
|
+
auth.context.prepare_for_request!(request) if auth.context.respond_to?(:prepare_for_request!)
|
|
39
|
+
|
|
37
40
|
context = BetterAuth::Endpoint::Context.new(
|
|
38
41
|
path: request_path(request),
|
|
39
42
|
method: request_method(request),
|
|
@@ -41,7 +44,7 @@ module BetterAuth
|
|
|
41
44
|
body: {},
|
|
42
45
|
params: {},
|
|
43
46
|
headers: {"cookie" => request_cookie(request)},
|
|
44
|
-
context:
|
|
47
|
+
context: auth.context,
|
|
45
48
|
request: request
|
|
46
49
|
)
|
|
47
50
|
BetterAuth::Session.find_current(context, disable_refresh: true)
|
|
@@ -43,10 +43,14 @@ module BetterAuth
|
|
|
43
43
|
|
|
44
44
|
def update_routes
|
|
45
45
|
path = File.join(destination_root, "config/routes.rb")
|
|
46
|
-
|
|
46
|
+
unless File.exist?(path)
|
|
47
|
+
Kernel.warn("[better_auth-hanami] InstallGenerator: #{path} not found; skipping routes wiring. Add Hanami routes manually.")
|
|
48
|
+
return
|
|
49
|
+
end
|
|
47
50
|
|
|
48
51
|
content = File.read(path)
|
|
49
52
|
content = content.gsub(%(require "better_auth/hanami/routing"), %(require "better_auth/hanami"))
|
|
53
|
+
content = dedupe_better_auth_requires(content)
|
|
50
54
|
content = %(require "better_auth/hanami"\n) + content unless content.include?(%("better_auth/hanami"))
|
|
51
55
|
content = content.sub("class Routes < Hanami::Routes\n", "class Routes < Hanami::Routes\n include BetterAuth::Hanami::Routing\n") unless content.include?("include BetterAuth::Hanami::Routing")
|
|
52
56
|
content = content.sub(/(include BetterAuth::Hanami::Routing\n)(?!\s*better_auth)/, "\\1 better_auth\n") unless content.match?(/^\s*better_auth\b/)
|
|
@@ -55,7 +59,10 @@ module BetterAuth
|
|
|
55
59
|
|
|
56
60
|
def update_settings
|
|
57
61
|
path = File.join(destination_root, "config/settings.rb")
|
|
58
|
-
|
|
62
|
+
unless File.exist?(path)
|
|
63
|
+
Kernel.warn("[better_auth-hanami] InstallGenerator: #{path} not found; skipping settings wiring. Add better_auth_secret and better_auth_url manually.")
|
|
64
|
+
return
|
|
65
|
+
end
|
|
59
66
|
|
|
60
67
|
content = File.read(path)
|
|
61
68
|
return if content.include?("setting :better_auth_secret")
|
|
@@ -64,10 +71,21 @@ module BetterAuth
|
|
|
64
71
|
" setting :better_auth_secret, constructor: Types::String.constrained(min_size: 32)",
|
|
65
72
|
" setting :better_auth_url, constructor: Types::String.optional"
|
|
66
73
|
].join("\n")
|
|
67
|
-
content = content.sub(
|
|
74
|
+
content = content.sub(/(class[ \t]+Settings[ \t]*<[ \t]*Hanami::Settings[ \t]*\n)/, "\\1#{insertion}\n")
|
|
68
75
|
File.write(path, content)
|
|
69
76
|
end
|
|
70
77
|
|
|
78
|
+
def dedupe_better_auth_requires(content)
|
|
79
|
+
previous = nil
|
|
80
|
+
content.lines.each_with_object([]) do |line, output|
|
|
81
|
+
stripped = line.strip
|
|
82
|
+
next if stripped == previous && stripped == %(require "better_auth/hanami")
|
|
83
|
+
|
|
84
|
+
output << line
|
|
85
|
+
previous = stripped
|
|
86
|
+
end.join
|
|
87
|
+
end
|
|
88
|
+
|
|
71
89
|
def provider_template
|
|
72
90
|
<<~RUBY
|
|
73
91
|
# frozen_string_literal: true
|
|
@@ -7,25 +7,28 @@ module BetterAuth
|
|
|
7
7
|
module Hanami
|
|
8
8
|
module Generators
|
|
9
9
|
class MigrationGenerator
|
|
10
|
-
def initialize(destination_root: Dir.pwd, configuration: nil)
|
|
10
|
+
def initialize(destination_root: Dir.pwd, configuration: nil, force: false)
|
|
11
11
|
@destination_root = destination_root
|
|
12
12
|
@configuration = configuration
|
|
13
|
+
@force = force
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
def run
|
|
16
|
-
|
|
16
|
+
def run(force: nil)
|
|
17
|
+
force = @force if force.nil?
|
|
18
|
+
return existing_migration_path if existing_migration_path && !force
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
path = existing_migration_path || migration_path
|
|
21
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
22
|
+
File.write(path, BetterAuth::Hanami::Migration.render(generator_config))
|
|
23
|
+
path
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
private
|
|
24
27
|
|
|
25
28
|
attr_reader :destination_root, :configuration
|
|
26
29
|
|
|
27
|
-
def
|
|
28
|
-
Dir[File.join(destination_root, "config/db/migrate/*_create_better_auth_tables.rb")].
|
|
30
|
+
def existing_migration_path
|
|
31
|
+
@existing_migration_path ||= Dir[File.join(destination_root, "config/db/migrate/*_create_better_auth_tables.rb")].min
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
def migration_path
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module BetterAuth
|
|
4
4
|
module Hanami
|
|
5
|
+
# Rewrites PATH_INFO so the core router sees paths under +mount_path+.
|
|
6
|
+
# Hanami's +Slice::Router+ passes PATH_INFO as exercised in routing specs;
|
|
7
|
+
# custom Rack mounts that differ from that contract may need app-level
|
|
8
|
+
# rewriting adjustments. Compare the Rails adapter when debugging path
|
|
9
|
+
# behavior involving SCRIPT_NAME.
|
|
5
10
|
class MountedApp
|
|
6
11
|
def initialize(auth, mount_path:)
|
|
7
12
|
@auth = auth
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
|
+
require "json"
|
|
4
5
|
require "time"
|
|
5
6
|
require "sequel"
|
|
6
7
|
|
|
7
8
|
module BetterAuth
|
|
8
9
|
module Hanami
|
|
9
10
|
class SequelAdapter < BetterAuth::Adapters::Base
|
|
11
|
+
include BetterAuth::Adapters::JoinSupport
|
|
12
|
+
|
|
10
13
|
attr_reader :connection
|
|
11
14
|
|
|
12
15
|
def self.from_hanami(options, container: nil)
|
|
13
16
|
if container.nil? && defined?(::Hanami) && ::Hanami.respond_to?(:app)
|
|
14
17
|
container = ::Hanami.app
|
|
15
18
|
end
|
|
16
|
-
return
|
|
19
|
+
return memory_fallback(options) unless container
|
|
17
20
|
|
|
18
21
|
from_container(container, options)
|
|
19
22
|
end
|
|
@@ -24,7 +27,7 @@ module BetterAuth
|
|
|
24
27
|
elsif container.respond_to?(:[]) && safe_fetch(container, "db.gateway")
|
|
25
28
|
container["db.gateway"]
|
|
26
29
|
end
|
|
27
|
-
return
|
|
30
|
+
return memory_fallback(options) unless gateway
|
|
28
31
|
|
|
29
32
|
connection = gateway.respond_to?(:connection) ? gateway.connection : gateway
|
|
30
33
|
new(options, connection: connection)
|
|
@@ -36,6 +39,14 @@ module BetterAuth
|
|
|
36
39
|
nil
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
def self.memory_fallback(options)
|
|
43
|
+
Kernel.warn(
|
|
44
|
+
"[better_auth-hanami] SequelAdapter: using BetterAuth::Adapters::Memory " \
|
|
45
|
+
"(no Hanami container or db.gateway). Persisted auth data will not survive process restart."
|
|
46
|
+
)
|
|
47
|
+
BetterAuth::Adapters::Memory.new(options)
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
def initialize(options, connection:)
|
|
40
51
|
super(options)
|
|
41
52
|
@connection = connection
|
|
@@ -125,19 +136,21 @@ module BetterAuth
|
|
|
125
136
|
column = storage_field(model, field)
|
|
126
137
|
identifier = Sequel[column.to_sym]
|
|
127
138
|
operator = (fetch_key(clause, :operator) || "eq").to_s
|
|
128
|
-
|
|
139
|
+
attributes = schema_for(model).fetch(:fields).fetch(field)
|
|
140
|
+
raw_value = fetch_key(clause, :value)
|
|
141
|
+
value = coerce_where_value(raw_value, attributes)
|
|
129
142
|
|
|
130
143
|
case operator
|
|
131
|
-
when "in" then {column.to_sym => Array(
|
|
132
|
-
when "not_in" then Sequel.~(column.to_sym => Array(
|
|
144
|
+
when "in" then {column.to_sym => Array(raw_value).map { |entry| coerce_where_value(entry, attributes) }}
|
|
145
|
+
when "not_in" then Sequel.~(column.to_sym => Array(raw_value).map { |entry| coerce_where_value(entry, attributes) })
|
|
133
146
|
when "ne" then Sequel.~(column.to_sym => value)
|
|
134
147
|
when "gt" then identifier > value
|
|
135
148
|
when "gte" then identifier >= value
|
|
136
149
|
when "lt" then identifier < value
|
|
137
150
|
when "lte" then identifier <= value
|
|
138
|
-
when "contains" then Sequel.like(identifier, "%#{value}%")
|
|
139
|
-
when "starts_with" then Sequel.like(identifier, "#{value}%")
|
|
140
|
-
when "ends_with" then Sequel.like(identifier, "%#{value}")
|
|
151
|
+
when "contains" then Sequel.like(identifier, "%#{escape_like(value)}%", escape: "\\")
|
|
152
|
+
when "starts_with" then Sequel.like(identifier, "#{escape_like(value)}%", escape: "\\")
|
|
153
|
+
when "ends_with" then Sequel.like(identifier, "%#{escape_like(value)}", escape: "\\")
|
|
141
154
|
else {column.to_sym => value}
|
|
142
155
|
end
|
|
143
156
|
end
|
|
@@ -155,20 +168,57 @@ module BetterAuth
|
|
|
155
168
|
def attach_joins(model, records, join)
|
|
156
169
|
return records unless join
|
|
157
170
|
|
|
171
|
+
join_config = normalized_join(model, join)
|
|
158
172
|
records.each do |record|
|
|
159
|
-
|
|
160
|
-
join_model = join_model
|
|
161
|
-
case [model.to_s, join_model]
|
|
162
|
-
when ["session", "user"], ["account", "user"]
|
|
163
|
-
record[join_model] = find_one(model: join_model, where: [{field: "id", value: record["userId"]}])
|
|
164
|
-
when ["user", "account"]
|
|
165
|
-
record[join_model] = find_many(model: "account", where: [{field: "userId", value: record["id"]}])
|
|
166
|
-
end
|
|
173
|
+
join_config.each do |join_model, config|
|
|
174
|
+
record[join_model] = joined_records(record, join_model, config)
|
|
167
175
|
end
|
|
168
176
|
end
|
|
169
177
|
records
|
|
170
178
|
end
|
|
171
179
|
|
|
180
|
+
def joined_records(record, join_model, config)
|
|
181
|
+
local_value = record[config.fetch(:from)]
|
|
182
|
+
where = [{field: config.fetch(:to), value: local_value}]
|
|
183
|
+
|
|
184
|
+
if one_to_one_join?(config)
|
|
185
|
+
find_one(model: join_model, where: where)
|
|
186
|
+
else
|
|
187
|
+
records = find_many(model: join_model, where: where)
|
|
188
|
+
config[:limit] ? records.first(Integer(config[:limit])) : records
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def one_to_one_join?(config)
|
|
193
|
+
config[:relation] == "one-to-one" || config[:unique] == true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def inferred_join_config(model, join_model)
|
|
197
|
+
foreign_keys = schema_for(join_model).fetch(:fields).select do |_field, attributes|
|
|
198
|
+
reference_model_matches?(attributes, model)
|
|
199
|
+
end
|
|
200
|
+
forward_join = true
|
|
201
|
+
|
|
202
|
+
if foreign_keys.empty?
|
|
203
|
+
foreign_keys = schema_for(model).fetch(:fields).select do |_field, attributes|
|
|
204
|
+
reference_model_matches?(attributes, join_model)
|
|
205
|
+
end
|
|
206
|
+
forward_join = false
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
raise Error, "No foreign key found for model #{join_model} and base model #{model} while performing join operation." if foreign_keys.empty?
|
|
210
|
+
raise Error, "Multiple foreign keys found for model #{join_model} and base model #{model} while performing join operation. Only one foreign key is supported." if foreign_keys.length > 1
|
|
211
|
+
|
|
212
|
+
foreign_key, attributes = foreign_keys.first
|
|
213
|
+
reference = attributes.fetch(:references)
|
|
214
|
+
if forward_join
|
|
215
|
+
unique = attributes[:unique] == true
|
|
216
|
+
{from: reference.fetch(:field).to_s, to: foreign_key, relation: unique ? "one-to-one" : "one-to-many", unique: unique}
|
|
217
|
+
else
|
|
218
|
+
{from: foreign_key, to: reference.fetch(:field).to_s, relation: "one-to-one", unique: true}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
172
222
|
def transform_input(model, data, action, force_allow_id)
|
|
173
223
|
fields = schema_for(model).fetch(:fields)
|
|
174
224
|
input = stringify_keys(data)
|
|
@@ -242,6 +292,7 @@ module BetterAuth
|
|
|
242
292
|
def coerce_value(value, attributes)
|
|
243
293
|
return value if value.nil?
|
|
244
294
|
return Time.parse(value) if attributes[:type] == "date" && value.is_a?(String)
|
|
295
|
+
return JSON.generate(value) if json_like?(attributes) && !value.is_a?(String)
|
|
245
296
|
|
|
246
297
|
value
|
|
247
298
|
end
|
|
@@ -250,10 +301,37 @@ module BetterAuth
|
|
|
250
301
|
return value if value.nil?
|
|
251
302
|
return coerce_boolean(value) if attributes[:type] == "boolean"
|
|
252
303
|
return Time.parse(value.to_s) if attributes[:type] == "date" && !value.is_a?(Time)
|
|
304
|
+
return parse_json_value(value) if json_like?(attributes) && value.is_a?(String)
|
|
253
305
|
|
|
254
306
|
value
|
|
255
307
|
end
|
|
256
308
|
|
|
309
|
+
def coerce_where_value(value, attributes)
|
|
310
|
+
return value if value.nil?
|
|
311
|
+
|
|
312
|
+
case attributes[:type]
|
|
313
|
+
when "boolean"
|
|
314
|
+
return coerce_value(false, attributes) if value == false || value == 0 || value.to_s.downcase == "false" || value.to_s == "0"
|
|
315
|
+
return coerce_value(true, attributes) if value == true || value == 1 || value.to_s.downcase == "true" || value.to_s == "1"
|
|
316
|
+
when "number"
|
|
317
|
+
return coerce_number(value)
|
|
318
|
+
when "date"
|
|
319
|
+
return Time.parse(value) if value.is_a?(String)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
coerce_value(value, attributes)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def json_like?(attributes)
|
|
326
|
+
%w[json string[] number[]].include?(attributes[:type])
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def parse_json_value(value)
|
|
330
|
+
JSON.parse(value)
|
|
331
|
+
rescue JSON::ParserError
|
|
332
|
+
value
|
|
333
|
+
end
|
|
334
|
+
|
|
257
335
|
def coerce_boolean(value)
|
|
258
336
|
return value if value == true || value == false
|
|
259
337
|
return false if value == 0 || value.to_s == "0" || value.to_s.downcase == "f" || value.to_s.downcase == "false"
|
|
@@ -262,6 +340,18 @@ module BetterAuth
|
|
|
262
340
|
value
|
|
263
341
|
end
|
|
264
342
|
|
|
343
|
+
def coerce_number(value)
|
|
344
|
+
return value unless value.is_a?(String)
|
|
345
|
+
return value.to_i if /\A-?\d+\z/.match?(value)
|
|
346
|
+
return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
|
|
347
|
+
|
|
348
|
+
value
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def escape_like(value)
|
|
352
|
+
value.to_s.gsub(/[\\%_]/) { |match| "\\#{match}" }
|
|
353
|
+
end
|
|
354
|
+
|
|
265
355
|
def stringify_keys(data)
|
|
266
356
|
data.each_with_object({}) do |(key, value), result|
|
|
267
357
|
result[storage_key(key)] = value
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -181,8 +181,9 @@ dependencies:
|
|
|
181
181
|
- - "~>"
|
|
182
182
|
- !ruby/object:Gem::Version
|
|
183
183
|
version: '1.0'
|
|
184
|
-
description: Hanami integration for Better Auth Ruby.
|
|
185
|
-
|
|
184
|
+
description: Hanami integration for Better Auth Ruby. Better Auth Ruby is an independent
|
|
185
|
+
modern authentication framework for Ruby inspired by Better Auth. Provides route
|
|
186
|
+
mounting, ROM/Sequel persistence, migrations, helpers, and generators.
|
|
186
187
|
email:
|
|
187
188
|
- sebastian.sala.tech@gmail.com
|
|
188
189
|
executables: []
|