better_auth 0.1.1 → 0.2.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 +6 -0
- data/README.md +106 -16
- data/lib/better_auth/adapters/base.rb +49 -0
- data/lib/better_auth/adapters/internal_adapter.rb +439 -0
- data/lib/better_auth/adapters/memory.rb +232 -0
- data/lib/better_auth/adapters/mongodb.rb +369 -0
- data/lib/better_auth/adapters/mssql.rb +42 -0
- data/lib/better_auth/adapters/mysql.rb +33 -0
- data/lib/better_auth/adapters/postgres.rb +17 -0
- data/lib/better_auth/adapters/sql.rb +425 -0
- data/lib/better_auth/adapters/sqlite.rb +20 -0
- data/lib/better_auth/api.rb +226 -0
- data/lib/better_auth/api_error.rb +53 -0
- data/lib/better_auth/auth.rb +42 -0
- data/lib/better_auth/configuration.rb +399 -0
- data/lib/better_auth/context.rb +210 -0
- data/lib/better_auth/cookies.rb +278 -0
- data/lib/better_auth/core.rb +37 -1
- data/lib/better_auth/crypto/jwe.rb +76 -0
- data/lib/better_auth/crypto.rb +191 -0
- data/lib/better_auth/database_hooks.rb +114 -0
- data/lib/better_auth/endpoint.rb +326 -0
- data/lib/better_auth/error.rb +52 -0
- data/lib/better_auth/middleware/origin_check.rb +128 -0
- data/lib/better_auth/password.rb +120 -0
- data/lib/better_auth/plugin.rb +129 -0
- data/lib/better_auth/plugin_context.rb +16 -0
- data/lib/better_auth/plugin_registry.rb +67 -0
- data/lib/better_auth/plugins/access.rb +87 -0
- data/lib/better_auth/plugins/additional_fields.rb +29 -0
- data/lib/better_auth/plugins/admin/schema.rb +28 -0
- data/lib/better_auth/plugins/admin.rb +518 -0
- data/lib/better_auth/plugins/anonymous.rb +198 -0
- data/lib/better_auth/plugins/api_key.rb +16 -0
- data/lib/better_auth/plugins/bearer.rb +128 -0
- data/lib/better_auth/plugins/captcha.rb +159 -0
- data/lib/better_auth/plugins/custom_session.rb +84 -0
- data/lib/better_auth/plugins/device_authorization.rb +302 -0
- data/lib/better_auth/plugins/email_otp.rb +536 -0
- data/lib/better_auth/plugins/expo.rb +88 -0
- data/lib/better_auth/plugins/generic_oauth.rb +780 -0
- data/lib/better_auth/plugins/have_i_been_pwned.rb +94 -0
- data/lib/better_auth/plugins/jwt.rb +482 -0
- data/lib/better_auth/plugins/last_login_method.rb +92 -0
- data/lib/better_auth/plugins/magic_link.rb +181 -0
- data/lib/better_auth/plugins/mcp.rb +342 -0
- data/lib/better_auth/plugins/multi_session.rb +173 -0
- data/lib/better_auth/plugins/oauth_protocol.rb +348 -0
- data/lib/better_auth/plugins/oauth_provider.rb +16 -0
- data/lib/better_auth/plugins/oauth_proxy.rb +257 -0
- data/lib/better_auth/plugins/oidc_provider.rb +597 -0
- data/lib/better_auth/plugins/one_tap.rb +154 -0
- data/lib/better_auth/plugins/one_time_token.rb +106 -0
- data/lib/better_auth/plugins/open_api.rb +489 -0
- data/lib/better_auth/plugins/organization/schema.rb +106 -0
- data/lib/better_auth/plugins/organization.rb +990 -0
- data/lib/better_auth/plugins/passkey.rb +16 -0
- data/lib/better_auth/plugins/phone_number.rb +321 -0
- data/lib/better_auth/plugins/scim.rb +16 -0
- data/lib/better_auth/plugins/siwe.rb +242 -0
- data/lib/better_auth/plugins/sso.rb +16 -0
- data/lib/better_auth/plugins/stripe.rb +16 -0
- data/lib/better_auth/plugins/two_factor.rb +514 -0
- data/lib/better_auth/plugins/username.rb +278 -0
- data/lib/better_auth/plugins.rb +46 -0
- data/lib/better_auth/rate_limiter.rb +215 -0
- data/lib/better_auth/request_ip.rb +70 -0
- data/lib/better_auth/router.rb +365 -0
- data/lib/better_auth/routes/account.rb +211 -0
- data/lib/better_auth/routes/email_verification.rb +108 -0
- data/lib/better_auth/routes/error.rb +102 -0
- data/lib/better_auth/routes/ok.rb +15 -0
- data/lib/better_auth/routes/password.rb +164 -0
- data/lib/better_auth/routes/session.rb +137 -0
- data/lib/better_auth/routes/sign_in.rb +90 -0
- data/lib/better_auth/routes/sign_out.rb +15 -0
- data/lib/better_auth/routes/sign_up.rb +145 -0
- data/lib/better_auth/routes/social.rb +188 -0
- data/lib/better_auth/routes/user.rb +193 -0
- data/lib/better_auth/schema/sql.rb +191 -0
- data/lib/better_auth/schema.rb +275 -0
- data/lib/better_auth/session.rb +122 -0
- data/lib/better_auth/session_store.rb +91 -0
- data/lib/better_auth/social_providers/apple.rb +55 -0
- data/lib/better_auth/social_providers/base.rb +67 -0
- data/lib/better_auth/social_providers/discord.rb +59 -0
- data/lib/better_auth/social_providers/github.rb +59 -0
- data/lib/better_auth/social_providers/gitlab.rb +54 -0
- data/lib/better_auth/social_providers/google.rb +65 -0
- data/lib/better_auth/social_providers/microsoft_entra_id.rb +65 -0
- data/lib/better_auth/social_providers.rb +9 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +87 -2
- metadata +218 -21
- data/.ruby-version +0 -1
- data/.standard.yml +0 -12
- data/.vscode/settings.json +0 -22
- data/AGENTS.md +0 -50
- data/CLAUDE.md +0 -1
- data/CODE_OF_CONDUCT.md +0 -173
- data/CONTRIBUTING.md +0 -187
- data/Gemfile +0 -12
- data/Makefile +0 -207
- data/Rakefile +0 -25
- data/SECURITY.md +0 -28
- data/docker-compose.yml +0 -63
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require_relative "error"
|
|
7
|
+
|
|
8
|
+
module BetterAuth
|
|
9
|
+
module Password
|
|
10
|
+
PREFIX = "bcrypt_sha256$"
|
|
11
|
+
BCRYPT_PREFIXES = ["$2a$", "$2b$", "$2x$", "$2y$"].freeze
|
|
12
|
+
SCRYPT = {
|
|
13
|
+
N: 16_384,
|
|
14
|
+
r: 16,
|
|
15
|
+
p: 1,
|
|
16
|
+
length: 64
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def hash(password, hasher: nil, algorithm: :scrypt)
|
|
22
|
+
return hasher.call(password) if hasher.respond_to?(:call)
|
|
23
|
+
|
|
24
|
+
case (hasher || algorithm || :scrypt).to_sym
|
|
25
|
+
when :scrypt
|
|
26
|
+
hash_scrypt(password)
|
|
27
|
+
when :bcrypt
|
|
28
|
+
hash_bcrypt(password)
|
|
29
|
+
else
|
|
30
|
+
raise Error, "Unsupported password hasher: #{hasher || algorithm}. Supported hashers are :scrypt and :bcrypt."
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def verify(password:, hash:, verifier: nil, algorithm: :scrypt)
|
|
35
|
+
return call_verifier(verifier, password, hash) if verifier.respond_to?(:call)
|
|
36
|
+
|
|
37
|
+
digest = hash.to_s
|
|
38
|
+
if digest.start_with?(PREFIX)
|
|
39
|
+
return verify_bcrypt(password_input(password), digest.delete_prefix(PREFIX))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
return verify_bcrypt(password.to_s, digest) if bcrypt_hash?(digest)
|
|
43
|
+
return verify_scrypt(password, digest) if scrypt_hash?(digest)
|
|
44
|
+
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def password_input(password)
|
|
49
|
+
Digest::SHA256.hexdigest(password.to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hash_scrypt(password)
|
|
53
|
+
salt = SecureRandom.random_bytes(16).unpack1("H*")
|
|
54
|
+
key = scrypt_key(password, salt)
|
|
55
|
+
"#{salt}:#{key.unpack1("H*")}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def verify_scrypt(password, digest)
|
|
59
|
+
salt, key = digest.to_s.split(":", 2)
|
|
60
|
+
return false unless salt && key
|
|
61
|
+
|
|
62
|
+
expected = scrypt_key(password, salt).unpack1("H*")
|
|
63
|
+
return false unless expected.bytesize == key.bytesize
|
|
64
|
+
|
|
65
|
+
OpenSSL.fixed_length_secure_compare(expected, key.downcase)
|
|
66
|
+
rescue OpenSSL::KDF::KDFError, ArgumentError
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def scrypt_key(password, salt)
|
|
71
|
+
OpenSSL::KDF.scrypt(
|
|
72
|
+
password.to_s.unicode_normalize(:nfkc),
|
|
73
|
+
salt: salt,
|
|
74
|
+
N: SCRYPT.fetch(:N),
|
|
75
|
+
r: SCRYPT.fetch(:r),
|
|
76
|
+
p: SCRYPT.fetch(:p),
|
|
77
|
+
length: SCRYPT.fetch(:length)
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hash_bcrypt(password)
|
|
82
|
+
klass = require_bcrypt!
|
|
83
|
+
"#{PREFIX}#{klass.create(password_input(password))}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def verify_bcrypt(password, digest)
|
|
87
|
+
klass = require_bcrypt!
|
|
88
|
+
klass.new(digest) == password.to_s
|
|
89
|
+
rescue BCrypt::Errors::InvalidHash
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def bcrypt_hash?(digest)
|
|
94
|
+
BCRYPT_PREFIXES.any? { |prefix| digest.start_with?(prefix) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def scrypt_hash?(digest)
|
|
98
|
+
/\A[0-9a-fA-F]{32}:[0-9a-fA-F]{128}\z/.match?(digest.to_s)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def call_verifier(verifier, password, digest)
|
|
102
|
+
if verifier.arity == 1
|
|
103
|
+
verifier.call(password: password, hash: digest)
|
|
104
|
+
else
|
|
105
|
+
verifier.call(password, digest)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def bcrypt_password_class
|
|
110
|
+
require "bcrypt"
|
|
111
|
+
BCrypt::Password
|
|
112
|
+
rescue LoadError
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def require_bcrypt!
|
|
117
|
+
bcrypt_password_class || raise(Error, "The :bcrypt password hasher requires the optional bcrypt gem. Add `gem \"bcrypt\"` to your Gemfile.")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
class Plugin
|
|
5
|
+
FIELDS = [
|
|
6
|
+
:id,
|
|
7
|
+
:init,
|
|
8
|
+
:endpoints,
|
|
9
|
+
:middlewares,
|
|
10
|
+
:hooks,
|
|
11
|
+
:schema,
|
|
12
|
+
:migrations,
|
|
13
|
+
:options,
|
|
14
|
+
:rate_limit,
|
|
15
|
+
:error_codes,
|
|
16
|
+
:on_request,
|
|
17
|
+
:on_response,
|
|
18
|
+
:adapter
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
attr_reader(*FIELDS)
|
|
22
|
+
|
|
23
|
+
def self.coerce(value)
|
|
24
|
+
return value if value.is_a?(self)
|
|
25
|
+
|
|
26
|
+
new(value || {})
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(data = {}, **keywords)
|
|
30
|
+
data = data.to_h if data.respond_to?(:to_h) && !data.is_a?(Hash)
|
|
31
|
+
raw = normalize_hash((data || {}).merge(keywords))
|
|
32
|
+
|
|
33
|
+
@id = raw[:id].to_s
|
|
34
|
+
@init = raw[:init]
|
|
35
|
+
@endpoints = normalize_endpoint_keys(raw[:endpoints] || {})
|
|
36
|
+
@middlewares = normalize_middlewares(raw[:middlewares] || [])
|
|
37
|
+
@hooks = normalize_hooks(raw[:hooks] || {})
|
|
38
|
+
@schema = raw[:schema] || {}
|
|
39
|
+
@migrations = raw[:migrations] || {}
|
|
40
|
+
@options = raw[:options] || {}
|
|
41
|
+
@rate_limit = Array(raw[:rate_limit])
|
|
42
|
+
@error_codes = normalize_error_codes(raw)
|
|
43
|
+
@on_request = raw[:on_request]
|
|
44
|
+
@on_response = raw[:on_response]
|
|
45
|
+
@adapter = raw[:adapter]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def [](key)
|
|
49
|
+
to_h[normalize_key(key)]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fetch(key, *default, &block)
|
|
53
|
+
normalized = normalize_key(key)
|
|
54
|
+
return to_h.fetch(normalized, *default, &block) if default.any? || block
|
|
55
|
+
|
|
56
|
+
to_h.fetch(normalized)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def dig(*keys)
|
|
60
|
+
keys.reduce(to_h) do |value, key|
|
|
61
|
+
return nil unless value.respond_to?(:[])
|
|
62
|
+
|
|
63
|
+
value[normalize_key(key)] || value[key]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def merge_options!(defaults)
|
|
68
|
+
@options = deep_merge(@options, normalize_hash(defaults || {}))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_h
|
|
72
|
+
FIELDS.each_with_object({}) do |field, result|
|
|
73
|
+
result[field] = public_send(field)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def normalize_endpoint_keys(value)
|
|
80
|
+
normalize_hash(value).each_with_object({}) do |(key, endpoint), result|
|
|
81
|
+
result[normalize_key(key)] = endpoint
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def normalize_middlewares(value)
|
|
86
|
+
Array(value).map { |middleware| normalize_hash(middleware) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize_hooks(value)
|
|
90
|
+
data = normalize_hash(value)
|
|
91
|
+
{
|
|
92
|
+
before: Array(data[:before]).map { |hook| normalize_hash(hook) },
|
|
93
|
+
after: Array(data[:after]).map { |hook| normalize_hash(hook) }
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def normalize_error_codes(raw)
|
|
98
|
+
codes = raw[:error_codes] || raw[:ERROR_CODES] || raw[:$ERROR_CODES]
|
|
99
|
+
normalize_hash(codes || {}).transform_keys { |key| key.to_s.upcase }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def normalize_hash(value)
|
|
103
|
+
return {} unless value.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
value.each_with_object({}) do |(key, object), result|
|
|
106
|
+
result[normalize_key(key)] = object.is_a?(Hash) ? normalize_hash(object) : object
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def normalize_key(key)
|
|
111
|
+
key.to_s
|
|
112
|
+
.delete_prefix("$")
|
|
113
|
+
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
|
|
114
|
+
.tr("-", "_")
|
|
115
|
+
.downcase
|
|
116
|
+
.to_sym
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def deep_merge(base, override)
|
|
120
|
+
base.merge(override) do |_key, old_value, new_value|
|
|
121
|
+
if old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
|
122
|
+
deep_merge(old_value, new_value)
|
|
123
|
+
else
|
|
124
|
+
new_value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
class PluginContext
|
|
5
|
+
attr_reader :context, :plugin
|
|
6
|
+
|
|
7
|
+
def initialize(context, plugin = nil)
|
|
8
|
+
@context = context
|
|
9
|
+
@plugin = plugin
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def apply!(attributes)
|
|
13
|
+
context.apply_plugin_context!(attributes || {})
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
class PluginRegistry
|
|
5
|
+
attr_reader :context, :plugins
|
|
6
|
+
|
|
7
|
+
def initialize(context)
|
|
8
|
+
@context = context
|
|
9
|
+
@plugins = context.options.plugins
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run_init!
|
|
13
|
+
plugins.each do |plugin|
|
|
14
|
+
next unless plugin.init
|
|
15
|
+
|
|
16
|
+
result = plugin.init.call(context)
|
|
17
|
+
next unless result.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
apply_options(plugin, result[:options] || result["options"])
|
|
20
|
+
PluginContext.new(context, plugin).apply!(result[:context] || result["context"])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context.refresh_from_options!
|
|
24
|
+
context.set_internal_adapter(Adapters::InternalAdapter.new(context.adapter, context.options))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def endpoints
|
|
28
|
+
plugins.each_with_object({}) do |plugin, result|
|
|
29
|
+
result.merge!(plugin.endpoints)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def error_codes(base)
|
|
34
|
+
plugins.each_with_object(base.dup) do |plugin, codes|
|
|
35
|
+
plugin.error_codes.each do |key, value|
|
|
36
|
+
codes[key.to_s.upcase] = value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def apply_options(plugin, options)
|
|
44
|
+
return unless options.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
normalized = normalize_hash(options)
|
|
47
|
+
plugin.merge_options!(normalized)
|
|
48
|
+
context.options.merge_defaults!(normalized)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def normalize_hash(value)
|
|
52
|
+
return {} unless value.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
value.each_with_object({}) do |(key, object), result|
|
|
55
|
+
result[normalize_key(key)] = object.is_a?(Hash) ? normalize_hash(object) : object
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def normalize_key(key)
|
|
60
|
+
key.to_s
|
|
61
|
+
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
|
|
62
|
+
.tr("-", "_")
|
|
63
|
+
.downcase
|
|
64
|
+
.to_sym
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Plugins
|
|
5
|
+
class Role
|
|
6
|
+
attr_reader :statements
|
|
7
|
+
|
|
8
|
+
def initialize(statements)
|
|
9
|
+
@statements = stringify_statements(statements)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authorize(request, connector = "AND")
|
|
13
|
+
success = false
|
|
14
|
+
stringify_request(request).each do |resource, requested_actions|
|
|
15
|
+
allowed_actions = statements[resource]
|
|
16
|
+
unless allowed_actions
|
|
17
|
+
return {success: false, error: "You are not allowed to access resource: #{resource}"}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
success = if requested_actions.is_a?(Array)
|
|
21
|
+
requested_actions.all? { |action| allowed_actions.include?(action.to_s) }
|
|
22
|
+
elsif requested_actions.is_a?(Hash)
|
|
23
|
+
unless requested_actions.key?("actions") || requested_actions.key?(:actions)
|
|
24
|
+
raise Error, "Invalid access control request"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
raw_actions = requested_actions["actions"] || requested_actions[:actions]
|
|
28
|
+
raise Error, "Invalid access control request" if raw_actions.nil?
|
|
29
|
+
|
|
30
|
+
actions = Array(raw_actions).map(&:to_s)
|
|
31
|
+
action_connector = (requested_actions["connector"] || requested_actions[:connector] || "AND").to_s.upcase
|
|
32
|
+
if action_connector == "OR"
|
|
33
|
+
actions.any? { |action| allowed_actions.include?(action) }
|
|
34
|
+
else
|
|
35
|
+
actions.all? { |action| allowed_actions.include?(action) }
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
raise Error, "Invalid access control request"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return {success: true} if success && connector.to_s.upcase == "OR"
|
|
42
|
+
return {success: false, error: "unauthorized to access resource \"#{resource}\""} if !success && connector.to_s.upcase == "AND"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
success ? {success: true} : {success: false, error: "Not authorized"}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def stringify_statements(value)
|
|
51
|
+
(value || {}).each_with_object({}) do |(resource, actions), result|
|
|
52
|
+
result[resource.to_s] = Array(actions).map(&:to_s)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def stringify_request(value)
|
|
57
|
+
(value || {}).each_with_object({}) do |(resource, actions), result|
|
|
58
|
+
result[resource.to_s] = actions
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class AccessControl
|
|
64
|
+
attr_reader :statements
|
|
65
|
+
|
|
66
|
+
def initialize(statements)
|
|
67
|
+
@statements = (statements || {}).each_with_object({}) do |(resource, actions), result|
|
|
68
|
+
result[resource.to_s] = Array(actions).map(&:to_s)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def new_role(statements)
|
|
73
|
+
Role.new(statements)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
alias_method :newRole, :new_role
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
module_function
|
|
80
|
+
|
|
81
|
+
def create_access_control(statements)
|
|
82
|
+
AccessControl.new(statements)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
singleton_class.alias_method :createAccessControl, :create_access_control
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Plugins
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def additional_fields(schema = {})
|
|
8
|
+
config = normalize_hash(schema)
|
|
9
|
+
user_fields = storage_fields(config[:user] || {})
|
|
10
|
+
session_fields = storage_fields(config[:session] || {})
|
|
11
|
+
|
|
12
|
+
Plugin.new(
|
|
13
|
+
id: "additional-fields",
|
|
14
|
+
schema: {
|
|
15
|
+
user: {fields: user_fields},
|
|
16
|
+
session: {fields: session_fields}
|
|
17
|
+
},
|
|
18
|
+
init: lambda do |_context|
|
|
19
|
+
{
|
|
20
|
+
options: {
|
|
21
|
+
user: {additional_fields: user_fields},
|
|
22
|
+
session: {additional_fields: session_fields}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterAuth
|
|
4
|
+
module Plugins
|
|
5
|
+
module AdminSchema
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def build(custom = nil)
|
|
9
|
+
schema = {
|
|
10
|
+
user: {
|
|
11
|
+
fields: {
|
|
12
|
+
role: {type: "string", required: false, input: false},
|
|
13
|
+
banned: {type: "boolean", required: false, input: false, default_value: false},
|
|
14
|
+
banReason: {type: "string", required: false, input: false},
|
|
15
|
+
banExpires: {type: "date", required: false, input: false}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
session: {
|
|
19
|
+
fields: {
|
|
20
|
+
impersonatedBy: {type: "string", required: false}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
OrganizationSchema.merge_custom_schema(schema, custom)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|