diff --git a/app/commands/authenticate_user.rb b/app/commands/authenticate_user.rb index bfa0d71..5684892 100644 --- a/app/commands/authenticate_user.rb +++ b/app/commands/authenticate_user.rb @@ -7,7 +7,7 @@ def initialize(email, password) end def call - JsonWebToken.encode(user_id: user.id) if user + JsonWebToken.encode(payload: { user_id: user.id }, secret_key: Rails.application.secrets.secret_key_base) if user end private diff --git a/app/commands/authorize_api_request.rb b/app/commands/authorize_api_request.rb index 54b1df1..66152a7 100644 --- a/app/commands/authorize_api_request.rb +++ b/app/commands/authorize_api_request.rb @@ -18,7 +18,7 @@ def user end def decoded_auth_token - @decoded_auth_token ||= JsonWebToken.decode(http_auth_header) + @decoded_auth_token ||= JsonWebToken.decode(token: http_auth_header, secret_key: Rails.application.secrets.secret_key_base) end def http_auth_header diff --git a/app/commands/generate_verify_token.rb b/app/commands/generate_verify_token.rb new file mode 100644 index 0000000..a049c0f --- /dev/null +++ b/app/commands/generate_verify_token.rb @@ -0,0 +1,11 @@ +class GenerateVerifyToken + prepend SimpleCommand + + def initialize(user_id) + @user_id = user_id + end + + def call + JsonWebToken.encode(payload: { user_id: @user_id }, secret_key: Rails.application.secrets.secret_key_email) + end +end diff --git a/app/commands/json_web_token.rb b/app/commands/json_web_token.rb index ba4e9ed..de9b844 100644 --- a/app/commands/json_web_token.rb +++ b/app/commands/json_web_token.rb @@ -1,12 +1,12 @@ class JsonWebToken class << self - def encode(payload, exp = 24.hours.from_now) + def encode(payload:, exp: 24.hours.from_now, secret_key:) payload[:exp] = exp.to_i - JWT.encode(payload, Rails.application.secrets.secret_key_base) + JWT.encode(payload, secret_key) end - def decode(token) - body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0] + def decode(token:, secret_key:) + body = JWT.decode(token, secret_key)[0] HashWithIndifferentAccess.new body rescue nil diff --git a/app/controllers/authentication_controller.rb b/app/controllers/authentication_controller.rb index b3ca520..e5e53ad 100644 --- a/app/controllers/authentication_controller.rb +++ b/app/controllers/authentication_controller.rb @@ -10,4 +10,18 @@ def authenticate render json: { error: command.errors } , status: :unauthorized end end + + def confirm_email + begin + token = params[:token] + decoded_token = JWT.decode(token, Rails.application.secrets.secret_key_email)[0] + current_user = User.find(decoded_token["user_id"].to_i) + email = current_user.email + current_user.confirmation_token = true + current_user.save + render json: { status: 200, message: "User confirmed" }.to_json + rescue JWT::DecodeError => e + render json: { status: 401, message: "Invalid token" }.to_json + end + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e04f37b..24d486f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,11 +12,17 @@ class UsersController < ApplicationController # GET /users/1 def show - @user = User.find(params[:id].to_i) - render json: @user + begin + @user = User.find(params[:id].to_i) + render json: @user + rescue ActiveRecord::RecordNotFound => e + render json: { + error: e.to_s + }, status: :not_found + end end - # POST /users + # POST /users V1 def create @user = User.new(user_params) if @user.save @@ -86,7 +92,13 @@ def destroy private def set_user - @user = User.find(params[:id]) + begin + @user = User.find(params[:id]) + rescue ActiveRecord::RecordNotFound => e + render json: { + error: e.to_s + }, status: :not_found + end end def user_params diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb new file mode 100644 index 0000000..800dcba --- /dev/null +++ b/app/controllers/v1/users_controller.rb @@ -0,0 +1,33 @@ +require "rest-client" +class V1::UsersController < ApplicationController + include ValidationsHelper + include UsersDoc + + skip_before_action :authenticate_request, only: [:create, :all] + + before_action only: [:show, :update, :destroy] do + set_user + validate_user(:id, 0) + end + + # POST /users + def create + @user = User.new(user_params) + if @user.save + @token = GenerateVerifyToken.call(@user.id) + UserMailer.with(user: @user, token: @token).verify_email.deliver_now! + render json: @token, status: :created + else + render json: @user.errors, status: :unprocessable_entity + end + end + + private + def set_user + @user = User.find(params[:id]) + end + + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..d88720e 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base default from: "from@example.com" - layout "mailer" + layout "verify_email" end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 365a73d..af399d1 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -1,9 +1,18 @@ class UserMailer < ApplicationMailer default from: "noreply@falko.com" + layout "verify_email" def recover_password_email @user = params[:user] @uri = ENV["PASSWORD_RESET_ADDRESS"].gsub(//, @user.reset_password_token) mail(to: @user.email, subject: "Falko password recovery") end + + def verify_email + @user = params[:user] + @email = @user[:email] + user_token = params[:token] + @token = user_token.result + mail to: @email, subject: "Email confirmation token" + end end diff --git a/app/views/layouts/verify_email.html.erb b/app/views/layouts/verify_email.html.erb new file mode 100644 index 0000000..62edbbf --- /dev/null +++ b/app/views/layouts/verify_email.html.erb @@ -0,0 +1,11 @@ +

