diff --git a/backend-rails/.env.sample b/backend-rails/.env.sample index a3508476..7657e63d 100644 --- a/backend-rails/.env.sample +++ b/backend-rails/.env.sample @@ -1,7 +1,9 @@ -SERVER_HOST=localhost -PASSWORD_RESET_URL=http://127.0.0.1:3000 -SENDGRID_API_KEY= AWS_ACCESS_KEY_ID= +AWS_BUCKET_REGION= AWS_SECRET_ACCESS_KEY= +BROWSER=/dev/null +LAUNCHY_DRY_RUN=true +PASSWORD_RESET_URL=http://localhost:3000/auth/reset_password +RESEND_API_KEY= +SERVER_HOST=localhost S3_BUCKET_NAME= -AWS_BUCKET_REGION= diff --git a/backend-rails/Gemfile b/backend-rails/Gemfile index 5da47f4d..f740234e 100644 --- a/backend-rails/Gemfile +++ b/backend-rails/Gemfile @@ -15,6 +15,7 @@ gem 'devise_token_auth', '~> 1.2', git: 'https://github.com/lynndylanhurley/devi gem 'draper', '~> 4.0', '>= 4.0.1' gem 'exception_hunter', '~> 1.0', '>= 1.0.1' gem 'jbuilder', '~> 2.10' +gem 'jquery-rails' gem 'matrix', '~> 0.4.2' gem 'oj', '~> 3.9', '>= 3.9.2' gem 'pagy', '~> 3.7', '>= 3.7.5' @@ -24,10 +25,11 @@ gem 'prawn' gem 'puma', '~> 5.6' gem 'pundit', '~> 2.1' gem 'rack-cors', '~> 1.0', '>= 1.0.6' +gem 'resend' gem 'sass-rails', '~> 6.0.0' -gem 'sendgrid', '~> 1.2.4' gem 'sprockets', '~> 3.7.2' gem 'yaaf', '~> 2.2' + # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use Active Model has_secure_password diff --git a/backend-rails/Gemfile.lock b/backend-rails/Gemfile.lock index a001620c..ff0a96fc 100644 --- a/backend-rails/Gemfile.lock +++ b/backend-rails/Gemfile.lock @@ -178,6 +178,9 @@ GEM activesupport (>= 5.2) hashdiff (1.0.1) highline (2.1.0) + httparty (0.19.1) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) i18n (1.12.0) concurrent-ruby (~> 1.0) i18n-tasks (0.9.37) @@ -236,10 +239,14 @@ GEM marcel (1.0.2) matrix (0.4.2) method_source (1.0.0) + mime-types (3.4.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2023.0218.1) mini_mime (1.1.2) mini_portile2 (2.8.1) minitest (5.17.0) msgpack (1.6.0) + multi_xml (0.6.0) mustache (1.1.1) net-imap (0.3.4) date @@ -341,6 +348,8 @@ GEM request_store (1.5.1) rack (>= 1.4) require_all (3.0.0) + resend (0.5.0) + httparty (~> 0.19.1) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) @@ -400,8 +409,6 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sendgrid (1.2.4) - json sexp_processor (4.16.1) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) @@ -468,6 +475,7 @@ DEPENDENCIES faker (~> 2.13) i18n-tasks (~> 0.9.30) jbuilder (~> 2.10) + jquery-rails letter_opener (~> 1.7) listen (~> 3.2) matrix (~> 0.4.2) @@ -484,12 +492,12 @@ DEPENDENCIES rails (~> 7.0.3) rails_best_practices (~> 1.20) reek (~> 6.1, >= 6.1.1) + resend rspec-rails (~> 4.1) rspec_api_documentation (~> 6.1.0) rubocop-rails (~> 2.16, >= 2.16.1) rubocop-rootstrap (~> 1.2) sass-rails (~> 6.0.0) - sendgrid (~> 1.2.4) shoulda-matchers (~> 4.1, >= 4.1.2) simplecov (~> 0.13.0) spring (~> 4.0) diff --git a/backend-rails/app/assets/javascripts/application.js b/backend-rails/app/assets/javascripts/application.js new file mode 100644 index 00000000..b51a50e6 --- /dev/null +++ b/backend-rails/app/assets/javascripts/application.js @@ -0,0 +1,2 @@ +// require_self +// require_tree. diff --git a/backend-rails/app/assets/javascripts/jquery_init.js b/backend-rails/app/assets/javascripts/jquery_init.js new file mode 100644 index 00000000..31a96fd2 --- /dev/null +++ b/backend-rails/app/assets/javascripts/jquery_init.js @@ -0,0 +1,2 @@ +//= require jquery +//= require jquery_ujs diff --git a/backend-rails/app/assets/javascripts/reset_password.js b/backend-rails/app/assets/javascripts/reset_password.js new file mode 100644 index 00000000..58db363b --- /dev/null +++ b/backend-rails/app/assets/javascripts/reset_password.js @@ -0,0 +1,31 @@ +//= require_self + +$(document).ready(function () { + $('#reset_password_form').submit(function (event) { + event.preventDefault(); + + const params = $(this).data('params') + + const headers = { + uid: params.uid, + client: params.client, + 'access-token': params['access-token'] + } + + $.ajax({ + headers, + url: $(this).attr('action'), + type: "PUT", + datatype: "application/js", + data: $(this).serialize(), + success: function (response) { + alert(response.message) + }, + error: function (_XMLHttpRequest, textStatus, errorThrown) { + alert("Status: " + textStatus); alert("Error: " + errorThrown); + } + }) + + return false; + }); +}) diff --git a/backend-rails/app/controllers/auth_controller.rb b/backend-rails/app/controllers/auth_controller.rb new file mode 100644 index 00000000..20aef53d --- /dev/null +++ b/backend-rails/app/controllers/auth_controller.rb @@ -0,0 +1,9 @@ +class AuthController < ApplicationController + def reset_password; end + + private + + def auth_params + params.permit('access-token', :client, :client_id, :token, :uid) + end +end diff --git a/backend-rails/app/views/auth/reset_password.html.erb b/backend-rails/app/views/auth/reset_password.html.erb new file mode 100644 index 00000000..4ed64510 --- /dev/null +++ b/backend-rails/app/views/auth/reset_password.html.erb @@ -0,0 +1,19 @@ +<%= javascript_include_tag "jquery_init" %> +<%= javascript_include_tag "reset_password", "data-turbolinks-track" => true %> + +
+

