Страницы

Поиск по вопросам

Показаны сообщения с ярлыком devise. Показать все сообщения
Показаны сообщения с ярлыком devise. Показать все сообщения

воскресенье, 15 марта 2020 г.

Почему перестал работать выход с сайта Rails (devise)?

#ruby_on_rails #devise


Здравствуйте, скажите пожалуйста как исправить ошибку. При выходе с сайта выкидывает
сообщение - Couldn't find Doctor with 'id'=sign_out и указывает на строку 

@doctor = Doctor.find(params[:id])


в контроллере 

class DoctorsController < ApplicationController
  before_action :set_doctor, only: [:show, :edit, :update, :destroy]
  def index
    @doctor = Doctor.all
  end

  def show
    @length =  User.where(doctor_id: params[:id]).size
  end

  private 

  def set_doctor
    @doctor = Doctor.find(params[:id])
  end
end


Раньше с сайта выходил, но теперь такая возможность пропала. Использую гем Devise

UPDATE
routes.rb

  Rails.application.routes.draw do
get 'contact/index'

resources :news
resources :prices
get 'healthy/index'
get 'about/index'

resources :users
resources :doctors

devise_for :doctors, :controllers => { registrations: 'registrations' }
root 'users#main'


end

Ссылка на выход из сессии

<%= link_to 'Вийти', destroy_doctor_session_path %>



    


Ответы

Ответ 1



Возьмите ваш маршрут GET /doctors/sign_out и смотрите по списку сверху вниз, по какому правилу он совпадёт первым. И совпадает он с GET /doctors/:id, собранным из resources :doctors. Почему не GET /doctors/sign_out (из devise_for), если он подходит лучше? А неважно, он первый подходящий. Он и используется. Решение простое — поместите вызов devise_for выше resources :doctors, тем самым изменив порядок этих маршрутов в списке так, чтобы первым совпадал GET /doctors/sign_out. Альтернативно, повышенной сложности: не меняя порядок, установите к параметру :id в маршруте требование состоять только из цифр, аналогично примеру из документации: get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ } Это единственный выход, когда маршруты сильно перекрываются. Это не ваш случай, в вашем случае достаточно изменить порядок. Но только первого приёма для разруливания таких проблем в общем случае недостаточно.

понедельник, 24 февраля 2020 г.

Rails не видит введенных в форме данных

#ruby_on_rails #devise


У меня сразу несколько вопросов:


Добавил в Devise атрибуты name (string) и phone (integer). При отправке формы выдает
ошибки "Name can't be blank" и "Phone can't be blank", хотя поля заполнены.
Как заменить лэйблы (например, перевести на русский)? Когда меняю :name на :ФИО -
все нормально, но когда меняю остальные - выдает ошибку.


Сама форма:

<%= form_for(resource, :as => resource_name, 
                         :url => registration_path(resource_name), 
                         :html => { :role => 'form'}) do |f| %>
    

Регистрация

<%= devise_error_messages! %>
<%= f.label :name %>
<%= f.text_field :name, autofocus: true %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :phone %>
<%= f.text_field :phone %>
<%= f.label :password %> <%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit 'Sign up', :class => 'button right' %> <% end %>


Ответы

Ответ 1



Добавил в Devise атрибуты name (string) и phone (integer). При отправке формы выдает ошибки "Name can't be blank" и "Phone can't be blank", хотя поля заполнены. Скорее всего, вы наступили на те же грабли, что и я когда-то, забыв про Strong Parameters. Как заменить лэйблы (например перевести на русский)? Когда меняю :name на :ФИО - все нормально, но когда меняю остальные - выдает ошибку. Разумеется, выдаёт. Надо 'ФИО', а не :ФИО и так далее. С двоеточия начинаются ключи, а вам надо задать конкретное строковое значение. Однако, если вы собираетесь именно переводить, то лучше сразу начинать использовать API интернационализации.

пятница, 24 января 2020 г.

Почему хелпер “devise_controller?” возвращает false для моего кода?

#ruby_on_rails #devise


Почему хелпер devise_controller? возвращает false для следующего кода:

class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
layout :resource
protected
def resource
  if devise_controller?
    #"admin"
    puts "admin"
  else
    #"application"
    puts "app"
  end
end
end


при этом в строке браузера переход на "http://127.0.0.1:3000/admin/review"