Email Confirmation

+ +

+ Hi <%= @email %>, +

+ +

+ Confirm your email http://localhost:3000/verify_token/?token=<%= @token %> + + :) +

\ No newline at end of file diff --git a/app/views/layouts/verify_email.text.erb b/app/views/layouts/verify_email.text.erb new file mode 100644 index 0000000..62edbbf --- /dev/null +++ b/app/views/layouts/verify_email.text.erb @@ -0,0 +1,11 @@ +

Email Confirmation

+ +

+ Hi <%= @email %>, +

+ +

+ Confirm your email http://localhost:3000/verify_token/?token=<%= @token %> + + :) +

\ No newline at end of file diff --git a/app/views/user_mailer/verify_email.html.erb b/app/views/user_mailer/verify_email.html.erb new file mode 100644 index 0000000..62edbbf --- /dev/null +++ b/app/views/user_mailer/verify_email.html.erb @@ -0,0 +1,11 @@ +

Email Confirmation

+ +

+ Hi <%= @email %>, +

+ +

+ Confirm your email http://localhost:3000/verify_token/?token=<%= @token %> + + :) +

\ No newline at end of file diff --git a/app/views/user_mailer/verify_email.text.erb b/app/views/user_mailer/verify_email.text.erb new file mode 100644 index 0000000..62edbbf --- /dev/null +++ b/app/views/user_mailer/verify_email.text.erb @@ -0,0 +1,11 @@ +

Email Confirmation

+ +

+ Hi <%= @email %>, +

+ +

+ Confirm your email http://localhost:3000/verify_token/?token=<%= @token %> + + :) +

\ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb index cac5315..2d93214 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -3,3 +3,13 @@ # Initialize the Rails application. Rails.application.initialize! + +ActionMailer::Base.smtp_settings = { + user_name: "apikey", + password: "", + domain: "gmail.com", + address: "smtp.sendgrid.net", + port: 587, + authentication: :plain, + enable_starttls_auto: true +} diff --git a/config/environments/development.rb b/config/environments/development.rb index 8146fea..9ecd98f 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -11,6 +11,7 @@ # Show full error reports. config.consider_all_requests_local = true + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } # Enable/disable caching. By default caching is disabled. if Rails.root.join("tmp/caching-dev.txt").exist? diff --git a/config/routes.rb b/config/routes.rb index 8528c8c..dce0ed3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,8 @@ post "request_github_token", to: "users#request_github_token" post "remove_github_token", to: "users#remove_github_token" + get "verify_token", to: "authentication#confirm_email" + post "password/forgot", to: "passwords#forgot" post "password/reset", to: "passwords#reset" get "password/validate_token", to: "passwords#validate_token" @@ -33,6 +35,10 @@ post "projects/:id/reopen_issue", to: "issues#reopen_issue" post "/projects/:id/issues/graphic", to: "issues#issue_graphic_data" + namespace :v1 do + post "/users", to: "users#create" + end + resources :users, shallow: true do resources :projects do resources :grades diff --git a/config/secrets.yml b/config/secrets.yml index e57954b..461360d 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -19,10 +19,10 @@ development: secret_key_base: fbaf4d96ad5701178ae81fc6158701d7117a0e212845183f90429489101c4e3dadce2a63ef87f228b2dd2aa7109dda79c73a9386bac2bcd85237d9984d68cf5c + secret_key_email: fd701599009e745a0eaf501075679588344bb385e979a77f01223f87918a1fae9fa306d97216422ead69c3767a360f1f12587d44ba9b9ad04b3a28acf71c65bb test: secret_key_base: 4b182d9b7c7d4c04e3229e2d0294952305f2c475cd73884316b3bf6230e95f00a53364702860ea59ecb6c88746ef52aa92043f0ab3bac84d4ecab1e1a8e0e0f4 - # Do not keep production secrets in the unencrypted secrets file. # Instead, either read values from the environment. # Or, use `bin/rails secrets:setup` to configure encrypted secrets diff --git a/db/migrate/20191013155716_add_authenticated_field_to_user.rb b/db/migrate/20191013155716_add_authenticated_field_to_user.rb new file mode 100644 index 0000000..5c314de --- /dev/null +++ b/db/migrate/20191013155716_add_authenticated_field_to_user.rb @@ -0,0 +1,5 @@ +class AddAuthenticatedFieldToUser < ActiveRecord::Migration[5.1] + def up + add_column :users, :confirmation_token, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index c788f10..71ebfaf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190418161604) do +ActiveRecord::Schema.define(version: 20191013155716) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -105,6 +105,7 @@ t.string "access_token" t.string "reset_password_token" t.datetime "reset_password_sent_at" + t.boolean "confirmation_token", default: false end add_foreign_key "grades", "projects"