Reset your password

+ + <%= form_with url: user_password_path, + local: false, method: :put, html: { id: "reset_password_form", data: { params: params} } do |form| %> +
+ <%= form.label :password %> + <%= form.password_field :password %> +
+
+ <%= form.label :password_confirmation %> + <%= form.password_field :password_confirmation %> +
+ <%= form.submit 'Reset Password', style: 'margin-top: 24px; padding: 12px;' %> + <% end %> +
diff --git a/backend-rails/app/views/devise/mailer/confirmation_instructions.html.erb b/backend-rails/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 00000000..1b19eccb --- /dev/null +++ b/backend-rails/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

<%= t(:welcome).capitalize + ' ' + @email %>!

+ +

<%= t '.confirm_link_msg' %>

+ +

<%= link_to t('.confirm_account_link'), user_confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %>

diff --git a/backend-rails/app/views/devise/mailer/reset_password_instructions.html.erb b/backend-rails/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 00000000..e10d76f7 --- /dev/null +++ b/backend-rails/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

<%= t(:hello).capitalize %> <%= @resource.email %>!

+ +

<%= t '.request_reset_link_msg' %>

+ +

<%= link_to t('.password_change_link'), edit_user_password_url(@resource, reset_password_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s).html_safe %>

+ +

<%= t '.ignore_mail_msg' %>

+

<%= t '.no_changes_msg' %>

