Skip to content

Commit

Permalink
Merge 5616f9a into e631861
Browse files Browse the repository at this point in the history
  • Loading branch information
ghatighorias committed Oct 18, 2017
2 parents e631861 + 5616f9a commit 1dc136c
Show file tree
Hide file tree
Showing 87 changed files with 1,152 additions and 1,006 deletions.
1 change: 1 addition & 0 deletions assets/js/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@
);

req.open( "GET", `/calendar?date=${ startDate }&my_classes=${ !displayEvery }` );
req.setRequestHeader("authorization", `Bearer ${JWT}`)
req.send( );
}

Expand Down
30 changes: 12 additions & 18 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use Mix.Config

config :course_planner,
ecto_repos: [CoursePlanner.Repo]
ecto_repos: [CoursePlanner.Repo],
site_name: "CoursePlanner",
auth_email_reply_to: nil,
auth_email_title: "Course Planner",
auth_password_reset_token_validation_days: 2

config :course_planner, CoursePlannerWeb.Endpoint,
url: [host: "localhost"],
Expand All @@ -17,23 +21,6 @@ config :logger, :console,
config :course_planner, CoursePlanner.Mailer,
adapter: Swoosh.Adapters.Local

# %% Coherence Configuration %% Don't remove this line
config :coherence,
user_schema: CoursePlanner.Accounts.User,
repo: CoursePlanner.Repo,
module: CoursePlanner,
web_module: CoursePlannerWeb,
router: CoursePlannerWeb.Router,
logged_out_url: "/",
title: "Course Planner",
layout: {CoursePlannerWeb.Coherence.LayoutView, "app.html"},
messages_backend: CoursePlannerWeb.Coherence.Messages,
opts: [:authenticatable, :recoverable, :lockable, :trackable, :unlockable_with_token]

config :coherence, CoursePlannerWeb.Coherence.Mailer,
adapter: Swoosh.Adapters.Local
# %% End Coherence Configuration %%

config :canary,
repo: CoursePlanner.Repo,
unauthorized_handler: {CoursePlannerWeb.Helper, :handle_unauthorized}
Expand All @@ -48,3 +35,10 @@ config :email_checker,
validations: [EmailChecker.Check.Format]

import_config "#{Mix.env}.exs"

config :guardian, Guardian,
issuer: "CoursePlanner.#{Mix.env}",
ttl: {1, :days},
verify_issuer: true,
serializer: CoursePlanner.Auth.GuardianSerializer,
secret_key: to_string(Mix.env) <> "SuPerseCret_aBraCadabrA"
6 changes: 3 additions & 3 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ config :course_planner, CoursePlanner.Repo,
hostname: System.get_env("DATABASE_HOST") || "localhost",
pool_size: 10

config :coherence,
email_from_name: "Dev Name",
email_from_email: "dev@email"
config :course_planner,
auth_email_from_name: "Dev Name",
auth_email_from_email: "dev@email"
12 changes: 4 additions & 8 deletions config/heroku.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@ config :course_planner, CoursePlanner.Repo,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
ssl: true

config :coherence, CoursePlannerWeb.Coherence.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: System.get_env("SENDGRID_API_KEY")

config :coherence,
email_from_name: System.get_env("EMAIL_FROM_NAME"),
email_from_email: System.get_env("EMAIL_FROM_EMAIL")

config :course_planner, CoursePlanner.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: System.get_env("SENDGRID_API_KEY")

config :course_planner,
auth_email_from_name: "${EMAIL_FROM_NAME}",
auth_email_from_email: "${EMAIL_FROM_EMAIL}"
12 changes: 4 additions & 8 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@ config :course_planner, CoursePlanner.Repo,
pool_size: 10,
ssl: true

config :coherence, CoursePlannerWeb.Coherence.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: {:system, "SENDGRID_API_KEY"}

config :coherence,
email_from_name: "${EMAIL_FROM_NAME}",
email_from_email: "${EMAIL_FROM_EMAIL}"

config :course_planner, CoursePlanner.Mailer,
adapter: Swoosh.Adapters.Sendgrid,
api_key: {:system, "SENDGRID_API_KEY"}

