More Secure Cookie Auto Login for TF-Login

14 January 2020

I've implemented a more secure cookie-based auto-login in TF-Login 'password' branch to replace the existing simple and insecure cookie scheme.

To load, start with fresh Pharo 7 image:

"First load Seaside."
Metacello new 
    baseline: 'Seaside3'; 
    repository: 'github://SeasideSt/Seaside:v3.3.3/repository'; 
    load. 
    
"Then load TF-Login."
Metacello new 
    baseline: 'TFLogin'; 
    repository: 'github://PierceNg/TF-Login:password/src'; 
    load.

Start Test Runner to run TF-Login's tests. All 78 tests should pass. The class TLTest runs "scripted interactive" tests on the TLTester Seaside application. TLTest's testLoginLogout and testLoginThenAutomaticLogin methods exercise the cookie-based auto-login functionality.

Auto-login is also implemented in the TLTestApp demo Seaside application. Here's a screenshot of the cookie stored in Chromium upon logging into TLTestApp.

TF-Login cookie in Chromium

The original cookie-based auto-login stores username and the SHA1-hashed password in client cookies. This replacement implementation is based on Paragon Initiative's blog post on "remember me" cookies.

PBKDF2-HMAC-SHA1 password hashing for TF-Login

17 November 2019

I've implemented PBKDF2-HMAC-SHA1 in TF-Login 'password' branch to replace the existing simple and insecure SHA1-based password hashing scheme.

To load, start with fresh Pharo 7 image:

"First load Seaside."
Metacello new 
    baseline: 'Seaside3'; 
    repository: 'github://SeasideSt/Seaside:v3.3.3/repository'; 
    load. 
    
"Then load TF-Login."
Metacello new 
    baseline: 'TFLogin'; 
    repository: 'github://PierceNg/TF-Login:password/src'; 
    load.

As originally implemented, TF-Login also supports cookie-based auto-login, which works by storing username and the SHA1-hashed password in client cookies. This scheme is certainly not secure by current standards and can't be used together with PBKDF2-HMAC-SHA1 password hashing.

Possible future work on TF-Login password management:

  • OAuth2, to replace the existing insecure cookie-based auto-login

  • 2FA

[ANN] TF-Login for Seaside 3.3 for Pharo 7

15 November 2019

I've ported TF-Login to Seaside 3.3 and Pharo 7.

To load, start with fresh Pharo 7 image:

Metacello new 
    baseline: 'Seaside3'; 
    repository: 'github://SeasideSt/Seaside:v3.3.3/repository'; 
    load. 
    
Metacello new 
    baseline: 'TFLogin'; 
    repository: 'github://PierceNg/TF-Login:pharo7/src'; 
    load.

The baseline doesn't load Seaside, in case you want to load it into an image that already has Seaside.

Start Test Runner and run the TF-Login tests. All 62 tests should pass.

TF-Login for Seaside

14 November 2019

TF-Login is a package that provides basic user authentication, registration, and account management for Seaside. It was originally developed on Pharo 1 by Tony Fleig, and ported to Pharo 2 by sergio101.

I am attempting to make TF-Login work with Seaside 3.3.x on Pharo 7. For now, I've reorganized the packages and fixed TLMultiFileDatabase to pass its tests.

Collaborators are welcome.

Seaside-REST APIs for RedditSt20

13 January 2018

I've implemented RESTful APIs for RedditSt20 using Seaside-REST. The API endpoints are accessible using Curl:

% curl -X GET -H "Accept: text/json" http://127.0.0.1:8080/redditst20-api/latestLinks
<JSON output in one long line>

The same from Zinc, using NeoJSON to pretty-print:

NeoJSONWriter toStringPretty: 
  (NeoJSONObject fromString: 
    (ZnEasy get: 'http://127.0.0.1:8080/redditst20-api/latestLinks') contents)

Output:

[
    {
        "points" : 0,
        "url" : "http://postgresql.org",
        "created" : "2018-01-13T20:03:56.405254+08:00",
        "title" : "PostgreSQL",
        "id" : 9
    },
    {
        "points" : 0,
        "url" : "http://sqlite.org",
        "created" : "2018-01-13T20:03:56.404526+08:00",
        "title" : "SQLite",
        "id" : 10
    },
    {
        "points" : 0,
        "url" : "http://pharo.org",
        "created" : "2018-01-13T20:03:56.403656+08:00",
        "title" : "Pharo",
        "id" : 11
    }
]

Voting is implemented using POST:

% curl -X POST \
    -H "Accept: text/json" \
    -H "Content-Type: text/json" \
    http://127.0.0.1:8080/redditst20-api/11/voteUp  
{"Updated":"true"}
%                                                                           

Let's check out the highest ranking links:

NeoJSONWriter toStringPretty: 
  (NeoJSONObject fromString: 
    (ZnEasy get: 'http://127.0.0.1:8080/redditst20-api/highestRankingLinks') contents)

