Error: SQLite3::SQLException: no such column: allowlisted_jwts.allowlisted_jwt_id
abratashov opened this issue · 3 comments
I'm implementing the JWT Allowlist strategy, and when I try to revoke the token the error arises:
AllowlistedJwt.revoke_jwt(payload, user)
=>
SQLite3::SQLException: no such column: allowlisted_jwts.allowlisted_jwt_id
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sqlite3-1.7.3-x86_64-linux/lib/sqlite3/database.rb:177:in `initialize'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sqlite3-1.7.3-x86_64-linux/lib/sqlite3/database.rb:177:in `new'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/sqlite3-1.7.3-x86_64-linux/lib/sqlite3/database.rb:177:in `prepare'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb:47:in `block (2 levels) in internal_exec_query'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract_adapter.rb:1028:in `block in with_raw_connection'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract_adapter.rb:1000:in `with_raw_connection'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb:33:in `block in internal_exec_query'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract_adapter.rb:1143:in `log'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/sqlite3/database_statements.rb:32:in `internal_exec_query'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:630:in `select'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:71:in `select_all'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/query_cache.rb:112:in `block in select_all'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/query_cache.rb:152:in `block in cache_sql'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/query_cache.rb:147:in `cache_sql'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/query_cache.rb:112:in `select_all'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/querying.rb:62:in `_query_by_sql'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/querying.rb:51:in `find_by_sql'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/statement_cache.rb:150:in `execute'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/associations/association.rb:235:in `find_target'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/associations/collection_association.rb:270:in `load_target'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/associations/has_many_association.rb:28:in `handle_dependency'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/associations/builder/association.rb:141:in `block in add_destroy_callbacks'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:470:in `instance_exec'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:470:in `block in make_lambda'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:202:in `block (2 levels) in halting'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:707:in `block (2 levels) in default_terminator'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:706:in `catch'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:706:in `block in default_terminator'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:203:in `block in halting'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:598:in `block in invoke_before'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:598:in `each'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:598:in `invoke_before'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:109:in `run_callbacks'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/callbacks.rb:952:in `_run_destroy_callbacks'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/callbacks.rb:423:in `destroy'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:305:in `block in destroy'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:365:in `block in with_transaction_returning_status'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/transaction.rb:535:in `block in within_new_transaction'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activesupport-7.1.3.4/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/transaction.rb:532:in `within_new_transaction'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:344:in `transaction'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:361:in `with_transaction_returning_status'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/transactions.rb:305:in `destroy'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/activerecord-7.1.3.4/lib/active_record/persistence.rb:797:in `destroy!'
/home/alex/.asdf/installs/ruby/3.3.3/lib/ruby/gems/3.3.0/gems/devise-jwt-0.12.1/lib/devise/jwt/revocation_strategies/allowlist.rb:36:in `revoke_jwt'
/home/alex/projects/abratashov/DailyTracker/app/interactors/api/auth/jwt_sign_out.rb:22:in `call'
Here JWT setup:
# Migration
class CreateAllowlistedJwts < ActiveRecord::Migration[7.1]
def change
create_table :allowlisted_jwts do |t|
t.string :jti, null: false
t.string :aud
t.datetime :exp, null: false
t.references :user, foreign_key: { on_delete: :cascade }, null: false
end
add_index :allowlisted_jwts, :jti, unique: true
end
end
# Models
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :lockable, :timeoutable, :trackable, # :omniauthable
:jwt_authenticatable, jwt_revocation_strategy: AllowlistedJwt
has_many :allowlisted_jwts
attribute :jwt_token, :string
end
class AllowlistedJwt < ApplicationRecord
include Devise::JWT::RevocationStrategies::Allowlist
class << self
def decode_jwt_payload(token, secret_key = Rails.application.credentials.jwt_secret_key!, alg = 'HS256')
body = JWT.decode(token, secret_key, true, algorithm: alg)[0]
HashWithIndifferentAccess.new body
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError => e
raise StandardError.new(e.message)
end
end
end
# Service
module Api
module Auth
class JwtSignOut
include Interactor
delegate :user, :auth_header, to: :context
attr_reader :token
before do
context.fail!(error: 'User not found', status: :not_found) if user.blank?
@token = auth_header.to_s.split(' ').last
context.fail!(error: 'Authorization header should contains JWT token', status: :not_found) if token.blank?
end
def call
payload = AllowlistedJwt.decode_jwt_payload(token)
# The error arises here:
# AllowlistedJwt.revoke_jwt(payload, user)
# Manual revocation:
allowlisted_jwt = user.allowlisted_jwts.find_by(jti: payload[:jti])
context.fail!(error: 'Token not found', status: :not_found) if allowlisted_jwt.blank?
# The same error in case manual destroying:
# allowlisted_jwt.destroy!
# Only this works fine:
allowlisted_jwt.delete
end
end
end
end
Versions:
rails (7.1.3.4)
devise (4.9.4)
devise-jwt (0.12.1)
sqlite3 (1.7.3)
Hey there 👋 Sorry, you have custom code that is not part of the recommended setup. Please, tyr to reproduce your bug in a pristine repository where the canonical installation is used. There's no previous notice of a problem with the allow list strategy so you should no be being experimenting this issue. I'll happily reopen this one if proven to be a bug here. Cheers.
Yeah, as mentioned in the docs of the Allowlist strategy, if I move Devise::JWT::RevocationStrategies::Allowlist
to the User
:
class AllowlistedJwt < ApplicationRecord
end
class User < ApplicationRecord
include Devise::JWT::RevocationStrategies::Allowlist
devise :database_authenticatable,
:jwt_authenticatable, jwt_revocation_strategy: self
end
It will work fine, thanks!
P.S.
I missed this docs part because I followed another guide to adding JWT to Rails.
Happy you figured it out!! 🙂