config :course_planner,
auth_email_from_name: "${EMAIL_FROM_NAME}",
auth_email_from_email: "${EMAIL_FROM_EMAIL}"
9 changes: 3 additions & 6 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@ config :course_planner, CoursePlanner.Repo,
config :course_planner, CoursePlanner.Mailer,
adapter: Swoosh.Adapters.Test

config :comeonin,
bcrypt_log_rounds: 4

config :coherence,
email_from_name: "Test Name",
email_from_email: "test@email"
config :course_planner,
auth_email_from_name: "Test Name",
auth_email_from_email: "test@email"

config :course_planner, :notifier, CoursePlanner.TestNotifier
111 changes: 100 additions & 11 deletions lib/course_planner/accounts/user.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
defmodule CoursePlanner.Accounts.User do
@moduledoc false
use Ecto.Schema
use Coherence.Schema
import Ecto.Changeset

alias CoursePlanner.Types.{UserRole, ParticipationType}
alias CoursePlanner.Notifications.Notification
alias Ecto.Changeset
alias Comeonin.Bcrypt

@target_params [
:name, :family_name, :nickname,
:email, :student_id, :comments,
:role, :participation_type,
:phone_number, :notified_at,
:notification_period_days
:notification_period_days,
:current_password, :password, :password_confirmation, :password_hash,
:reset_password_token, :reset_password_sent_at,
:failed_attempts, :locked_at,
:sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip, :last_sign_in_ip,
:unlock_token
]

schema "users" do
Expand All @@ -29,36 +35,68 @@ defmodule CoursePlanner.Accounts.User do
field :notification_period_days, :integer
has_many :notifications, Notification, on_delete: :delete_all

coherence_schema()
field :password_hash, :string
field :current_password, :string, virtual: true
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true

field :reset_password_token, :string
field :reset_password_sent_at, Ecto.DateTime

field :failed_attempts, :integer, default: 0
field :locked_at, Ecto.DateTime

field :sign_in_count, :integer, default: 0
field :current_sign_in_at, Ecto.DateTime
field :last_sign_in_at, Ecto.DateTime
field :current_sign_in_ip, :string
field :last_sign_in_ip, :string

field :unlock_token, :string

timestamps()
end

def changeset(model, params \\ %{}) do
model
|> cast(params, @target_params ++ coherence_fields())
|> cast(params, @target_params)
|> update_change(:email, &String.downcase/1)
|> validate_required([:email, :role])
|> validate_email_format(:email)
|> unique_constraint(:email)
|> validate_length(:comments, max: 255)
|> validate_coherence(params)
|> validate_number(:notification_period_days,
greater_than_or_equal_to: 1, less_than_or_equal_to: 7)
end

def changeset(model, params, :password) do
def changeset(model, params, :password_reset) do
model
|> cast(params,
~w(password password_confirmation reset_password_token reset_password_sent_at))
|> validate_coherence_password_reset(params)
|> validate_required(:password)
|> validate_length(:password, min: 8)
|> validate_confirmation(:password)
|> updates_hashed_password()
end

def changeset(model, params, :seed) do
changeset(model, params)
model
|> changeset(params)
|> validate_current_password()
|> validate_password_if_changed()
end

def changeset(model, params, :create) do
model
|> changeset(params)
|> validate_password_if_changed()
end

def changeset(model, params, :update) do
changeset(model, params)
model
|> changeset(params)
|> validate_current_password()
|> validate_password_if_changed()
end

defp validate_email_format(changeset, field) do
Expand All @@ -74,8 +112,59 @@ defmodule CoursePlanner.Accounts.User do
|> Changeset.get_field(field)
|> EmailChecker.valid?()
|> case do
true -> changeset
false -> Changeset.add_error(changeset, field, "has invalid format", [validation: :format])
true -> changeset
false -> Changeset.add_error(changeset, field, "has invalid format", [validation: :format])
end
end

def validate_current_password(%{changes: changes, valid?: true} = changeset) do
password_hash = Map.get(changeset.data, :password_hash)
current_password = Map.get(changes, :current_password)
param_has_password = Map.has_key?(changes, :password)

if param_has_password do
do_validate_current_password(changeset, current_password, password_hash)
else
changeset
end
end
def validate_current_password(changeset), do: changeset

defp do_validate_current_password(changeset, current_password, password_hash) do
if is_nil(current_password) do
add_error(changeset, :current_password, "cant be blank.")
else
if Bcrypt.checkpw(current_password, password_hash) do
changeset
else
add_error(changeset, :current_password, "current password is invalid.")
end
end
end

