Permalink
Browse files

Version 2.2.0

  • Loading branch information...
1 parent ab7889f commit c5c5b499029609ed691dc8c31d55776316db199d @abitdodgy committed Jul 2, 2012
View
18 README.md
@@ -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:
@@ -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.
View
54 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");
+ }
+ }
+}
View
12 controllers/Controller.cfc
@@ -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.
*/
View
4 controllers/PasswordResets.cfc
@@ -10,7 +10,7 @@
}
// --------------------------------------------------
- // REST
+ // RESTful Actions
/**
* @hint Renders the reset form page.
@@ -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);
View
13 controllers/Users.cfc
@@ -14,21 +14,11 @@
// 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;
}
}
@@ -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 {
View
17 models/User.cfc
@@ -6,6 +6,7 @@
* @hint Constructor
*/
public void function init() {
+ beforeCreate("setEmailConfirmationToken");
beforeSave("sanitize,securePassword");
validatesConfirmationOf("email,password");
@@ -44,6 +45,13 @@
}
}
+ /**
+ * @hint Sets the emailConfirmationToken for the user.
+ */
+ private void function setEmailConfirmationToken() {
+ this.emailConfirmationToken = generateToken();
+ }
+
// --------------------------------------------------
// Public
@@ -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();
}
@@ -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");
+ }
}
View
2 views/admin/adminusers/show.cfm
@@ -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">
View
37 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>
View
9 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>
View
3 views/users/index.cfm
@@ -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>
View
6 views/users/show.cfm
@@ -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">

0 comments on commit c5c5b49

Please sign in to comment.