From 5e21a4b9156d6e8ae6b53f2f3f95b5247dad399a Mon Sep 17 00:00:00 2001 From: dracco1993 Date: Tue, 7 Jan 2020 02:09:29 -0500 Subject: [PATCH] 'working' WIP on API user auth --- Gemfile | 1 + app/controllers/application_controller.rb | 38 ++++++++++++++++ app/controllers/dashboards_controller.rb | 7 --- app/controllers/sessions_controller.rb | 13 ++++++ app/controllers/users_controller.rb | 12 +----- app/models/user.rb | 14 ++++++ app/views/dashboards/show.html.erb | 13 ------ .../devise/registrations/create.json.jbuilder | 3 ++ .../devise/sessions/create.json.jbuilder | 3 ++ app/views/users/_form.html.erb | 29 ------------- app/views/users/_user.json.jbuilder | 1 + app/views/users/confirmations/new.html.erb | 16 ------- app/views/users/edit.html.erb | 6 --- app/views/users/index.html.erb | 31 ------------- .../mailer/confirmation_instructions.html.erb | 5 --- app/views/users/mailer/email_changed.html.erb | 7 --- .../users/mailer/password_change.html.erb | 3 -- .../reset_password_instructions.html.erb | 8 ---- .../users/mailer/unlock_instructions.html.erb | 7 --- app/views/users/new.html.erb | 5 --- app/views/users/passwords/edit.html.erb | 25 ----------- app/views/users/passwords/new.html.erb | 16 ------- app/views/users/registrations/edit.html.erb | 43 ------------------- app/views/users/registrations/new.html.erb | 29 ------------- app/views/users/sessions/new.html.erb | 26 ----------- .../users/shared/_error_messages.html.erb | 15 ------- app/views/users/shared/_links.html.erb | 25 ----------- app/views/users/show.html.erb | 6 --- app/views/users/show.json.jbuilder | 4 +- app/views/users/unlocks/new.html.erb | 16 ------- config/initializers/jbuilder.rb | 1 + config/routes.rb | 26 +++-------- 32 files changed, 84 insertions(+), 370 deletions(-) delete mode 100644 app/controllers/dashboards_controller.rb create mode 100644 app/controllers/sessions_controller.rb delete mode 100644 app/views/dashboards/show.html.erb create mode 100644 app/views/devise/registrations/create.json.jbuilder create mode 100644 app/views/devise/sessions/create.json.jbuilder delete mode 100644 app/views/users/_form.html.erb delete mode 100644 app/views/users/confirmations/new.html.erb delete mode 100644 app/views/users/edit.html.erb delete mode 100644 app/views/users/index.html.erb delete mode 100644 app/views/users/mailer/confirmation_instructions.html.erb delete mode 100644 app/views/users/mailer/email_changed.html.erb delete mode 100644 app/views/users/mailer/password_change.html.erb delete mode 100644 app/views/users/mailer/reset_password_instructions.html.erb delete mode 100644 app/views/users/mailer/unlock_instructions.html.erb delete mode 100644 app/views/users/new.html.erb delete mode 100644 app/views/users/passwords/edit.html.erb delete mode 100644 app/views/users/passwords/new.html.erb delete mode 100644 app/views/users/registrations/edit.html.erb delete mode 100644 app/views/users/registrations/new.html.erb delete mode 100644 app/views/users/sessions/new.html.erb delete mode 100644 app/views/users/shared/_error_messages.html.erb delete mode 100644 app/views/users/shared/_links.html.erb delete mode 100644 app/views/users/show.html.erb delete mode 100644 app/views/users/unlocks/new.html.erb create mode 100644 config/initializers/jbuilder.rb diff --git a/Gemfile b/Gemfile index e47b57e..ffffa7f 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem 'jbuilder', '~> 2.7' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' gem 'devise' +# gem 'jwt' # Use Active Storage variant # gem 'image_processing', '~> 1.2' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..fecd132 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,40 @@ +# frozen_string_literal: true + 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: :null_session + skip_before_action :verify_authenticity_token + + respond_to :json + + before_action :authenticate_user + + private + + def authenticate_user!(options = {}) + head :unauthorized unless signed_in? + end + + def current_user + @current_user ||= super || User.find(@current_user_id) + end + + def signed_in? + @current_user_id.present? + end + + def authenticate_user + if request.headers['Authorization'].present? + authenticate_or_request_with_http_token do |token| + begin + jwt_payload = JWT.decode(token, Rails.application.secrets.secret_key_base).first + + @current_user_id = jwt_payload['id'] + rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError + head :unauthorized + end + end + end + end end diff --git a/app/controllers/dashboards_controller.rb b/app/controllers/dashboards_controller.rb deleted file mode 100644 index fe67c20..0000000 --- a/app/controllers/dashboards_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class DashboardsController < ApplicationController - def show - render "dashboards/show", formats: :html - end -end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..7ec0a8e --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class SessionsController < Devise::SessionsController + def create + user = User.find_by_email(sign_in_params[:email]) + + if user && user.valid_password?(sign_in_params[:password]) + @current_user = user + else + render json: { errors: { 'email or password' => ['is invalid'] } }, status: :unprocessable_entity + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fb180bc..8a0284d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,5 @@ class UsersController < ApplicationController - before_action :set_user, only: [:show, :edit, :update, :destroy] + # before_action :set_user, only: [:show, :edit, :update, :destroy] before_action :authenticate_user! # GET /users @@ -29,10 +29,8 @@ def create respond_to do |format| if @user.save - format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else - format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end @@ -43,10 +41,8 @@ def create def update respond_to do |format| if @user.update(user_params) - format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else - format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end @@ -57,7 +53,6 @@ def update def destroy @user.destroy respond_to do |format| - format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end @@ -71,11 +66,8 @@ def set_user # Never trust parameters from the scary internet, only allow the white list through. def user_params params.require(:user).permit( - :username, :email, - :password, - :salt, - :encrypted_password + :password ) end end diff --git a/app/models/user.rb b/app/models/user.rb index 91fcdae..29ba04e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,4 +3,18 @@ class User < ApplicationRecord devise :database_authenticatable, :lockable, :recoverable, :rememberable, :validatable, :trackable + + validates :email, + presence: true, + uniqueness: true, + format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i } + + def generate_jwt + JWT.encode({ + id: id, + exp: 7.days.from_now.to_i + }, + Rails.application.secrets.secret_key_base + ) + end end diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb deleted file mode 100644 index 3a2271a..0000000 --- a/app/views/dashboards/show.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -<%= javascript_pack_tag 'main_container' %> - -<%= content_tag :div, - nil, - id: 'main-container', - class: 'main-container', - data: { environment: Rails.env, - isSignedIn: user_signed_in?, - username: (user_signed_in? ? current_user.email : ""), - notifications: flash.map {|alert| { - type: alert[0], - message: alert[1] - }}}.to_json %> diff --git a/app/views/devise/registrations/create.json.jbuilder b/app/views/devise/registrations/create.json.jbuilder new file mode 100644 index 0000000..c5ec3e5 --- /dev/null +++ b/app/views/devise/registrations/create.json.jbuilder @@ -0,0 +1,3 @@ +json.user do |json| + json.partial! 'users/user', user: current_user +end diff --git a/app/views/devise/sessions/create.json.jbuilder b/app/views/devise/sessions/create.json.jbuilder new file mode 100644 index 0000000..c5ec3e5 --- /dev/null +++ b/app/views/devise/sessions/create.json.jbuilder @@ -0,0 +1,3 @@ +json.user do |json| + json.partial! 'users/user', user: current_user +end diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb deleted file mode 100644 index 2c6fb83..0000000 --- a/app/views/users/_form.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -<%= form_with(model: user, local: true) do |form| %> - <% if user.errors.any? %> -
-