файл routes.rb:

 Rails.application.routes.draw do
  get 'admin/show'
  get 'admin/upload'
  get 'admin/review'
  get 'welcome/index'
  get 'welcome/portfolio'
  get 'welcome/about'
  get 'welcome/contact'
  get 'welcome/blog'
  get 'welcome/review'
 end


при этом для остальных маршрутов   get 'admin/show'и get 'admin/upload' хелпер выдает
true ....

Обновление

В общем проблему решил так. Добавил в ApplicationController фильтр

before_filter :my_filter, unless: :devise_controller?

 def my_filter
    if params['controller'] == 'admin'
      render layout: "admin"
    end
 end


Но вопрос по существу остается - почему через layout тоже самое со сбоями работает?
    


Ответы

Ответ 1



Метод devise_controller? лишь проверяет унаследован ли ваш класс от класса DeviseController def devise_controller? is_a?(::DeviseController) end У вас контроллер наследуется от ActionController::Base, поэтому условие не всегда может быть выполнено. Для проверки факта аутентификации лучше воспользоваться хелперами Devise: user_signed_in? и current_user. Если вам нужно сменить layout не зависимо от того, аутентифицирован пользователь или нет - просто меняйте его в render (как вы собственно и поступили в Обновлении).

воскресенье, 15 декабря 2019 г.

Rails + Devise + Angular2: аутентификация

#ruby_on_rails #безопасность #devise #csrf #angular2


У меня просто огромное количество вопросов... Но перед этим нужно описать, что собственно
происходит.

Преамбула

Я пытаюсь научить Angular2 авторизироваться в Rails 5 beta 3 приложении. При этом
Rails работает в режиме API. Как я понимаю, для того, чтобы Devise работал в режиме
API, нужен gem devise_token_auth. Гем я установил и настроил, но вот после этого начались
проблемы:

Unpermitted parameters: user, session

Первое, что я попробовал сделать после настройки, послать простой POST запрос и посмотреть,
что произойдёт. А произошло следующее: 

Processing by DeviseTokenAuth::SessionsController#create as *json*
  Parameters: {"user"=>{"username"=>"demo", "password"=>"[FILTERED]"}, "session"=>{"user"=>{"username"=>"demo",
"password"=>"[FILTERED]"}}}
Unpermitted parameters: user, session
  Rendered devise_token_auth/sessions/create.json (0.2ms)
Completed 401 Unauthorized in 18ms (Views: 3.2ms | ActiveRecord: 0.0ms)


И вот с этого момента я немного не понимаю, с каких это пор я должен прописывать
permit для параметров, которые ActionController::ParamsWrapper создаёт динамически
в качестве обёртки? Более того:

devise_parameter_sanitizer.permit(:sign_in) do |user_params|
  user_params.permit(:user, :session)
end


ничего не меняет. Кто-нибудь сталкивался с подобным ранее?

Authentication header

Покопавшись в документации в поисках решения проблемы я нашёл другую:


  The authentication information should be included by the client in the headers
of each request. The headers follow the RFC 6750 Bearer Token format


Т.е. в заголовке запроса должны быть следующие данные:

"access-token": "wwwww",
"token-type":   "Bearer",
"client":       "xxxxx",
"expiry":       "yyyyy",
"uid":          "zzzzz"


В AngularJS они генерировались модулем ng-token-auth, но как мне сгенерировать их
посредством Angular2, у которого данного модуля нет?



Подводя промежуточный итог (потому что чувствую я, после этих проблем я наткнусь
на их ещё большее количество), вот список моих вопросов:


Как исправить unpermitted parameters: user, session в devise_token_auth?
Как сгенерировать access-token?
Как высчитать expiry и есть ли "rails-way" для времени жизни токена?
Что следует писать в client? Раз уж этот идентификатор позволяет пользователю одновременно
авторизироваться с нескольких устройств, то как он должен выглядить? Что туда следует
записать?





Какие данные следует возвращать клиенту после авторизации (помимо 200)?
Какие ещё подводные камни могут меня ждать при попытке авторизироваться через Rails API?




UPD

Вот запрос, который я шлю:

private _user_session_url = 'auth/sign_in';

