codeword 0.2.0.beta3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6ffa70037e0cddb9a44c6fc47a7bd9e8f903570f3e78a2961221d25cd465e5b
4
- data.tar.gz: 500afbda48f4632781878ef18b174505b1380b1d1afa86dd85e9f56c70d4b053
3
+ metadata.gz: edc4c98d1d7d4aaeecc0e2d0b7afcb8a532753e057ae77998f804d4fc74203b7
4
+ data.tar.gz: 0abf677be13b090ab310441dd01e07b403fe7296b747f92b8729dc87cc1b21d1
5
5
  SHA512:
6
- metadata.gz: dd9703d78d830e6127fe13f77160d6ce6279b16f427a17283a100fb885934f5c97ffc6a345442b09e54c8fde882fee14474fef5e9e171d70dc90ad3133c0cd57
7
- data.tar.gz: ef8fef69c8596dbb4f978f969841251de17ec1f87f245c4afc65254211f5ecac9e7ef94c48813b9f82e9c6f034b41c37c8904e805056f50b639cf5ec34316271
6
+ metadata.gz: bd5f76a4bd9cc4560fb55d2645bbe6229c00fec7a9969b003835ef85927ede1fac0eb27dcc918fe626671643e64d5e1cd2ee448379b4f5209e42deab498e70b4
7
+ data.tar.gz: 24e3c1cddb91cf2a2f08582fbde8184b81abcdf96c29fb70c10fcbcba2dcadd281a53c5e931eda35f25cb66bef32e878c121da4cd9c45f5d8f402d28f5e183c9
data/CHANGELOG.md CHANGED
@@ -1,9 +1,17 @@
1
- ## [Unreleased]
1
+ ## [0.2.0] - 2025-09-25
2
2
 
3
+ - BREAKING: Only support namespaced Rails credentials under `codeword` (e.g. `codeword.codeword`, `codeword.hint`, `codeword.cookie_lifetime_in_weeks`)
3
4
  - BREAKING: Drop support for Rails < 7.2
4
5
  - BREAKING: Rename `check_for_codeword` to `require_codeword!`
5
6
  - BREAKING: Deprecate Rails secrets in favor of Rails credentials
6
- - BREAKING: Drop support for Rails < 6.0
7
+ - BREAKING: Remove lowercase ENV variable support (only uppercase ENV variables are now supported)
8
+
9
+ - Prevent open redirects by using `allow_other_host: false`
10
+ - Harden cookie with `httponly: true`, `same_site: :lax`, and `secure: request.ssl?`
11
+ - `codeword_cookie_lifetime` now returns an `ActiveSupport::Duration`; cookie expiry uses `from_now`
12
+ - Updated crawler detection regex (removed duplicate `spider` and generic `click` token)
13
+
14
+ Migration: see [UPGRADE-0.2.md](./UPGRADE-0.2.md) for steps to upgrade from 0.1.x to 0.2.0.
7
15
 
8
16
  ## [0.1.1] - 2021-12-17
9
17
 
data/README.md CHANGED
@@ -21,22 +21,22 @@ gem 'codeword'
21
21
  mount Codeword::Engine, at: '/codeword'
22
22
  ```
23
23
 
24
- 4. Include the `Codeword` module in your application_controller.rb file and check for codeword:
24
+ 4. Include the `Codeword::Authentication` module in your application_controller.rb file and check for codeword:
25
25
 
26
26
  ```ruby
27
27
  # app/controllers/application_controller.rb
28
28
  class ApplicationController < ActionController::Base
29
- include Codeword
29
+ include Codeword::Authentication
30
30
 
31
31
  before_action :require_codeword!
32
32
  end
33
33
  ```
34
34
 
35
- 5. Skip the check for codeword in the controller(s) you would like to restrict:
35
+ 5. Skip the check for codeword in the controller(s) you would like to allow:
36
36
 
37
37
  ```ruby
38
38
  class APIController < ApplicationController
