+55
app/authenticator.rb
+55
app/authenticator.rb
···
1
+
require 'base58'
2
+
require 'base64'
3
+
require 'didkit'
4
+
require 'json'
5
+
require 'jwt'
6
+
require 'openssl'
7
+
8
+
class Authenticator
9
+
def initialize(hostname:)
10
+
@@pkey_cache ||= {}
11
+
@hostname = hostname
12
+
end
13
+
14
+
def decode_user_from_jwt(auth_header, endpoint)
15
+
return nil unless auth_header.start_with?('Bearer ')
16
+
17
+
token = auth_header.gsub(/\ABearer /, '')
18
+
data = JSON.parse(Base64.decode64(token.split('.')[1]))
19
+
did = data['iss']
20
+
return nil if data['aud'] != "did:web:#{@hostname}" || data['lxm'] != endpoint
21
+
22
+
pkey = pkey_for_user(did)
23
+
24
+
decoded = JWT.decode(token, pkey, true, { algorithm: 'ES256K' })
25
+
decoded[0] && decoded[0]['iss']
26
+
end
27
+
28
+
def pkey_for_user(did)
29
+
# I have no idea what this does, but it seems to be working ¯\_(ツ)_/¯
30
+
31
+
if pkey = @@pkey_cache[did]
32
+
return pkey
33
+
end
34
+
35
+
doc = DID.new(did).document.json
36
+
key_obj = (doc['verificationMethod'] || []).detect { |x| x['type'] == 'Multikey' }
37
+
key_multi = key_obj&.dig('publicKeyMultibase')
38
+
return nil unless key_multi
39
+
40
+
key_decoded = Base58.base58_to_binary(key_multi[1..], :bitcoin)
41
+
comp_key = key_decoded[2..-1]
42
+
43
+
alg_id = OpenSSL::ASN1::Sequence([
44
+
OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
45
+
OpenSSL::ASN1::ObjectId('secp256k1')
46
+
])
47
+
48
+
der = OpenSSL::ASN1::Sequence([alg_id, OpenSSL::ASN1::BitString(comp_key)]).to_der
49
+
pkey = OpenSSL::PKey.read(der)
50
+
51
+
@@pkey_cache[did] = pkey
52
+
53
+
pkey
54
+
end
55
+
end
+6
-59
app/server.rb
+6
-59
app/server.rb
···
1
-
require 'base58'
2
-
require 'base64'
3
-
require 'didkit'
4
1
require 'json'
5
-
require 'jwt'
6
-
require 'openssl'
7
2
require 'sinatra/base'
8
3
9
4
require_relative 'init'
5
+
require_relative 'authenticator'
10
6
require_relative 'models/user'
11
7
require_relative 'query_parser'
12
8
13
9
class Server < Sinatra::Application
14
-
class PKeyCache
15
-
def self.get(did)
16
-
@cache ||= {}
17
-
@cache[did]
18
-
end
19
-
20
-
def self.set(did, pkey)
21
-
@cache ||= {}
22
-
@cache[did] = pkey
23
-
end
24
-
end
25
-
26
10
register Sinatra::ActiveRecordExtension
27
11
set :port, 3000
28
12
···
39
23
content_type :json
40
24
[status, JSON.generate({ error: name, message: message })]
41
25
end
42
-
43
-
def decode_user_from_jwt(auth_header, endpoint)
44
-
return nil unless auth_header.start_with?('Bearer ')
26
+
end
45
27
46
-
token = auth_header.gsub(/\ABearer /, '')
47
-
data = JSON.parse(Base64.decode64(token.split('.')[1]))
48
-
did = data['iss']
49
-
return nil if data['aud'] != "did:web:#{HOSTNAME}" || data['lxm'] != endpoint
50
-
51
-
pkey = pkey_for_user(did)
52
-
53
-
decoded = JWT.decode(token, pkey, true, { algorithm: 'ES256K' })
54
-
decoded[0] && decoded[0]['iss']
55
-
end
56
-
57
-
def pkey_for_user(did)
58
-
# I have no idea what this does, but it seems to be working ¯\_(ツ)_/¯
59
-
60
-
if pkey = PKeyCache.get(did)
61
-
return pkey
62
-
end
63
-
64
-
doc = DID.new(did).document.json
65
-
key_obj = (doc['verificationMethod'] || []).detect { |x| x['type'] == 'Multikey' }
66
-
key_multi = key_obj&.dig('publicKeyMultibase')
67
-
return nil unless key_multi
68
-
69
-
key_decoded = Base58.base58_to_binary(key_multi[1..], :bitcoin)
70
-
comp_key = key_decoded[2..-1]
71
-
72
-
alg_id = OpenSSL::ASN1::Sequence([
73
-
OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
74
-
OpenSSL::ASN1::ObjectId('secp256k1')
75
-
])
76
-
77
-
der = OpenSSL::ASN1::Sequence([alg_id, OpenSSL::ASN1::BitString(comp_key)]).to_der
78
-
pkey = OpenSSL::PKey.read(der)
79
-
80
-
PKeyCache.set(did, pkey)
81
-
82
-
pkey
83
-
end
28
+
before do
29
+
@authenticator = Authenticator.new(hostname: HOSTNAME)
84
30
end
85
31
86
32
get '/xrpc/blue.feeds.lycan.searchPosts' do
···
91
37
return json_error('UserNotFound', 'Missing "user" parameter') if user.nil?
92
38
else
93
39
begin
94
-
did = decode_user_from_jwt(env['HTTP_AUTHORIZATION'], 'blue.feeds.lycan.searchPosts')
40
+
auth_header = env['HTTP_AUTHORIZATION']
41
+
did = @authenticator.decode_user_from_jwt(auth_header, 'blue.feeds.lycan.searchPosts')
95
42
rescue StandardError => e
96
43
p e
97
44
end