shakha 0.1.3 → 0.1.4

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: 7ff145da09437c51afcef947199c0225b2fb557a6e0559e1792e07d7af646250
4
- data.tar.gz: d95b3e5a110d49b7defde447935240d27e0faf2a76e3874b4b05dba7f174336e
3
+ metadata.gz: 2b5eb8fae72d4a779316f8266fc29f1078bf5b31c11a5d90ae922f46d3e37928
4
+ data.tar.gz: 95bc2e261d8a08c818ad75b4beefc8e5a4fa97802ef5cf28afdfbe7a895cdcc1
5
5
  SHA512:
6
- metadata.gz: f61b03a6afdb461afde6274f2892b54f1f7979369023a1421b5037f99b333351c5c7bca5194c7f76db22d9f516594c4c737d96f501f82b8663c2ad5fafa472d1
7
- data.tar.gz: 344441bf4fcfc2dc8061bb25fab5dfbe64485919405658cbbd16c376ffcdddc685682e0256d7e2fe2401c29e22a8122a157439e111748ccb8d6e0b8c266dc9a4
6
+ metadata.gz: 0ee17e65c726cb0564e720fb73648b74567fcfb1edae5befbd14a16942ca877d5c007cfc8b622fed8c949211de3bf5b792fdff1190482bfdd414351b451e6204
7
+ data.tar.gz: 2205e02b9c9ebde26def46da665011571a5071a50bd79eaf6bb2d7075aed353d2d3b52eff1d6986113bf612ce2a6e2244e200e53b4aadf2b4c7fba8020ed354b
@@ -4,6 +4,8 @@ module Shakha
4
4
  class ApplicationController < ActionController::Base
5
5
  include ErrorHandler
6
6
  include ControllerHelpers
7
+ include RateLimiter
8
+ include Auditable
7
9
 
8
10
  protect_from_forgery with: :exception
9
11
 
@@ -28,6 +28,10 @@ module Shakha
28
28
  pkce_result = verify_pkce!(params[:code], params[:state])
29
29
  exchange_code_for_tokens(params[:code], pkce_result[:verifier], pkce_result[:return_to])
30
30
  rescue PKCEError, GoogleOAuthError => e
31
+ ActiveSupport::Notifications.instrument("shakha.sign_in_failed", {
32
+ reason: e.class.name,
33
+ ip: request.remote_ip
34
+ })
31
35
  Rails.logger.warn("[Shakha] Auth error: #{e.class}: #{e.message}")
32
36
  redirect_to "/auth/shakha/error?message=#{URI.encode_www_form_component(user_facing_error(e))}"
33
37
  end
@@ -4,6 +4,25 @@ module Shakha
4
4
  class SessionController < ApplicationController
5
5
  skip_before_action :verify_authenticity_token, only: [:check]
6
6
 
7
+ def index
8
+ return render json: { error: "Authentication required" }, status: :unauthorized unless signed_in?
9
+
10
+ sessions = current_user.sessions.active.order(created_at: :desc)
11
+
12
+ render json: {
13
+ current_token: current_session.token,
14
+ sessions: sessions.map { |s|
15
+ {
16
+ id: s.id,
17
+ token: s.token,
18
+ created_at: s.created_at.iso8601,
19
+ expires_at: s.expires_at.iso8601,
20
+ current: s.token == current_session.token
21
+ }
22
+ }
23
+ }
24
+ end
25
+
7
26
  def show
