Rusty Divine

Live, Love, Learn, Teach

Defeated BrowserQuest

 

Just spent a fun hour playing Mozilla's new HTML 5 retro MMO game, BrowserQuest (more info).  They are using node.js to do web sockets, which allow them to communicate bi-directionally between the browser and the server.  Since it's just HTML5 and Javascript, you should be able to play it on your mobile phone, too.

After playing for about 30 minutes, my browser (Chrome) crashed and I had to restart my computer to get my mouse pointer back.  I was relieved that BrowserQuest also uses HTML 5's local storage, so I was able to pick up the game right where I left off!  

And, of course I had to play until I won (that's me below after defeating the final boss)!

 

Win7 Auto-login via Registry

I sometimes need to setup a computer to automatically login to windows on restart.  Here's how to do this:

 

  1. regedit to  HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\winlogon
  2. set AutoAdminLogon to 1  to turn it on
  3. set or create the strings for:
    1. DefaultUserName
    2. DefaultPassword
    3. DefaultDomainName

IP Address and Domain Restrictions - IIS6 and IPv6

I have a WCF service running on an IIS 6 server that I wanted to restrict access to.  I have one outside domain that has a static IP address, and one internal website that uses the service.

When I checked the IIS logs to see what IP addresses were hitting the service, I saw the expected external IP address, but for the internal address it was in the form of IPv6.  I wanted to use IIS' "IP Address and Domain Restrictions" tool, but IIS 6 does not support IPv6 with this tool (although it looks like IIS7 does, sort of).

The IPv6 in the log file results from a web site installed on the same box as the WCF service that references the WCF service.  So, in the web.config the service reference was something like: http://abc-server/myservice.svc where "abc-server" was the name of my server.  When I changed this to the IP address of the server, like this: http://192.168.111.12/myservice.svc then the IIS logs showed that the internal website was using the IPv4 address instead of the IPv6 one.

At that point it was fairly straight forward.

  1. Open IIS, select the website with the service to protect
  2. Open the IP Address and Domain Restrictions
  3. Add Allow entries for both the external IP address and the 192.168.111.12 internal address
  4. Open the "Edit Feature Settings" link in the right side and select "Deny" for "Access for unspecified clients"; don't check the "Enable Domain Name Restrictions" since we're using IP addresses

Email Bounce: The error that the other server returned was: 550 550, no such user here

"No such user here" - I read that and hear a country song with a steel guitar and a sad southern drawl.

I just switched my hosting provider, but my domain is registered at a different provider.  I forgot to update my MX records so that email to @osmyn.com would get routed to the mail servers at the registrar for my domain.

I always have to muddle my way through this process, so here's what I did:

My registrar is GoDaddy (I know, I'm going to change it in a few months):

 

To Check Your MX Records in the Email Control Center

  1. Log in to your Account Manager.
  2. From the Products tab, select Email.
  3. Next to the account you want to change, click Launch. The Email Control Center displays.
  4. Go to the Domains tab.
  5. Select the email account you want to verify MX records for, and then click Server Addresses.
I took the suggested correct settings: 

 

 

And logged into discountASP.Net's server and updated my MX records and A Records there (to get the IP address of the A Records, I used c:\ping domain.name):

The update isn't instantaneous; it will take a few hours to propagate.

BlogEngine 2.5 .cshtml Razor Page Not Found

I fixed the 404.3 error I was getting in BlogEngine when navigating to pages that end in the Razor extension ".cshtml" by editing the links to those pages to remove the .cshtml extension.  The menu links I saw affected were admin\Default (dashboard), admin\Settings\Themes, admin\Extentions, admin\Logout, 

First, I did a global find in VS (ctrl+shift+f) to find anything that matched .chstml"  (I included the closing double-quote in my search since I was looking for links to these pages).  Then, I changed any code that was creating a hyperlink to a .cshtml page so that it instead was calling the appropriate action method.  For example, I changed "account/logout.cshtml" to "account/logout", and I changed "~/admin/default.cshtml" to "~admin/default" because even though "default" will be the default action and therefore not necessary, the tab menu tool needs to have the word default there or else it thinks you are always on the dashboard tab.

The files I changed:

  • Web.siteMap
  • admin\Settings\Themes.cshtml
  • admin\_Layout.cshtml
  • admin\Settings\Menu.ascx
  • admin\Settings\_menu.cshtml

How to encrypt a password reset email link in asp.Net MVC

This MVC3 application creates a membership account for visitors when they fill out a form that includes their email address.  The email address becomes their username, and a password is automatically generated and emailed to them:

public static string FriendlyPassword()
        {
            string newPassword = Membership.GeneratePassword(8, 0);
            newPassword = Regex.Replace(newPassword, @"[^a-zA-Z0-9]", m => "9");
            returnnewPassword;
        }

Users can change their password, but I needed a way to let them reset it incase they forgot it.  The application doesn’t collect security questions, and I didn’t want users to be able to enter any email and have the password reset for that account, so I came up with a solution that involved requesting an emailed password reset link that is only valid for X minutes, then clicking that link sends another email with a new password generated using the method above. 

public static string ResetPassword(MembershipUser user)
        {
            string newPass = FriendlyPassword();
            user.ChangePassword(user.ResetPassword(), newPass);
            return newPass;
        }

One advantage with the ResetPassword method above is that neither the user nor the application need know the user’s current password in order to reset it.  The method resets the password, uses that as the current password, then updates the password with a known password that uses a RegEx to remove any characters that are not numbers or letters – for easier typing.

[HttpGet]
        public ActionResult RequestResetPasswordLink()
        {
            return View();
        }

        [HttpPost]
        public ActionResult RequestResetPasswordLink(string userName)
        {
            ViewBag.Message = "Thank you for submitting your request. 
 Please check your email for a reset password link.";

            if(string.IsNullOrEmpty(userName)) return View();

            var existingUser = MembershipHelper.GetUser(userName);

            if (existingUser == null) return View();

            MembershipHelper.SendResetEmail(existingUser);

            return View();
        }

The Account Controller has two actions for requesting the password reset email.  The Get action just returns a view that has a textbox for the user to input their email address, which is their user name.  The Post action uses the viewbag to alert the user that their submission has been processed.  It tells them this same message whether it is a valid email or not so that a hacker cannot use this page to guess valid email addresses.  If the user is found in membership, then it sends the password reset email.

public static void SendResetEmail(MembershipUser user)
        {
            string encrypted = Encryption.Encrypt(String.Format("{0}&{1}",
                user.UserName,
                DateTime.Now.AddMinutes(_minExpires).Ticks),
                ConfigurationManager.AppSettings[Constants.keyEncryptionKey]);

            var passwordLink = ApplicationHelpers.GetBaseURL() + 
                  "Account/ResetPassword?digest=" + 
                  HttpUtility.UrlEncode(encrypted);

            var email = new MailMessage();

            email.From = new MailAddress(admin@domain.com);
            email.To.Add(new MailAddress(user.Email));

            email.Subject = "Password Reset";
            email.IsBodyHtml = true;

            email.Body += "<p>A request has been recieved to reset your password. 
 If you did not initiate the request, then please ignore this email.</p>";
            email.Body += "<p>Please click the following link to reset your password: 
 <a href='" + passwordLink + "'>" + passwordLink + "</a></p>";

            SmtpClient smtpClient = new SmtpClient();

            try
            {
                smtpClient.Send(email);
            }
            catch (Exception ex)
            {
                ErrorHandler.HandleError(ex, ErrorHandler.Level.Error);
            }
        }

The request reset email link uses an encrypted querystring of the format digest=3992023882&user@email.com where the first part of the digest is the ticks of a datetime expriation date that I set to 30 minutes from now, and the second part is the user’s email address.  The encryption utility below is a modification of something I found on Code Project:

public class Encryption
    {
        private const string _defaultKey = "*3ld+43j";


        public static string Encrypt(string toEncrypt, string key)
        {
            var des = new DESCryptoServiceProvider();
            var ms = new MemoryStream();

            VerifyKey(ref key);

            des.Key = HashKey(key, des.KeySize / 8);
            des.IV = HashKey(key, des.KeySize / 8);
            byte[] inputBytes = Encoding.UTF8.GetBytes(toEncrypt);

            var cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(inputBytes, 0, inputBytes.Length);
            cs.FlushFinalBlock();

            return HttpServerUtility.UrlTokenEncode(ms.ToArray());
        }

        public static string Decrypt(string toDecrypt, string key)
        {
            var des = new DESCryptoServiceProvider();
            var ms = new MemoryStream();

            VerifyKey(ref key);

            des.Key = HashKey(key, des.KeySize / 8);
            des.IV = HashKey(key, des.KeySize / 8);
            byte[] inputBytes = HttpServerUtility.UrlTokenDecode(toDecrypt);

            var cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
            cs.Write(inputBytes, 0, inputBytes.Length);
            cs.FlushFinalBlock();

            var encoding = Encoding.UTF8;
            return encoding.GetString(ms.ToArray());

        }

        /// <summary>
        /// Make sure key is exactly 8 characters
        /// </summary>
        /// <param name="key"></param>
        private static void VerifyKey(ref string key)
        {
            if (string.IsNullOrEmpty(key)) 
                key = _defaultKey;

            key = key.Length > 8 ? key.Substring(0, 8) : key;

            if (key.Length < 8)
            {
                for (int i = key.Length; i < 8; i++)
                {
                    key += _defaultKey[i];
                }
            }
        }

        private static byte[] HashKey(string key, int length)
        {
            var sha = new SHA1CryptoServiceProvider();
            byte[] keyBytes = Encoding.UTF8.GetBytes(key);
            byte[] hash = sha.ComputeHash(keyBytes);
            byte[] truncateHash = new byte[length];
            Array.Copy(hash, 0, truncateHash, 0, length);
            return truncateHash;
        }
    }

}

 

When the user gets the email, they click on a link that routes them back to the Account Controller into a ResetPassword action.  And in the MembershipHelper, the validation method decrypts the digest and validates the expiration and username.

[RequireHttps]
        [HttpGet]
        public ActionResult ResetPassword(string digest)
        {
            var parts = MembershipHelper.ValidateResetCode(HttpUtility.UrlDecode(digest));

            if (!parts.IsValid)
            {
                ViewBag.Message = "Invalid or expired link. Please try again";
                return View();
            }

            string newPass = MembershipHelper.ResetPassword(parts.User);

            MembershipHelper.SendNewPasswordEmail(parts.User, newPass);

            ViewBag.Message = "Thank you. Your new password has been emailed to you. 
You may change it after you log in.";
            return View();
        }
 
        public static ResetPasswordParts ValidateResetCode(string encryptedParam)
        {
            string decrypted = "";
            var results = new ResetPasswordParts();

            try
            {
                decrypted = Encryption.Decrypt(encryptedParam, ConfigurationManager
                   .AppSettings[Constants.keyEncryptionKey]);
            }
            catch (Exception ex)
            {
                ErrorHandler.HandleError(ex, ErrorHandler.Level.Information);
                return results;
            }

            var parts = decrypted.Split('&');

            if(parts.Length != 2) return results;

            var expires = DateTime.Now.AddHours(-1);


            results.User = Membership.GetUser(parts[0]);
            if (results.User == null) return results;

            long ticks = 0;
            if(!long.TryParse(parts[1], out ticks)) return results;
            expires = new DateTime(ticks);
            results.Expires = expires;

            if (expires < DateTime.Now) return results;
            results.IsValid = true;

            return results;
        }
 

Finally, the user’s new password is emailed to them with a link to the login page.

 
public static void SendNewPasswordEmail(MembershipUser user, string newPassword)
        {
            MailMessage m = new MailMessage();
            m.From = new MailAddress("admin@domain.com");
            m.To.Add(user.UserName);
            m.Subject = "Your Credentials";
            var body = @"Your password has been reset. You can change this password once you have 
logged in. Your user name is: {0} Your reset password is: {1} To login, 
go to: http://domain.com/Account/LogOn";

            m.Body = string.Format(body, user.UserName, newPassword);

            SmtpClient c = new SmtpClient();
            try
            {
                c.Send(m);
            }
            catch (Exception ex)
            {
                ErrorHandler.HandleError(string.Format(

"Error sending welcome email to {0}: {1}",
                    user.UserName, ex.Message), ErrorHandler.Level.Error);
            }
            finally
            {
                m.Dispose();
                c.Dispose();
            }
        }