Here's the output:

[
    {
        "points" : 1,
        "url" : "http://pharo.org",
        "created" : "2018-01-13T20:03:56.403656+08:00",
        "title" : "Pharo",
        "id" : 11
    },
    {
        "points" : 0,
        "url" : "http://postgresql.org",
        "created" : "2018-01-13T20:03:56.405254+08:00",
        "title" : "PostgreSQL",
        "id" : 9
    },
    {
        "points" : 0,
        "url" : "http://sqlite.org",
        "created" : "2018-01-13T20:03:56.404526+08:00",
        "title" : "SQLite",
        "id" : 10
    }
]

To create a new link, use PUT:

% curl -X PUT \
    -H "Accept: text/json" \
    -H "Content-Type: text/json" \
    -d '{"title":"Glorp", "url":"http://glorp.org"}' \
    http://127.0.0.1:8080/redditst20-api/putLink   
{"Created":true}
%                      

And here it is:

[
    {
        "points" : 0,
        "url" : "http://glorp.org",
        "created" : "2018-01-13T20:15:15.569197+08:00",
        "title" : "Glorp",
        "id" : 12
    },
        ... 
]

Code is on STH. I plan to move it to GH anytime now.

RedditSt20

16 July 2017

I have started a booklet on Pharo, hopefully the first of, um, more than one. It is entitled RedditSt20, on my fork and extension of Sven Van Caekenberghe's excellent "Reddit.st in 10 elegant classes", to cover the following in another 10 or so classes:

  • GlorpSQLite
  • Seaside-MDL
  • username/password authentication
  • logging
  • 2-factor authentication

The book is hosted on Github. Source code is on Smalltalkhub.

The book is being written using Pillar, of course. Note that the Pharo 5 version of Pillar that I downloaded from InriaCI doesn't work - the supporting makefiles aren't able to obtain the output of "./pillar introspect ". Use the Pharo 6 version.

Seaside and Pharo 5

21 May 2017

I just tried loading Seaside into the latest Pharo 6 pre-release image from the Catalog Browser. It loads cleanly. However, the Seaside control panel doesn't start because it uses NewListModel which does not exist in this image:

WAPharoServerAdapterSpecBrowser>>#initializeWidgets

  self instantiateModels: #(
    listModel NewListModel "<== Exists In Pharo 5, not in Pharo 6"
    textModel TextModel
    toolbarModel WAServerAdapterToolbar).

Ok, how about Pharo 5? Using Pharo 50772, which is currently the latest Pharo 5 image, I loaded Seaside 3.1.5 programmatically after visually inspecting ConfigurationOfSeaside3:

Gofer it 
  smalltalkhubUser: 'Seaside' project: 'Seaside31';
  package: 'ConfigurationOfSeaside3';
  load.
