PDA

View Full Version : A simple login script



kesteb
24 Apr 2008, 2:26 PM
I am attempting a simple login script and decided to extend Obeservable and make it event driven. I already have a callback based version that runs OK. The error that I am getting is "this.fireEvent is not a function" when getSalt() is called.

Any help would be appreciated. Here is the code, I suspect that I am doing something dump.




Ext.SSL_SECURE_URL="/static/ext-2.0.2/resources/images/default/s.gif";
Ext.BLANK_IMAGE_URL="/static/ext-2.0.2/resources/images/default/s.gif";

Login = function(config) {

Ext.apply(this, config);

this.addEvents({
'announce': true,
'denied': true,
'errors': true,
'login': true,
'loadapp': true,
'salt': true,
'welcome': true
});

};

Ext.extend(Login, Ext.util.Observable, {

salt: '',
app_rootp: '/',

init: function() {

this.fireEvent('salt');

},

getText: function(url, title, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
success: function(response, opts) {
var text = response.responseText.trim();
if ((text.length > 0) && (text != "\"\"")) {
displayText(title, text, event);
} else { this.fireEvent(event) }
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

function displayText(title, text, event) {

var text_panel = new Ext.Panel({
baseCls: 'x-plain',
id: title + '-text',
region: 'center',
html: text
});

var text_dialog = new Ext.Window({
buttons: [{
handler: function() {
text_dialog.destroy(true);
this.fireEvent(event);
},
scope: Login,
text: 'OK'
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: title + '-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [ text_panel ],
title: title,
width: 530
});

text_dialog.show();

}

},

getSalt: function(url, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
success: function(response, opts) {
var datum = Ext.decode(response.responseText);
this.salt = datum.data[0].salt;
this.fireEvent(event);
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

getAuth: function(url) {

Ext.QuickTips.init();

var logo_panel = new Ext.Panel({
baseCls: 'x-plain',
id: 'login-logo',
region: 'center'
});

var form_panel = new Ext.form.FormPanel({
baseCls: 'x-plain',
defaults: {
width: 200
},
defaultType: 'textfield',
frame: false,
height: 70,
id: 'login-form',
items: [{
fieldLabel: 'Username',
inputType: 'text',
name: 'username',
value: 'demo'
},{
fieldLabel: 'Password',
inputType: 'password',
name: 'password',
value: 'demo'
}],
labelWidth: 120,
region: 'south',
url: url
});

var dialog = new Ext.Window({
buttons: [{
handler: form_submit,
scope: this,
text: 'Login'
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: 'login-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [
logo_panel,
form_panel
],
title: 'Login',
width: 530
});

var form = form_panel.getForm();
dialog.show();

function form_submit() {

var password = form.findField('password');
var text = password.getValue();
var hash = hex_hmac_md5(this.salt, text);
password.setValue(hash);

form.submit({
waitMsg: 'Authenticating...',
reset: true,
success: function(form, action) {
if (action.result.success) {
dialog.destroy(true);
var event = action.result.action.path;
this.fireEvent(event);
} else {
this.fireEvent('errors', 'Authentication Error', 'Unable to find username');
}
},
failure: function(form, action) {
this.fireEvent('errors', 'Server Error', 'Unable to communicate with server');
}
});

}

}

});

Ext.onReady(function() {

var login = new Login();

login.on({
'announce': {
fn: function() {
login.getText('/login/announce', 'Announcements', 'loadapp');
},
scope: this
},
'denied': {
fn: function() {
login.app_rootp = '/';
login.getText('/login/denied', 'Denied', 'login');
},
scope: this
},
'login': {
fn: function() {
login.getAuth('/login');
},
scope: this
},
'loadapp': {
fn: function() {
window.location = login.app_rootp;
},
scope: this
},
'salt': {
fn: function() {
login.getSalt('/login/salt', 'welcome', 'welcome');
},
scope: this
},
'welcome': {
fn: function() {
login.getText('/login/welcome', 'Welcome', 'login');
},
scope: this
},
'errors': {
fn: function(title, text) {
Ext.Msg.alert(title, text);
login.fireEvent('login');
},
scope: this
}
});

login.init();

});

evant
24 Apr 2008, 4:56 PM
In general, when you pass function references (callbacks) around, they will execute in a specific scope. If you don't specify a scope, they will execute under the global 'window' object.

As such, you need to specify the scope in which your callbacks execute in.



getSalt: function(url, event) {
Ext.Ajax.request({
url: url,
method: 'GET',
scope: this,
success: function(response, opts) {
var datum = Ext.decode(response.responseText);
this.salt = datum.data[0].salt;
this.fireEvent(event);
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

kesteb
25 Apr 2008, 1:50 PM
Thanks for the help. That fixed the problem. Everything was working OK.

So I decided to go one more step. The following code starts to use predefined classes. In Login.TextDialog(), I add a "okclicked" event. In the button defination, I tell it to use "okclicked" as the click event. In the main code I wait on the "okclicked" event to do further processing. The problem is that "okclicked" never seems to fire. Not sure why, or maybe there is better way of doing this. New code follows:




Ext.SSL_SECURE_URL="/static/ext-2.0.2/resources/images/default/s.gif";
Ext.BLANK_IMAGE_URL="/static/ext-2.0.2/resources/images/default/s.gif";

Login = function(config) {

this.app_rootp = '/';

Ext.apply(this, config);

this.addEvents({
'announce': true,
'denied': true,
'errors': true,
'login': true,
'loadapp': true,
'salt': true,
'newpwd': true,
'welcome': true
});

};

Login.LogoPanel = Ext.extend(Ext.Panel, {

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
id: 'login-logo',
region: 'center'
});

Login.LogoPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.FormPanel = Ext.extend(Ext.form.FormPanel, {

url: '',

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
defaults: {
width: 200
},
defaultType: 'textfield',
frame: false,
height: 70,
id: 'login-form',
items: [{
fieldLabel: 'Username',
inputType: 'text',
name: 'username',
value: 'demo'
},{
fieldLabel: 'Password',
inputType: 'password',
name: 'password',
value: 'demo'
}],
labelWidth: 120,
region: 'south',
url: this.url
});

Login.FormPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.TextPanel = Ext.extend(Ext.Panel, {

title: "",
text: "",

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
id: this.title + '-text',
region: 'center',
html: this.text
});

Login.TextPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.TextDialog = Ext.extend(Ext.Window, {

title: "",
text: "",

initComponent: function() {

this.addEvents({
'okclicked': true
});

var text_panel = new Login.TextPanel({title: this.title,
text: this.text});

Ext.apply(this, {
buttons: [{
text: 'OK',
clickEvent: 'okclicked'
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: this.title + '-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [ text_panel ],
title: this.title,
width: 530
});

Login.TextDialog.superclass.initComponent.apply(this, arguments);

}

});

Ext.extend(Login, Ext.util.Observable, {

salt: '',

init: function() {

this.fireEvent('salt');

},

getText: function(url, title, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
scope: this,
success: function(response, opts) {
var text = response.responseText.trim();
if ((text.length > 0) && (text != "\"\"")) {
var text_dialog = new Login.TextDialog({title: title,
text: text});
text_dialog.show();
text_dialog.on('okclicked', function() {
text_dialog.destroy(true);
this.fireEvent(event);
}, this);
} else { this.fireEvent(event) }
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

getSalt: function(url, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
scope: this,
success: function(response, opts) {
var datum = Ext.decode(response.responseText);
this.salt = datum.data[0].salt;
this.fireEvent(event);
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

getAuth: function(url) {

Ext.QuickTips.init();

var logo_panel = new Login.LogoPanel();
var form_panel = new Login.FormPanel({url: url});
var login_dialog = new Ext.Window({
buttons: [{
handler: form_submit,
scope: this,
text: 'Login'
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: 'login-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [
logo_panel,
form_panel
],
title: 'Login',
width: 530
});

var form = form_panel.getForm();
login_dialog.show();

function form_submit() {

var password = form.findField('password');
var text = password.getValue();
var hash = hex_hmac_md5(this.salt, text);
password.setValue(hash);

form.submit({
waitMsg: 'Authenticating...',
reset: true,
scope: this,
success: function(form, action) {
login_dialog.hide(true);
var event = action.result.action.event;
this.fireEvent(event);
},
failure: function(form, action) {
this.fireEvent('errors', 'Authentication Error', 'Unable to authenicate, please try again');
}
});

}

}

});

Ext.onReady(function() {

var login = new Login();

login.on({
'announce': {
fn: function() {
login.getText('/login/announce', 'Announcements', 'loadapp');
},
scope: this
},
'denied': {
fn: function() {
login.getText('/login/denied', 'Denied', 'login');
},
scope: this
},
'login': {
fn: function() {
login.getAuth('/login');
},
scope: this
},
'loadapp': {
fn: function() {
window.location = login.app_rootp;
},
scope: this
},
'salt': {
fn: function() {
login.getSalt('/login/salt', 'welcome', 'welcome');
},
scope: this
},
'welcome': {
fn: function() {
login.getText('/login/welcome', 'Welcome', 'login');
},
scope: this
},
'errors': {
fn: function(title, text) {
Ext.Msg.show({
title: title,
msg: text,
buttons: Ext.Msg.OK,
width: 440
});
login.fireEvent('login');
},
scope: this
}
});

login.init();

});

evant
25 Apr 2008, 7:42 PM
You're using the wrong property. clickEvent refers to what triggers the click event (single click, double click).

You're looking for handler (http://extjs.com/deploy/ext-2.1/docs/?class=Ext.Button&member=handler) and scope (http://extjs.com/deploy/ext-2.1/docs/?class=Ext.Button&member=scope).

kesteb
28 Apr 2008, 7:48 AM
Hmmm, the documentation for "clickEvent" leads me to believe otherwise. The documentation team may want to clairify how this property works.

I changed the button defination in Login.TextDialog() to use a handler and it now works as expected. Now on to abstracting out the Login dialog.

evant
28 Apr 2008, 7:51 AM
I think it's reasonably clear:



clickEvent : String
The type of event to map to the button's event handler (defaults to 'click')


However, I guess it couldn't hurt to expand on it.

kesteb
28 Apr 2008, 10:33 AM
And yet, I took that to mean that I could define an event and have the "action" trgger the defined event. So while it may be clear to you, it wasn't to me.

Here is the final version of the code.




Ext.SSL_SECURE_URL="/static/ext-2.0.2/resources/images/default/s.gif";
Ext.BLANK_IMAGE_URL="/static/ext-2.0.2/resources/images/default/s.gif";

Login = function(config) {

this.app_rootp = '/';

Ext.apply(this, config);

this.addEvents({
'announce': true,
'denied': true,
'errors': true,
'login': true,
'loadapp': true,
'salt': true,
'newpwd': true,
'welcome': true
});

};

Login.LogoPanel = Ext.extend(Ext.Panel, {

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
id: 'login-logo',
region: 'center'
});

Login.LogoPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.FormPanel = Ext.extend(Ext.form.FormPanel, {

url: '',

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
defaults: {
width: 200
},
defaultType: 'textfield',
frame: false,
height: 70,
id: 'login-form',
items: [{
fieldLabel: 'Username',
inputType: 'text',
name: 'username',
value: 'demo'
},{
fieldLabel: 'Password',
inputType: 'password',
name: 'password',
value: 'demo'
}],
labelWidth: 120,
region: 'south',
url: this.url
});

Login.FormPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.TextPanel = Ext.extend(Ext.Panel, {

title: "",
text: "",

initComponent: function() {

Ext.apply(this, {
baseCls: 'x-plain',
id: this.title + '-text',
region: 'center',
html: this.text
});

Login.TextPanel.superclass.initComponent.apply(this, arguments);

}

});

Login.TextDialog = Ext.extend(Ext.Window, {

text: "",
title: "",
textPanel: null,

initComponent: function() {

this.addEvents({
'okclicked': true
});

this.textPanel = new Login.TextPanel({title: this.title,
text: this.text});

Ext.apply(this, {
buttons: [{
text: 'OK',
handler: function() {
this.fireEvent('okclicked');
},
scope: this
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: this.title + '-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [ this.textPanel ],
title: this.title,
width: 530
});

Login.TextDialog.superclass.initComponent.apply(this, arguments);

}

});

Login.LoginDialog = Ext.extend(Ext.Window, {

url: "",
salt: "",
logoPanel: null,
formPanel: null,

formSubmit: function() {

var form = this.formPanel.getForm();
var password = form.findField('password');
var text = password.getValue();
var hash = hex_hmac_md5(this.salt, text);
password.setValue(hash);

form.submit({
waitMsg: 'Authenticating...',
reset: true,
scope: this,
success: function(form, action) {
this.fireEvent('formsuccess', action.result.action.event);
},
failure: function(form, action) {
this.fireEvent('errors', 'Authentication Error', 'Unable to authenicate, please try again');
}
});

},

initComponent: function() {

Ext.QuickTips.init();

this.logoPanel = new Login.LogoPanel();
this.formPanel = new Login.FormPanel({url: this.url});

this.addEvents({
'formsuccess': true
});

Ext.apply(this, {
buttons: [{
handler: this.formSubmit,
scope: this,
text: 'Login'
}],
buttonAlign: 'right',
closable: false,
draggable: true,
height: 250,
id: 'login-win',
layout: 'border',
minHeight: 250,
minWidth: 530,
plain: false,
resizable: true,
items: [
this.logoPanel,
this.formPanel
],
title: 'Login',
width: 530
});

Login.LoginDialog.superclass.initComponent.apply(this, arguments);

}

});

Ext.extend(Login, Ext.util.Observable, {

salt: '',

init: function() {

this.fireEvent('salt');

},

getText: function(url, title, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
scope: this,
success: function(response, opts) {
var text = response.responseText.trim();
if ((text.length > 0) && (text != "\"\"")) {
var text_dialog = new Login.TextDialog({title: title,
text: text});
text_dialog.show();
text_dialog.on('okclicked', function() {
text_dialog.destroy(true);
this.fireEvent(event);
}, this);
} else { this.fireEvent(event) }
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

getSalt: function(url, event) {

Ext.Ajax.request({
url: url,
method: 'GET',
scope: this,
success: function(response, opts) {
var datum = Ext.decode(response.responseText);
this.salt = datum.data[0].salt;
this.fireEvent(event);
},
failure: function(response, opts) {
this.fireEvent('errors', 'Server Error', response.responseText);
}
});

},

getAuth: function(url, salt) {

login_dialog = new Login.LoginDialog({url: url, salt: salt});
login_dialog.show();

login_dialog.on('formsuccess', function(event) {
login_dialog.hide(true);
this.fireEvent(event);
}, this);

}

});

Ext.onReady(function() {

var login = new Login();

login.on({
'announce': {
fn: function() {
login.getText('/login/announce', 'Announcements', 'loadapp');
},
scope: this
},
'denied': {
fn: function() {
login.getText('/login/denied', 'Denied', 'login');
},
scope: this
},
'login': {
fn: function() {
login.getAuth('/login', login.salt);
},
scope: this
},
'loadapp': {
fn: function() {
window.location = login.app_rootp;
},
scope: this
},
'salt': {
fn: function() {
login.getSalt('/login/salt', 'welcome');
},
scope: this
},
'welcome': {
fn: function() {
login.getText('/login/welcome', 'Welcome', 'login');
},
scope: this
},
'errors': {
fn: function(title, text) {
Ext.Msg.show({
title: title,
msg: text,
buttons: Ext.Msg.OK,
width: 440
});
login.fireEvent('login');
},
scope: this
}
});

login.init();

});






This code works as follows:

1) The backend checks if the current session is authenticated. If the session is not
authenticated it will issue a 302 redirect to /login.

2) This will load the approbiate html that includes the above script. At this point the script
takes over and controls the authentication process.

3) The script does the following actions:

a) Send a request to /login/salt. This returns the following data structure.

{
success: true,
count: 1,
data: [
{salt: xxxxxx}
]
}

The salt is used to hash the password. In this case it is using MD5.

b) Send a request to /login/welcome. This returns either a html fragment or a "". If a ""
then nothing is displayed. Wait for the enduser to ckick the "OK" button.

c) Display a login form. This form will submit the username and hashed password as
standard POST items. The backend will then veifiy and send back an "event". The
event can vary, but the current backend only knows "denied" or "announce".

i) If the event is "denied", then send a request to '/login/denied'. This will load a
suitable html fragment to be displayed. When the enduser clicks the "OK" button
the login form is shown again.

ii) If the event is "announce" then send a request to "/login/annonce". This will load a
suitable html fragment to be displayed. When the enduser clicks the "OK" button
the script will recirect to "/" to load the application.



This code is a work in progress and the current working version can be found at:

http://snv.kesteb.us/repos/Test/trunk

Thanks for the help.