Don't forget to lycansubscribe

added jwt-based authentication

Changed files
+76
app
+3
Gemfile
··· 12 12 gem 'minisky', '~> 0.5' 13 13 gem 'didkit', '~> 0.2', git: 'https://tangled.sh/@mackuba.eu/didkit' 14 14 15 + gem 'base58' 16 + gem 'jwt' 17 + 15 18 group :development do 16 19 gem 'puma' 17 20 gem 'rackup'
+5
Gemfile.lock
··· 25 25 minitest (>= 5.1) 26 26 securerandom (>= 0.3) 27 27 tzinfo (~> 2.0, >= 2.0.5) 28 + base58 (0.2.3) 28 29 base64 (0.3.0) 29 30 benchmark (0.4.1) 30 31 bigdecimal (3.2.2) ··· 40 41 pp (>= 0.6.0) 41 42 rdoc (>= 4.0.0) 42 43 reline (>= 0.4.2) 44 + jwt (3.1.2) 45 + base64 43 46 logger (1.7.0) 44 47 minisky (0.5.0) 45 48 base64 (~> 0.1) ··· 108 111 109 112 DEPENDENCIES 110 113 activerecord (~> 7.2) 114 + base58 111 115 didkit (~> 0.2)! 112 116 irb 117 + jwt 113 118 minisky (~> 0.5) 114 119 pg 115 120 puma
+68
app/server.rb
··· 1 + require 'base58' 2 + require 'base64' 3 + require 'didkit' 1 4 require 'json' 5 + require 'jwt' 6 + require 'openssl' 2 7 require 'sinatra/base' 3 8 4 9 require_relative 'init' ··· 6 11 require_relative 'models/user' 7 12 8 13 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 + 9 26 register Sinatra::ActiveRecordExtension 10 27 set :port, 3000 11 28 ··· 21 38 content_type :json 22 39 [status, JSON.generate({ error: name, message: message })] 23 40 end 41 + 42 + def decode_user_from_jwt(auth_header, endpoint) 43 + return nil unless auth_header.start_with?('Bearer ') 44 + 45 + token = auth_header.gsub(/\ABearer /, '') 46 + data = JSON.parse(Base64.decode64(token.split('.')[1])) 47 + did = data['iss'] 48 + return nil if data['aud'] != 'did:web:lycan.feeds.blue' || data['lxm'] != endpoint 49 + 50 + pkey = pkey_for_user(did) 51 + 52 + decoded = JWT.decode(token, pkey, true, { algorithm: 'ES256K' }) 53 + decoded[0] && decoded[0]['iss'] 54 + end 55 + 56 + def pkey_for_user(did) 57 + # I have no idea what this does, but it seems to be working ¯\_(ツ)_/¯ 58 + 59 + if pkey = PKeyCache.get(did) 60 + return pkey 61 + end 62 + 63 + doc = DID.new(did).document.json 64 + key_obj = (doc['verificationMethod'] || []).detect { |x| x['type'] == 'Multikey' } 65 + key_multi = key_obj&.dig('publicKeyMultibase') 66 + return nil unless key_multi 67 + 68 + key_decoded = Base58.base58_to_binary(key_multi[1..], :bitcoin) 69 + comp_key = key_decoded[2..-1] 70 + 71 + alg_id = OpenSSL::ASN1::Sequence([ 72 + OpenSSL::ASN1::ObjectId('id-ecPublicKey'), 73 + OpenSSL::ASN1::ObjectId('secp256k1') 74 + ]) 75 + 76 + der = OpenSSL::ASN1::Sequence([alg_id, OpenSSL::ASN1::BitString(comp_key)]).to_der 77 + pkey = OpenSSL::PKey.read(der) 78 + 79 + PKeyCache.set(did, pkey) 80 + 81 + pkey 82 + end 24 83 end 25 84 26 85 get '/xrpc/blue.feeds.lycan.searchPosts' do ··· 29 88 if settings.development? 30 89 user = User.find_by(did: params[:user]) 31 90 return json_error('UserNotFound', 'Missing "user" parameter') if user.nil? 91 + else 92 + begin 93 + did = decode_user_from_jwt(env['HTTP_AUTHORIZATION'], 'blue.feeds.lycan.searchPosts') 94 + rescue StandardError => e 95 + p e 96 + end 97 + 98 + user = did && User.find_by(did: did) 99 + return json_error('UserNotFound', 'Missing authentication header') if user.nil? 32 100 end 33 101 34 102 if params[:query]