Port of http://www.smalltalkhub.com/#!/~sergio101/TF-Login/
Originally from http://www.squeaksource.com/TFLogin.html, but tests were failing. It turns out that this was written for pharo 1.1.1. I wanted to use pharo 2, so I refactored the parts that didn't work in pharo 2. The following are the changes i made:
- ReadStream was updated to use Fuel.
- The tests now only support ZnClient and ZnServer.
All tests now pass, except for testCapacity test. I will look at this in the next week, but I thought it would be worthwhile to get it out.
This package provides basic user authentication, registration, and account management for Seaside.
Features include:
- User login component with username and password text fields, forgot username, forgot password, register, and login buttons.
- User registration component with optional BowWave reCaptcha spambot protection.
- Edit account settings component allows user to change username, password, email address and application-specific properties.
- Optional email confirmation for registration and account changes. Email content is provided by the host application. Examples are included.
- Multi-part HTML/plain text email.
- Configurable confirmation email timeouts and cookie retention periods.
- Forgot username and forgot password support.
- New password validation by the host application (to enforce password rules like minimum-length, etc.)
- Options to allow empty passwords, remember username in cookie, and automatic login on return to the website.
- Host application can provide login filter block for such things as disabling accounts or limiting login frequency.
- Host application can use TFLogin's email confirmation mechanism for application-specific purposes. Additional user-related objects may be included in the account edit form.
- Persistence is provided by a storage adaptor that can be replaced with the persistence mechanism of your choice. The provided default file storage adaptor scales to 100,000 users. Each user's information is saved in a separate versioned file.
- An application properties dictionary is provided that allows you to store and retrieve additional objects along with the user account information.
- All settings are available in the Seaside configuration page. Test/demo application included.
- TFLogin Guide document included (see below).
- Tested with Seaside 3.0.3 and Pharo 1.1.1. Email does NOT work with prior versions of Seaside.
All feedback is welcome.
# TFLogin Guide
Note that the tests included in the TFLogin package require the following packages to run:
http://www.squeaksource.com/Soup
Soup
http://www.squeaksource.com/ZincHTTPComponent
Zinc-HTTP
## Integrating TFLogin with Your Application
Take a look at the TLTestApp to see an example of the steps below.
There are a number of steps to introducing TFLogin authentication to an existing Seaside application. They are described below:
1. Create `MyCoolComponent class>>#initialize` like the following:
```smalltalk
initialize
| application |
application := WAAdmin register: self asApplicationAt: self handlerName.
application preferenceAt: #sessionClass put: TLSession.
"Add TL-specific configuration options"
application configuration
addParent: TLConfiguration instance;
addParent: WAEmailConfiguration instance
```
2. If you plan to use reCaptcha to protect your registration form from spammers, then you will need the BowWave reCaptcha package that you can obtain by evaluating:
```smalltalk
Gofer it
url: 'http://www.squeaksource.com/BowWave';
package: 'BowWave-Captcha-Core';
package: 'BowWave-Captcha-Recaptcha';
load.
```
Then add lines like the following to `MyCoolComponent class>>#initialize`, substituting your public and private reCaptcha keys that you obtain free from http://www.google.com/recaptcha.
```smalltalk
application configuration parents add: BWRecaptchaConfiguration instance.
application preferenceAt: #publicKey put: '{{your-public-key}}'.
application preferenceAt: #privateKey put: '{{your-private-key}}'.
```
Note that these preferences can also be set using your application's Seaside configuration page.
If you do not plan to use reCaptcha spambot protection, then it is not necessary to install the BowWave package.
3. You can also set TFLogin preferences in `MyCoolComponent class>>#initialize` if you wish, or you can set the using the Seaside application config page. Here is an example of what can be done in the initialize method:
```smalltalk
application preferenceAt: #sendRegistrationConfirmationEmail put: true.
application preferenceAt: #confirmationTimeoutMinutes put: 10.
application preferenceAt: #useRecaptchaInRegistrationForm put: false.
application preferenceAt: #smtpServer put: 'localhost'.
application preferenceAt: #confirmEmailChangeViaEmail put: false.
application preferenceAt: #confirmAccountChangesViaEmail put: false.
application preferenceAt: #allowEmptyPasswords put: false.
application preferenceAt: #allowUsernameChange put: false.
application preferenceAt: #allowRememberUsername put: true.
application preferenceAt: #allowAutomaticLogin put: false.
```
Note that these preferences can also be set using your application's Seaside configuration page.
4. In your component's instance-side `#initialize` method (not the class initialize method as described above) put the following to make the TFLogin components known to your application:
```smalltalk
initialize
super initialize.
loginComponent := (TLLoginComponent appName: '<your-app-name>').
loginComponent onAnswer: [ :user | user ifNotNilDo: [ :u | self loggedIn: u ] ].
self children add: loginComponent.
editAccountComponent := (TLEditAccountComponent loginComponent: loginComponent).
editAccountComponent onAnswer: [ self editingAccount: false ].
self children add: editAccountComponent.
```
If you don't already have one, you will need a children method that looks like this:
```smalltalk
children
^ children ifNil: [ children := Bag new ]
```
5. If you will be using email confirmation, then you will need to tell TFLogin where your email sending methods are like this (also in the application initialize method). An example of the email sending methods referenced here is presented later on in this guide.
```smalltalk
loginComponent registrationConfirmationEmailSender: [ :url :email :timeout |
self
sendRegistrationConfirmationEmailTo: email
confirmUrl: url
timeoutMinutes: timeout ].
loginComponent passwordResetEmailSender: [ :url :email :timeout |
self
sendPasswordResetUrl: url
to: email
timeout: timeout ].
loginComponent usernameReminderEmailSender: [ :usernames :email |
self
sendUsernameReminderFor: usernames
to: email ].
editAccountComponent emailChangeConfirmationEmailSender: [ :url :email :timeout :newUser |
self
sendEmailChangeConfirmationTo: email
confirmUrl: url
timeout: timeout
newUser: newUser ].
editAccountComponent accountChangeConfirmationEmailSender: [ :url :email :timeout :newUser |
self
sendAccountChangeConfirmationTo: email
confirmUrl: url
timeout: timeout
newUser: newUser ].
```
6. If you want to support automatic login using username/password cookies for your users, you will need to include the following in your application's initialRequest method:
```smalltalk
loginComponent cookieLogin
```
This will log the user in immediately before your application has had a change to display the loginComponent if they have the correct cookies defined (as would be the case if they checked the "Log me in automatically when I return to this site" checkbox when they last logged in.)
7. Here is an example of a mail-sending method. The text would of course vary depending on your application and the specific message being sent.
```smalltalk
sendRegistrationConfirmationEmailTo: email confirmUrl: url timeoutMinutes: timo
"Compose and send email. Answer true on success, false on failure."
| textBody htmlBody emailOk appName |
appName := 'Login Test App'.
"Compose a pain text message."
textBody := (WriteStream on: String new)
<< 'This email is in response to your request to register at '; << appName; << '.'; crlf;crlf;
<< 'Direct your browser to the following URL within ';<< timo asString; << ' minutes to confirm your registration.'; crlf;crlf;
<< ' '; << url; crlf;crlf;
<< 'If you did not attempt to register for a'; << appName; << ' account then this message was sent in error and should be ignored.'.
"Compose a nice HTML message."
htmlBody := WAHtmlCanvas builder fullDocument: true; render: [ :html |
html div
style: 'font-size:11pt;';
with: [
html div
style: 'margin-bottom: 10px;';
with: 'This email is in response to your request to register at ', appName, '.'.
html text: 'Click on the link below within ', timo asString, ' minutes to confirm your registration.'.
html div
style: 'margin-left:20pt;margin-top:10px;margin-bottom:10px;';
with: [
html anchor
url: url;
with: 'Confirm registration'].
html text: 'If the link above is unresponsive, copy and paste the URL below into your browser''s address field to confirm your registration.'.
html div
style: 'margin-left:20pt;margin-top:10px;margin-bottom:10px;';
with: url.
html text: 'If you did not attempt to register for a', appName, ' account then this message was sent in error and should be ignored.']].
"Send the message."
(emailOk := self sendEmailTo: email subject: appName, ' Registration - action required' text: textBody html: htmlBody)
ifFalse: [ Transcript cr; show: url ].
^ emailOk
sendEmailTo: toAddress subject: subj text: textBody html: htmlBody
"Send multi-part MIME email message."
| sem em|
em := TLMailMessage empty.
em addAlternativePart: textBody contents contentType: 'text/plain'.
em addAlternativePart: htmlBody contents contentType: 'text/html'.
sem := em
seasideMailMessageFrom: 'Registrar@' , self ema