Problem with the logout on Rails API + Devise + JWT - ruby-on-rails

Im building a Rails API. All my devise routes working fine, execpt for one : the logout.
I followed this tutorial : https://jameschambers.co.uk/rails-api
I already tried few things, like :
tried the api/logout.json route
tried to change rb #111 for : config.skip_session_storage = [:http_auth, :params_auth] ( config/initializers/devise.rb )
tried to change rb #280 for : config.sign_out_via = :get ( config/initializers/devise.rb )
Im sending a DELETE request on this address : localhost:3000/api/logout, with a JWT/Bearer token.
error
"status": 500,
"error": "Internal Server Error",
"exception": "#<ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column jwt_denylist.exp does not exist\nLINE 1: ...jwt_denylist\" WHERE \"jwt_denylist\".\"jti\" = $1 AND \"jwt_denyl...
rails routes
destroy_user_session DELETE /api/logout(.:format) sessions#destroy {:format=>:json}
db/schema.rb
ActiveRecord::Schema.define(version: 2021_09_01_233823) do
create_table "jwt_denylist", force: :cascade do |t|
t.string "jti", null: false
t.datetime "expired_at", null: false
t.index ["jti"], name: "index_jwt_denylist_on_jti"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
private
def respond_with(resource, _opts = {})
render_jsonapi_response(resource)
end
def respond_to_on_destroy
head :no_content
end
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
end
config/routes.rb
Rails.application.routes.draw do
default_url_options :host => "http://localhost:3000"
namespace :api, defaults: { format: :json } do
resources :users, only: %w[show]
end
devise_for :users,
defaults: { format: :json },
path: '',
path_names: {
sign_in: 'api/login',
sign_out: 'api/logout',
registration: 'api/signup'
},
controllers: {
sessions: 'sessions',
registrations: 'registrations'
}
end

The error ERROR: column jwt_denylist.exp does not exist indicates it's looking for a column (exp) that doesn't exist on the jwt_denylist table.
Your schema shows you have an expired_at but no exp column.
From the jwt-denylist gem the schema it expects exp. Perhaps the tutorial is out of date? Here's what the gem's documentation recommends:
create_table :jwt_denylist do |t|
t.string :jti, null: false
t.datetime :exp, null: false
end
add_index :jwt_denylist, :jti
So i would rename the :expired_at column to :exp, e.g.
rails generate migration rename_expired_at_to_exp_on_jwt_denylist and make the change method something like:
def change
rename_column :jwt_denylist, :expired_at, :exp
end

It works, thanks Melcher !! :)
This is what the migration looked like
class CreateJwtDenylist < ActiveRecord::Migration[6.1]
def change
create_table :jwt_denylist do |t|
t.string :jti, null: false
t.datetime :expired_at, null: false
end
add_index :jwt_denylist, :jti
end
end
This is what is looks like now
class CreateJwtDenylist < ActiveRecord::Migration[6.1]
def change
create_table :jwt_denylist do |t|
t.string :jti, null: false
t.datetime :exp, null: false
end
add_index :jwt_denylist, :jti
end
end

Related

API Rails : How to make a RESTFul API using Devise & JWT?