(#ConfigurationOfSeaside3 asClass project version: '3.1.5') load.

The load sequence includes messages to String>>#subStrings: which is deprecated. The senders are GRStringTest>>#testSubStrings, JQAjax>>#callback:passengers:, WAAdmin class>>#register:at:in: and WAAdmin class>>#unregister:in:.

Otherwise Seaside 3.1.5 loads cleanly. Test Runner reports 1173 run, 1171 passes and 2 expected failures.

Seaside jQuery Plugin Recipe

15 August 2015

Here's how to create a Seaside wrapper using, in this case, the Rainbow Text jQuery plugin as the example.

0 - Download the source into, say, '/home/pierce/src/jq/Rainbow-Text'.

1 - Create a file library.

WAFileLibrary subclass: #JQRainbowTextDevelopmentLibrary
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

2 - Load the Javascript (and CSS, if any) into the file library:

JQRainbowTextDevelopmentLibrary addFileAt: '/home/pierce/src/jq/Rainbow-Text/rainbow.js'.

3 - Subclass JQWidget.

JQWidget subclass: #JQRainbowText
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

4 - Add instance methods.

method
    ^ 'rainbow'
    
initialize
    super initialize.
    self animate: true;
        animateInterval: 100;
        colors: ('#ff0000' '#f26522' '#fff200' '#00a651' '#28abe2' '#2e3192' '#6868ff')
    
animate: aBooleanOrStringOrNumber
    self optionAt: 'animate' put: aBooleanOrStringOrNumber
    
animateInterval: aNumber
    self optionAt: 'animateInterval' put: aNumber
    
colors: anArray
    self optionAt: 'colors' put: anArray

5 - Create the example component.

WAComponent subclass: #JQRainbowTextExample
    instanceVariableNames: ''
    classVariableNames: ''
    category: 'PN-JQuery-UI'

6 - Add class-side methods.

canBeRoot
    ^ true
    
description
    ^ 'jQuery Rainbow Text'
    
initialize
    | app |
    app := WAAdmin register: self asApplicationAt: '/jqrainbowtext'.
    app addLibrary: JQDevelopmentLibrary;
       addLibrary: JQRainbowTextDevelopmentLibrary.

7 - Add instance methods.

updateRoot: aRoot
    super updateRoot: aRoot.
    aRoot script url: (JQRainbowTextDevelopmentLibrary urlOf: #rainbowJs).
    
renderContentOn: html
    | id |
    id := html nextId.
    html div id: id; with: 'Because Rainbow Text Rules!'.
    html script: (html jQuery id: id) rainbowText

8 - In a workspace, initialize JQRainbowTextExample.

JQRainbowTextExample initialize

9 - Create a method in JQueryInstance.

rainbowText
    ^ self create: JQRainbowText

10 - In the browser, visit 127.0.0.1:8080/jqrainbowtext.

The above is the standalone way. Alternatively, preferably, integrate the plugin into JQueryWidgetBox.

Pattern Language for Relational Databases and Smalltalk

13 April 2014

As I develop NBSQLite3 (tag, repository), I've been surfing the web for material on using Smalltalk with SQL databases effectively. This article, A Pattern Language for Relational Databases and Smalltalk by Kyle Brown and Bruce Whitenack, looks like it is from the late 90s but remains an interesting read.

The Seaside book is a bit light on persistency as is. The Hasso-Platter-Institut Seaside tutorial does present an abstract database wrapper class with these words:

In order to decouple your application from the chosen database system, it is a wise decision to encapsulate the required functionality within a separate database wrapper class. This class provides an interface for the required persistence functionality of the application but leaves the concrete implementation of those functions to its subclasses.

Now Has Dynamic Content

6 April 2012

This blog began life as a set of static pages, generated by a home-grown content management system written in Smalltalk, imaginatively called SmallCMS1.

I've now rewritten SmallCMS1 to serve content dynamically, to support tag linking, like this: SQLite.

Each blog post page now has forward and backward navigational links just above the blog post title.

Rendering code now uses Seaside. More than a year ago, I blogged on that. Seaside now has a cleaner way to render static HTML, or maybe that previous blog post got it wrong. Anyhow, here's how SmallCMS1 uses Seaside's HTML rendering engine:

^ WAHtmlCanvas builder
    fullDocument: true;
    rootBlock: [ :root | self renderSiteRootOn: root ];
    render: [ :html | self renderContentOn: html ].

Similarly, RSS is rendered thusly:

^ RRRssRenderCanvas builder
    fullDocument: true;
    render: [ :rss | self renderRssOn: rss ]).

Seaside and static files with Comanche

10 July 2011

I've had this for a while. Putting it up here in case this is helpful to others. To use, create "htdocs" in "FileDirectory default". Web-wise, "htdocs" is known as "/static". I serve my CSS and JS files from there.

WAComancheAdaptor subclass: #WAComancheStaticAdaptor
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Seaside-Adaptors-Comanche'

WAComancheStaticAdaptor>>createService
    | contentPath dirPath  svc ma |

    contentPath := 'htdocs'.
    dirPath := FileDirectory default fullNameFor: contentPath.
    svc := (HttpService on: self port)
        name: 'seaside-' , self port greaseString.
    ma := ModuleAssembly core.
    ma alias: '/static' to: [
        ma serverRoot: dirPath.
        ma documentRoot: dirPath.
        ma directoryIndex: 'index.html index.htm'.
        ma serveFiles ].
    ma addPlug: self.
    svc plug: ma rootModule.
    ^ svc

Static RSS Rendering with Seaside

7 February 2011

Back to Seaside, it also provides an API to generate RSS. Indeed, that is how this blog's feed is generated:

| doc ctx root rss |
String streamContents: [ :stream |
    doc := WAXmlDocument new initializeWithStream: stream codec: nil.
    ctx := WARenderContext new document: doc.
    root := RRRssRoot new openOn: doc.
    rss title: self siteTitle.
    rss description: self siteSlogan.
    self blog do: [ :ea |
        rss item: [
            rss title: ea title.
            rss author: self siteAuthor.
            rss link: self baseUrl, ea blogUrlPath.
            rss guid: self baseUrl, ea blogUrlPath.
            rss publicationDate: ea timestamp printHttpFormat.
            rss description: ea outputHtml ]].
    root closeOn: doc ]

Static HTML Rendering with Seaside

15 January 2011

One of Seaside's distinctive features is that it generates HTML in Smalltalk, i.e., Seaside provides an API to produce HTML using Smalltalk code. This API can be used for generation of static web pages too:

| doc ctx root html |
String streamContents: [ :stream |
    doc := WAHtmlDocument on: stream.
    ctx := WARenderContext new document: doc.
    root := WAHtmlRoot new.
    root beXhtml11.
    root title: 'Static HTML Generation using Seaside'.
    root writeHeadOn: doc.
    html := WARenderCanvas new initializeWithContext: ctx.      
    html html: '<p>Seaside!</p>'.
    root writeFootOn: doc ]