login (username: string, password: string) : Observable {
    let body = JSON.stringify({ username: username, password: password });
    // TODO: generate header data.
    let headers = new Headers({
        'Content-Type': 'application/json',
        'accept': 'json'
    });
    let options = new RequestOptions({ headers: headers});

    return this.http.post(this._user_session_url, body, options)
        .map((data: any) => data.json())
        .catch(this.handleError)
}


Как видите, session в теле запроса нет. Его создаёт ActionController::ParamsWrapper

В подтверждение привожу Request Payload:

{"username":"demo","password":"123"}    

    


Ответы

Ответ 1



Согласно краткой документации к devise_token_auth (DTA) POST /sign_in принимает только параметры email и password прямо в корне. То есть, параметры должны выглядеть так: { "email": "foo@example.com", "password": "bar" } Никакой вложенности. Не надо дополнительно настраивать санитайзер и придумывать ненужные ключи. А теперь ответ разом на все оставшиеся вопросы: Token-Type, Access-Token, Client и Expiry. В AngularJS они генерировались модулем ng-token-auth Нет, они генерируются сервером, клиент ничем умным не занимается. Хранятся они в БД (в частично искажённом, но проверяемом виде, как пароли), а обмен ими происходит с помощью заголовков. /sign_in возвращает все интересующие заголовки. Все эти значения вы просто будете передавать туда-сюда (и обновлять, ведь по умолчанию после каждого отдельного запроса токен меняется). Кроме, разве что, Expiry, значением которого можно следить, "когда токен отвалится и потребует обновления", что можно использовать для выкидывания на форму входа. Пользоваться DTA без ng-token-auth можно, но прежде чем за это браться, стоит прочесть документацию к используемым методам. Я пользовался этим гемом ранее и не рекомендую его для случаев, где важна настраиваемость принимаемых и отдаваемых этой системой данных, потому что расширяемость гема близка к нулю.

понедельник, 8 июля 2019 г.

Rails SQLite3::ConstraintException: UNIQUE constraint failed: index 'index_users_on_reset_password_token'

Делаю регистрацию с devise
Добавил поле username для User. Для авторизации использую email + pwd.
class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys:[:username]) end end
class User < ApplicationRecord validates :username, presence: true devise :database_authenticatable, :registerable, :rememberable, :trackable, :validatable end
ActiveRecord::Schema.define(version: 20170228132910) do create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "username", default: "", null: false 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.index ["email"], name: "index_users_on_email", unique: true t.index [nil], name: "index_users_on_reset_password_token", unique: true end
end
Первый пользователь зарагистрировался нормально, на втором получаю ошибку
ActiveRecord::RecordNotUnique in Devise::RegistrationsController#create SQLite3::ConstraintException: UNIQUE constraint failed: index 'index_users_on_reset_password_token': INSERT INTO "users" ("email", "encrypted_password", "username", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)
Что там ни так с index_users_on_reset_password_token ?
Добавил миграцию
class DeviseCreateUsers < ActiveRecord::Migration[5.0] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: ""
# Registrable t.string :username, null: false, default: ""
## Rememberable t.datetime :remember_created_at
## Trackable 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.timestamps null: false end
add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true end end


Ответ

