castle_devise 0.4.3 → 0.7.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/.github/workflows/lint.yml +10 -13
- data/.github/workflows/specs.yml +55 -27
- data/.gitignore +1 -0
- data/.octocov.yml +14 -0
- data/.standard.yml +3 -0
- data/.tool-versions +1 -0
- data/CHANGELOG.md +32 -1
- data/Gemfile +5 -2
- data/Gemfile.lock +232 -164
- data/README.md +16 -28
- data/bin/console +1 -1
- data/castle_devise.gemspec +6 -8
- data/gemfiles/rails_6.0.gemfile +5 -4
- data/gemfiles/rails_6.1.gemfile +5 -4
- data/gemfiles/rails_7.0.gemfile +4 -3
- data/gemfiles/rails_7.1.gemfile +18 -0
- data/gemfiles/rails_7.2.gemfile +18 -0
- data/gemfiles/rails_8.0.gemfile +18 -0
- data/gemfiles/rails_8.1.gemfile +17 -0
- data/lib/castle_devise/configuration.rb +32 -18
- data/lib/castle_devise/helpers/castle_devise.js +48 -0
- data/lib/castle_devise/helpers/castle_helper.rb +33 -13
- data/lib/castle_devise/hooks/castle_protectable.rb +1 -1
- data/lib/castle_devise/version.rb +1 -1
- data/mise.toml +11 -0
- metadata +21 -29
- data/Appraisals +0 -11
data/gemfiles/rails_7.0.gemfile
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
# This file was generated by Appraisal
|
|
2
|
-
|
|
3
1
|
source "https://rubygems.org"
|
|
4
2
|
|
|
5
3
|
gem "actionmailer"
|
|
6
4
|
gem "activerecord"
|
|
5
|
+
gem "byebug"
|
|
7
6
|
gem "railties", "~> 7.0"
|
|
8
7
|
gem "rake"
|
|
9
8
|
gem "rspec"
|
|
10
9
|
gem "rspec-rails"
|
|
11
10
|
gem "simplecov"
|
|
12
11
|
gem "standard"
|
|
13
|
-
gem "sqlite3"
|
|
12
|
+
gem "sqlite3", "~> 1.7"
|
|
14
13
|
gem "vcr"
|
|
15
14
|
gem "webmock"
|
|
15
|
+
gem "mutex_m"
|
|
16
|
+
gem "logger"
|
|
16
17
|
|
|
17
18
|
gemspec path: "../"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gem "actionmailer"
|
|
4
|
+
gem "activerecord"
|
|
5
|
+
gem "byebug"
|
|
6
|
+
gem "railties", "~> 7.1"
|
|
7
|
+
gem "rake"
|
|
8
|
+
gem "rspec"
|
|
9
|
+
gem "rspec-rails"
|
|
10
|
+
gem "simplecov"
|
|
11
|
+
gem "standard"
|
|
12
|
+
gem "sqlite3", "~> 1.7"
|
|
13
|
+
gem "vcr"
|
|
14
|
+
gem "webmock"
|
|
15
|
+
gem "mutex_m"
|
|
16
|
+
gem "logger"
|
|
17
|
+
|
|
18
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gem "actionmailer"
|
|
4
|
+
gem "activerecord"
|
|
5
|
+
gem "byebug"
|
|
6
|
+
gem "railties", "~> 7.2"
|
|
7
|
+
gem "rake"
|
|
8
|
+
gem "rspec"
|
|
9
|
+
gem "rspec-rails"
|
|
10
|
+
gem "simplecov"
|
|
11
|
+
gem "standard"
|
|
12
|
+
gem "sqlite3", "~> 1.7"
|
|
13
|
+
gem "vcr"
|
|
14
|
+
gem "webmock"
|
|
15
|
+
gem "mutex_m"
|
|
16
|
+
gem "logger"
|
|
17
|
+
|
|
18
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gem "actionmailer"
|
|
4
|
+
gem "activerecord"
|
|
5
|
+
gem "byebug"
|
|
6
|
+
gem "railties", "~> 8.0.0"
|
|
7
|
+
gem "rake"
|
|
8
|
+
gem "rspec"
|
|
9
|
+
gem "rspec-rails"
|
|
10
|
+
gem "simplecov"
|
|
11
|
+
gem "standard"
|
|
12
|
+
gem "sqlite3"
|
|
13
|
+
gem "vcr"
|
|
14
|
+
gem "webmock"
|
|
15
|
+
gem "mutex_m"
|
|
16
|
+
gem "logger"
|
|
17
|
+
|
|
18
|
+
gemspec path: "../"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gem "actionmailer"
|
|
4
|
+
gem "activerecord"
|
|
5
|
+
gem "byebug"
|
|
6
|
+
gem "devise", "~> 5.0"
|
|
7
|
+
gem "railties", "~> 8.1.0"
|
|
8
|
+
gem "rake"
|
|
9
|
+
gem "rspec"
|
|
10
|
+
gem "rspec-rails"
|
|
11
|
+
gem "simplecov"
|
|
12
|
+
gem "standard"
|
|
13
|
+
gem "sqlite3"
|
|
14
|
+
gem "vcr"
|
|
15
|
+
gem "webmock"
|
|
16
|
+
|
|
17
|
+
gemspec path: "../"
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/configurable"
|
|
4
3
|
require "logger"
|
|
5
4
|
|
|
6
5
|
module CastleDevise
|
|
7
|
-
#
|
|
6
|
+
# Plain Ruby configuration object for CastleDevise.
|
|
7
|
+
#
|
|
8
|
+
# Previously this used +ActiveSupport::Configurable+, which is deprecated
|
|
9
|
+
# and slated for removal in Rails 8.2.
|
|
8
10
|
class Configuration
|
|
9
|
-
include ActiveSupport::Configurable
|
|
10
|
-
|
|
11
11
|
# @!attribute api_secret
|
|
12
12
|
# @return [String] Your API secret
|
|
13
|
-
|
|
13
|
+
attr_accessor :api_secret
|
|
14
14
|
|
|
15
|
-
# @!attribute
|
|
16
|
-
# @return [String]
|
|
17
|
-
|
|
15
|
+
# @!attribute publishable_key
|
|
16
|
+
# @return [String] Publishable key passed to @castleio/castle-js as { pk: }
|
|
17
|
+
attr_accessor :publishable_key
|
|
18
18
|
|
|
19
19
|
# @!attribute monitoring_mode
|
|
20
20
|
# When CastleDevise is in monitoring mode, it sends requests to Castle
|
|
@@ -25,27 +25,41 @@ module CastleDevise
|
|
|
25
25
|
# from logging in/registering.
|
|
26
26
|
#
|
|
27
27
|
# @return [true, false] whether to act on deny requests or not
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# @!attribute logger
|
|
31
|
-
# @return [Logger] A Logger instance. You might want to use Rails.logger here.
|
|
32
|
-
config_accessor(:logger) { Logger.new("/dev/null") }
|
|
28
|
+
attr_accessor :monitoring_mode
|
|
33
29
|
|
|
34
30
|
# @!attribute before_request_hooks
|
|
35
31
|
# @return [Array<Proc>] Array of procs that will get called before a request to the Castle API
|
|
36
|
-
|
|
32
|
+
attr_accessor :before_request_hooks
|
|
37
33
|
|
|
38
34
|
# @!attribute after_request_hooks
|
|
39
35
|
# @return [Array<Proc>] Array of procs that will get called after a request to the Castle API
|
|
40
|
-
|
|
36
|
+
attr_accessor :after_request_hooks
|
|
37
|
+
|
|
38
|
+
attr_writer :logger, :castle_sdk_facade_class, :castle_client
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
@monitoring_mode = false
|
|
42
|
+
@before_request_hooks = []
|
|
43
|
+
@after_request_hooks = []
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @!attribute logger
|
|
47
|
+
# @return [Logger] A Logger instance. You might want to use Rails.logger here.
|
|
48
|
+
def logger
|
|
49
|
+
@logger ||= Logger.new(File::NULL)
|
|
50
|
+
end
|
|
41
51
|
|
|
42
52
|
# @!attribute castle_sdk_facade_class
|
|
43
53
|
# @return [Class] Castle API implementation
|
|
44
|
-
|
|
54
|
+
def castle_sdk_facade_class
|
|
55
|
+
@castle_sdk_facade_class ||= ::CastleDevise::SdkFacade
|
|
56
|
+
end
|
|
45
57
|
|
|
46
58
|
# @!attribute castle_client
|
|
47
|
-
# @return [
|
|
48
|
-
|
|
59
|
+
# @return [Castle::Client] Castle SDK client
|
|
60
|
+
def castle_client
|
|
61
|
+
@castle_client ||= ::Castle::Client.new
|
|
62
|
+
end
|
|
49
63
|
|
|
50
64
|
# Adds a new before_request hook
|
|
51
65
|
# @param blk [Proc]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var CastleGlobal = window.Castle || window["@castleio/castle-js"];
|
|
3
|
+
var pk = __CASTLE_DEVISE_PK__;
|
|
4
|
+
if (!window.__castleDevise && CastleGlobal && typeof CastleGlobal.configure === "function") {
|
|
5
|
+
window.__castleDevise = CastleGlobal.configure({ pk: pk });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
window.castleDeviseOnFormSubmit = function (event) {
|
|
9
|
+
var client = window.__castleDevise || window.Castle || window["@castleio/castle-js"];
|
|
10
|
+
// 2.x exposes injectTokenOnSubmit; 3.x only has createRequestToken on the configured instance.
|
|
11
|
+
if (client && typeof client.injectTokenOnSubmit === "function") {
|
|
12
|
+
return client.injectTokenOnSubmit(event);
|
|
13
|
+
}
|
|
14
|
+
if (!client || typeof client.createRequestToken !== "function") {
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
var form = event.target;
|
|
19
|
+
if (form.getAttribute("data-castle-submitting") === "1") {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
client.createRequestToken().then(function (token) {
|
|
24
|
+
var field = form.querySelector('input[name="castle_request_token"]');
|
|
25
|
+
if (!field) {
|
|
26
|
+
field = document.createElement("input");
|
|
27
|
+
field.type = "hidden";
|
|
28
|
+
field.name = "castle_request_token";
|
|
29
|
+
form.appendChild(field);
|
|
30
|
+
}
|
|
31
|
+
field.value = token || "";
|
|
32
|
+
form.setAttribute("data-castle-submitting", "1");
|
|
33
|
+
if (typeof form.requestSubmit === "function") {
|
|
34
|
+
form.requestSubmit();
|
|
35
|
+
} else {
|
|
36
|
+
form.submit();
|
|
37
|
+
}
|
|
38
|
+
}).catch(function () {
|
|
39
|
+
form.setAttribute("data-castle-submitting", "1");
|
|
40
|
+
if (typeof form.requestSubmit === "function") {
|
|
41
|
+
form.requestSubmit();
|
|
42
|
+
} else {
|
|
43
|
+
form.submit();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return false;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
@@ -4,13 +4,23 @@ module CastleDevise
|
|
|
4
4
|
module Helpers
|
|
5
5
|
# Methods defined here will be available in all your views.
|
|
6
6
|
module CastleHelper
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
BOOTSTRAP_JS = File.read(File.expand_path("castle_devise.js", __dir__)).freeze
|
|
8
|
+
# 3.x UMD is named @castleio/castle-js, so seed module.exports as window.Castle before the script tag.
|
|
9
|
+
UMD_SHIM = <<~JS
|
|
10
|
+
if (!window.Castle) {
|
|
11
|
+
window.exports = window.exports || {};
|
|
12
|
+
window.module = window.module || { exports: window.exports };
|
|
13
|
+
window.Castle = window.module.exports;
|
|
14
|
+
}
|
|
15
|
+
JS
|
|
16
|
+
|
|
17
|
+
DEFAULT_UMD_SRC = "/vendor/castle-js/castle.umd.js"
|
|
18
|
+
|
|
19
|
+
# Loads castle.umd.js and configures it with { pk: }. Pass src: to use a
|
|
20
|
+
# different UMD URL, or src: nil when the host app already loaded the SDK.
|
|
12
21
|
#
|
|
13
|
-
#
|
|
22
|
+
# @param src [String, nil] URL or path of castle.umd.js (keep workers in the same directory)
|
|
23
|
+
# @return [String]
|
|
14
24
|
#
|
|
15
25
|
# @example
|
|
16
26
|
# # app/views/layouts/application.html.erb
|
|
@@ -21,14 +31,18 @@ module CastleDevise
|
|
|
21
31
|
# <title>Your app title</title>
|
|
22
32
|
#
|
|
23
33
|
# <!-- the rest of your layout -->
|
|
24
|
-
def castle_javascript_tag
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
def castle_javascript_tag(src: DEFAULT_UMD_SRC)
|
|
35
|
+
parts = []
|
|
36
|
+
if src
|
|
37
|
+
parts << javascript_tag(UMD_SHIM)
|
|
38
|
+
parts << javascript_include_tag(src)
|
|
39
|
+
end
|
|
40
|
+
parts << javascript_tag(castle_js_bootstrap)
|
|
41
|
+
safe_join(parts)
|
|
28
42
|
end
|
|
29
43
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
44
|
+
# onsubmit handler that mints castle_request_token via 2.x injectTokenOnSubmit
|
|
45
|
+
# or 2.x/3.x createRequestToken.
|
|
32
46
|
#
|
|
33
47
|
# @example
|
|
34
48
|
# <%= form_for(resource, as: resource_name, url: sessions_path(resource_name), html: { onsubmit: castle_on_form_submit }) do |f| %>
|
|
@@ -38,7 +52,13 @@ module CastleDevise
|
|
|
38
52
|
#
|
|
39
53
|
# @return [String]
|
|
40
54
|
def castle_on_form_submit
|
|
41
|
-
"
|
|
55
|
+
"castleDeviseOnFormSubmit(event)"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def castle_js_bootstrap
|
|
61
|
+
BOOTSTRAP_JS.sub("__CASTLE_DEVISE_PK__", CastleDevise.configuration.publishable_key.to_json)
|
|
42
62
|
end
|
|
43
63
|
end
|
|
44
64
|
end
|
|
@@ -63,6 +63,6 @@ Warden::Manager.before_failure do |env, opts|
|
|
|
63
63
|
context: context
|
|
64
64
|
)
|
|
65
65
|
rescue Castle::Error => e
|
|
66
|
-
CastleDevise.logger.
|
|
66
|
+
CastleDevise.logger.warn("[CastleDevise] filter($login, $failed): #{e}")
|
|
67
67
|
end
|
|
68
68
|
end
|
data/mise.toml
ADDED
metadata
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: castle_devise
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kacper Madej
|
|
8
8
|
- Dawid Libiszewski
|
|
9
|
+
- Bartosz Knapik
|
|
9
10
|
- Johan Brissmyr
|
|
10
|
-
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activesupport
|
|
@@ -18,14 +18,14 @@ dependencies:
|
|
|
18
18
|
requirements:
|
|
19
19
|
- - ">="
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version: '
|
|
21
|
+
version: '6.0'
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '
|
|
28
|
+
version: '6.0'
|
|
29
29
|
- !ruby/object:Gem::Dependency
|
|
30
30
|
name: castle-rb
|
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -35,7 +35,7 @@ dependencies:
|
|
|
35
35
|
version: '7.2'
|
|
36
36
|
- - "<"
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '
|
|
38
|
+
version: '10.0'
|
|
39
39
|
type: :runtime
|
|
40
40
|
prerelease: false
|
|
41
41
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -45,7 +45,7 @@ dependencies:
|
|
|
45
45
|
version: '7.2'
|
|
46
46
|
- - "<"
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '10.0'
|
|
49
49
|
- !ruby/object:Gem::Dependency
|
|
50
50
|
name: devise
|
|
51
51
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -55,7 +55,7 @@ dependencies:
|
|
|
55
55
|
version: 4.3.0
|
|
56
56
|
- - "<"
|
|
57
57
|
- !ruby/object:Gem::Version
|
|
58
|
-
version: '
|
|
58
|
+
version: '6.0'
|
|
59
59
|
type: :runtime
|
|
60
60
|
prerelease: false
|
|
61
61
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -65,25 +65,11 @@ dependencies:
|
|
|
65
65
|
version: 4.3.0
|
|
66
66
|
- - "<"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: appraisal
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 2.3.0
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: 2.3.0
|
|
68
|
+
version: '6.0'
|
|
83
69
|
description: castle_devise provides out-of-the-box protection against bot registrations
|
|
84
70
|
and account takeover attacks.
|
|
85
71
|
email:
|
|
86
|
-
-
|
|
72
|
+
- team@castle.io
|
|
87
73
|
executables: []
|
|
88
74
|
extensions: []
|
|
89
75
|
extra_rdoc_files: []
|
|
@@ -91,8 +77,10 @@ files:
|
|
|
91
77
|
- ".github/workflows/lint.yml"
|
|
92
78
|
- ".github/workflows/specs.yml"
|
|
93
79
|
- ".gitignore"
|
|
80
|
+
- ".octocov.yml"
|
|
94
81
|
- ".rspec"
|
|
95
|
-
-
|
|
82
|
+
- ".standard.yml"
|
|
83
|
+
- ".tool-versions"
|
|
96
84
|
- CHANGELOG.md
|
|
97
85
|
- Gemfile
|
|
98
86
|
- Gemfile.lock
|
|
@@ -105,10 +93,15 @@ files:
|
|
|
105
93
|
- gemfiles/rails_6.0.gemfile
|
|
106
94
|
- gemfiles/rails_6.1.gemfile
|
|
107
95
|
- gemfiles/rails_7.0.gemfile
|
|
96
|
+
- gemfiles/rails_7.1.gemfile
|
|
97
|
+
- gemfiles/rails_7.2.gemfile
|
|
98
|
+
- gemfiles/rails_8.0.gemfile
|
|
99
|
+
- gemfiles/rails_8.1.gemfile
|
|
108
100
|
- lib/castle_devise.rb
|
|
109
101
|
- lib/castle_devise/configuration.rb
|
|
110
102
|
- lib/castle_devise/context.rb
|
|
111
103
|
- lib/castle_devise/controllers/helpers.rb
|
|
104
|
+
- lib/castle_devise/helpers/castle_devise.js
|
|
112
105
|
- lib/castle_devise/helpers/castle_helper.rb
|
|
113
106
|
- lib/castle_devise/hooks/castle_protectable.rb
|
|
114
107
|
- lib/castle_devise/models/castle_protectable.rb
|
|
@@ -118,6 +111,7 @@ files:
|
|
|
118
111
|
- lib/castle_devise/rails.rb
|
|
119
112
|
- lib/castle_devise/sdk_facade.rb
|
|
120
113
|
- lib/castle_devise/version.rb
|
|
114
|
+
- mise.toml
|
|
121
115
|
homepage: https://github.com/castle/castle_devise
|
|
122
116
|
licenses:
|
|
123
117
|
- MIT
|
|
@@ -125,7 +119,6 @@ metadata:
|
|
|
125
119
|
homepage_uri: https://github.com/castle/castle_devise
|
|
126
120
|
source_code_uri: https://github.com/castle/castle_devise
|
|
127
121
|
changelog_uri: https://github.com/castle/castle_devise/CHANGELOG.md
|
|
128
|
-
post_install_message:
|
|
129
122
|
rdoc_options: []
|
|
130
123
|
require_paths:
|
|
131
124
|
- lib
|
|
@@ -133,15 +126,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
133
126
|
requirements:
|
|
134
127
|
- - ">="
|
|
135
128
|
- !ruby/object:Gem::Version
|
|
136
|
-
version: 2.
|
|
129
|
+
version: 3.2.0
|
|
137
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
131
|
requirements:
|
|
139
132
|
- - ">="
|
|
140
133
|
- !ruby/object:Gem::Version
|
|
141
134
|
version: '0'
|
|
142
135
|
requirements: []
|
|
143
|
-
rubygems_version: 3.
|
|
144
|
-
signing_key:
|
|
136
|
+
rubygems_version: 3.6.9
|
|
145
137
|
specification_version: 4
|
|
146
138
|
summary: Integrates Castle with Devise
|
|
147
139
|
test_files: []
|