Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump @types/node from 20.11.25 to 20.12.7 in /frontend #260

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f4cac6f
Tweak Modal scrollbar
andres-vidal Mar 10, 2024
16acb40
Rename Button type prop to variant
andres-vidal Mar 11, 2024
c7ab71b
Implement the Contact Form's UI
andres-vidal Mar 11, 2024
5022c6b
Refactor useForm interface
andres-vidal Mar 11, 2024
6003e81
Implement validation on blur
andres-vidal Mar 11, 2024
42be394
Rename onSuccess to onSubmit
andres-vidal Mar 11, 2024
389ef3e
Change ContactButton label to 'Contact Us'
andres-vidal Mar 12, 2024
fcf21fd
Configure Swoosh.Adapter.SMTP
andres-vidal Mar 16, 2024
a1fc0a6
Send email from server
andres-vidal Mar 16, 2024
1f693a7
Add reCAPTCHA verification
andres-vidal Mar 16, 2024
17e6a08
Update README with new environment variables
andres-vidal Mar 17, 2024
b847acf
Update Docker configuration
andres-vidal Mar 17, 2024
1edafdc
Tweak backend configuration
andres-vidal Mar 11, 2024
bfac2ab
Tweak email sender name
andres-vidal Mar 17, 2024
119f0d3
Prevent modal from closing when selecting text
andres-vidal Mar 17, 2024
a727740
Add focus trap to the modal
andres-vidal Mar 17, 2024
0c52df4
Mock the recaptcha service
andres-vidal Mar 17, 2024
e545821
Mock the mailer service
andres-vidal Mar 17, 2024
6280e0a
Add basic tests
andres-vidal Mar 17, 2024
0a64ada
Enhance error feedback in ContactModal
andres-vidal Mar 17, 2024
f4735a4
Refactor Email to avoid nested case
andres-vidal Mar 17, 2024
e1be772
Decouple RichardBurton.Email from Swoosh.Email
andres-vidal Mar 17, 2024
a59fa7f
Send confirmation email to contact form sender
andres-vidal Mar 17, 2024
9764059
Fix frontend tests
andres-vidal Mar 17, 2024
a5685cb
Add basic tests for useForm
andres-vidal Mar 17, 2024
670549e
Fix confirmation email recipient
andres-vidal Mar 18, 2024
76dce21
Fix consistency issue
andres-vidal Apr 10, 2024
f80a33b
Merge pull request #251 from wyeworks/contact-form
andres-vidal Apr 10, 2024
d67d9a4
Bump @types/node from 20.11.25 to 20.12.7 in /frontend
dependabot[bot] Apr 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"elixir.projectPath": "./backend",
"elixirLS.projectDir": "./backend",
"files.associations": {
".env*": "properties"
Expand All @@ -9,5 +8,8 @@
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.requireConfig": true
"prettier.requireConfig": true,
"[elixir]": {
"editor.defaultFormatter": "JakeBecker.elixir-ls"
}
}
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Environment variables are configuration units relevant to the app's build or run
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `NEXT_PUBLIC_API_URL` | URL of the backend server API | `http://localhost:4000/api` |
| `NEXT_PUBLIC_FILES_URL` | URL of the backend server files | `http://localhost:4000/files` |
|`NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITEKEY`| Sitekey from Google Recaptcha| Sign up for one in `http://www.google.com/recaptcha/admin`
| `NEXT_INTERNAL_API_URL` | URL of the backend server API. For use in a closed environment. Set to the same value of `NEXT_PUBLIC_API_URL` if Phoenix server public url is reachable from NextJS server. | `http://localhost:4000/api` |
| `NEXTAUTH_URL` | The canonical URL of the site ([read more](https://next-auth.js.org/configuration/options#nextauth_url)) | `http://localhost:3000` |
| `NEXTAUTH_SECRET` | Secret for JWT encryption | Generate with `openssl rand -base64 32` |
Expand All @@ -92,6 +93,18 @@ Environment variables are configuration units relevant to the app's build or run
| `PHX_SECRET_KEY_BASE` | Phoenix secret key base | Generate with `mix phx.gen.secret` |
| `PHX_CONSUMER_URL` | URL of the frontend api. This is used to configure CORS headers | `http://localhost:3000` |
| `POSTGREX_TIMEOUT` | Timeout in milliseconds for database requests. Optional. Default is 15000. **Must be available in compile time.** | 15000 |
|`GOOGLE_RECAPTCHA_SECRET_KEY`| Sitekey from Google Recaptcha| Sign up for one in `http://www.google.com/recaptcha/admin`
|`GOOGLE_RECAPTCHA_VERIFICATION_URL`|URL to verify Google Recaptcha tokens |`https://www.google.com/recaptcha/api/siteverify`
|`SMTP_HOST`|SMTP relay host||
|`SMTP_PORT`|Port used to connect to the configured SMTP service|
|`SMTP_USER`|User to be used to send emails through the configured SMTP service||
|`SMTP_PASS`|Password of the user to be used to send emails through the configured SMTP service||
|`SMTP_FROM`|Default address to be used in behalf of the application||
|`SMTP_FROM`|Default name to be used in behalf of the application|`Richard Burton`|





# Development mode

Expand Down Expand Up @@ -138,7 +151,7 @@ Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

### Backend secrets

To configure backend secrets in development mode, you should create a file named `dev.local.exs` in `backend/config` and add the relevant configuration for `google_client_id`, `google_openid_config_url` and `google_oauth2_certs_url`. These variables are the same as those described in the [environment variables](#environment-variables) section, although downcased. `phx_consumer_id` is already predefined in `config/dev.exs`, but it can be overrided by defining a value in `dev.local.exs`.
To configure backend secrets in development mode, you should create a file named `dev.local.exs` in `backend/config` and add the relevant configuration for `google_*` and `smtp_*`. These variables are the same as those described in the [environment variables](#environment-variables) section, although downcased. `phx_consumer_id` is already predefined in `config/dev.exs`, but it can be overrided by defining a value in `dev.local.exs`.

The configuration file must follow this format:

Expand Down
2 changes: 1 addition & 1 deletion backend/.tool-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
elixir 1.16.2-otp-26
erlang 26.2.3
erlang 26.2.1
postgres 15.5
4 changes: 2 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# - Ex: hexpm/elixir:1.14.0-erlang-25.1-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.16.2
ARG OTP_VERSION=26.2.3
ARG OTP_VERSION=26.2.1
ARG DEBIAN_VERSION=bullseye-20240130-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
Expand Down Expand Up @@ -46,7 +46,7 @@ RUN mkdir config
# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
COPY config/config.exs config/${MIX_ENV}.exs config/${MIX_ENV}.local.exs config/
RUN mix deps.compile

COPY priv priv
Expand Down
4 changes: 2 additions & 2 deletions backend/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
# This configuration file is loaded before any dependency and
# is restricted to this project.

# General application configuration
# Configures the app
import Config

config :richard_burton,
ecto_repos: [RichardBurton.Repo]

# Postgrex configuration
# Configures Postgrex
config :richard_burton, RichardBurton.Repo,
timeout: String.to_integer(System.get_env("POSTGREX_TIMEOUT", "15000"))

Expand Down
2 changes: 1 addition & 1 deletion backend/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ config :richard_burton, RichardBurtonWeb.Endpoint,
server: false

# Print only warnings and errors during test
config :logger, backends: [:console], level: :warn
config :logger, backends: [:console], level: :warning

# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
11 changes: 10 additions & 1 deletion backend/lib/richard_burton/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ defmodule RichardBurton.Application do
"PHX_CONSUMER_URL",
"GOOGLE_CLIENT_ID",
"GOOGLE_OPENID_CONFIG_URL",
"GOOGLE_OAUTH2_CERTS_URL"
"GOOGLE_OAUTH2_CERTS_URL",
"GOOGLE_RECAPTCHA_VERIFICATION_URL",
"GOOGLE_RECAPTCHA_SECRET_KEY",
"SMTP_HOST",
"SMTP_USER",
"SMTP_PASS",
"SMTP_PORT",
"SMTP_FROM",
"SMTP_NAME",
"SMTP_TLS"
]
|> Enum.map(&{&1, &1 |> String.downcase() |> String.to_atom()})
|> Enum.filter(fn {key, _} -> is_nil(System.get_env(key)) end)
Expand Down
18 changes: 18 additions & 0 deletions backend/lib/richard_burton/auth_recaptcha_service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule RichardBurton.Auth.Recaptcha do
@moduledoc """
Behaviour for recaptcha authorization services
"""

@callback verify(token :: String.t()) :: {:ok, String.t()} | {:error, String.t()}

@spec verify(token :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
def verify(token), do: impl().verify(token)

defp impl,
do:
Application.get_env(
:richard_burton,
:auth_recaptcha_service,
RichardBurton.Auth.Recaptcha.Google
)
end
32 changes: 32 additions & 0 deletions backend/lib/richard_burton/auth_recaptcha_service_google.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule RichardBurton.Auth.Recaptcha.Google do
@moduledoc """
Google Recaptcha implementation for RichardBurton.Auth.Recaptcha behaviour
"""
@behaviour RichardBurton.Auth.Recaptcha

@impl true
@spec verify(token :: String.t()) :: {:ok, String.t()} | {:error, String.t()}
def verify(token) do
verification_url = Application.get_env(:richard_burton, :google_recaptcha_verification_url)
verification_secret = Application.get_env(:richard_burton, :google_recaptcha_secret_key)

request_body = "secret=#{verification_secret}&response=#{token}"

request_headers = [
{"Content-Type", "application/x-www-form-urlencoded"},
{"Content-Length", Integer.to_string(byte_size(request_body))}
]

case HTTPoison.post(verification_url, request_body, request_headers) do
{:ok, %{body: response_body}} ->
case Jason.decode(response_body) do
{:ok, %{"success" => true}} -> :ok
{:ok, %{"success" => false, "error-codes" => issues}} -> {:error, issues}
{:error, _reason} -> {:error, "Failed to decode JSON response"}
end

{:error, _reason} ->
{:error, "Failed to make verification request"}
end
end
end
78 changes: 78 additions & 0 deletions backend/lib/richard_burton/email.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule RichardBurton.Email do
@moduledoc """
Schema for emails
"""
use Ecto.Schema
import Ecto.Changeset
import EctoCommons.EmailValidator

alias RichardBurton.Email
alias RichardBurton.Mailer

schema "emails" do
field :name, :string
field :institution, :string
field :address, :string
field :subject, :string
field :message, :string
field :to, :string
end

def changeset(email, params) do
email
|> cast(params, [:name, :institution, :address, :subject, :message, :to])
|> validate_required([:name, :address, :subject, :message])
|> validate_email(:address)
end

def contact(params) do
case changeset = Email.changeset(%Email{}, params) do
%Ecto.Changeset{valid?: true} ->
changeset |> Ecto.Changeset.apply_changes() |> send_email(confirmation: true)

%Ecto.Changeset{valid?: false} ->
{:error, {:invalid, RichardBurton.Validation.get_errors(changeset)}}
end
end

defp send_email(email, confirmation: false) do
case Mailer.send(email) do
{:ok, _} -> :ok
{:error, reason} -> {:error, reason}
end
end

defp send_email(email = %{address: address}, confirmation: true) do
case Mailer.send(email) do
{:ok, _} ->
send_email(
%{
name: "Richard Burton",
institution: "IFRS Canoas",
address: Application.get_env(:richard_burton, :smtp_from),
subject: "Contact Confirmation from Richard Burton",
message: get_confimation_message(email),
to: address
},
confirmation: false
)

{:error, reason} ->
{:error, reason}
end
end

defp get_confimation_message(email) do
"""
Thank you for contacting the Richard Burton Platform research team. We will get back to you as soon as possible.

The contact information and message you sent are as follows:

Name: #{email.name}
Institution: #{email.institution}
Email: #{email.address}
Subject: #{email.subject}
Message: #{email.message}
"""
end
end
13 changes: 13 additions & 0 deletions backend/lib/richard_burton/mailer.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule RichardBurton.Mailer do
@moduledoc """
Behaviour for Mailer
"""

@callback send(email :: RichardBurton.Email.t()) :: {:ok, String.t()} | {:error, String.t()}

@spec send(email :: RichardBurton.Email.t()) :: {:ok, any()} | {:error, any()}
def send(email), do: impl().send(email)

defp impl,
do: Application.get_env(:richard_burton, :mailer, RichardBurton.Mailer.SMTP)
end
52 changes: 52 additions & 0 deletions backend/lib/richard_burton/mailer_smtp.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
defmodule RichardBurton.Mailer.SMTP do
@moduledoc """
STMP implementation for RichardBurton.Mailer behaviour
"""
@behaviour RichardBurton.Mailer

import Swoosh.Email

use Swoosh.Mailer,
otp_app: :richard_burton,
adapter: Swoosh.Adapters.SMTP,
relay: Application.compile_env(:richard_burton, :smtp_host),
username: Application.compile_env(:richard_burton, :smtp_user),
password: Application.compile_env(:richard_burton, :smtp_pass),
port: Application.compile_env(:richard_burton, :smtp_port),
tls: Application.compile_env(:richard_burton, :smtp_tls),
retries: 1,
no_mx_lookups: false

@spec send(RichardBurton.Email.t()) :: {:ok, any()} | {:error, any()}
def send(email) do
case deliver(get_swoosh_email(email)) do
{:ok, payload} -> {:ok, payload}
{:error, reason} -> {:error, reason}
end
end

@spec get_swoosh_email(RichardBurton.Email.t()) :: Swoosh.Email.t()
defp get_swoosh_email(email = %{address: address, subject: subject, message: message, to: nil}) do
new(
from: {get_contact_name(email), address},
to: Application.get_env(:richard_burton, :smtp_from),
subject: subject,
text_body: message
)
end

@spec get_swoosh_email(RichardBurton.Email.t()) :: Swoosh.Email.t()
defp get_swoosh_email(email = %{address: address, subject: subject, message: message, to: to}) do
new(
from: {get_contact_name(email), address},
to: to,
subject: subject,
text_body: message
)
end

defp get_contact_name(%{name: name, institution: nil}), do: name
defp get_contact_name(%{name: name, institution: ""}), do: name
defp get_contact_name(%{name: name, institution: institution}), do: "#{name} (#{institution})"
defp get_contact_name(%{name: name}), do: name
end
1 change: 1 addition & 0 deletions backend/lib/richard_burton/validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ defmodule RichardBurton.Validation do
defp get_description(%{validation: :length, kind: :min, count: 1}), do: :required
defp get_description(%{validation: :assoc}), do: :required
defp get_description(%{validation: :alpha2}), do: :alpha2
defp get_description(%{validation: :email}), do: :invalid
defp get_description(%{constraint: :unique}), do: :conflict

defp simplify_errors(node) when is_map(node) do
Expand Down
18 changes: 18 additions & 0 deletions backend/lib/richard_burton_web/controllers/email_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule RichardBurtonWeb.EmailController do
use Phoenix.Controller
alias RichardBurton.Email

@spec contact(Plug.Conn.t(), any()) :: Plug.Conn.t()
def contact(conn, params) do
case Email.contact(params) do
:ok ->
json(conn, %{message: "Email sent."})

{:error, {:invalid, issues}} ->
conn |> put_status(400) |> json(%{issues: issues})

{:error, _} ->
conn |> put_status(500) |> json(%{message: "Could not send email."})
end
end
end
33 changes: 33 additions & 0 deletions backend/lib/richard_burton_web/plugs/authorize_recaptcha.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule RichardBurtonWeb.Plugs.AuthorizeRecaptcha do
@moduledoc """
Plug for recaptcha verification
"""

alias RichardBurton.Auth

import Plug.Conn

@spec init(any()) :: any()
def init(params), do: params

@spec call(Plug.Conn.t(), any()) :: Plug.Conn.t()
def call(conn, _params) do
case verify(conn) do
:ok -> conn
:error -> halt_unauthorized(conn)
end
end

defp verify(%{params: %{"recaptcha_token" => recaptcha_token}}) do
case Auth.Recaptcha.verify(recaptcha_token) do
:ok -> :ok
{:error, _} -> :error
end
end

defp verify(_conn), do: :error

defp halt_unauthorized(conn) do
conn |> send_resp(:unauthorized, "Unauthorized, recaptcha token is invalid") |> halt
end
end
10 changes: 10 additions & 0 deletions backend/lib/richard_burton_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ defmodule RichardBurtonWeb.Router do
plug(RichardBurtonWeb.Plugs.AuthorizeAdmin)
end

pipeline :authorize_recaptcha do
plug(RichardBurtonWeb.Plugs.AuthorizeRecaptcha)
end

scope "/api", RichardBurtonWeb do
pipe_through(:api)
get("/publications", PublicationController, :index)
end

scope "/api", RichardBurtonWeb do
pipe_through(:api)
pipe_through(:authorize_recaptcha)
post("/contact", EmailController, :contact)
end

scope "/api", RichardBurtonWeb do
pipe_through(:api)
pipe_through(:authorize_admin)
Expand Down
Loading
Loading