diff --git a/backend-rails/app/views/layouts/application.html.erb b/backend-rails/app/views/layouts/application.html.erb new file mode 100644 index 00000000..4fec1ae2 --- /dev/null +++ b/backend-rails/app/views/layouts/application.html.erb @@ -0,0 +1,16 @@ + + + + My App + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %> + + + + <%= yield %> + + diff --git a/backend-rails/config/application.rb b/backend-rails/config/application.rb index 5730d483..56e7e671 100644 --- a/backend-rails/config/application.rb +++ b/backend-rails/config/application.rb @@ -34,15 +34,6 @@ class Application < Rails::Application config.add_autoload_paths_to_load_path = false - ActionMailer::Base.smtp_settings = { - address: 'smtp.sendgrid.net', - authentication: :plain, - domain: ENV.fetch('SERVER_HOST', nil), - enable_starttls_auto: true, - password: ENV.fetch('SENDGRID_API_KEY', nil), - port: 587, - user_name: 'apikey' - } config.action_mailer.default_url_options = { host: ENV.fetch('SERVER_HOST', nil), port: ENV.fetch('PORT', 3000) } config.action_mailer.default_options = { diff --git a/backend-rails/config/environments/development.rb b/backend-rails/config/environments/development.rb index 1c606efc..084d92fe 100644 --- a/backend-rails/config/environments/development.rb +++ b/backend-rails/config/environments/development.rb @@ -42,9 +42,12 @@ # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :letter_opener - + config.action_mailer.perform_deliveries = true + config.action_mailer.default_options = { from: 'no-reply@yourapi.com' } config.action_mailer.perform_caching = false + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/backend-rails/config/environments/production.rb b/backend-rails/config/environments/production.rb index ce764241..969ffd35 100644 --- a/backend-rails/config/environments/production.rb +++ b/backend-rails/config/environments/production.rb @@ -73,6 +73,10 @@ # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false + config.action_mailer.delivery_method = :resend + config.action_mailer.resend_settings = { + api_key: ENV.fetch('RESEND_API_KEY', nil) + } # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). diff --git a/backend-rails/config/initializers/assets.rb b/backend-rails/config/initializers/assets.rb index 5a98a7e0..3c715ad7 100644 --- a/backend-rails/config/initializers/assets.rb +++ b/backend-rails/config/initializers/assets.rb @@ -11,4 +11,4 @@ # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -Rails.application.config.assets.precompile += %w[active_admin.js active_admin.css] +Rails.application.config.assets.precompile += %w[active_admin.js active_admin.css reset_password.js jquery_init.js] diff --git a/backend-rails/config/initializers/devise.rb b/backend-rails/config/initializers/devise.rb index 8e82857c..379d06f5 100644 --- a/backend-rails/config/initializers/devise.rb +++ b/backend-rails/config/initializers/devise.rb @@ -188,7 +188,7 @@ # ==> Configuration for :recoverable # # Defines which key will be used when recovering the password for an account - # config.reset_password_keys = [:email] + config.reset_password_keys = [:email] # Time interval you can reset your password with a reset password key. # Don't put a too small interval or your users won't have the time to diff --git a/backend-rails/config/initializers/mailer.rb b/backend-rails/config/initializers/mailer.rb new file mode 100644 index 00000000..9ac75db0 --- /dev/null +++ b/backend-rails/config/initializers/mailer.rb @@ -0,0 +1 @@ +Resend.api_key = ENV.fetch('RESEND_API_KEY', nil) diff --git a/backend-rails/config/routes.rb b/backend-rails/config/routes.rb index 0895643a..d5081f33 100644 --- a/backend-rails/config/routes.rb +++ b/backend-rails/config/routes.rb @@ -2,12 +2,15 @@ devise_for :admin_users, ActiveAdmin::Devise.config ActiveAdmin.routes(self) ExceptionHunter.routes(self) + mount_devise_token_auth_for 'User', at: '/api/v1/users', controllers: { registrations: 'api/v1/registrations', sessions: 'api/v1/sessions', passwords: 'api/v1/passwords' } + get 'auth/reset_password', to: 'auth#reset_password' + namespace :api do namespace :v1, defaults: { format: :json } do resources :daily_habits, only: %i[index create]