castle_devise 0.1.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 +7 -0
- data/.github/workflows/lint.yml +18 -0
- data/.github/workflows/specs.yml +25 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +169 -0
- data/LICENSE +21 -0
- data/README.md +197 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/castle_devise.gemspec +35 -0
- data/lib/castle_devise.rb +78 -0
- data/lib/castle_devise/configuration.rb +54 -0
- data/lib/castle_devise/context.rb +86 -0
- data/lib/castle_devise/controllers/helpers.rb +29 -0
- data/lib/castle_devise/helpers/castle_helper.rb +83 -0
- data/lib/castle_devise/hooks/castle_protectable.rb +63 -0
- data/lib/castle_devise/models/castle_protectable.rb +74 -0
- data/lib/castle_devise/patches.rb +13 -0
- data/lib/castle_devise/patches/registrations_controller.rb +57 -0
- data/lib/castle_devise/rails.rb +13 -0
- data/lib/castle_devise/sdk_facade.rb +144 -0
- data/lib/castle_devise/version.rb +5 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c13e653cf7d00df4cdb2436431a031be5c4120d5e667ff2c5feb00effd374011
|
4
|
+
data.tar.gz: 8c4c4fafd623cda48e9f9c125c81cda8071ae81e7ec62cad74bc0476bd89c4ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3066ab56d17f1d3097f4aecaa115162e0e9263016cd4dce602a0491d5071ad5fcb933871d6830147303ca859cd60e5c503cdd30dc23112e0b94c0337e4cabee2
|
7
|
+
data.tar.gz: b8c18958cc92790e26e4e222c8c7f34f4db8be72c1c55d54225ca23dd427bcad7082c1efe7208655dbe43c3d31840fbc4875797e53d7b074eef88e21d1a7d118
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Lint
|
2
|
+
|
3
|
+
on: [pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
standardrb:
|
7
|
+
name: runner / standardrb
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- name: Check out code
|
11
|
+
uses: actions/checkout@v1
|
12
|
+
- name: standardrb
|
13
|
+
uses: SennaLabs/action-standardrb@v0.0.3
|
14
|
+
with:
|
15
|
+
github_token: ${{ secrets.github_token }}
|
16
|
+
reporter: github-pr-review # Default is github-pr-check
|
17
|
+
rubocop_version: 1.1.1 # note: this actually refers to standardb version, not Rubocop
|
18
|
+
rubocop_flags: --format progress
|
@@ -0,0 +1,25 @@
|
|
1
|
+
name: Specs
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
environment: tests
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v2
|
11
|
+
- name: Set up Ruby
|
12
|
+
uses: ruby/setup-ruby@v1
|
13
|
+
with:
|
14
|
+
ruby-version: 2.6.5
|
15
|
+
- name: Run specs
|
16
|
+
run: |
|
17
|
+
gem install bundler -v 2.2.19
|
18
|
+
bundle install
|
19
|
+
bundle exec rake
|
20
|
+
env:
|
21
|
+
CASTLE_API_SECRET: ${{ secrets.CASTLE_API_SECRET }}
|
22
|
+
- name: Simplecov Report
|
23
|
+
uses: aki77/simplecov-report-action@v1
|
24
|
+
with:
|
25
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in castle-devise.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "activerecord"
|
9
|
+
gem "railties", "~> 5.2"
|
10
|
+
gem "rake"
|
11
|
+
gem "rspec"
|
12
|
+
gem "rspec-rails"
|
13
|
+
gem "simplecov"
|
14
|
+
gem "standard"
|
15
|
+
gem "sqlite3"
|
16
|
+
gem "vcr"
|
17
|
+
gem "webmock"
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
castle_devise (0.1.0)
|
5
|
+
activesupport (>= 5.0)
|
6
|
+
castle-rb (>= 7.0, < 8.0)
|
7
|
+
devise (>= 4.3.0, < 5.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
actionpack (5.2.6)
|
13
|
+
actionview (= 5.2.6)
|
14
|
+
activesupport (= 5.2.6)
|
15
|
+
rack (~> 2.0, >= 2.0.8)
|
16
|
+
rack-test (>= 0.6.3)
|
17
|
+
rails-dom-testing (~> 2.0)
|
18
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
19
|
+
actionview (5.2.6)
|
20
|
+
activesupport (= 5.2.6)
|
21
|
+
builder (~> 3.1)
|
22
|
+
erubi (~> 1.4)
|
23
|
+
rails-dom-testing (~> 2.0)
|
24
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
25
|
+
activemodel (5.2.6)
|
26
|
+
activesupport (= 5.2.6)
|
27
|
+
activerecord (5.2.6)
|
28
|
+
activemodel (= 5.2.6)
|
29
|
+
activesupport (= 5.2.6)
|
30
|
+
arel (>= 9.0)
|
31
|
+
activesupport (5.2.6)
|
32
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
33
|
+
i18n (>= 0.7, < 2)
|
34
|
+
minitest (~> 5.1)
|
35
|
+
tzinfo (~> 1.1)
|
36
|
+
addressable (2.7.0)
|
37
|
+
public_suffix (>= 2.0.2, < 5.0)
|
38
|
+
arel (9.0.0)
|
39
|
+
ast (2.4.2)
|
40
|
+
bcrypt (3.1.16)
|
41
|
+
builder (3.2.4)
|
42
|
+
castle-rb (7.1.1)
|
43
|
+
concurrent-ruby (1.1.9)
|
44
|
+
crack (0.4.5)
|
45
|
+
rexml
|
46
|
+
crass (1.0.6)
|
47
|
+
devise (4.8.0)
|
48
|
+
bcrypt (~> 3.0)
|
49
|
+
orm_adapter (~> 0.1)
|
50
|
+
railties (>= 4.1.0)
|
51
|
+
responders
|
52
|
+
warden (~> 1.2.3)
|
53
|
+
diff-lcs (1.4.4)
|
54
|
+
docile (1.4.0)
|
55
|
+
erubi (1.10.0)
|
56
|
+
hashdiff (1.0.1)
|
57
|
+
i18n (1.8.10)
|
58
|
+
concurrent-ruby (~> 1.0)
|
59
|
+
loofah (2.10.0)
|
60
|
+
crass (~> 1.0.2)
|
61
|
+
nokogiri (>= 1.5.9)
|
62
|
+
method_source (1.0.0)
|
63
|
+
minitest (5.14.4)
|
64
|
+
nokogiri (1.11.7-x86_64-darwin)
|
65
|
+
racc (~> 1.4)
|
66
|
+
orm_adapter (0.5.0)
|
67
|
+
parallel (1.20.1)
|
68
|
+
parser (3.0.1.1)
|
69
|
+
ast (~> 2.4.1)
|
70
|
+
public_suffix (4.0.6)
|
71
|
+
racc (1.5.2)
|
72
|
+
rack (2.2.3)
|
73
|
+
rack-test (1.1.0)
|
74
|
+
rack (>= 1.0, < 3)
|
75
|
+
rails-dom-testing (2.0.3)
|
76
|
+
activesupport (>= 4.2.0)
|
77
|
+
nokogiri (>= 1.6)
|
78
|
+
rails-html-sanitizer (1.3.0)
|
79
|
+
loofah (~> 2.3)
|
80
|
+
railties (5.2.6)
|
81
|
+
actionpack (= 5.2.6)
|
82
|
+
activesupport (= 5.2.6)
|
83
|
+
method_source
|
84
|
+
rake (>= 0.8.7)
|
85
|
+
thor (>= 0.19.0, < 2.0)
|
86
|
+
rainbow (3.0.0)
|
87
|
+
rake (13.0.3)
|
88
|
+
regexp_parser (2.1.1)
|
89
|
+
responders (3.0.1)
|
90
|
+
actionpack (>= 5.0)
|
91
|
+
railties (>= 5.0)
|
92
|
+
rexml (3.2.5)
|
93
|
+
rspec (3.10.0)
|
94
|
+
rspec-core (~> 3.10.0)
|
95
|
+
rspec-expectations (~> 3.10.0)
|
96
|
+
rspec-mocks (~> 3.10.0)
|
97
|
+
rspec-core (3.10.1)
|
98
|
+
rspec-support (~> 3.10.0)
|
99
|
+
rspec-expectations (3.10.1)
|
100
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
101
|
+
rspec-support (~> 3.10.0)
|
102
|
+
rspec-mocks (3.10.2)
|
103
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
104
|
+
rspec-support (~> 3.10.0)
|
105
|
+
rspec-rails (5.0.1)
|
106
|
+
actionpack (>= 5.2)
|
107
|
+
activesupport (>= 5.2)
|
108
|
+
railties (>= 5.2)
|
109
|
+
rspec-core (~> 3.10)
|
110
|
+
rspec-expectations (~> 3.10)
|
111
|
+
rspec-mocks (~> 3.10)
|
112
|
+
rspec-support (~> 3.10)
|
113
|
+
rspec-support (3.10.2)
|
114
|
+
rubocop (1.14.0)
|
115
|
+
parallel (~> 1.10)
|
116
|
+
parser (>= 3.0.0.0)
|
117
|
+
rainbow (>= 2.2.2, < 4.0)
|
118
|
+
regexp_parser (>= 1.8, < 3.0)
|
119
|
+
rexml
|
120
|
+
rubocop-ast (>= 1.5.0, < 2.0)
|
121
|
+
ruby-progressbar (~> 1.7)
|
122
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
123
|
+
rubocop-ast (1.7.0)
|
124
|
+
parser (>= 3.0.1.1)
|
125
|
+
rubocop-performance (1.11.2)
|
126
|
+
rubocop (>= 1.7.0, < 2.0)
|
127
|
+
rubocop-ast (>= 0.4.0)
|
128
|
+
ruby-progressbar (1.11.0)
|
129
|
+
simplecov (0.21.2)
|
130
|
+
docile (~> 1.1)
|
131
|
+
simplecov-html (~> 0.11)
|
132
|
+
simplecov_json_formatter (~> 0.1)
|
133
|
+
simplecov-html (0.12.3)
|
134
|
+
simplecov_json_formatter (0.1.3)
|
135
|
+
sqlite3 (1.4.2)
|
136
|
+
standard (1.1.1)
|
137
|
+
rubocop (= 1.14.0)
|
138
|
+
rubocop-performance (= 1.11.2)
|
139
|
+
thor (1.1.0)
|
140
|
+
thread_safe (0.3.6)
|
141
|
+
tzinfo (1.2.9)
|
142
|
+
thread_safe (~> 0.1)
|
143
|
+
unicode-display_width (2.0.0)
|
144
|
+
vcr (6.0.0)
|
145
|
+
warden (1.2.9)
|
146
|
+
rack (>= 2.0.9)
|
147
|
+
webmock (3.13.0)
|
148
|
+
addressable (>= 2.3.6)
|
149
|
+
crack (>= 0.3.2)
|
150
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
151
|
+
|
152
|
+
PLATFORMS
|
153
|
+
x86_64-darwin-18
|
154
|
+
|
155
|
+
DEPENDENCIES
|
156
|
+
activerecord
|
157
|
+
castle_devise!
|
158
|
+
railties (~> 5.2)
|
159
|
+
rake
|
160
|
+
rspec
|
161
|
+
rspec-rails
|
162
|
+
simplecov
|
163
|
+
sqlite3
|
164
|
+
standard
|
165
|
+
vcr
|
166
|
+
webmock
|
167
|
+
|
168
|
+
BUNDLED WITH
|
169
|
+
2.2.15
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Castle
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
**Disclaimer:** CastleDevise is currently in beta. There might be some upcoming breaking changes to the gem before we stabilize the API.
|
2
|
+
|
3
|
+
---
|
4
|
+
|
5
|
+
# CastleDevice
|
6
|
+
|
7
|
+
CastleDevise is a [Devise](https://github.com/heartcombo/devise) plugin that integrates [Castle](https://castle.io).
|
8
|
+
|
9
|
+
It currently provides the following features:
|
10
|
+
- preventing bots from registration attacks using Castle's [Filter API](https://docs.castle.io/v1/reference/api-reference/#filter)
|
11
|
+
- preventing ATO attacks using Castle's [Risk API](https://docs.castle.io/v1/reference/api-reference/#risk)
|
12
|
+
|
13
|
+
If you want to learn about all capabilities of Castle, please take a look at [our documentation](https://docs.castle.io/).
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Include `castle_devise` in your Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'castle_devise'
|
21
|
+
```
|
22
|
+
|
23
|
+
Create `config/initializers/castle_devise.rb` and fill in your API secret and APP_ID from the [Castle Dashboard](https://dashboard.castle.io/settings/general)
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
CastleDevise.configure do |config|
|
27
|
+
config.api_secret = ENV.fetch('CASTLE_API_SECRET')
|
28
|
+
config.app_id = ENV.fetch('CASTLE_APP_ID')
|
29
|
+
|
30
|
+
# When monitoring mode is enabled, CastleDevise sends
|
31
|
+
# requests to Castle but it doesn't act on the "deny" verdicts.
|
32
|
+
#
|
33
|
+
# This is useful when you want to check out how Castle scores
|
34
|
+
# your traffic without blocking any of your users.
|
35
|
+
#
|
36
|
+
# Once you are ready to use Castle as your security provider,
|
37
|
+
# you can set monitoring_mode to false.
|
38
|
+
config.monitoring_mode = true
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Add `:castle_protectable` Devise module to your User model:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class User < ApplicationRecord
|
46
|
+
devise :database_authenticatable, :registerable,
|
47
|
+
:recoverable, :rememberable, :validatable,
|
48
|
+
:castle_protectable # <--- add this
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
Add an additional translation to your `config/locales/devise.en.yml`:
|
53
|
+
|
54
|
+
```yml
|
55
|
+
en:
|
56
|
+
devise:
|
57
|
+
registrations:
|
58
|
+
blocked_by_castle: "Account cannot be created at this moment. Please try again later."
|
59
|
+
```
|
60
|
+
|
61
|
+
(See [devise.en.yml in our specs](spec/dummy_app/config/locales/devise.en.yml#L40))
|
62
|
+
|
63
|
+
#### Further steps if you're not using Webpacker
|
64
|
+
|
65
|
+
Include Castle's c.js script in the head section of your layout:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
<%= castle_javascript_tag %>
|
69
|
+
```
|
70
|
+
|
71
|
+
Add the following tag to the the `<form>` tag in both `devise/registrations/new.html.erb` and `devise/sessions/new.html.erb` (if you haven't generated them yet, run `rails generate devise:views`):
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
<%= form_for @user do |f| %>
|
75
|
+
…
|
76
|
+
<%= castle_request_token %>
|
77
|
+
…
|
78
|
+
<% end %>
|
79
|
+
```
|
80
|
+
|
81
|
+
You're set! Now verify that everything works by logging in to your application as any user. You should be able to see that User on the [Castle Users Page](https://dashboard.castle.io/users)
|
82
|
+
|
83
|
+
|
84
|
+
#### Further steps if you're using Webpacker
|
85
|
+
|
86
|
+
Add `castle.js` to your package.json file.
|
87
|
+
|
88
|
+
TODO: fill this in.
|
89
|
+
|
90
|
+
|
91
|
+
## How-Tos
|
92
|
+
|
93
|
+
### Customize the login flow
|
94
|
+
|
95
|
+
#### Do something after Castle denies a login
|
96
|
+
|
97
|
+
We aim to provide sensible defaults, which means that when Castle denies a login, your application
|
98
|
+
will behave as if the User has not been authenticated. You might still want to log such an event,
|
99
|
+
and you can do this in a Warden hook:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Warden::Manager.before_failure do |env, opts|
|
103
|
+
# The raw Castle response if a request to Castle has been made
|
104
|
+
castle_response = env["castle_devise.risk_response"]
|
105
|
+
# CastleDevise::Context, if a request to Castle has been made
|
106
|
+
castle_context = env["castle_devise.risk_context"]
|
107
|
+
|
108
|
+
if castle_response&.dig(:policy, :action) == "deny"
|
109
|
+
# auth failed because Castle denied
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
```
|
114
|
+
|
115
|
+
#### Implement your own challenge flow or do something after an "allow" action
|
116
|
+
|
117
|
+
In your `SessionsController`:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class SessionsController < Devise::SessionsController
|
121
|
+
def create
|
122
|
+
super do |resource|
|
123
|
+
if castle_challenge?
|
124
|
+
# At this point a User is already authenticated, you might want so sign out:
|
125
|
+
sign_out(resource)
|
126
|
+
# .... write your own MFA flow
|
127
|
+
# You can call #castle_risk_response to access Castle response
|
128
|
+
# see https://docs.castle.io/v1/reference/api-reference/#risk for details
|
129
|
+
|
130
|
+
# Fetch the Device token to use it for user feedback
|
131
|
+
# https://docs.castle.io/v1/tutorials/advanced-features/end-user-feedback
|
132
|
+
device_token = castle_risk_response.dig(:device, :token)
|
133
|
+
|
134
|
+
# You might want to fetch our risk signals as well
|
135
|
+
# https://docs.castle.io/v1/reference/signals/
|
136
|
+
event_signals = castle_risk_response[:signals].keys
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
# do any other action you'd like to perform after a user has been signed in below
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
Please note that some Devise extensions might completely override `Devise::SessionsController#create`.
|
147
|
+
In this case, you have to handle everything manually - `castle_challenge?` should be called after
|
148
|
+
a call to `warden.authenticate!` has been successful.
|
149
|
+
|
150
|
+
#### Do not sent login/registration events
|
151
|
+
|
152
|
+
You can configure CastleDevise not to send login or registration events for a given Devise model:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
class User < ApplicationRecord
|
156
|
+
devise :database_authenticatable, :registerable,
|
157
|
+
:castle_protectable,
|
158
|
+
castle_hooks: {
|
159
|
+
# set it to false to prevent CastleDevise
|
160
|
+
# from sending filter($login)
|
161
|
+
before_registration: true,
|
162
|
+
# set it to false to prevent CastleDevise from
|
163
|
+
# sending risk($login) and log($login, $failed)
|
164
|
+
after_login: true
|
165
|
+
}
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
#### Intercept request/response
|
170
|
+
|
171
|
+
You can register before- and after- request hooks in CastleDevise.
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
CastleDevise.configure do |config|
|
175
|
+
# Add custom properties to the request but only when sending
|
176
|
+
# requests to the Risk endpoint
|
177
|
+
# action - Castle API endpoint (eg. :risk, :filter, :log)
|
178
|
+
# context - CastleDevise::Context
|
179
|
+
# payload - Hash (payload passed to the Castle SDK)
|
180
|
+
config.before_request do |action, context, payload|
|
181
|
+
if action == :risk
|
182
|
+
payload[:properties] = {
|
183
|
+
from_eu: context.resource.ip.from_eu?
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
config.before_request do |action, context, payload|
|
189
|
+
# you can register multiple before_request hooks
|
190
|
+
end
|
191
|
+
|
192
|
+
# Intercept the response - enrich your logs with Castle signals
|
193
|
+
config.after_request do |action, context, payload, response|
|
194
|
+
Logging.add_tags(response[:signals].keys)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|