Don't forget to lycansubscribe
1require 'base58'
2require 'base64'
3require 'didkit'
4require 'json'
5require 'jwt'
6require 'openssl'
7
8class Authenticator
9 class InvalidTokenError < StandardError
10 end
11
12 def initialize(hostname:)
13 @@pkey_cache ||= {}
14 @hostname = hostname
15 end
16
17 def decode_user_from_jwt(auth_header, endpoint)
18 return nil unless auth_header.start_with?('Bearer ')
19
20 token = auth_header.gsub(/\ABearer /, '')
21 parts = token.split('.')
22 raise InvalidTokenError, "Invalid JWT token" if parts.length != 3
23
24 begin
25 decoded_data = Base64.decode64(parts[1])
26 data = JSON.parse(decoded_data)
27 rescue StandardError => e
28 raise InvalidTokenError, "Invalid JWT token"
29 end
30
31 did = data['iss']
32 return nil if did.nil? || data['aud'] != "did:web:#{@hostname}" || data['lxm'] != endpoint
33
34 pkey = pkey_for_user(did)
35
36 decoded = JWT.decode(token, pkey, true, { algorithm: 'ES256K' })
37 decoded[0] && decoded[0]['iss']
38 end
39
40 def pkey_for_user(did)
41 # I have no idea what this does, but it seems to be working ¯\_(ツ)_/¯
42
43 if pkey = @@pkey_cache[did]
44 return pkey
45 end
46
47 doc = DID.new(did).document.json
48 key_obj = (doc['verificationMethod'] || []).detect { |x| x['type'] == 'Multikey' }
49 key_multi = key_obj&.dig('publicKeyMultibase')
50 return nil unless key_multi
51
52 key_decoded = Base58.base58_to_binary(key_multi[1..], :bitcoin)
53 comp_key = key_decoded[2..-1]
54
55 alg_id = OpenSSL::ASN1::Sequence([
56 OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
57 OpenSSL::ASN1::ObjectId('secp256k1')
58 ])
59
60 der = OpenSSL::ASN1::Sequence([alg_id, OpenSSL::ASN1::BitString(comp_key)]).to_der
61 pkey = OpenSSL::PKey.read(der)
62
63 @@pkey_cache[did] = pkey
64
65 pkey
66 end
67end