<%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:

- - -
- <% end %> - -
-

Basic Information

-
-
- <%= form.text_field :email, class: "form-control", placeholder: "Enter email", autofocus: true %> -
-
- <%= form.password_field :password, class: "form-control", placeholder: "Enter password", autofocus: true %> -
-
-
- -
- <%= form.submit %> -
-<% end %> diff --git a/app/views/users/_user.json.jbuilder b/app/views/users/_user.json.jbuilder index 2f081bb..dd821c5 100644 --- a/app/views/users/_user.json.jbuilder +++ b/app/views/users/_user.json.jbuilder @@ -1,2 +1,3 @@ json.extract! user, :id, :created_at, :updated_at json.url user_url(user, format: :json) +json.token user.generate_jwt diff --git a/app/views/users/confirmations/new.html.erb b/app/views/users/confirmations/new.html.erb deleted file mode 100644 index a5e860f..0000000 --- a/app/views/users/confirmations/new.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -

Resend confirmation instructions

- -<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> -
- -
- <%= f.submit "Resend confirmation instructions" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb deleted file mode 100644 index 1a5c2a6..0000000 --- a/app/views/users/edit.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -

Editing User

- -<%= render 'form', user: @user %> - -<%= link_to 'Show', @user %> | -<%= link_to 'Back', users_path %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb deleted file mode 100644 index 84e4e9c..0000000 --- a/app/views/users/index.html.erb +++ /dev/null @@ -1,31 +0,0 @@ -