Об этом сообщали в баг-трекер Rails (#27782) и это проблема в SQLite:
SQLite has a (mis-)feature that double-quoted names that cannot be resolved to a table or column name are treated as strings. This was a very early design decision, made long before SQLite went viral and found itself running in everything device on the planet, and was intended to make SQLite more compatible with MySQL, which at the time was the most widely deployed database engine in the world. I regret that choice now, but I cannot undo it without breaking backwards compatibility. — D. Richard Hipp, создатель SQLite
По-русски:
У SQLite есть (упоротая) особенность: идентификаторы в двойных кавычках, которые не удалось разрешить в название таблицы или столбца, воспринимаются как строки. Это был осознанный выбор, сделанный задолго до того, как SQLite "выстрелил" и оказался в самых разных устройствах по всему миру, и сделан он был для обеспечения лучшей совместимости с MySQL, в то время самой популярной СУБД в мире. Сейчас я об этом выборе жалею, но избавиться от него, не ломая обратной совместимости, уже не могу.
В Rails вроде полны решимости это поправить на своей стороне, но получится ли у них, вопрос открытый. Нельзя просто так взять и убрать кавычки у колонок в определении индекса. Это исправит именно этот баг, но добавит ограничений на то, какими могут быть имена колонок, а то и просто добавит новых багов.

Вы в миграции добавили уникальный индекс на колонку, которой не существует, reset_password_token. Из-за "особенности" выше в результате получился функциональный (вычисляемый) индекс по константной строке ("reset_password_token"). В результате вы не можете иметь в таблице под этим индексом больше одной строчки, потому что вставка ещё одной потребует добавления в уникальный индекс значения, которое там уже есть. Красота!
Решение сейчас — сделать новую миграцию, в которой стереть этот индекс
remove_index :users, name: :index_users_on_reset_password_token

вторник, 4 июня 2019 г.

Почему перестал работать выход с сайта Rails (devise)?

Здравствуйте, скажите пожалуйста как исправить ошибку. При выходе с сайта выкидывает сообщение - Couldn't find Doctor with 'id'=sign_out и указывает на строку
@doctor = Doctor.find(params[:id])
в контроллере
class DoctorsController < ApplicationController before_action :set_doctor, only: [:show, :edit, :update, :destroy] def index @doctor = Doctor.all end
def show @length = User.where(doctor_id: params[:id]).size end
private
def set_doctor @doctor = Doctor.find(params[:id]) end end
Раньше с сайта выходил, но теперь такая возможность пропала. Использую гем Devise
UPDATE routes.rb
Rails.application.routes.draw do get 'contact/index'
resources :news resources :prices get 'healthy/index' get 'about/index'
resources :users resources :doctors
devise_for :doctors, :controllers => { registrations: 'registrations' } root 'users#main'
end
Ссылка на выход из сессии
<%= link_to 'Вийти', destroy_doctor_session_path %>


Ответ

Возьмите ваш маршрут GET /doctors/sign_out и смотрите по списку сверху вниз, по какому правилу он совпадёт первым.
И совпадает он с GET /doctors/:id, собранным из resources :doctors. Почему не GET /doctors/sign_out (из devise_for), если он подходит лучше? А неважно, он первый подходящий. Он и используется.
Решение простое — поместите вызов devise_for выше resources :doctors, тем самым изменив порядок этих маршрутов в списке так, чтобы первым совпадал GET /doctors/sign_out

Альтернативно, повышенной сложности: не меняя порядок, установите к параметру :id в маршруте требование состоять только из цифр, аналогично примеру из документации
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
Это единственный выход, когда маршруты сильно перекрываются. Это не ваш случай, в вашем случае достаточно изменить порядок. Но только первого приёма для разруливания таких проблем в общем случае недостаточно.

понедельник, 6 мая 2019 г.

Rails не видит введенных в форме данных

У меня сразу несколько вопросов:
Добавил в Devise атрибуты name (string) и phone (integer). При отправке формы выдает ошибки "Name can't be blank" и "Phone can't be blank", хотя поля заполнены. Как заменить лэйблы (например, перевести на русский)? Когда меняю :name на :ФИО - все нормально, но когда меняю остальные - выдает ошибку.
Сама форма:
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :role => 'form'}) do |f| %>

Регистрация

<%= devise_error_messages! %>
<%= f.label :name %>
<%= f.text_field :name, autofocus: true %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :phone %>
<%= f.text_field :phone %>
<%= f.label :password %> <%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit 'Sign up', :class => 'button right' %> <% end %>


Ответ

Добавил в Devise атрибуты name (string) и phone (integer). При отправке формы выдает ошибки "Name can't be blank" и "Phone can't be blank", хотя поля заполнены.
Скорее всего, вы наступили на те же грабли, что и я когда-то, забыв про Strong Parameters.
Как заменить лэйблы (например перевести на русский)? Когда меняю :name на :ФИО - все нормально, но когда меняю остальные - выдает ошибку.
Разумеется, выдаёт. Надо 'ФИО', а не :ФИО и так далее. С двоеточия начинаются ключи, а вам надо задать конкретное строковое значение. Однако, если вы собираетесь именно переводить, то лучше сразу начинать использовать API интернационализации

суббота, 27 октября 2018 г.

Rails + Devise + Angular2: аутентификация

