shakha 0.2.0 → 0.3.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/README.md +3 -4
- data/app/controllers/shakha/application_controller.rb +1 -1
- data/app/controllers/shakha/auth_controller.rb +96 -198
- data/app/controllers/shakha/session_controller.rb +15 -60
- data/app/models/shakha/client.rb +0 -4
- data/app/models/shakha/session.rb +0 -5
- data/app/models/shakha/user.rb +3 -5
- data/app/views/shakha/auth/new.html.erb +6 -18
- data/lib/shakha/config.rb +5 -29
- data/lib/shakha/config_validator.rb +1 -2
- data/lib/shakha/controller_helpers.rb +14 -33
- data/lib/shakha/engine.rb +6 -16
- data/lib/shakha/error_handler.rb +2 -3
- data/lib/shakha/providers/base.rb +27 -0
- data/lib/shakha/providers/github.rb +99 -0
- data/lib/shakha/providers/google.rb +90 -0
- data/lib/shakha/providers.rb +19 -0
- data/lib/shakha/version.rb +1 -1
- data/lib/shakha.rb +2 -28
- metadata +5 -8
- data/app/controllers/shakha/jwks_controller.rb +0 -10
- data/app/controllers/shakha/openid_controller.rb +0 -21
- data/app/views/shakha/auth/sessions.html.erb +0 -66
- data/lib/shakha/auditable.rb +0 -47
- data/lib/shakha/jwt_handler.rb +0 -127
- data/lib/shakha/middleware.rb +0 -49
- data/lib/shakha/pairwise.rb +0 -26
data/lib/shakha/jwt_handler.rb
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "jwt"
|
|
4
|
-
|
|
5
|
-
module Shakha
|
|
6
|
-
class ConfigurationError < StandardError; end
|
|
7
|
-
class JWTError < StandardError; end
|
|
8
|
-
|
|
9
|
-
class JwtHandler
|
|
10
|
-
ALGORITHM = "ES256"
|
|
11
|
-
|
|
12
|
-
class << self
|
|
13
|
-
def encode(payload, exp: 24.hours.from_now)
|
|
14
|
-
secret = signing_key || raise(ConfigurationError, "RSA/EC private key required for signing")
|
|
15
|
-
|
|
16
|
-
header = {
|
|
17
|
-
alg: ALGORITHM,
|
|
18
|
-
typ: "JWT",
|
|
19
|
-
kid: key_id
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
payload = payload.with_indifferent_access.merge(
|
|
23
|
-
iss: Shakha.config.issuer,
|
|
24
|
-
aud: Shakha.config.audience,
|
|
25
|
-
iat: Time.current.to_i,
|
|
26
|
-
exp: exp.to_i,
|
|
27
|
-
jti: SecureRandom.uuid
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
JWT.encode(payload, secret, ALGORITHM, header)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def verify(token, audience: nil)
|
|
34
|
-
public_key = verification_key || raise(ConfigurationError, "RSA/EC public key required for verification")
|
|
35
|
-
|
|
36
|
-
decoded = JWT.decode(
|
|
37
|
-
token,
|
|
38
|
-
public_key,
|
|
39
|
-
true,
|
|
40
|
-
{
|
|
41
|
-
algorithm: ALGORITHM,
|
|
42
|
-
iss: Shakha.config.issuer,
|
|
43
|
-
aud: audience || Shakha.config.audience,
|
|
44
|
-
verify_iss: true,
|
|
45
|
-
verify_aud: true,
|
|
46
|
-
verify_expiration: true
|
|
47
|
-
}
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
decoded[0].with_indifferent_access
|
|
51
|
-
rescue JWT::DecodeError => e
|
|
52
|
-
raise JWTError, e.message
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def jwks
|
|
56
|
-
{
|
|
57
|
-
keys: [
|
|
58
|
-
{
|
|
59
|
-
kty: "EC",
|
|
60
|
-
crv: "P-256",
|
|
61
|
-
x: Base64.urlsafe_encode64(public_key_point&.x || public_key_raw_point[0..31], padding: false),
|
|
62
|
-
y: Base64.urlsafe_encode64(public_key_point&.y || public_key_raw_point[32..63], padding: false),
|
|
63
|
-
use: "sig",
|
|
64
|
-
alg: ALGORITHM,
|
|
65
|
-
kid: key_id
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
}.to_json
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
def signing_key
|
|
74
|
-
return @signing_key if defined?(@signing_key)
|
|
75
|
-
|
|
76
|
-
key_material = Shakha.config.signing_key
|
|
77
|
-
return @signing_key = nil unless key_material
|
|
78
|
-
|
|
79
|
-
if key_material.is_a?(OpenSSL::PKey::EC)
|
|
80
|
-
@signing_key = key_material
|
|
81
|
-
elsif key_material.start_with?("-----BEGIN")
|
|
82
|
-
@signing_key = OpenSSL::PKey::EC.new(key_material)
|
|
83
|
-
else
|
|
84
|
-
@signing_key = OpenSSL::PKey::EC.new(Base64.decode64(key_material))
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def verification_key
|
|
89
|
-
return signing_key&.public_key if signing_key
|
|
90
|
-
|
|
91
|
-
public_material = Shakha.config.verification_key
|
|
92
|
-
return nil unless public_materiall
|
|
93
|
-
|
|
94
|
-
if public_material.start_with?("-----BEGIN")
|
|
95
|
-
OpenSSL::PKey::EC.new(public_material)
|
|
96
|
-
else
|
|
97
|
-
OpenSSL::PKey::EC.new(Base64.decode64(public_material))
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def public_key_point
|
|
102
|
-
@public_key_point ||= begin
|
|
103
|
-
key = verification_key || signing_key&.public_key
|
|
104
|
-
return nil unless key
|
|
105
|
-
|
|
106
|
-
group = key.group
|
|
107
|
-
point = key.public_key
|
|
108
|
-
{ x: point.x, y: point.y }
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def public_key_raw_point
|
|
113
|
-
@public_key_raw_point ||= begin
|
|
114
|
-
key = verification_key || signing_key&.public_key
|
|
115
|
-
return nil unless key
|
|
116
|
-
|
|
117
|
-
point = key.public_key
|
|
118
|
-
[point.x, point.y].map { |n| n.to_s(16).rjust(64, "0") }.join.scan(/../).map { |b| b.to_i(16).chr }.join
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def key_id
|
|
123
|
-
Shakha.config.key_id || "default"
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
data/lib/shakha/middleware.rb
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Shakha
|
|
4
|
-
class Middleware
|
|
5
|
-
def initialize(app)
|
|
6
|
-
@app = app
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def call(env)
|
|
10
|
-
@env = env
|
|
11
|
-
@request = ActionDispatch::Request.new(env)
|
|
12
|
-
|
|
13
|
-
if verify_token_request?
|
|
14
|
-
verify_token!
|
|
15
|
-
else
|
|
16
|
-
@app.call(env)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
attr_reader :request
|
|
23
|
-
|
|
24
|
-
def verify_token_request?
|
|
25
|
-
request.path == "/auth/shakha/token" && request.post?
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def verify_token!
|
|
29
|
-
token = extract_token || raise(JWTError, "Missing token")
|
|
30
|
-
payload = Shakha.verify_token(token)
|
|
31
|
-
|
|
32
|
-
@env["shakha.user_id"] = payload[:pairwise_sub]
|
|
33
|
-
@env["shakha.email"] = payload[:email]
|
|
34
|
-
@env["shakha.name"] = payload[:name]
|
|
35
|
-
|
|
36
|
-
@app.call(@env)
|
|
37
|
-
rescue JWTError => e
|
|
38
|
-
[401, { "Content-Type" => "application/json" }, [{ error: e.message }.to_json]]
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def extract_token
|
|
42
|
-
if request.content_type == "application/json"
|
|
43
|
-
JSON.parse(request.body.read)["id_token"]
|
|
44
|
-
elsif request.params["id_token"].present?
|
|
45
|
-
request.params["id_token"]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
data/lib/shakha/pairwise.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "base64"
|
|
4
|
-
require "openssl"
|
|
5
|
-
require "jwt"
|
|
6
|
-
|
|
7
|
-
module Shakha
|
|
8
|
-
module Pairwise
|
|
9
|
-
HMAC_DIGEST = OpenSSL::Digest::SHA256.new
|
|
10
|
-
|
|
11
|
-
class << self
|
|
12
|
-
def derive(google_sub, client_id)
|
|
13
|
-
secret = secret_key
|
|
14
|
-
input = "#{google_sub}:#{client_id}"
|
|
15
|
-
digest = OpenSSL::HMAC.hexdigest(HMAC_DIGEST, secret, input)
|
|
16
|
-
"ps_#{digest}"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def secret_key
|
|
22
|
-
Shakha.config.service_secret || raise(Shakha::ConfigurationError, "SHAKHA_SECRET not set")
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|