omniauth-identity 3.1.3 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -4
- data/CITATION.cff +20 -0
- data/CODE_OF_CONDUCT.md +3 -4
- data/CONTRIBUTING.md +129 -32
- data/FUNDING.md +66 -0
- data/README.md +290 -136
- data/REEK +0 -0
- data/RUBOCOP.md +71 -0
- data/SECURITY.md +6 -18
- data/lib/omniauth/identity/model.rb +29 -7
- data/lib/omniauth/identity/models/active_record.rb +46 -2
- data/lib/omniauth/identity/models/couch_potato.rb +56 -4
- data/lib/omniauth/identity/models/mongoid.rb +47 -2
- data/lib/omniauth/identity/models/nobrainer.rb +47 -3
- data/lib/omniauth/identity/models/rom.rb +151 -0
- data/lib/omniauth/identity/models/sequel.rb +71 -9
- data/lib/omniauth/identity/secure_password.rb +33 -0
- data/lib/omniauth/identity/version.rb +5 -1
- data/lib/omniauth/identity.rb +30 -0
- data/lib/omniauth/strategies/identity.rb +127 -6
- data.tar.gz.sig +0 -0
- metadata +164 -33
- metadata.gz.sig +0 -0
data/REEK
ADDED
File without changes
|
data/RUBOCOP.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# RuboCop Usage Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
A tale of two RuboCop plugin gems.
|
6
|
+
|
7
|
+
### RuboCop Gradual
|
8
|
+
|
9
|
+
This project uses `rubocop_gradual` instead of vanilla RuboCop for code style checking. The `rubocop_gradual` tool allows for gradual adoption of RuboCop rules by tracking violations in a lock file.
|
10
|
+
|
11
|
+
### RuboCop LTS
|
12
|
+
|
13
|
+
This project uses `rubocop-lts` to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2.
|
14
|
+
RuboCop rules are meticulously configured by the `rubocop-lts` family of gems to ensure that a project is compatible with a specific version of Ruby. See: https://rubocop-lts.gitlab.io for more.
|
15
|
+
|
16
|
+
## Checking RuboCop Violations
|
17
|
+
|
18
|
+
To check for RuboCop violations in this project, always use:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
bundle exec rake rubocop_gradual:check
|
22
|
+
```
|
23
|
+
|
24
|
+
**Do not use** the standard RuboCop commands like:
|
25
|
+
- `bundle exec rubocop`
|
26
|
+
- `rubocop`
|
27
|
+
|
28
|
+
## Understanding the Lock File
|
29
|
+
|
30
|
+
The `.rubocop_gradual.lock` file tracks all current RuboCop violations in the project. This allows the team to:
|
31
|
+
|
32
|
+
1. Prevent new violations while gradually fixing existing ones
|
33
|
+
2. Track progress on code style improvements
|
34
|
+
3. Ensure CI builds don't fail due to pre-existing violations
|
35
|
+
|
36
|
+
## Common Commands
|
37
|
+
|
38
|
+
- **Check violations**
|
39
|
+
- `bundle exec rake rubocop_gradual`
|
40
|
+
- `bundle exec rake rubocop_gradual:check`
|
41
|
+
- **(Safe) Autocorrect violations, and update lockfile if no new violations**
|
42
|
+
- `bundle exec rake rubocop_gradual:autocorrect`
|
43
|
+
- **Force update the lock file (w/o autocorrect) to match violations present in code**
|
44
|
+
- `bundle exec rake rubocop_gradual:force_update`
|
45
|
+
|
46
|
+
## Workflow
|
47
|
+
|
48
|
+
1. Before submitting a PR, run `bundle exec rake rubocop_gradual:autocorrect`
|
49
|
+
a. or just the default `bundle exec rake`, as autocorrection is a pre-requisite of the default task.
|
50
|
+
2. If there are new violations, either:
|
51
|
+
- Fix them in your code
|
52
|
+
- Run `bundle exec rake rubocop_gradual:force_update` to update the lock file (only for violations you can't fix immediately)
|
53
|
+
3. Commit the updated `.rubocop_gradual.lock` file along with your changes
|
54
|
+
|
55
|
+
## Never add inline RuboCop disables
|
56
|
+
|
57
|
+
Do not add inline `rubocop:disable` / `rubocop:enable` comments anywhere in the codebase (including specs, except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). We handle exceptions in two supported ways:
|
58
|
+
|
59
|
+
- Permanent/structural exceptions: prefer adjusting the RuboCop configuration (e.g., in `.rubocop.yml`) to exclude a rule for a path or file pattern when it makes sense project-wide.
|
60
|
+
- Temporary exceptions while improving code: record the current violations in `.rubocop_gradual.lock` via the gradual workflow:
|
61
|
+
- `bundle exec rake rubocop_gradual:autocorrect` (preferred; will autocorrect what it can and update the lock only if no new violations were introduced)
|
62
|
+
- If needed, `bundle exec rake rubocop_gradual:force_update` (as a last resort when you cannot fix the newly reported violations immediately)
|
63
|
+
|
64
|
+
In general, treat the rules as guidance to follow; fix violations rather than ignore them. For example, RSpec conventions in this project expect `described_class` to be used in specs that target a specific class under test.
|
65
|
+
|
66
|
+
## Benefits of rubocop_gradual
|
67
|
+
|
68
|
+
- Allows incremental adoption of code style rules
|
69
|
+
- Prevents CI failures due to pre-existing violations
|
70
|
+
- Provides a clear record of code style debt
|
71
|
+
- Enables focused efforts on improving code quality over time
|
data/SECURITY.md
CHANGED
@@ -18,23 +18,11 @@ Tidelift will coordinate the fix and disclosure.
|
|
18
18
|
|
19
19
|
## Additional Support
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay
|
27
|
-
[⛳liberapay]: https://liberapay.com/pboling/donate
|
28
|
-
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
|
29
|
-
[🖇sponsor]: https://github.com/sponsors/pboling
|
30
|
-
[🖇polar-img]: https://img.shields.io/badge/polar-donate-yellow.svg
|
31
|
-
[🖇polar]: https://polar.sh/pboling
|
32
|
-
[🖇kofi-img]: https://img.shields.io/badge/a_more_different_coffee-✓-yellow.svg
|
33
|
-
[🖇kofi]: https://ko-fi.com/O5O86SNP4
|
34
|
-
[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg
|
35
|
-
[🖇patreon]: https://patreon.com/galtzo
|
36
|
-
[🖇buyme]: https://www.buymeacoffee.com/pboling
|
37
|
-
[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-✓-yellow.svg?style=flat
|
21
|
+
If you are interested in support for versions older than the latest release,
|
22
|
+
please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate,
|
23
|
+
or find other sponsorship links in the [README].
|
24
|
+
|
25
|
+
[README]: README.md
|
38
26
|
|
39
27
|
## Enterprise Support
|
40
28
|
|
@@ -42,4 +30,4 @@ Available as part of the Tidelift Subscription.
|
|
42
30
|
|
43
31
|
The maintainers of this library and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers for the exact packages you use. [Learn more.][tidelift-ref]
|
44
32
|
|
45
|
-
[tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-omniauth-identity?utm_source=rubygems-omniauth-identity&utm_medium=referral&utm_campaign=enterprise&utm_term=repo
|
33
|
+
[tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-omniauth-identity?utm_source=rubygems-omniauth-identity&utm_medium=referral&utm_campaign=enterprise&utm_term=repo
|
@@ -9,21 +9,36 @@ module OmniAuth
|
|
9
9
|
# All methods marked as abstract must be implemented in the
|
10
10
|
# including class for things to work properly.
|
11
11
|
#
|
12
|
-
### Singleton API
|
12
|
+
# ### Singleton API
|
13
13
|
#
|
14
14
|
# * locate(key)
|
15
15
|
# * create(*args) - Deprecated in v3.0.5; Will be removed in v4.0
|
16
16
|
#
|
17
|
-
### Instance API
|
17
|
+
# ### Instance API
|
18
18
|
#
|
19
19
|
# * save
|
20
20
|
# * persisted?
|
21
21
|
# * authenticate(password)
|
22
22
|
#
|
23
|
+
# @example Including the Model
|
24
|
+
# class User
|
25
|
+
# include OmniAuth::Identity::Model
|
26
|
+
# # Implement required methods...
|
27
|
+
# end
|
23
28
|
module Model
|
29
|
+
# @!attribute [r] SCHEMA_ATTRIBUTES
|
30
|
+
# Standard OmniAuth schema attributes that may be stored in the model.
|
31
|
+
# @return [Array<String>] List of attribute names.
|
24
32
|
SCHEMA_ATTRIBUTES = %w[name email nickname first_name last_name location description image phone].freeze
|
25
33
|
|
26
34
|
class << self
|
35
|
+
# Called when this module is included in a model class.
|
36
|
+
#
|
37
|
+
# Extends the base class with ClassMethods and includes necessary APIs
|
38
|
+
# if they are not already defined.
|
39
|
+
#
|
40
|
+
# @param base [Class] the model class including this module
|
41
|
+
# @return [void]
|
27
42
|
def included(base)
|
28
43
|
base.extend(ClassMethods)
|
29
44
|
base.extend(ClassCreateApi) unless base.respond_to?(:create)
|
@@ -33,12 +48,13 @@ module OmniAuth
|
|
33
48
|
end
|
34
49
|
end
|
35
50
|
|
51
|
+
# Class-level methods for OmniAuth Identity models.
|
36
52
|
module ClassMethods
|
37
53
|
# Authenticate a user with the given key and password.
|
38
54
|
#
|
39
55
|
# @param [String] conditions The unique login key provided for a given identity.
|
40
56
|
# @param [String] password The presumed password for the identity.
|
41
|
-
# @return [Model, false] An instance of the identity model class.
|
57
|
+
# @return [Model, false] An instance of the identity model class or false if authentication fails.
|
42
58
|
def authenticate(conditions, password)
|
43
59
|
instance = locate(conditions)
|
44
60
|
return false unless instance
|
@@ -48,6 +64,8 @@ module OmniAuth
|
|
48
64
|
|
49
65
|
# Used to set or retrieve the method that will be used to get
|
50
66
|
# and set the user-supplied authentication key.
|
67
|
+
#
|
68
|
+
# @param method [String, Symbol, false] The method name to set, or false to retrieve.
|
51
69
|
# @return [String] The method name.
|
52
70
|
def auth_key(method = false)
|
53
71
|
@auth_key = method.to_s unless method == false
|
@@ -58,7 +76,7 @@ module OmniAuth
|
|
58
76
|
|
59
77
|
# Locate an identity given its unique login key.
|
60
78
|
#
|
61
|
-
# @abstract
|
79
|
+
# @abstract Subclasses must implement this method.
|
62
80
|
# @param [String] _key The unique login key.
|
63
81
|
# @return [Model] An instance of the identity model class.
|
64
82
|
def locate(_key)
|
@@ -66,6 +84,7 @@ module OmniAuth
|
|
66
84
|
end
|
67
85
|
end
|
68
86
|
|
87
|
+
# Provides a create method for models that don't have one.
|
69
88
|
module ClassCreateApi
|
70
89
|
# Persists a new Identity object to the ORM.
|
71
90
|
# Only included if the class doesn't define create, as a reminder to define create.
|
@@ -81,6 +100,7 @@ module OmniAuth
|
|
81
100
|
end
|
82
101
|
end
|
83
102
|
|
103
|
+
# Provides a save method for models that don't have one.
|
84
104
|
module InstanceSaveApi
|
85
105
|
# Persists a new Identity object to the ORM.
|
86
106
|
# Default raises an error. Override as needed per ORM.
|
@@ -88,6 +108,7 @@ module OmniAuth
|
|
88
108
|
# since it is a pattern many ORMs follow
|
89
109
|
#
|
90
110
|
# @abstract
|
111
|
+
# @param _options [Hash] Options for saving.
|
91
112
|
# @return [Model] An instance of the identity model class.
|
92
113
|
# @since 3.0.5
|
93
114
|
def save(**_options, &_block)
|
@@ -95,12 +116,13 @@ module OmniAuth
|
|
95
116
|
end
|
96
117
|
end
|
97
118
|
|
119
|
+
# Provides a persisted? method for models that don't have one.
|
98
120
|
module InstancePersistedApi
|
99
121
|
# Checks if the Identity object is persisted in the ORM.
|
100
122
|
# Default raises an error. Override as needed per ORM.
|
101
123
|
#
|
102
124
|
# @abstract
|
103
|
-
# @return [true
|
125
|
+
# @return [true, false] true if object exists, false if not.
|
104
126
|
# @since 3.0.5
|
105
127
|
def persisted?
|
106
128
|
raise NotImplementedError
|
@@ -110,9 +132,9 @@ module OmniAuth
|
|
110
132
|
# Returns self if the provided password is correct, false
|
111
133
|
# otherwise.
|
112
134
|
#
|
113
|
-
# @abstract
|
135
|
+
# @abstract Subclasses must implement this method.
|
114
136
|
# @param [String] _password The password to check.
|
115
|
-
# @return [self
|
137
|
+
# @return [self, false] Self if authenticated, false if not.
|
116
138
|
def authenticate(_password)
|
117
139
|
raise NotImplementedError
|
118
140
|
end
|
@@ -6,8 +6,34 @@ module OmniAuth
|
|
6
6
|
module Identity
|
7
7
|
module Models
|
8
8
|
# ActiveRecord is an ORM for MySQL, PostgreSQL, and SQLite3:
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# https://guides.rubyonrails.org/active_record_basics.html
|
10
|
+
#
|
11
|
+
# This class provides a base for OmniAuth Identity models using ActiveRecord,
|
12
|
+
# including secure password handling and authentication key management.
|
13
|
+
#
|
14
|
+
# @example Usage
|
15
|
+
# class User < OmniAuth::Identity::Models::ActiveRecord
|
16
|
+
# # Add your fields here, e.g.:
|
17
|
+
# # self.table_name = 'users'
|
18
|
+
# # has_many :posts
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # Migration example:
|
22
|
+
# # create_table :users do |t|
|
23
|
+
# # t.string :email, null: false
|
24
|
+
# # t.string :password_digest, null: false
|
25
|
+
# # t.timestamps
|
26
|
+
# # end
|
27
|
+
#
|
28
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
29
|
+
# user.save
|
30
|
+
#
|
31
|
+
# # Authenticate a user
|
32
|
+
# authenticated_user = User.locate(email: 'user@example.com')
|
33
|
+
# authenticated_user.authenticate('password') # => user or false
|
34
|
+
#
|
35
|
+
# @note ActiveRecord is based on ActiveModel, so validations are enabled by default.
|
36
|
+
# @note This is an abstract class; inherit from it to create your user model.
|
11
37
|
class ActiveRecord < ::ActiveRecord::Base
|
12
38
|
include ::OmniAuth::Identity::Model
|
13
39
|
include ::OmniAuth::Identity::SecurePassword
|
@@ -16,11 +42,29 @@ module OmniAuth
|
|
16
42
|
# validations: true (default) incurs a dependency on ActiveModel, but ActiveRecord is ActiveModel based.
|
17
43
|
has_secure_password
|
18
44
|
|
45
|
+
# @!method self.auth_key=(key)
|
46
|
+
# Sets the authentication key for the model and adds uniqueness validation.
|
47
|
+
#
|
48
|
+
# @param key [Symbol, String] the attribute to use as the authentication key
|
49
|
+
# @return [void]
|
50
|
+
# @example
|
51
|
+
# class User < OmniAuth::Identity::Models::ActiveRecord
|
52
|
+
# self.auth_key = :email
|
53
|
+
# end
|
19
54
|
def self.auth_key=(key)
|
20
55
|
super
|
21
56
|
validates_uniqueness_of(key, case_sensitive: false)
|
22
57
|
end
|
23
58
|
|
59
|
+
# @!method self.locate(search_hash)
|
60
|
+
# Finds a record by the given search criteria.
|
61
|
+
#
|
62
|
+
# If the model has a 'provider' column, it defaults to 'identity'.
|
63
|
+
#
|
64
|
+
# @param search_hash [Hash] the attributes to search for
|
65
|
+
# @return [ActiveRecord::Base, nil] the first matching record or nil
|
66
|
+
# @example
|
67
|
+
# User.locate(email: 'user@example.com')
|
24
68
|
def self.locate(search_hash)
|
25
69
|
search_hash = search_hash.reverse_merge!("provider" => "identity") if column_names.include?("provider")
|
26
70
|
where(search_hash).first
|
@@ -6,11 +6,39 @@ module OmniAuth
|
|
6
6
|
module Identity
|
7
7
|
module Models
|
8
8
|
# CouchPotato is an ORM adapter for CouchDB:
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
9
|
+
# https://github.com/langalex/couch_potato
|
10
|
+
#
|
11
|
+
# This module provides OmniAuth Identity functionality for CouchPotato models,
|
12
|
+
# including secure password handling and authentication key management.
|
13
|
+
#
|
14
|
+
# @example Usage
|
15
|
+
# class User
|
16
|
+
# include CouchPotato::Persistence
|
17
|
+
#
|
18
|
+
# include OmniAuth::Identity::Models::CouchPotatoModule
|
19
|
+
#
|
20
|
+
# property :email
|
21
|
+
# property :password_digest
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
25
|
+
# user.save
|
26
|
+
#
|
27
|
+
# # Authenticate a user
|
28
|
+
# authenticated_user = User.locate(email: 'user@example.com')
|
29
|
+
# authenticated_user.authenticate('password') # => user or false
|
30
|
+
#
|
31
|
+
# @note CouchPotato is based on ActiveModel, so validations are enabled by default.
|
32
|
+
# @note CouchPotato::Persistence must be included before OmniAuth::Identity::Models::CouchPotatoModule.
|
33
|
+
# @note Includes "Module" in the name for invalid legacy reasons. Rename only with a major version bump.
|
13
34
|
module CouchPotatoModule
|
35
|
+
# Called when this module is included in a model class.
|
36
|
+
#
|
37
|
+
# This method extends the base class with OmniAuth Identity functionality,
|
38
|
+
# including secure password support and authentication key validation.
|
39
|
+
#
|
40
|
+
# @param base [Class] the model class including this module
|
41
|
+
# @return [void]
|
14
42
|
def self.included(base)
|
15
43
|
base.class_eval do
|
16
44
|
include(::OmniAuth::Identity::Model)
|
@@ -19,15 +47,39 @@ module OmniAuth
|
|
19
47
|
# validations: true (default) incurs a dependency on ActiveModel, but CouchPotato is ActiveModel based.
|
20
48
|
has_secure_password
|
21
49
|
|
50
|
+
# @!method self.auth_key=(key)
|
51
|
+
# Sets the authentication key for the model and adds uniqueness validation.
|
52
|
+
#
|
53
|
+
# @param key [Symbol, String] the attribute to use as the authentication key
|
54
|
+
# @return [void]
|
55
|
+
# @example
|
56
|
+
# class User
|
57
|
+
# include OmniAuth::Identity::Models::CouchPotatoModule
|
58
|
+
# self.auth_key = :email
|
59
|
+
# end
|
22
60
|
def self.auth_key=(key)
|
23
61
|
super
|
24
62
|
validates_uniqueness_of(key, case_sensitive: false)
|
25
63
|
end
|
26
64
|
|
65
|
+
# @!method self.locate(search_hash)
|
66
|
+
# Finds a record by the given search criteria.
|
67
|
+
#
|
68
|
+
# @param search_hash [Hash] the attributes to search for
|
69
|
+
# @return [Object, nil] the first matching record or nil
|
70
|
+
# @example
|
71
|
+
# User.locate(email: 'user@example.com')
|
27
72
|
def self.locate(search_hash)
|
28
73
|
where(search_hash).first
|
29
74
|
end
|
30
75
|
|
76
|
+
# @!method save
|
77
|
+
# Saves the document to the CouchDB database.
|
78
|
+
#
|
79
|
+
# @return [Boolean] true if saved successfully, false otherwise
|
80
|
+
# @example
|
81
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
82
|
+
# user.save # => true
|
31
83
|
def save
|
32
84
|
CouchPotato.database.save_document(self)
|
33
85
|
end
|
@@ -6,9 +6,37 @@ module OmniAuth
|
|
6
6
|
module Identity
|
7
7
|
module Models
|
8
8
|
# Mongoid is an ORM adapter for MongoDB:
|
9
|
-
#
|
10
|
-
#
|
9
|
+
# https://github.com/mongodb/mongoid
|
10
|
+
#
|
11
|
+
# This module provides OmniAuth Identity functionality for Mongoid models,
|
12
|
+
# including secure password handling and authentication key management.
|
13
|
+
#
|
14
|
+
# @example Usage
|
15
|
+
# class User
|
16
|
+
# include Mongoid::Document
|
17
|
+
#
|
18
|
+
# include OmniAuth::Identity::Models::Mongoid
|
19
|
+
#
|
20
|
+
# field :email, type: String
|
21
|
+
# field :password_digest, type: String
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
25
|
+
# user.save
|
26
|
+
#
|
27
|
+
# # Authenticate a user
|
28
|
+
# authenticated_user = User.locate(email: 'user@example.com')
|
29
|
+
# authenticated_user.authenticate('password') # => user or false
|
30
|
+
#
|
31
|
+
# @note Mongoid is based on ActiveModel, so validations are enabled by default.
|
11
32
|
module Mongoid
|
33
|
+
# Called when this module is included in a model class.
|
34
|
+
#
|
35
|
+
# This method extends the base class with OmniAuth Identity functionality,
|
36
|
+
# including secure password support and authentication key validation.
|
37
|
+
#
|
38
|
+
# @param base [Class] the model class including this module
|
39
|
+
# @return [void]
|
12
40
|
def self.included(base)
|
13
41
|
base.class_eval do
|
14
42
|
include(::OmniAuth::Identity::Model)
|
@@ -17,11 +45,28 @@ module OmniAuth
|
|
17
45
|
# validations: true (default) incurs a dependency on ActiveModel, but Mongoid is ActiveModel based.
|
18
46
|
has_secure_password
|
19
47
|
|
48
|
+
# @!method self.auth_key=(key)
|
49
|
+
# Sets the authentication key for the model and adds uniqueness validation.
|
50
|
+
#
|
51
|
+
# @param key [Symbol, String] the attribute to use as the authentication key
|
52
|
+
# @return [void]
|
53
|
+
# @example
|
54
|
+
# class User
|
55
|
+
# include OmniAuth::Identity::Models::Mongoid
|
56
|
+
# self.auth_key = :email
|
57
|
+
# end
|
20
58
|
def self.auth_key=(key)
|
21
59
|
super
|
22
60
|
validates_uniqueness_of(key, case_sensitive: false)
|
23
61
|
end
|
24
62
|
|
63
|
+
# @!method self.locate(search_hash)
|
64
|
+
# Finds a record by the given search criteria.
|
65
|
+
#
|
66
|
+
# @param search_hash [Hash] the attributes to search for
|
67
|
+
# @return [Mongoid::Document, nil] the first matching record or nil
|
68
|
+
# @example
|
69
|
+
# User.locate(email: 'user@example.com')
|
25
70
|
def self.locate(search_hash)
|
26
71
|
where(search_hash).first
|
27
72
|
end
|
@@ -5,10 +5,37 @@ require "nobrainer"
|
|
5
5
|
module OmniAuth
|
6
6
|
module Identity
|
7
7
|
module Models
|
8
|
-
# NoBrainer is an ORM adapter for RethinkDB:
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# NoBrainer is an ORM adapter for RethinkDB: http://nobrainer.io/
|
9
|
+
#
|
10
|
+
# This module provides OmniAuth Identity functionality for NoBrainer models,
|
11
|
+
# including secure password handling and authentication key management.
|
12
|
+
#
|
13
|
+
# @example Usage
|
14
|
+
# class User
|
15
|
+
# include NoBrainer::Document
|
16
|
+
#
|
17
|
+
# include OmniAuth::Identity::Models::NoBrainer
|
18
|
+
#
|
19
|
+
# field :email
|
20
|
+
# field :password_digest
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# user = User.new(email: 'user@example.com', password: 'password')
|
24
|
+
# user.save
|
25
|
+
#
|
26
|
+
# # Authenticate a user
|
27
|
+
# authenticated_user = User.locate(email: 'user@example.com')
|
28
|
+
# authenticated_user.authenticate('password') # => user or false
|
29
|
+
#
|
30
|
+
# @note NoBrainer is based on ActiveModel, so validations are enabled by default.
|
11
31
|
module NoBrainer
|
32
|
+
# Called when this module is included in a model class.
|
33
|
+
#
|
34
|
+
# This method extends the base class with OmniAuth Identity functionality,
|
35
|
+
# including secure password support and authentication key validation.
|
36
|
+
#
|
37
|
+
# @param base [Class] the model class including this module
|
38
|
+
# @return [void]
|
12
39
|
def self.included(base)
|
13
40
|
base.class_eval do
|
14
41
|
include(::OmniAuth::Identity::Model)
|
@@ -17,11 +44,28 @@ module OmniAuth
|
|
17
44
|
# validations: true (default) incurs a dependency on ActiveModel, but NoBrainer is ActiveModel based.
|
18
45
|
has_secure_password
|
19
46
|
|
47
|
+
# @!method self.auth_key=(key)
|
48
|
+
# Sets the authentication key for the model and adds uniqueness validation.
|
49
|
+
#
|
50
|
+
# @param key [Symbol, String] the attribute to use as the authentication key
|
51
|
+
# @return [void]
|
52
|
+
# @example
|
53
|
+
# class User
|
54
|
+
# include OmniAuth::Identity::Models::NoBrainer
|
55
|
+
# self.auth_key = :email
|
56
|
+
# end
|
20
57
|
def self.auth_key=(key)
|
21
58
|
super
|
22
59
|
validates_uniqueness_of(key, case_sensitive: false)
|
23
60
|
end
|
24
61
|
|
62
|
+
# @!method self.locate(search_hash)
|
63
|
+
# Finds a record by the given search criteria.
|
64
|
+
#
|
65
|
+
# @param search_hash [Hash] the attributes to search for
|
66
|
+
# @return [Object, nil] the first matching record or nil
|
67
|
+
# @example
|
68
|
+
# User.locate(email: 'user@example.com')
|
25
69
|
def self.locate(search_hash)
|
26
70
|
where(search_hash).first
|
27
71
|
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bcrypt"
|
4
|
+
|
5
|
+
require "rom"
|
6
|
+
require "rom-sql"
|
7
|
+
|
8
|
+
module OmniAuth
|
9
|
+
module Identity
|
10
|
+
module Models
|
11
|
+
# ROM adapter for OmniAuth::Identity
|
12
|
+
# This provides a reusable adapter that makes ROM entities compatible with OmniAuth::Identity
|
13
|
+
#
|
14
|
+
# Usage:
|
15
|
+
# class Identity
|
16
|
+
# include OmniAuth::Identity::Models::Rom
|
17
|
+
#
|
18
|
+
# # Configure the ROM container and relation using the DSL (no `self.`):
|
19
|
+
# rom_container -> { MyDatabase.rom } # accepts a proc or a container object
|
20
|
+
# rom_relation_name :identities # optional, defaults to :identities
|
21
|
+
# owner_relation_name :owners # optional, for loading associated owner
|
22
|
+
# # Uses OmniAuth::Identity::Model.auth_key to set the auth key (defaults to :email)
|
23
|
+
# auth_key :email
|
24
|
+
# # Optional: override the password digest field name (defaults to :password_digest)
|
25
|
+
# password_field :password_digest
|
26
|
+
# end
|
27
|
+
module Rom
|
28
|
+
def self.included(base)
|
29
|
+
# Align with other adapters: rely on OmniAuth::Identity::Model for API
|
30
|
+
base.include(::OmniAuth::Identity::Model)
|
31
|
+
base.extend(ClassMethods)
|
32
|
+
|
33
|
+
base.class_eval do
|
34
|
+
# OmniAuth::Identity required instance API
|
35
|
+
# Authenticates the instance with the provided password
|
36
|
+
# Returns self on success, false otherwise (to match Model.authenticate contract)
|
37
|
+
def authenticate(password)
|
38
|
+
digest_key = self.class.password_field
|
39
|
+
password_digest = @identity_data[digest_key]
|
40
|
+
return false unless password_digest
|
41
|
+
|
42
|
+
begin
|
43
|
+
BCrypt::Password.new(password_digest) == password && self
|
44
|
+
rescue BCrypt::Errors::InvalidHash
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module ClassMethods
|
52
|
+
# Default ROM relation name when none is configured
|
53
|
+
DEFAULT_RELATION_NAME = :identities
|
54
|
+
|
55
|
+
# Configuration DSL
|
56
|
+
# These methods act like the DSL on `OmniAuth::Identity::Model` (e.g. `auth_key`) —
|
57
|
+
# when called with an argument they set the configuration, and when called
|
58
|
+
# without an argument they return the current value (with sensible defaults).
|
59
|
+
def rom_container(value = false)
|
60
|
+
@rom_container = value unless value == false
|
61
|
+
container = @rom_container
|
62
|
+
container.respond_to?(:call) ? container.call : container
|
63
|
+
end
|
64
|
+
|
65
|
+
def rom_relation_name(value = false)
|
66
|
+
@rom_relation_name = value unless value == false
|
67
|
+
@rom_relation_name || DEFAULT_RELATION_NAME
|
68
|
+
end
|
69
|
+
|
70
|
+
def owner_relation_name(value = false)
|
71
|
+
@owner_relation_name = value unless value == false
|
72
|
+
@owner_relation_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def password_field(value = false)
|
76
|
+
@password_field = value unless value == false
|
77
|
+
(@password_field || :password_digest).to_sym
|
78
|
+
end
|
79
|
+
|
80
|
+
# Align with other adapters: use Model.auth_key (getter/setter) for the login attribute
|
81
|
+
# Model.auth_key returns a String; convert to Symbol for ROM queries when needed.
|
82
|
+
def auth_key_symbol
|
83
|
+
(auth_key || "email").to_sym
|
84
|
+
end
|
85
|
+
|
86
|
+
# Locate an identity given conditions (Hash) or a raw key value.
|
87
|
+
# Mirrors other adapters by accepting a conditions hash.
|
88
|
+
# Returns an instance or nil.
|
89
|
+
def locate(conditions)
|
90
|
+
key_value = if conditions.is_a?(Hash)
|
91
|
+
conditions[auth_key_symbol] || conditions[auth_key.to_s]
|
92
|
+
else
|
93
|
+
conditions
|
94
|
+
end
|
95
|
+
return if key_value.nil?
|
96
|
+
|
97
|
+
relation = rom_container.relations[rom_relation_name]
|
98
|
+
identity_data = relation.where(auth_key_symbol => key_value).one
|
99
|
+
return unless identity_data
|
100
|
+
|
101
|
+
if owner_relation_name && identity_data[:owner_id]
|
102
|
+
owner_data = rom_container.relations[owner_relation_name].where(id: identity_data[:owner_id]).one
|
103
|
+
new(identity_data, owner_data)
|
104
|
+
else
|
105
|
+
new(identity_data)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Instance shape matches other adapters for downstream usage
|
111
|
+
attr_reader :uid, :email, :name, :info, :owner
|
112
|
+
|
113
|
+
def initialize(identity_data, owner_data = nil)
|
114
|
+
@identity_data = identity_data
|
115
|
+
@owner_data = owner_data
|
116
|
+
@uid = identity_data[:id]
|
117
|
+
@email = identity_data[:email]
|
118
|
+
|
119
|
+
# Prefer owner name if available, else fall back to email
|
120
|
+
@name = if owner_data && owner_data[:name]
|
121
|
+
owner_data[:name]
|
122
|
+
else
|
123
|
+
identity_data[:email]
|
124
|
+
end
|
125
|
+
|
126
|
+
@info = {
|
127
|
+
"email" => @email,
|
128
|
+
"name" => @name,
|
129
|
+
}
|
130
|
+
|
131
|
+
@owner = owner_data
|
132
|
+
end
|
133
|
+
|
134
|
+
# Hash-like access to underlying ROM tuple
|
135
|
+
def [](key)
|
136
|
+
@identity_data[key]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Convenience accessor for owner id
|
140
|
+
def owner_id
|
141
|
+
@identity_data[:owner_id]
|
142
|
+
end
|
143
|
+
|
144
|
+
# OmniAuth info hash
|
145
|
+
def to_hash
|
146
|
+
@info
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|