39
- skip_before_action :require_codeword!, raise: false
39
+ skip_before_action :require_codeword!
40
40
  end
41
41
  ```
42
42
 
@@ -56,17 +56,11 @@ ENV['CODEWORD_HINT'] = 'Something that you do not tell everyone.'
56
56
 
57
57
  You can add your codeword via Rails credentials in your `credentials.yml.enc` file (`$ bin/rails credentials:edit`):
58
58
 
59
- ```yml
60
- codeword: "love"
61
- codeword_hint: "Pepé Le Pew"
62
- ```
63
-
64
- Alternately, credentials in Rails >= 5.2 may be organized under the `codeword` namespace:
65
-
66
59
  ```yml
67
60
  codeword:
68
61
  codeword: "love"
69
62
  hint: "Pepé Le Pew"
63
+ cookie_lifetime_in_weeks: 4
70
64
  ```
71
65
 
72
66
  **Codewords are not case-sensitive, by design. Keep it simple.**
data/UPGRADE-0.2.md ADDED
@@ -0,0 +1,51 @@
1
+ # Migrating from 0.1.x to 0.2.0
2
+
3
+ This guide walks you through the changes required to upgrade from 0.1.x to 0.2.0.
4
+
5
+ ## TL;DR checklist
6
+
7
+ - Update to Rails >= 7.2.
8
+ - Move credentials to the namespaced structure under `codeword`.
9
+ - Ensure controllers use `require_codeword!` (and skip it only where access is allowed).
10
+ - Stop relying on external `return_to` redirects; they are blocked now.
11
+
12
+ ## 1) Credentials: namespaced only
13
+
14
+ Codeword now reads credentials only from the namespaced structure:
15
+
16
+ ```yml
17
+ # config/credentials.yml.enc
18
+ codeword:
19
+ codeword: "love"
20
+ hint: "Pepé Le Pew"
21
+ cookie_lifetime_in_weeks: 4
22
+ ```
23
+
24
+ - Non‑namespaced keys like `codeword_hint:` at the root are no longer read.
25
+ - Environment variable fallbacks still work: `ENV['CODEWORD']`, `ENV['CODEWORD_HINT']`, `ENV['COOKIE_LIFETIME_IN_WEEKS']`.
26
+
27
+ ## 2) Controller hook rename and usage
28
+
29
+ - Use `require_codeword!` to enforce the codeword gate.
30
+
31
+ ```ruby
32
+ class ApplicationController < ActionController::Base
33
+ include Codeword::Authentication
34
+ before_action :require_codeword!
35
+ end
36
+ ```
37
+
38
+ - Skip it only for controllers/actions you want to allow without the codeword:
39
+
40
+ ```ruby
41
+ class APIController < ApplicationController
42
+ skip_before_action :require_codeword!
43
+ end
44
+ ```
45
+
46
+ - The old `check_for_codeword` name is deprecated; switch to `require_codeword!`.
47
+
48
+ ## 3) Redirect hardening (open redirects)
49
+
50
+ - After a successful unlock, external `return_to` URLs are no longer allowed. Redirects now use `allow_other_host: false` and will fall back to the root path if unsafe.
51
+ - If you previously depended on redirecting to external domains, move that flow behind your own internal path and perform external navigation server‑side or client‑side after the unlock.
@@ -1,6 +1,6 @@
1
1
  module Codeword
2
2
  class CodewordController < Codeword::ApplicationController
3
- CRAWLER_REGEX = /crawl|googlebot|slurp|spider|bingbot|tracker|click|parser|spider/
3
+ CRAWLER_REGEX = /crawl|googlebot|slurp|spider|bingbot|tracker|parser/
4
4
 
5
5
  skip_before_action :require_codeword!, raise: false
6
6
 
@@ -14,7 +14,7 @@ module Codeword
14
14
  if params[:codeword].present?
15
15
  @codeword = params[:codeword].to_s.downcase
16
16
  @return_to = params[:return_to]
17
- if @codeword == codeword_code.to_s.downcase
17
+ if @codeword == Codeword::Configuration.codeword_code.to_s.downcase
18
18
  set_cookie
19
19
  run_redirect
20
20
  else
@@ -28,15 +28,23 @@ module Codeword
28
28
  private
29
29
 
30
30
  def set_cookie
31
- cookies[:codeword] = { value: @codeword.to_s.downcase, expires: (Time.now + codeword_cookie_lifetime) }
31
+ cookies[:codeword] = {
32
+ value: @codeword.to_s.downcase,
33
+ expires: Codeword::Configuration.codeword_cookie_lifetime.from_now,
34
+ httponly: true,
35
+ secure: request.ssl?,
36
+ same_site: :lax
37
+ }
32
38
  end
33
39
 
34
40
  def run_redirect
35
41
  if @return_to.present?
36
- redirect_to @return_to.to_s
42
+ redirect_to @return_to.to_s, allow_other_host: false
37
43
  else
38
44
  redirect_to '/'
39
45
  end
46
+ rescue ActionController::Redirecting::UnsafeRedirectError
47
+ redirect_to '/'
40
48
  end
41
49
  end
42
50
  end
@@ -3,7 +3,7 @@
3
3
  module Codeword
4
4
  module CodewordHelper
5
5
  def codeword_hint
6
- @codeword_hint ||= ENV['CODEWORD_HINT'] || ENV['codeword_hint'] || ::Codeword.from_config(:hint)
6
+ @codeword_hint ||= ENV['CODEWORD_HINT'] || Codeword::Configuration.from_credentials(:hint)
7
7
  end
8
8
  end
9
9
  end
data/codeword.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ['Dan Kim', 'gb Studio']
9
9
  spec.email = ['git@dan.kim']
10
10
 
11
- spec.summary = 'Lock staging servers from search engines and prying eyespec.'
11
+ spec.summary = 'Lock staging servers from search engines and prying eyes.'
12
12
  spec.description = 'A simple gem to more elegantly place a staging server or other in-progress application behind a basic codeword. It’s easy to implement, share with clients/collaborators, and more beautiful than the typical password-protection sheet.'
13
13
  spec.homepage = 'https://github.com/dankimio/codeword'
14
14
  spec.license = 'MIT'
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency 'rails', '>= 7.2'
25
25
 
26
- spec.add_development_dependency 'capybara', '~> 2.9'
26
+ spec.add_development_dependency 'capybara'
27
27
  spec.add_development_dependency 'debug'
28
28
  spec.add_development_dependency 'launchy', '~> 2.4'
29
- spec.add_development_dependency 'rspec-rails', '~> 6.0'
29
+ spec.add_development_dependency 'rspec-rails', '~> 7.0'
30
30
  end
@@ -0,0 +1,17 @@
1
+ module Codeword
2
+ module Authentication
3
+ extend ActiveSupport::Concern
4
+
5
+ def require_codeword!
6
+ return unless respond_to?(:codeword) && Codeword::Configuration.codeword_code
7
+ return if cookies[:codeword].present? && cookies[:codeword] == Codeword::Configuration.codeword_code.to_s.downcase
8
+
9
+ redirect_to codeword.unlock_path(
10
+ return_to: request.fullpath.split('?codeword')[0],
11
+ codeword: params[:codeword]
12
+ )
13
+ end
14
+
15
+ alias_method :check_for_codeword, :require_codeword!
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Codeword
4
+ module Configuration
5
+ def self.from_credentials(setting)
6
+ store = Rails.application.credentials
7
+
8
+ store.dig(:codeword, setting)
9
+ end
10
+
11
+ def self.cookie_lifetime
12
+ @cookie_lifetime ||=
13
+ ENV['COOKIE_LIFETIME_IN_WEEKS'] ||
14
+ from_credentials(:cookie_lifetime_in_weeks)
15
+ end
16
+
17
+ def self.codeword_code
18
+ @codeword_code ||= ENV['CODEWORD'] || from_credentials(:codeword)
19
+ end
20
+
21
+ def self.codeword_cookie_lifetime
22
+ weeks = cookie_lifetime.to_f
23
+ if weeks.positive?
24
+ weeks.weeks
25
+ else
26
+ 5.years
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Codeword
2
- VERSION = '0.2.0.beta3'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
data/lib/codeword.rb CHANGED
@@ -1,49 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'codeword/engine'
4
+ require 'codeword/authentication'
5
+ require 'codeword/configuration'
4
6
 
5
7
  module Codeword
6
- extend ActiveSupport::Concern
7
-
8
- def self.from_config(setting)
9
- store = Rails.application.credentials
10
-
11
- store.codeword.respond_to?(:fetch) &&
12
- store.codeword.fetch(setting, store.public_send("codeword_#{setting}")) ||
13
- store.public_send("codeword_#{setting}") || store.public_send(setting)
14
- end
15
-
16
- private
17
-
18
- def require_codeword!
19
- return unless respond_to?(:codeword) && codeword_code
20
- return if cookies[:codeword].present? && cookies[:codeword] == codeword_code.to_s.downcase
21
-
22
- redirect_to codeword.unlock_path(
23
- return_to: request.fullpath.split('?codeword')[0],
24
- codeword: params[:codeword]
25
- )
26
- end
27
-
28
- alias_method :check_for_codeword, :require_codeword!
29
-
30
- def cookie_lifetime
31
- @cookie_lifetime ||=
32
- ENV['COOKIE_LIFETIME_IN_WEEKS'] ||
33
- ENV['cookie_lifetime_in_weeks'] ||
34
- Codeword.from_config(:cookie_lifetime_in_weeks)
35
- end
36
-
37
- def codeword_code
38
- @codeword_code ||= ENV['CODEWORD'] || ENV['codeword'] || Codeword.from_config(:codeword)
39
- end
40
-
41
- def codeword_cookie_lifetime
42
- seconds = (cookie_lifetime.to_f * 1.week).to_i
43
- if seconds.positive?
44
- seconds
45
- else
46
- 5.years
47
- end
48
- end
49
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codeword
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.beta3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Kim
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: capybara
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '2.9'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '2.9'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: debug
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '6.0'
75
+ version: '7.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '6.0'
82
+ version: '7.0'
83
83
  description: A simple gem to more elegantly place a staging server or other in-progress
84
84
  application behind a basic codeword. It’s easy to implement, share with clients/collaborators,
85
85
  and more beautiful than the typical password-protection sheet.
@@ -95,6 +95,7 @@ files:
95
95
  - LICENSE.txt
96
96
  - README.md
97
97
  - Rakefile
98
+ - UPGRADE-0.2.md
98
99
  - app/controllers/codeword/application_controller.rb
99
100
  - app/controllers/codeword/codeword_controller.rb
100
101
  - app/helpers/codeword/application_helper.rb
@@ -106,11 +107,12 @@ files:
106
107
  - codeword.gemspec
107
108
  - config/routes.rb
108
109
  - lib/codeword.rb
110
+ - lib/codeword/authentication.rb
111
+ - lib/codeword/configuration.rb
109
112
  - lib/codeword/engine.rb
110
113
  - lib/codeword/version.rb
111
114
  - mise.toml
112
115
  - screenshot.png
113
- - script/rails
114
116
  homepage: https://github.com/dankimio/codeword
115
117
  licenses:
116
118
  - MIT
@@ -134,5 +136,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
136
  requirements: []
135
137
  rubygems_version: 3.7.1
136
138
  specification_version: 4
137
- summary: Lock staging servers from search engines and prying eyespec.
139
+ summary: Lock staging servers from search engines and prying eyes.
138
140
  test_files: []
data/script/rails DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
-
4
- ENGINE_ROOT = File.expand_path('..', __dir__)
5
- ENGINE_PATH = File.expand_path('../lib/codeword/engine', __dir__)
6
-
7
- require 'rails/all'
8
- require 'rails/engine/commands'