Users

- - - - - - - - - - - - - - <% @users.each do |user| %> - - - - - - - - - - <% end %> - -
IDEmailDiscord NameDiscord ID
<%= user.id %><%= user.email %>{discord_name}{discord_id}<%= link_to 'Show', user %><%= link_to 'Edit', edit_user_path(user) %><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %>
- -
- -<%= link_to 'New User', new_user_path %> diff --git a/app/views/users/mailer/confirmation_instructions.html.erb b/app/views/users/mailer/confirmation_instructions.html.erb deleted file mode 100644 index dc55f64..0000000 --- a/app/views/users/mailer/confirmation_instructions.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -

Welcome <%= @email %>!

- -

You can confirm your account email through the link below:

- -

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/users/mailer/email_changed.html.erb b/app/views/users/mailer/email_changed.html.erb deleted file mode 100644 index 32f4ba8..0000000 --- a/app/views/users/mailer/email_changed.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -

Hello <%= @email %>!

- -<% if @resource.try(:unconfirmed_email?) %> -

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

-<% else %> -

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

-<% end %> diff --git a/app/views/users/mailer/password_change.html.erb b/app/views/users/mailer/password_change.html.erb deleted file mode 100644 index b41daf4..0000000 --- a/app/views/users/mailer/password_change.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -

Hello <%= @resource.email %>!

- -

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/users/mailer/reset_password_instructions.html.erb b/app/views/users/mailer/reset_password_instructions.html.erb deleted file mode 100644 index f667dc1..0000000 --- a/app/views/users/mailer/reset_password_instructions.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -

Hello <%= @resource.email %>!

- -

Someone has requested a link to change your password. You can do this through the link below.

- -

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

- -

If you didn't request this, please ignore this email.

-

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/users/mailer/unlock_instructions.html.erb b/app/views/users/mailer/unlock_instructions.html.erb deleted file mode 100644 index 41e148b..0000000 --- a/app/views/users/mailer/unlock_instructions.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -

Hello <%= @resource.email %>!

- -

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

- -

Click the link below to unlock your account:

- -

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb deleted file mode 100644 index 844c39b..0000000 --- a/app/views/users/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -

New User

- -<%= render 'form', user: @user %> - -<%= link_to 'Back', users_path %> diff --git a/app/views/users/passwords/edit.html.erb b/app/views/users/passwords/edit.html.erb deleted file mode 100644 index 69718fa..0000000 --- a/app/views/users/passwords/edit.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -

Change your password

- -<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - <%= f.hidden_field :reset_password_token %> - -
- <%= f.label :password, "New password" %>
- <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum)
- <% end %> - <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> -
- -
- <%= f.label :password_confirmation, "Confirm new password" %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.submit "Change my password" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/app/views/users/passwords/new.html.erb b/app/views/users/passwords/new.html.erb deleted file mode 100644 index e231a7d..0000000 --- a/app/views/users/passwords/new.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -

Forgot your password?

- -<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.submit "Send me reset password instructions" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb deleted file mode 100644 index 38d95b8..0000000 --- a/app/views/users/registrations/edit.html.erb +++ /dev/null @@ -1,43 +0,0 @@ -

Edit <%= resource_name.to_s.humanize %>

