Skip to content

Commit

Permalink
Version 2.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
abitdodgy committed Jul 2, 2012
1 parent ab7889f commit c5c5b49
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 20 deletions.
18 changes: 16 additions & 2 deletions README.md
Expand Up @@ -4,7 +4,7 @@ ColdFusion on Wheels User Manager Demo
User Manager is a demo app for ColdFusion on Wheels. It's meant to be a toolkit for learning or kickstarting a project that requires session management and authorization functionality.


Current Version 2.1.2
Current Version 2.2.0
---------------------

Current version includes the following functionality:
Expand All @@ -14,14 +14,28 @@ Current version includes the following functionality:
* CRUD functionality for User model;
* Password hashing and salting using bCrypt;
* Expiring password resets with confirmation e-mail;
* Email confirmation;
* Admin authorization;
* Admin CRUD for managing users.
* Friendly redirects

Change Log
----------

The following changes have been made in version 2.0:
This change requires a new SQL file (included). The following changes have been made:

**Version 2.2.0**
* Added a new RESTful Confirmations.cfc controller for confirming email addresses.
* Added two columns in the schema: boolean confirmed, and varchar confirmation token.
* Added new SQL file.
* Refactored how tokens are generated now for password resets and confirmations. Using a stripped UUID as generate secret key was causing bad URLs.
* Added an Admin link if the user is signed in as an admin.
* Moved isAthorized method to Controller.cfc so it can be reused by Confirmations.cfc.
* Added new callback to create a confirmation token when the user signs up.
* Removed dead code and email templates left over from version 1.
* Users#index.cfm now shows confirmation status for users.
* Switched all places from using DateFormat() to a custom formatDate(). This makes changing the date format easier as it's in a single place.


**Version 2.1.2**
* Switched password hashing from using a SHA-512 over 1024 iterations to using BCrypt.
Expand Down
54 changes: 54 additions & 0 deletions controllers/Confirmations.cfc
@@ -0,0 +1,54 @@
component
extends="Controller"
hint="Handles email confirmation requests."
{
/**
* @hint Constructor.
*/
public void function init() {
super.init();
filters(through="isAuthenticated,isAuthorized,redirectIfConfirmed", except="update");
}

// --------------------------------------------------
// Filters

/**
* @hint Redirect the user if their email is already confirmed.
*/
public void function redirectIfConfirmed() {
if ( user.confirmed ) {
redirectTo(route="profile", key=user.id, message="Your email address was previously confirmed.", messageType="warning");
}
}

// --------------------------------------------------
// RESTful Actions

/**
* @hint Renders a page where users request an email with instructions to confirm their email address.
*/
public void function new() {}

/**
* @hint Sends an email with instructions to confirm email address.
*/
public void function create() {
user.update(emailConfirmationToken = user.generateToken());
//sendMail(to=user.email, subject="Email confirmation", template="/templates/emailConfirmation", user=user);
redirectTo(route="profile", key=user.id, message="Email sent! Please see email for instructions.", messageType="success");
}

/**
* @hint Updates the user to confirmed status.
*/
public void function update() {
user = model("user").findOneByEmailConfirmationToken(params.key);
if ( isObject(user) && user.update(confirmed = 1, emailConfirmationToken = "") ) {
redirectTo(controller="users", action="show", key=user.id, message="Account confirmed successfully.", messageType="success");
}
else {
redirectTo(route="home", message="No such user.", messageType="error");
}
}
}
12 changes: 11 additions & 1 deletion controllers/Controller.cfc
Expand Up @@ -29,7 +29,17 @@ component
redirectTo(route="signIn");
}
}


/*
* @hint Ensures it's the correct user.
*/
private void function isAuthorized() {
user = model("user").findByKey(params.key);
if ( ! IsObject(user) || ! user.id == currentUser.id ) {
redirectTo(route="home");
}
}

/**
* @hint Redirects the user away if its logged in.
*/
Expand Down
4 changes: 2 additions & 2 deletions controllers/PasswordResets.cfc
Expand Up @@ -10,7 +10,7 @@
}

// --------------------------------------------------
// REST
// RESTful Actions