I'm trying to build a RESTFul API with devise & jwt.
I can register, and login/logout using my jwt bear token, using Postman.
Now I have a problem when I want to POST an Article.
I dont understand why my console goes for a login after I POST an Article with Postman.
Also I dont understand why I get this 401 error. It's really hard to find some content with RESTFul + API + Devise + JWT.
Do you think it's better in the long term to run with or without Devise ? Cause there is actually some content without Devise.
What I try on Postman
Authorization : <Bearer token>
{
"title":"the title",
"content":"the content"
}
Returned ERROR message from the console when I post an Article with Postman ( and with a the same bear token as for login/logout
Started POST "/articles" for ::1 at 2021-09-01 18:07:41 +0200
Processing by ArticlesController#create as */*
Parameters: {"title"=>"the title", "content"=>"the content", "article"=>{"title"=>"the title", "content"=>"the content"}}
Completed 401 Unauthorized in 76ms (Allocations: 113)
Started GET "/api/login" for ::1 at 2021-09-01 18:07:41 +0200
Processing by SessionsController#new as JSON
Completed 200 OK in 71ms (Views: 2.5ms | Allocations: 179)
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_todo, only: [:show, :update, :destroy]
before_action :authenticate_user!
# GET /todos
def index
#articles = Article.all
json_response(#articles)
end
# POST /todos
def create
#article = Article.create!(article_params)
#article.user = current_user
end
# GET /todos/:id
def show
json_response(#article)
end
# PUT /todos/:id
def update
#article.update(article_params)
head :no_content
end
# DELETE /todos/:id
def destroy
#article.destroy
head :no_content
end
private
def article_params
# whitelist params
params.permit(:title, :content, :user_id)
end
def set_article
#article = Article.find(params[:id])
end
end
db/shema.rb
ActiveRecord::Schema.define(version: 2021_09_01_124211) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_articles_on_user_id"
end
create_table "jwt_denylist", force: :cascade do |t|
t.string "jti", null: false
t.datetime "expired_at", null: false
t.index ["jti"], name: "index_jwt_denylist_on_jti"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "articles", "users"
end
app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
has_many :articles
end

NoMethodError in Account::PostsController#index

I'm the newbie, my rails version is 4.2.0 and I install three gem devise, cancancan, rolify. When I sign up success, I got this message
NoMethodError in Account::PostsController#index
undefined method `role' for #<User:0x007fd84c2afba8>
def admin?
self.role.name == "admin"
end
end
I guess the problem is users, roles the connection is miss. And I try to modify my user.rb but not work.
This is my app/models/ability.rb code
class Ability
include CanCan::Ability
def initialize(user)
if user.admin?
can :manage, :all
else
can :update, Post do |post|
post.user == user
end
can :destroy, Post do |post|
post.user == user
end
can :create, Post
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
has_many :posts
############################################################
# not work
# belongs_to :users_roles
# has_and_belongs_to_many :roles, :join_tabe => :users_roles
# has_one :users_role
############################################################
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def admin?
self.role.name == "admin"
end
end
app/models/role.rb
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
validates :resource_type,
:inclusion => { :in => Rolify.resource_types },
:allow_nil => true
scopify
end
When I sign up I will add a role to first sign up user, this is my application_controller.rb
def after_sign_in_path_for(resource)
if resource.is_a?(User)
if User.count == 1
resource.add_role 'admin'
else
resource.add_role 'normal'
end
resource
end
root_path
end
schema.rb
ActiveRecord::Schema.define(version: 20160225105659) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "ckeditor_assets", force: :cascade do |t|
t.string "data_file_name", null: false
t.string "data_content_type"
t.integer "data_file_size"
t.integer "assetable_id"
t.string "assetable_type", limit: 30
t.string "type", limit: 30
t.integer "width"
t.integer "height"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "ckeditor_assets", ["assetable_type", "assetable_id"], name: "idx_ckeditor_assetable", using: :btree
add_index "ckeditor_assets", ["assetable_type", "type", "assetable_id"], name: "idx_ckeditor_assetable_type", using: :btree
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
create_table "roles", force: :cascade do |t|
t.string "name"
t.integer "resource_id"
t.string "resource_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "roles", ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id", using: :btree
add_index "roles", ["name"], name: "index_roles_on_name", using: :btree
create_table "travel_events", force: :cascade do |t|
t.string "title"
t.datetime "start_date"
t.datetime "end_date"
t.text "note"
t.integer "travel_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "travels", force: :cascade do |t|
t.string "title"
t.datetime "start_date"
t.datetime "end_date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
create_table "users_roles", id: false, force: :cascade do |t|
t.integer "user_id"
t.integer "role_id"
end
add_index "users_roles", ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id", using: :btree
end
I think you forgot to add
has_one :users_role
in user.rb
I find the answer, but I'm not sure the question is table's relationship.
I remove my user.rb's admin method
user.rb
class User < ActiveRecord::Base
rolify
has_many :posts
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# def admin?
# self.role.name == "admin"
# end
end
And modify ability.rb
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
# if user.admin?
if user.has_role?(:admin) # modify this line
can :manage, :all
else
can :update, Post do |post|
post.user == user
end
can :destroy, Post do |post|
post.user == user
end
can :create, Post
can :read, Post
end
end
end

Ruby on Rails SQLite3::ConstraintException: NOT NULL constraint failed:

I am developing a simple app where a user can add a subject to a cart. Before I add the authentication I was able to add a subject to the cart but as I want a user to be able to has access to just his/her cart I used Devise to create User with authentication. Now, when I click on the button to add a subject to the cart I have the following error:
This is a snapshot of the error I get: 1
SQLite3::ConstraintException: NOT NULL constraint failed: carts.user_id: INSERT INTO "carts" ("created_at", "updated_at") VALUES (?, ?)
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
private
def current_cart
Cart.find(params[:user_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
params[:user_id] = cart.id
cart
end
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :cart
end
class Cart < ActiveRecord::Base
belongs_to :user
has_many :line_items, dependent: :destroy
scope :user_carts, ->(user) { where(['user_id= ?', user.id]) }
end
class AddUsersToCarts < ActiveRecord::Migration
def up
add_reference :carts, :user, index: true
Cart.reset_column_information
user = User.first
Cart.all.each do |cart|
cart.user_id = user.id
cart.save!
end
change_column_null :carts, :user_id, false
add_foreign_key :carts, :users
end
def down
remove_foreign_key :carts, :users
remove_reference :carts, :user, index: true
end
end
Edit: I added the schema.rb below:
ActiveRecord::Schema.define(version: 20151210213408) do
create_table "carts", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
end
add_index "carts", ["user_id"], name: "index_carts_on_user_id"
create_table "line_items", force: :cascade do |t|
t.integer "subject_id"
t.integer "cart_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subjects", force: :cascade do |t|
t.string "title", null: false
t.string "code", null: false
t.text "description", null: false
t.integer "credits", null: false
t.string "lecturer", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "subjects", ["title"], name: "index_subjects_on_title", unique: true
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
You current_cart method does not make much sense.
You cannot find the user's cart by calling Cart.find(params[:user_id]), because that looks for a cart by an id (not by an user_id).
Cart.create fails, because you do not provide an user_id that is required (your database migrations says that the filed cannot be null).
Furthermore, params[:user_id] = cart.id changes the params hash, but not the new cart.
Change that method to something like this (using find_or_create_by) and use the current_user.id instead of params[:user_id]:
def current_cart
Cart.find_or_create_by(user_id: current_user.id)
end

Issue with belongs_to and user (devise)

I'm new to RoR, and I would like to develop an app, but I have an issue with the belongs_to association. I am using devise for the authentication of my users, and I have an object called timesheet, I followed several tutorials and read a lot of forums but unfortunately user_id remains null in my db, so I do not know where does the problem come from.
If you can tell how to fix it, any links that can helps me, that would be great.
Schema.rb:
ActiveRecord::Schema.define(version: 20150128160116) do
create_table "timesheets", force: true do |t|
t.date "date"
t.time "working_start_time"
t.time "working_end_time"
t.integer "breaks"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "timesheets", ["user_id"], name: "index_timesheets_on_user_id"
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.integer "current_sign_in_ip"
t.integer "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
timesheets_controller.rb
class TimesheetsController < ApplicationController
layout 'application'
def show
#timesheet=Timesheet.find(params[:id])
end
def index
#timesheet = Timesheet.all
end
def new
#timesheet = Timesheet.new
end
def create
#timesheet = Timesheet.create(timesheet_params)
redirect_to new_timesheet_path
end
def edit
#timesheet=Timesheet.find(params[:id])
end
def update
#timesheet = Timesheet.find(params[:id])
#timesheet.update_attributes(timesheet_params)
redirect_to student_table_path
end
user.rb model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :timesheets
end
Timesheet.rb model
class Timesheet < ActiveRecord::Base
belongs_to :user
validates :user_id, :presence => true
end
Thanks in advance.
It will stay null because you are not using it in the timesheetsController, your create action should be like this :
def create
#timesheet = current_user.timesheets.build(timesheet_params)
redirect_to new_timesheet_path
end
You have to use that build method to reference the current_user, so the timesheet will have the current_user in the user_id field.

multi-tenant rails app user-account relationship issue

My application main gems: rails 4.1.1, pg, acts_as_tenant, devise and activeadmin.
Application description: saas multi-tenant app with subdomains (not using postgresql schemas).
I have 2 models: Account and User with:
account belongs_to :owner a class_name for User
user belongs_to :account and also acts_as_tenant(:account)
Note: if using acts_as_tenant, I am not sure I need to declare belongs_to?
On public domain (www.myapp.dev) user can create a new account and choose own subdomain. This user has the status of ‘owner’ for this particular subdomain/account. After account creation, user is redirected to custom subdomain for sign in. Once signed in, owner can create/invite other users to join his account.
On account creation, owner_id is correctly saved to account record. In ActiveAdmin I can filter my accounts by owners.
The problem is the account_id is not saved in the user record. Also in ActiveAdmin, when I try to edit the users records to add the account, the data is not saved…
I am learning Rails so please be easy :)
My Account model :
class Account < ActiveRecord::Base
RESTRICTED_SUBDOMAINS = %w(www)
belongs_to :owner, class_name: 'User'
validates :owner, presence: true
validates :name, presence: true
validates :subdomain, presence: true,
uniqueness: { case_sensitive: false },
format: { with: /\A[\w\-]+\Z/i, message: 'contains invalid characters' },
exclusion: { in: RESTRICTED_SUBDOMAINS, message: 'restricted' }
accepts_nested_attributes_for :owner
before_validation :downcase_subdomain
def self.current_id=(id)
Thread.current[:account_id] = id
end
def self.current_id
Thread.current[:account_id]
end
private
def downcase_subdomain
self.subdomain = subdomain.try(:downcase)
end
end
My User model :
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :account
acts_as_tenant(:account)
validates :lastname, presence: true, allow_nil: false
validates :firstname, presence: true, allow_nil: false
validates :password, presence: true, allow_nil: false
validates :email, presence: true, allow_nil: false #uniqueness: true,
def to_s
"#{firstname} #{lastname} (#{email})"
end
end
Application Controller :
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_filter :authenticate_user! #, :set_mailer_host
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:accept_invitation).concat([:firstname])
end
private
def current_account
#current_account ||= Account.find_by(subdomain: request.subdomain)
end
helper_method :current_account
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
def after_invite_path_for(resource)
users_path
end
end
Account controller :
class AccountsController < ApplicationController
skip_before_filter :authenticate_user!, only: [:new, :create]
def new
#account = Account.new
#account.build_owner
end
def create
#account = Account.new(account_params)
if #account.valid?
#account.save
redirect_to new_user_session_url(subdomain: #account.subdomain)
else
render action: 'new'
end
end
private
def account_params
params.require(:account).permit(:subdomain, :name, owner_attributes: [:lastname, :firstname, :email, :password, :password_confirmation, :account_id])
end
end
User controller :
class UsersController < ApplicationController
def index
#users = User.all
end
end
And my db schema :
ActiveRecord::Schema.define(version: 20140619202210) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "accounts", force: true do |t|
t.string "name"
t.string "subdomain"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "owner_id"
end
create_table "active_admin_comments", force: true do |t|
t.string "namespace"
t.text "body"
t.string "resource_id", null: false
t.string "resource_type", null: false
t.integer "author_id"
t.string "author_type"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "active_admin_comments", ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree
add_index "active_admin_comments", ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree
add_index "active_admin_comments", ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree
create_table "admin_users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "admin_users", ["email"], name: "index_admin_users_on_email", unique: true, using: :btree
add_index "admin_users", ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true, using: :btree
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "account_id"
t.string "lastname"
t.string "firstname"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
end
Try:
class Account < ActiveRecord::Base
has_one :account
...
class User < ActiveRecord::Base
acts_as_tenant(:account)
...
Without belongs_to for User model.

Resources