- -<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- - <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> -
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
- <% end %> - -
- <%= f.label :password %> (leave blank if you don't want to change it)
- <%= f.password_field :password, autocomplete: "new-password" %> - <% if @minimum_password_length %> -
- <%= @minimum_password_length %> characters minimum - <% end %> -
- -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.label :current_password %> (we need your current password to confirm your changes)
- <%= f.password_field :current_password, autocomplete: "current-password" %> -
- -
- <%= f.submit "Update" %> -
-<% end %> - -

Cancel my account

- -

Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

- -<%= link_to "Back", :back %> diff --git a/app/views/users/registrations/new.html.erb b/app/views/users/registrations/new.html.erb deleted file mode 100644 index d5ac672..0000000 --- a/app/views/users/registrations/new.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -

Sign up

- -<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %> - <% if @minimum_password_length %> - (<%= @minimum_password_length %> characters minimum) - <% end %>
- <%= f.password_field :password, autocomplete: "new-password" %> -
- -
- <%= f.label :password_confirmation %>
- <%= f.password_field :password_confirmation, autocomplete: "new-password" %> -
- -
- <%= f.submit "Sign up" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/app/views/users/sessions/new.html.erb b/app/views/users/sessions/new.html.erb deleted file mode 100644 index 8c4864b..0000000 --- a/app/views/users/sessions/new.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -

Log in

- -<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.label :password %>
- <%= f.password_field :password, autocomplete: "current-password" %> -
- - <% if devise_mapping.rememberable? %> -
- <%= f.check_box :remember_me %> - <%= f.label :remember_me %> -
- <% end %> - -
- <%= f.submit "Log in" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/app/views/users/shared/_error_messages.html.erb b/app/views/users/shared/_error_messages.html.erb deleted file mode 100644 index ba7ab88..0000000 --- a/app/views/users/shared/_error_messages.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -<% if resource.errors.any? %> -
-

- <%= I18n.t("errors.messages.not_saved", - count: resource.errors.count, - resource: resource.class.model_name.human.downcase) - %> -

- -
-<% end %> diff --git a/app/views/users/shared/_links.html.erb b/app/views/users/shared/_links.html.erb deleted file mode 100644 index 084af70..0000000 --- a/app/views/users/shared/_links.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -<%- if controller_name != 'sessions' %> - <%= link_to "Log in", new_session_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.registerable? && controller_name != 'registrations' %> - <%= link_to "Sign up", new_registration_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> - <%= link_to "Forgot your password?", new_password_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> - <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> - <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
-<% end %> - -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
- <% end %> -<% end %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb deleted file mode 100644 index a14c96b..0000000 --- a/app/views/users/show.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -

<%= notice %>

- -<%= render 'form', user: @user %> - -<%= link_to 'Edit', edit_user_path(@user) %> | -<%= link_to 'Back', users_path %> diff --git a/app/views/users/show.json.jbuilder b/app/views/users/show.json.jbuilder index ff40bb9..c5ec3e5 100644 --- a/app/views/users/show.json.jbuilder +++ b/app/views/users/show.json.jbuilder @@ -1 +1,3 @@ -json.partial! "users/user", user: @user +json.user do |json| + json.partial! 'users/user', user: current_user +end diff --git a/app/views/users/unlocks/new.html.erb b/app/views/users/unlocks/new.html.erb deleted file mode 100644 index 54b04a4..0000000 --- a/app/views/users/unlocks/new.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -

Resend unlock instructions

- -<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> - <%= render "devise/shared/error_messages", resource: resource %> - -
- <%= f.label :email %>
- <%= f.email_field :email, autofocus: true, autocomplete: "email" %> -
- -
- <%= f.submit "Resend unlock instructions" %> -
-<% end %> - -<%= render "users/shared/links" %> diff --git a/config/initializers/jbuilder.rb b/config/initializers/jbuilder.rb new file mode 100644 index 0000000..418045d --- /dev/null +++ b/config/initializers/jbuilder.rb @@ -0,0 +1 @@ +Jbuilder.key_format camelize: :lower diff --git a/config/routes.rb b/config/routes.rb index e2771f3..61647f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,24 +1,8 @@ Rails.application.routes.draw do - devise_for :users, controllers: { - sessions: 'users/sessions' - } + scope :api, defaults: { format: :json } do + devise_for :users, controllers: { sessions: :sessions }, + path_names: { sign_in: :login } + end - resources :users - - # resources :users, only: %i[create update] do - # collection do - # get :list - # get :permissions - # get :roles - # end - # end - - # The following row is effectively a wildcard match for - # the React single-page application. Since this will - # match almost everything, this line needs to be at the - # bottom, with the routes for the various JSON endpoints - # above. - get "/:id", to: "dashboards#show", as: :dashboard - - root to: "dashboards#show" + resource :user, only: [:show, :update] end