/**
* @hint Renders the reset form page.
Expand Down Expand Up @@ -46,7 +46,7 @@
}

/**
* @hint Renders the edit user page where users enter a new passwords.
* @hint Updates the user's password.
*/
public void function update() {
user = model("user").findOneByPasswordResetToken(params.key);
Expand Down
13 changes: 2 additions & 11 deletions controllers/Users.cfc
Expand Up @@ -13,22 +13,12 @@
// --------------------------------------------------
// Filters

/*
* @hint Ensures it's the correct user.
*/
private void function isAuthorized() {
user = model("user").findByKey(params.key);
if ( ! IsObject(user) || ! user.id == currentUser.id ) {
redirectTo(route="home");
}
}

/*
* @hint Ensures the admin setting is set to 0 in case a user tries to exploit mass assignment.
*/
private void function protectFromMassAssignment() {
if ( StructKeyExists(params, "user") ) {
params.user.admin = 0;
params.user.admin = 0;
}
}

Expand Down Expand Up @@ -63,6 +53,7 @@
user = model("user").new(params.user);
if ( user.save() ) {
signIn(user);
//sendMail(to=user.email, subject="Email confirmation", template="/templates/emailConfirmation", user=user);
redirectTo(route="profile", key=user.id, message="Account created successfully.", messageType="success");
}
else {
Expand Down
17 changes: 16 additions & 1 deletion models/User.cfc
Expand Up @@ -6,6 +6,7 @@
* @hint Constructor
*/
public void function init() {
beforeCreate("setEmailConfirmationToken");
beforeSave("sanitize,securePassword");

validatesConfirmationOf("email,password");
Expand Down Expand Up @@ -44,6 +45,13 @@
}
}

/**
* @hint Sets the emailConfirmationToken for the user.
*/
private void function setEmailConfirmationToken() {
this.emailConfirmationToken = generateToken();
}

// --------------------------------------------------
// Public

Expand All @@ -59,7 +67,7 @@
* @hint Creates a password reset token
*/
public void function createPasswordResetToken() {
this.passwordResetToken = URLEncodedFormat(GenerateSecretKey("AES", 256));
this.passwordResetToken = generateToken();
this.passwordResetAt = Now();
this.save();
}
Expand All @@ -71,4 +79,11 @@
if ( StructKeyExists(this, "password") ) this.password = "";
if ( StructKeyExists(this, "passwordConfirmation") ) this.passwordConfirmation = "";
}

/**
* @hint Generates a random token.
*/
public string function generateToken() {
return Replace(LCase(CreateUUID()), "-", "", "all");
}
}
2 changes: 1 addition & 1 deletion views/admin/adminusers/show.cfm
Expand Up @@ -9,7 +9,7 @@
<div class="span12">
#flashMessageTag()#
<h3>#user.name#</h3>
<p>Joined on #DateFormat(user.createdAt, "medium")#.</p>
<p>Joined on #formatDate(user.createdAt)#.</p>
<p>#linkTo(text="&larr; Back", action="index")#</p>
</div>
<div class="span4 ar">
Expand Down
37 changes: 37 additions & 0 deletions views/confirmations/new.cfm
@@ -0,0 +1,37 @@
<cfoutput>

#contentFor(pageTitle="Email Confirmation")#

<div class="container">
<div class="page-header">
<h1>Email Confirmation</h1>
</div>

<div class="row">
<div class="span10">
#flashMessageTag()#

<cfif user.confirmed>
<p>Your email address has been confirmed previously.</p>
<cfelse>
<p>Click below to receive a new email with instructions for confirming your account.</p>
</cfif>

#startFormTag(action="create", key=user.id)#
<fieldset>
<div class="clearfix">
<label for="email">E-mail Address</label>
<div class="input">
#textField(objectName="user", property="email", label=false, disabled=true)#
</div>
</div>
<div class="actions">
#submitTag(value="Send Email", class="btn primary")# #linkTo(text="Cancel", controller="sessions", action="new", class="btn")#
</div>
</fieldset>
#endFormTag()#
</div>
</div>
</div>

</cfoutput>
9 changes: 9 additions & 0 deletions views/templates/emailconfirmation.cfm
@@ -0,0 +1,9 @@
<cfoutput>

<h1>Dear #user.name#,</h1>
<p>You need to confirm your e-mail address. Please click on the link below to do so:</p>
<p>#URLFor(controller="confirmations", action="update", onlyPath=false, key=user.emailConfirmationToken)#</p>
<p>If clicking the above link does not work, just copy it and paste the URL in a new browser window instead.</p>
<p>Thanks!</p>

</cfoutput>
3 changes: 2 additions & 1 deletion views/users/index.cfm
Expand Up @@ -18,7 +18,8 @@
<div class="span4">
<div class="well">
<h5>#users.name#</h5>
<p>Joined on #DateFormat(users.createdAt, "medium")#</p>
<p>Joined on #formatDate(users.createdAt)#</p>
<p>Confirmed: #users.confirmed ? "Yes" : "No"#</p>
<p>#linkTo(text="View Profile", route="profile", key=users.id)#</p>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion views/users/show.cfm
Expand Up @@ -18,7 +18,11 @@
<div class="row">
<div class="span10">
#flashMessageTag()#
<p>Joined on #DateFormat(user.createdAt, "medium")#.</p>
<p>Joined on #formatDate(user.createdAt)#.</p>
<cfif (signedIn() AND user.id EQ currentUser.id) AND NOT user.confirmed>
<p>Your account has not been confirmed yet. We sent you an email requesting confirmation.</p>
<p>#linkTo(text="Click here", controller="confirmations", action="new", key=user.id)# to receive a new email if you can not find it.</p>
</cfif>
<p>#linkTo(text="&larr; Back", action="index")#</p>
</div>
<div class="span6 ar">
Expand Down

0 comments on commit c5c5b49

Please sign in to comment.