Login and cappuccino

In the first part of this tutorial, we have setup a test environment comprising a apache web server with php support (the tools are already installed on Mac OS 10.5). We have also learned how to use cappuccino to access web service in a password protected folder. We have seen the code to use GET and POST request (with a "Content-Type" of "text/plain;charset=UTF-8" ) to send or receive JSON data. You could expect that we will use the same JSON/POST method but we will not use this scheme. Why ? Well just to let you know how to use a POST with a "Content-Type" of "application/x-www-form-urlencoded". This is a tutorial after all ;-). Of course you can change to JSON/POST if you prefer.

The content type "application/x-www-form-urlencoded" is used when you code a "form" in html. So you will be able to reuse this tutorial if you want to send data to a web service that expect to talk with a "form" in a html page.

Setting up a test environment

Installing mysql 5.1.32 on Mac OS 10.5.6

Since we want to let a user register or login, we will have to store the user information (email/password) in a database server. We will first install and configure mysql. You can found the disk image for mysql 5.1.32 by following this link. I choosed the file mysql-5.1.32-osx10.5-x86_64.dmg for my Mac Book Pro.

Double click on the disk image file, and launch the installers : "mysql-5.1.32-osx10.5-x86_64.pkg" and "MySQLStartupItem.pkg". Copy the preference pane "MySQL.prefPane" to the directory "/Library/PreferencePanes". Open your System Preferences, select the "MySQL" pane and click on the "Start MySQL Server" button. Congrats, you installed mysql. We will have no to configure it.

Configuring mysql 5.1.32

Since we will use the command line tools, you first have to add "/usr/local/mysql/bin" to your PATH environment variable.

$ emacs ~/.bash_profile $ cat ~/.bash_profile export PATH=$PATH:/path/to/cappuccino_07beta/280north-cappuccino-07b/Build/Release/env/bin:/usr/local/mysql/bin:.

Check that your mysql server is running.

$ mysqlshow +--------------------+ | Databases | +--------------------+ | information_schema | | test | +--------------------+

You can found the reference manual of mysql, a tutorial for mysql, the sql syntax, and the MySQL PHP API by using these links.

First, setup a password for the root user and create a password for you.

$ mysql -u root mysql> SET PASSWORD FOR root@localhost=PASSWORD('SuperPassword'); mysql> SET PASSWORD FOR root@127.0.0.1=PASSWORD('SuperPassword'); mysql> SET PASSWORD FOR philippe@localhost = PASSWORD('BestPassword'); mysql> FLUSH PRIVILEGES;

You can check that the root password is setup correctly by using the command:

mysql> SHOW GRANTS FOR root@localhost; +--------------------------------------------------------------+ | Grants for root@localhost | +--------------------------------------------------------------+ | GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY PASSWORD 'AD51D525781249B381FY9F9A0D754837839A05C14' WITH GRANT OPTION +--------------------------------------------------------------+ mysql> exit

We now create the database "db_pictures" for this tutorial. You can refer to the available charset and unicode charset to choose the one that will best fit your killer application. We will use utf8 for the database and the table "accounts" that will store the user information.

$ mysql -u root -p mysql> CREATE DATABASE IF NOT EXISTS db_pictures DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_bin mysql> USE db_pictures; mysql> CREATE TABLE accounts (id INT NOT NULL AUTO_INCREMENT, email VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, password VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, PRIMARY KEY (id));

Now, grant all access to yourself on the "db_pictures" database.

mysql> GRANT ALL ON db_pictures.* TO philippe@localhost; mysql> SHOW GRANTS FOR philippe@localhost; +--------------------------------------------------------------+ | Grants for philippe@localhost | +--------------------------------------------------------------+ | GRANT USAGE ON *.* TO 'philippe'@'localhost' IDENTIFIED BY PASSWORD '*D51F6U9A1249C5615D9F9A0D77C83F989A05C14' | GRANT ALL PRIVILEGES ON `db_pictures`.* TO 'philippe'@'localhost' +--------------------------------------------------------------+ mysql> exit

Verify that you can login as yourself and use the "db_pictures" database.

$ mysql -u philippe -p db_pictures mysql> describe accounts; +----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | email | varchar(255) | NO | | NULL | | | password | varchar(255) | NO | | NULL | | +----------+--------------+------+-----+---------+----------------+

Well, that's enought for mysql. We have now our Apache, php and mysql environment setup. We can switch to cappuccino development.

Creating the user interface for login

You can refer to the source code in "AppController.j" to build four buttons and link them to these actions. You can see that we use the singleton [LoginController sharedLoginController] (see the source in "LoginController.j"). We can show/hide the login window with showLoginWindow: or hideLoginWindow:

-(void)showLogin:(id)sender { [[LoginController sharedLoginController] showLoginWindow:sender]; } -(void)hideLogin:(id)sender { [[LoginController sharedLoginController] hideLoginWindow:sender]; }

The user can login or register when the windows is displayed. We can log out the user by using logout:

-(void)logout:(id)sender { [[LoginController sharedLoginController] logout:sender]; }

We can determine if the user is login with the isLoginIn accessor and get the account id with accountID accessor.