def validate_password_if_changed(%{changes: changes, valid?: true} = changeset) do
password = Map.get(changes, :password)

if password do
changeset
|> validate_length(:password, min: 6)
|> validate_confirmation(:password)
|> updates_hashed_password()
else
changeset
end
end
def validate_password_if_changed(changeset), do: changeset

defp updates_hashed_password(%{changes: changes, valid?: true} = changeset) do
password = Map.get(changes, :password)

changeset
|> put_change(:password_hash, encrypt_password(password))
end
defp updates_hashed_password(changeset), do: changeset

def encrypt_password(password) do
Bcrypt.hashpwsalt(password)
end
end
48 changes: 44 additions & 4 deletions lib/course_planner/accounts/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ defmodule CoursePlanner.Accounts.Users do
@moduledoc """
Handle all interactions with Users, create, list, fetch, edit, and delete
"""
alias CoursePlanner.{Repo, Accounts.User, Notifications.Notification, Notifications}
alias CoursePlanner.{Repo, Accounts.User, Notifications.Notification, Notifications, Auth.Helper}
alias Ecto.{DateTime, Changeset, Multi}
alias Coherence.ControllerHelpers
alias Timex.Comparable
alias Comeonin.Bcrypt

import Ecto.Query

@notifier Application.get_env(:course_planner, :notifier, CoursePlanner.Notifications.Notifier)

defp auth_password_reset_token_validation_days,
do: Application.get_env(:course_planner, :auth_password_reset_token_validation_days)

def all do
Repo.all(User)
end

def add_default_password_params(user, token) do
random_default_password = ControllerHelpers.random_string 12
random_default_password = Helper.get_random_token_with_length(12)

user
|> Map.put_new("reset_password_token", token)
Expand All @@ -28,7 +32,7 @@ defmodule CoursePlanner.Accounts.Users do
updated_user = add_default_password_params(user, token)

%User{}
|> User.changeset(updated_user)
|> User.changeset(updated_user, :create)
|> Repo.insert()
end

Expand Down Expand Up @@ -87,4 +91,40 @@ defmodule CoursePlanner.Accounts.Users do
# credo:disable-for-next-line
|> Enum.each(&@notifier.notify_all/1)
end

def reset_password_token_valid?(%User{reset_password_sent_at: nil}), do: false
def reset_password_token_valid?(user) do
current_datetime = Timex.now()
reset_password_sent_at = user.reset_password_sent_at

days_since_reset_token_sent =
Comparable.diff(current_datetime, reset_password_sent_at, :days)

auth_password_reset_token_validation_days() >= days_since_reset_token_sent
end

def get_new_password_reset_token(user) do
if reset_password_token_valid?(user) do
%{
reset_password_token: user.reset_password_token,
reset_password_sent_at: user.reset_password_sent_at
}
else
%{
reset_password_token: Helper.get_random_token_with_length(12),
reset_password_sent_at: DateTime.utc()
}
end
end

def check_password(user, password) do
cond do
user && Bcrypt.checkpw(password, user.password_hash) -> {:ok, :login}

user -> {:error, :unauthorized}

true -> Bcrypt.dummy_checkpw()
{:error, :not_found}
end
end
end
13 changes: 13 additions & 0 deletions lib/course_planner/auth/current_user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule CoursePlanner.CurrentUser do
@moduledoc """
this module is used by guardian through router to populate loged-in user
"""
import Plug.Conn
import Guardian.Plug

def init(opts), do: opts
def call(conn, _opts) do
current_user = current_resource(conn)
assign(conn, :current_user, current_user)
end
end
14 changes: 14 additions & 0 deletions lib/course_planner/auth/guardian_error_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule CoursePlanner.Auth.GuardianErrorHandler do
@moduledoc """
Handles the case in which user tries accessing a resource without being loged-in
"""
import CoursePlannerWeb.Router.Helpers

alias Phoenix.Controller

def unauthenticated(conn, _params) do
conn
|> Controller.put_flash(:error, "You must be signed in to access that page.")
|> Controller.redirect(to: session_path(conn, :new))
end
end
Loading

0 comments on commit 1dc136c

Please sign in to comment.