Skip to content

Commit

Permalink
Merge pull request #772 from code-corps/forgot-password
Browse files Browse the repository at this point in the history
Add forgot_password controller action
  • Loading branch information
joshsmith committed May 25, 2017
2 parents ea786ca + 20319cd commit 11b8f2e
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 2 deletions.
29 changes: 29 additions & 0 deletions blueprint/api.apib
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,25 @@ Until the Code Corps platform is open to new organizations, only admin users can

+ Attributes (Unprocessable Entity Response)

# Group Password

This endpoint is a parent route for reset and forgot password endpoints

## Forgot [/forgot]

### Forgot Password [POST]

+ Request

+ Attributes (Forgot Password Request)

+ Headers

Accept: application/vnd.api+json
Authorization: Bearer <token>

+ Response 200 (application/vnd.api+json; charset=utf-8)

# Group Previews

Preview resources allow a user to create a temporary HTML preview for any kind of markdown.
Expand Down Expand Up @@ -2625,6 +2644,16 @@ The platform stores Stripe customers and cards so they can be reused across diff
+ data(array[Organization Resource])
+ include JSON API Version

## Forgot Password Resource (object)
+ include Forgot Password Resource Identifier

## Forgot Password Resource Identifier
+ email: `myemail@gmail.com` (string, required)

## Forgot Password Request (object)
+ data(Forgot Password Resource)
+ include JSON API Version

## Project User Attributes (object)
+ role: `pending` (enum[string])
+ Members
Expand Down
20 changes: 20 additions & 0 deletions lib/code_corps/emails/forgot_password_email.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule CodeCorps.Emails.ForgotPasswordEmail do
import Bamboo.Email
import Bamboo.PostmarkHelper

alias CodeCorps.Emails.BaseEmail

def create(user, token) do
BaseEmail.create
|> to(user.email)
|> template(template_id(), [link: link(token)])
end

defp template_id, do: Application.get_env(:code_corps, :postmark_forgot_password_template)

defp link(token) do
Application.get_env(:code_corps, :site_url)
|> URI.merge("password/reset?token=#{token}")
|> URI.to_string
end
end
18 changes: 18 additions & 0 deletions lib/code_corps/services/forgot_password.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule CodeCorps.Services.ForgotPasswordService do

alias CodeCorps.{AuthToken, Emails, Mailer, Repo, User}

@doc"""
forgot_password should take an email and generate an AuthToken model and send an email
"""
def forgot_password(email) do
with %User{} = user <- Repo.get_by(User, email: email),
{ :ok, %AuthToken{} = %{ value: token } } <- AuthToken.changeset(%AuthToken{}, user) |> Repo.insert
do
Emails.ForgotPasswordEmail.create(user, token) |> Mailer.deliver_now()
else
nil -> nil
end
end

end
32 changes: 32 additions & 0 deletions test/controllers/password_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule CodeCorps.PasswordControllerTest do
@moduledoc false

use CodeCorps.ApiCase, resource_name: :password
use Bamboo.Test

alias CodeCorps.AuthToken

test "creates and renders resource when email is valid", %{conn: conn} do
user = insert(:user)
attrs = %{"email" => user.email}
conn = post conn, password_path(conn, :forgot_password), attrs
response = json_response(conn, 200)

assert response == %{ "email" => user.email }

%AuthToken{value: token} = Repo.get_by(AuthToken, user_id: user.id)
assert_delivered_email CodeCorps.Emails.ForgotPasswordEmail.create(user, token)
end

test "does not create resource and renders 200 when email is invalid", %{conn: conn} do
user = insert(:user)
attrs = %{"email" => "random_email@gmail.com"}
conn = post conn, password_path(conn, :forgot_password), attrs
response = json_response(conn, 200)

assert response == %{ "email" => "random_email@gmail.com" }

refute_delivered_email CodeCorps.Emails.ForgotPasswordEmail.create(user, nil)
end

end
17 changes: 17 additions & 0 deletions test/lib/code_corps/emails/forgot_password_email_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule CodeCorps.Emails.ForgotPasswordEmailTest do
use CodeCorps.ModelCase
use Bamboo.Test

alias CodeCorps.{Emails.ForgotPasswordEmail, AuthToken}

test "forgot password email works" do
user = insert(:user)
{ :ok, %AuthToken{ value: token } } = AuthToken.changeset(%AuthToken{}, user) |> Repo.insert

email = ForgotPasswordEmail.create(user, token)
assert email.from == "Code Corps<team@codecorps.org>"
assert email.to == user.email
{ :link, encoded_link } = email.private.template_model |> Enum.at(0)
assert "#{Application.get_env(:code_corps, :site_url)}/password/reset?token=#{token}" == encoded_link
end
end
17 changes: 17 additions & 0 deletions test/views/password_view_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule CodeCorps.PasswordViewTest do
use CodeCorps.ViewCase

test "renders show" do
email = "wat@codecorps.org"

rendered_json = render(CodeCorps.PasswordView, "show.json", %{email: email})

expected_json = %{
email: email
}

assert expected_json == rendered_json
refute Map.has_key?(expected_json, :token)
end

end
14 changes: 14 additions & 0 deletions web/controllers/password_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule CodeCorps.PasswordController do
use CodeCorps.Web, :controller

alias CodeCorps.{Services.ForgotPasswordService}

@doc"""
forgot_password should take an email and generate an AuthToken model and send an email
"""
def forgot_password(conn, %{"email" => email}) do
ForgotPasswordService.forgot_password(email)
conn |> put_status(:ok) |> render("show.json", email: email)
end

end
3 changes: 1 addition & 2 deletions web/controllers/password_reset_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ defmodule CodeCorps.PasswordResetController do
and return email. 422 if pwd do not match or auth token does not exist
"""
def reset_password(conn, %{"token" => token, "password" => password, "password_confirmation" => password_confirmation}) do
user = conn.assigns.current_user
with %AuthToken{value: auth_token} <- Repo.get_by(CodeCorps.AuthToken, %{ value: token, user_id: user.id }),
with %AuthToken{value: auth_token, user: user} <- Repo.get_by(AuthToken, %{ value: token }) |> Repo.preload(:user),
{:ok, _} <- Phoenix.Token.verify(CodeCorps.Endpoint, "user", auth_token, max_age: 1209600) do
with %Changeset{valid?: true} <- User.reset_password_changeset(user,
%{password: password, password_confirmation: password_confirmation}) do
Expand Down
1 change: 1 addition & 0 deletions web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ defmodule CodeCorps.Router do
resources "/comments", CommentController, only: [:index, :show]
resources "/donation-goals", DonationGoalController, only: [:index, :show]
resources "/organizations", OrganizationController, only: [:index, :show]
post "/password/forgot", PasswordController, :forgot_password
resources "/project-categories", ProjectCategoryController, only: [:index, :show]
resources "/project-skills", ProjectSkillController, only: [:index, :show]
resources "/project-users", ProjectUserController, only: [:index, :show]
Expand Down
10 changes: 10 additions & 0 deletions web/views/password_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule CodeCorps.PasswordView do
use CodeCorps.Web, :view

def render("show.json", %{email: email}) do
%{
email: email
}
end

end

0 comments on commit 11b8f2e

Please sign in to comment.