-(void)storeTheUserWork:(id)sender { var sharedLoginController = [LoginController sharedLoginController]; if ([sharedLoginController isLoginIn]) { // Well, we can now store the user work on the server. var accountId = [sharedLoginController accountID]; } else { // The user has to login first [sharedLoginController showLoginWindow:sender]; } }

Code that you can find interresting

cappuccino run in the browser

Since cappuccino application are Objective-J programs that run in the browser, we can take advantage of this to validate all user data before submitting it to the server (in our case php web services). We check the email with a regular expression, the password length and we verify that both passwords are equal.

-(BOOL)checkEmail { var email = [[userTextField stringValue] lowercaseString]; if ([email length] > 0) { var re_email = new RegExp("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$"); return re_email.test(email); } return NO; } -(BOOL)checkPasswordLength:(int)length { var password = [passwordTextField stringValue]; return ([password length] >= length); } -(BOOL)checkPassword { var password = [passwordTextField stringValue]; var confirm = [confirmTextField stringValue]; if ([password compare:confirm] == CPOrderedSame) return YES; return NO; }

cappuccino lets you code with accessors

You should read the article "synthesizing-accessor-methods" if you want to use accessors in your Objective-J classes. Here is a short summary for the geeks that are smelling Objective-J.

CPString firstName @accessors; CPString _location @accessors(property=location); BOOL _hidden @accessors(getter=isHidden, setter=setIsHidden); id foo @accessors(readonly); id bar @accessors(copy);

You can see that we did our home work when implementing LoginController.

@implementation LoginController : CPWindowController { BOOL isLoginIn @accessors(readonly); CPNumber accountID @accessors(readonly); }

Creating a CPURLRequest for "application/x-www-form-urlencoded"

You can see below that we add our service protection (Basic http authorization). This time we have "application/x-www-form-urlencoded" for the content type. As the name states, we have to "urlencode" the data submitted by the POST. This is done by the [email urlencode] and [password urlencode]. The user can now enter password like "password&anotherKey=another Value". The special characters '&', '=' and ' ' will be encoded for you (see below for more details).

We normalize the email to lower case (this will enable us to check the presence of accounts based to their email). Password are keep with their case : ThisIsNotTheSame than thisIsNotTheSame

-(CPURLRequest)createFindRequestWithIdentifier:(BOOL)useIndentifier { var email = [[userTextField stringValue] lowercaseString]; var password = [passwordTextField stringValue]; var content = [[CPString alloc] initWithFormat:@"email=%@&password=%@", [email urlencode], [password urlencode]]; var contentLength = [[CPString alloc] initWithFormat:@"%d", [content length]]; var request = [[CPURLRequest alloc] initWithURL:@"http://macbookpro.local/~philippe/find_account.php"]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:content]; [request setValue:contentLength forHTTPHeaderField:@"Content-Length"]; [request setValue:"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; if (useIndentifier) { var serviceUser = @"Aladdin"; var servicePassword = @"open sesame"; var secret = [[CPString stringWithFormat:@"%@:%@", serviceUser, servicePassword] encodeBase64]; var authorization = [CPString stringWithFormat:@"Basic %@", secret]; [request setValue:authorization forHTTPHeaderField:@"Authorization"]; } return request; }

When receiving data, be sure to check for spaces. The tutorial php scripts were checked to return just string but I got some extra spaces to trim (for string comparison). So I had to implement this method but you should probably use the new version I wrote reusing the script of phpjs.org (see the file CPString_phpjs.j).

-(void)connection:(CPURLConnection)connection didReceiveData:(CPString)data { var trimmedData = [self trimRight:data]; if (connection == insertConnection) { // Check for errors if ([trimmedData caseInsensitiveCompare:@"Account already exist"] == CPOrderedSame) { // Report the error : an account found for this email already exist. The user need to login. [self displayAlertWithMessage:@"An account already exist for this email, please login."]; } else { // We got our brand new account id accountID = [trimmedData intValue]; isLoginIn = YES; [self hideLoginWindow:self]; } } }

phpjs.org is your friend

As you have seen, we have to "urlencode" some strings. This is done in a CPString category that can be found in the file "CPString_phpjs.j". This site want to implement the php functions in javascript. That's nice because you will be able to found here a lot of scripts that you can transform in Objective-J categories for easy reuse.

Implementing the server side with php

Well, I have implemented the server side with php since it is probably what is available at cheap web service provider. This is exactly what refrain me to use python or ruby: I should buy better and expansive services.

Check the files "dump_accounts.php", "insert_account.php" and "find_account.php". Modify the mysql password to reflect the one you choosed. Place the files in your "~/Sites" or "~/Sites/Private" folder (see the url in the source code).

References

Well I hope that this long tutorial can be helpfull to you. If you find some bugs, let me know, I will fix them for the other readers.

If you'd like to see the complete code listing from the tutorial, you can download it all in a single file: Tutorial-Login-Step2.zip. The web application is available online: Tutorial-Login-Step2

Copyright © 2009 - Philippe Laval. Cappuccino and Objective-J are registered Trademarks of 280 North.