8
27
  render json: {
9
28
  user_id: current_user&.pairwise_sub,
@@ -33,5 +52,22 @@ module Shakha
33
52
  format.json { render json: { status: "signed_out" } }
34
53
  end
35
54
  end
55
+
56
+ def revoke
57
+ return render json: { error: "Authentication required" }, status: :unauthorized unless signed_in?
58
+
59
+ session = current_user.sessions.find(params[:id])
60
+ session.destroy
61
+
62
+ cookies.delete(:shakha_session_token) if session.token == current_session&.token
63
+
64
+ ActiveSupport::Notifications.instrument("shakha.session_revoked", {
65
+ session_id: session.id,
66
+ user_id: current_user.id,
67
+ ip: request.remote_ip
68
+ })
69
+
70
+ render json: { status: "revoked" }
71
+ end
36
72
  end
37
73
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shakha
4
+ module Auditable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_action :log_sign_in, only: [:callback]
9
+ after_action :log_sign_out, only: [:destroy]
10
+ after_action :log_token_exchange, only: [:token]
11
+ end
12
+
13
+ private
14
+
15
+ def log_sign_in
16
+ return unless response.successful? && @current_user
17
+
18
+ ActiveSupport::Notifications.instrument("shakha.sign_in", {
19
+ user_id: @current_user&.id,
20
+ pairwise_sub: @current_user&.pairwise_sub,
21
+ client_id: @current_client&.id,
22
+ ip: request.remote_ip,
23
+ user_agent: request.user_agent
24
+ })
25
+ end
26
+
27
+ def log_sign_out
28
+ return unless action_name == "destroy"
29
+
30
+ ActiveSupport::Notifications.instrument("shakha.sign_out", {
31
+ session_id: @current_session&.id,
32
+ user_id: @current_session&.user_id,
33
+ ip: request.remote_ip
34
+ })
35
+ end
36
+
37
+ def log_token_exchange
38
+ return unless action_name == "token"
39
+
40
+ ActiveSupport::Notifications.instrument("shakha.token_exchange", {
41
+ ip: request.remote_ip,
42
+ user_agent: request.user_agent,
43
+ success: response.successful?
44
+ })
45
+ end
46
+ end
47
+ end
data/lib/shakha/config.rb CHANGED
@@ -11,11 +11,13 @@ module Shakha
11
11
  :session_lifetime,
12
12
  :signing_key,
13
13
  :verification_key,
14
- :key_id
14
+ :key_id,
15
+ :rate_limiting_enabled
15
16
 
16
17
  def initialize
17
18
  @session_lifetime = 30.days
18
19
  @issuer = "https://shakha.dev"
20
+ @rate_limiting_enabled = false
19
21
  end
20
22
 
21
23
  def embedded?
data/lib/shakha/engine.rb CHANGED
@@ -20,8 +20,10 @@ module Shakha
20
20
  get "error" => "auth#error"
21
21
 
22
22
  get "session" => "session#show"
23
+ get "sessions" => "session#index"
23
24
  post "session/check" => "session#check"
24
25
  delete "session" => "session#destroy"
26
+ delete "sessions/:id" => "session#revoke"
25
27
 
26
28
  get ".well-known/jwks.json" => "jwks#show"
27
29
  get ".well-known/openid-configuration" => "openid#configuration"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shakha
4
+ module RateLimiter
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :check_rate_limit_authorize, only: [:authorize]
9
+ before_action :check_rate_limit_token, only: [:token]
10
+ end
11
+
12
+ private
13
+
14
+ def check_rate_limit_authorize
15
+ check_rate_limit("authorize", max: 20, period: 1.minute)
16
+ end
17
+
18
+ def check_rate_limit_token
19
+ check_rate_limit("token", max: 10, period: 1.minute)
20
+ end
21
+
22
+ def check_rate_limit(key, max:, period:)
23
+ return unless Shakha.config.rate_limiting_enabled?
24
+
25
+ cache_key = "shakha-rate:#{key}:#{request.remote_ip}"
26
+
27
+ count = Rails.cache.read(cache_key).to_i + 1
28
+
29
+ if count == 1
30
+ Rails.cache.write(cache_key, count, expires_in: period.seconds)
31
+ elsif count > max
32
+ render json: { error: "Too many requests. Try again later." }, status: :too_many_requests
33
+ return
34
+ else
35
+ Rails.cache.write(cache_key, count, expires_in: period.seconds)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shakha
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/shakha.rb CHANGED
@@ -6,6 +6,8 @@ require "shakha/config_validator"
6
6
  require "shakha/pairwise"
7
7
  require "shakha/jwt_handler"
8
8
  require "shakha/pkce"
9
+ require "shakha/rate_limiter"
10
+ require "shakha/auditable"
9
11
  require "shakha/error_handler"
10
12
  require "shakha/controller_helpers"
11
13
  require "shakha/middleware"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shakha
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
@@ -96,6 +96,7 @@ files:
96
96
  - lib/generators/shakha/templates/initializer.rb.erb
97
97
  - lib/generators/shakha/templates/migration.rb.erb
98
98
  - lib/shakha.rb
99
+ - lib/shakha/auditable.rb
99
100
  - lib/shakha/config.rb
100
101
  - lib/shakha/config_validator.rb
101
102
  - lib/shakha/controller_helpers.rb
@@ -105,6 +106,7 @@ files:
105
106
  - lib/shakha/middleware.rb
106
107
  - lib/shakha/pairwise.rb
107
108
  - lib/shakha/pkce.rb
109
+ - lib/shakha/rate_limiter.rb
108
110
  - lib/shakha/version.rb
109
111
  homepage: https://shakha.dev
110
112
  licenses: