Don't forget to lycansubscribe
1require 'base58'
2require 'base64'
3require 'didkit'
4require 'json'
5require 'jwt'
6require 'openssl'
7
8class 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
55end