У меня просто огромное количество вопросов... Но перед этим нужно описать, что собственно происходит.
Преамбула
Я пытаюсь научить Angular2 авторизироваться в Rails 5 beta 3 приложении. При этом Rails работает в режиме API. Как я понимаю, для того, чтобы Devise работал в режиме API, нужен gem devise_token_auth. Гем я установил и настроил, но вот после этого начались проблемы:
Unpermitted parameters: user, session
Первое, что я попробовал сделать после настройки, послать простой POST запрос и посмотреть, что произойдёт. А произошло следующее:
Processing by DeviseTokenAuth::SessionsController#create as *json* Parameters: {"user"=>{"username"=>"demo", "password"=>"[FILTERED]"}, "session"=>{"user"=>{"username"=>"demo", "password"=>"[FILTERED]"}}} Unpermitted parameters: user, session Rendered devise_token_auth/sessions/create.json (0.2ms) Completed 401 Unauthorized in 18ms (Views: 3.2ms | ActiveRecord: 0.0ms)
И вот с этого момента я немного не понимаю, с каких это пор я должен прописывать permit для параметров, которые ActionController::ParamsWrapper создаёт динамически в качестве обёртки? Более того:
devise_parameter_sanitizer.permit(:sign_in) do |user_params| user_params.permit(:user, :session) end
ничего не меняет. Кто-нибудь сталкивался с подобным ранее?
Authentication header
Покопавшись в документации в поисках решения проблемы я нашёл другую:
The authentication information should be included by the client in the headers of each request. The headers follow the RFC 6750 Bearer Token format
Т.е. в заголовке запроса должны быть следующие данные:
"access-token": "wwwww", "token-type": "Bearer", "client": "xxxxx", "expiry": "yyyyy", "uid": "zzzzz"
В AngularJS они генерировались модулем ng-token-auth, но как мне сгенерировать их посредством Angular2, у которого данного модуля нет?

Подводя промежуточный итог (потому что чувствую я, после этих проблем я наткнусь на их ещё большее количество), вот список моих вопросов:
Как исправить unpermitted parameters: user, session в devise_token_auth? Как сгенерировать access-token? Как высчитать expiry и есть ли "rails-way" для времени жизни токена? Что следует писать в client? Раз уж этот идентификатор позволяет пользователю одновременно авторизироваться с нескольких устройств, то как он должен выглядить? Что туда следует записать?

Какие данные следует возвращать клиенту после авторизации (помимо 200)? Какие ещё подводные камни могут меня ждать при попытке авторизироваться через Rails API?

UPD
Вот запрос, который я шлю:
private _user_session_url = 'auth/sign_in';
login (username: string, password: string) : Observable { let body = JSON.stringify({ username: username, password: password }); // TODO: generate header data. let headers = new Headers({ 'Content-Type': 'application/json', 'accept': 'json' }); let options = new RequestOptions({ headers: headers});
return this.http.post(this._user_session_url, body, options) .map((data: any) => data.json()) .catch(this.handleError) }
Как видите, session в теле запроса нет. Его создаёт ActionController::ParamsWrapper
В подтверждение привожу Request Payload:
{"username":"demo","password":"123"}


Ответ

Согласно краткой документации к devise_token_auth (DTA) POST /sign_in принимает только параметры email и password прямо в корне. То есть, параметры должны выглядеть так:
{ "email": "foo@example.com", "password": "bar" }
Никакой вложенности. Не надо дополнительно настраивать санитайзер и придумывать ненужные ключи.

А теперь ответ разом на все оставшиеся вопросы:
Token-Type, Access-Token, Client и Expiry
В AngularJS они генерировались модулем ng-token-auth
Нет, они генерируются сервером, клиент ничем умным не занимается. Хранятся они в БД (в частично искажённом, но проверяемом виде, как пароли), а обмен ими происходит с помощью заголовков. /sign_in возвращает все интересующие заголовки. Все эти значения вы просто будете передавать туда-сюда (и обновлять, ведь по умолчанию после каждого отдельного запроса токен меняется).
Кроме, разве что, Expiry, значением которого можно следить, "когда токен отвалится и потребует обновления", что можно использовать для выкидывания на форму входа.
Пользоваться DTA без ng-token-auth можно, но прежде чем за это браться, стоит прочесть документацию к используемым методам.

Я пользовался этим гемом ранее и не рекомендую его для случаев, где важна настраиваемость принимаемых и отдаваемых этой системой данных, потому что расширяемость гема близка к нулю.