Raspberry BASIC > Programming Challenges
GUI Login
John Spikowski:
I wanted to try Nim / IUP on Windows 10 and I was able to get the login.nim to compile. I seem to be having a problem getting the image to load.
John Spikowski:
I had a little better luck with ScriptBasic and IUP. I still can't get the image to show even by making a .bmp file of it. The IUP I'm running is pretty old so I'm going to update to 3.28 and see if that helps.
John Spikowski:
It turns out my .bmp image needed to be 24 bit color depth. The conversion from .png to .bmp didn't maintain the transparency of the background.
I also learned how to create Windows executables rather than default console apps.
nim --app:gui c login.nim
I'm going to start a thread on AllBASIC for the Windows / Nim / IUP direction. Maybe Mike will show some interest.
John Spikowski:
ScriptBasic Update
This version adds Jalih's suggestion of maintaining a database of UserID and Password (encrypted) as an extension to the base challenge.
Change Log
[*] Added check for duplicate UserID's. 12/31
[*] Fixed MD5 hex string to be a constant 32 bytes. 12/31
[*] Reset error message to login error after a duplicate user add. 1/1
[/list]
--- Code: Script BASIC ---' ScriptBasic / IUP - Login - JRS IMPORT iup.basIMPORT sqlite.basDECLARE SUB MD5 ALIAS "md5fun" LIB "t" initdb = FILELEN("user.db")usrdb = sqlite::open("user.db")IF initdb = 0 THEN sqlite::execute(usrdb,"CREATE TABLE user (" & _ "UserID VARCHAR NOT NULL, " & _ "Password VARCHAR NOT NULL, " & _ "CONSTRAINT user_pk PRIMARY KEY (UserID));")END IF SUB login_clicked uid = Iup::GetAttribute(uidtxt,"VALUE") IF Iup::GetAttribute(showpwd, "VALUE") = "OFF" THEN pwd = Iup::GetAttribute(pwdtxt1,"VALUE") ELSE pwd = Iup::GetAttribute(pwdtxt2,"VALUE") END IF ' *** NEW USER *** IF newusr_cb = "ON" THEN IF uid <> "" AND pwd <> "" THEN rtnmsg = sqlite::execute(usrdb,"INSERT INTO user VALUES ('" & uid & "', '" & MD5STR(pwd) & "');") IF rtnmsg = 19 THEN Iup::SetAttribute errlbl, "TITLE", "Duplicate User ID" Iup::Show errlbl Iup::SetFocus uidtxt EXIT SUB END IF Iup::Message "Login", "User Added" GOSUB Init_Form EXIT SUB ELSE Iup::Show errlbl Iup::SetFocus uidtxt EXIT SUB END IF END IF ' *** LOGIN VERIFY *** Iup::SetAttribute errlbl, "TITLE", "User ID / Password Incorrect" stmt = sqlite::query(usrdb,"SELECT Password FROM user WHERE UserID = '" & uid & "';") IF sqlite::row(stmt) = sqlite::SQLITE3_ROW THEN sqlite::fetchhash(stmt, col) IF col{"Password"} = MD5STR(pwd) THEN Iup::Hide errlbl Iup::Message "Login", "Login Successful" GOSUB Init_Form EXIT SUB END IF END IF Iup::Show errlbl Iup::SetFocus uidtxt EXIT SUB Init_Form: Iup::Hide errlbl Iup::SetAttribute uidtxt, "VALUE", "" Iup::SetAttribute pwdtxt1, "VALUE", "" Iup::SetAttribute pwdtxt2, "VALUE", "" Iup::SetAttribute showpwd, "VALUE", "OFF" Iup::SetAttribute newusr, "VALUE", "OFF" Iup::SetAttribute zbtxt, "VALUE", "pwdtxt1" Iup::SetAttribute loginbut, "TITLE", "Login" newusr_cb = "OFF" Iup::SetFocus uidtxt RETURNEND SUB FUNCTION MD5STR(txtstr) LOCAL tmpstr, hexstr, x tmpstr = MD5(txtstr) FOR x = 1 TO 16 hexstr &= RIGHT("0" & HEX(ASC(MID(tmpstr, x, 1))), 2) NEXT MD5STR = hexstrEND FUNCTION SUB show_password IF Iup::GetAttribute(showpwd, "VALUE") = "OFF" THEN pwd2 = Iup::GetAttribute(pwdtxt2,"VALUE") Iup::SetAttribute pwdtxt1, "VALUE", pwd2 Iup::SetAttribute zbtxt, "VALUE", "pwdtxt1" ELSE pwd1 = Iup::GetAttribute(pwdtxt1,"VALUE") Iup::SetAttribute pwdtxt2, "VALUE", pwd1 Iup::SetAttribute zbtxt, "VALUE", "pwdtxt2" END IFEND SUB SUB new_user newusr_cb =Iup::GetAttribute(newusr, "VALUE") IF newusr_cb = "ON" THEN Iup::SetAttribute loginbut, "TITLE", "Add User" ELSE Iup::SetAttribute loginbut, "TITLE", "Login" END IFEND SUB SUB Win_exit Iup::ExitLoop = TRUEEND SUB ' *** DIALOG ***Iup::Opendlg = Iup::Create("dialog")Iup::SetAttributes dlg, _ "TITLE=\"Login\", " & _ "SIZE=200x200, " & _ "MAXBOX=NO, " & _ "MINBOX=NO, " & _ "RESIZE=NO, " & _ "DEFAULTENTER=\"loginbut\"" ' *** CONTAINER ***vb = Iup::Create("vbox") ' *** IMAGE ***hb1 = Iup::Create("hbox")piclbl = Iup::Create("label")Iup::SetAttributes piclbl, _ "IMAGE=\"./login.png\", " & _ "EXPAND=HORIZONTAL, " & _ "ALIGNMENT=ACENTER:ATOP"Iup::Append hb1, piclblIup::Append vb, hb1 ' *** ERROR ***hb2 = Iup::Create("hbox")errlbl = Iup::Create("label")Iup::SetAttributes errlbl, _ "TITLE=\"User ID / Password Incorrect\", " & _ "FGCOLOR=\"#ff0000\", " & _ "EXPAND=HORIZONTAL, " & _ "ALIGNMENT=ACENTER"Iup::Hide errlblIup::Append hb2, errlblIup::Append vb, hb2 ' *** ENTRY ***hb3 = Iup::Create("hbox")Iup::SetAttributes hb3, _ "MARGIN=20x10, " & _ "GAP=5"vb1 = Iup::Create("vbox")Iup::SetAttribute vb1, "GAP", "15"uidlbl = Iup::Create("label")Iup::SetAttribute uidlbl, "TITLE", "User ID"Iup::Append vb1, uidlblpwdlbl = Iup::Create("label")Iup::SetAttribute pwdlbl, "TITLE", "Password"Iup::Append vb1, pwdlblIup::Append hb3, vb1vb2 = Iup::Create("vbox")uidtxt = Iup::Create("text")Iup::SetAttribute uidtxt, "SIZE", "85x"Iup::Append vb2, uidtxtpwdtxt1 = Iup::Create("text")Iup::SetAttributes pwdtxt1, _ "PASSWORD=YES, " & _ "SIZE=85x"pwdtxt2 = Iup::Create("text")Iup::SetAttribute pwdtxt2, "SIZE", "85x"Iup::SetHandle "pwdtxt1", pwdtxt1Iup::SetHandle "pwdtxt2", pwdtxt2zbtxt = Iup::Zbox(pwdtxt1, pwdtxt2)Iup::SetHandle "zbtxt", zbtxtIup::Append vb2, zbtxtIup::Append hb3, vb2Iup::Append vb, hb3 ' *** SHOW/HIDE ***hb4 = Iup::Create("hbox")Iup::SetAttributes hb4, _ "MARGIN=35x, " & _ "GAP=40"showpwd = Iup::Create("toggle")Iup::SetAttributes showpwd, _ "TITLE=\"Show Password\", " & _ "CANFOCUS=NO"newusr = Iup::Create("toggle")Iup::SetAttributes newusr, _ "TITLE=\"New User\", " & _ "CANFOCUS=NO"Iup::Append hb4, showpwdIup::Append hb4, newusrIup::Append vb, hb4 ' *** LOGIN ***hb5 = Iup::Create("hbox")Iup::SetAttribute hb5, "MARGIN", "40x10"loginbut = Iup::Create("button")Iup::SetAttributes loginbut, _ "TITLE=\"Login\", " & _ "EXPAND=HORIZONTAL"Iup::SetHandle "loginbut", loginbutiup::Append hb5, loginbutiup::Append vb, hb5 ' *** ATTACH CONTAINER ***iup::Append dlg, vb ' *** CALLBACKS ***Iup::SetCallback dlg,"CLOSE_CB",ADDRESS(Win_exit())Iup::SetCallback loginbut, "ACTION", ADDRESS(login_clicked())Iup::SetCallback showpwd, "ACTION", ADDRESS(show_password())Iup::SetCallback newusr, "ACTION", ADDRESS(new_user()) ' *** PROCESS ***' Iup::Show(Iup::LayoutDialog(dlg))Iup::Show(dlg)Iup::MainLoopIup::Closesqlite::Close(usrdb)
ubuntu@rpi4b:~/guilc/sb-dev$ scriba showdb.sb
John - FAF43E604E6A820CAAAB88BF7E2328C7
AIR - 99B69173F38EB9F24FA4C05B4BE47CD7
Jalih - 78020D21C627349463D88142DC51F9B9
Peter - 0C5213856A47300D1E6D30CA00FAFFC1
ubuntu@rpi4b:~/guilc/sb-dev$
jalih:
Here is a updated 8th login dialog with database support for retrieving stored user data.
I currently generate 32 byte random buffer from the cryptographically strong random source and convert it to hex string for the salt. Key for user chosen password is generated with PBKDF2 algorithm using previously randomly generated salt and 10000 iterations as parameters. Username, key and salt is then stored into database.
--- Code: ---requires gui
var userdb
defer: auth
{
kind: "edit",
bounds: "edit1.left, lbl2.top, parent.width-20, top+24",
name: "edit2",
max-text: 32,
password-char: "*",
return-pressed: ' auth ,
text-changed: ( "lbl0" g:child "" g:text drop )
} g:new constant edit2-hide-passwd
{
kind: "edit",
bounds: "edit1.left, lbl2.top, parent.width-20, top+24",
name: "edit2",
max-text: 32,
password-char: "",
return-pressed: ' auth ,
text-changed: ( "lbl0" g:child "" g:text drop )
} g:new constant edit2-show-passwd
: alternator \ a -- a[0]
a:shift dup >r
a:push drop r> ;
[ ` edit2-show-passwd ` , ` edit2-hide-passwd ` ] ' alternator curry: next-state
\ auth states
0 constant NOT-FOUND
1 constant USER-PASSWD-MATCH
2 constant USER-PASSWD-FAIL
: auth-from-db \ id passwd -- state
s:len 0 n:= if
2drop
USER-PASSWD-FAIL
;;
then
>r >r
userdb @ "by-id" r> 1 a:close db:bind-exec[]
a:len 0 n:> if
a:open
"salt" m:@ r> swap 10000 cr:genkey
swap "key" m:@ nip b:= if
USER-PASSWD-MATCH
else
USER-PASSWD-FAIL
then
else
drop
rdrop
NOT-FOUND
then
nip ;
: authenticate
"edit2" g:child g:text? >r
"edit1" g:child g:text? r> auth-from-db
[ ( "lbl0" g:child "User not found!" g:text ) ,
( "Authenticated!" . cr bye ) ,
( "lbl0" g:child "User and password don't match!" g:text ) ] swap
caseof drop ;
' authenticate w:is auth
: init
"mytest.db" f:slurp "No database" thrownull
db:open
\ make a prepared statement
"SELECT * FROM user WHERE id=? LIMIT 1" "by-id" db:prep-name
userdb ! "edit1" g:child g:focus ;
{
kind: "win",
buttons: 5,
native-title-bar: false,
title: "Login",
wide: 520,
high: 220,
resizable: false,
center: true,
init: ' init ,
children:
[
{
kind: "box",
name: "frame",
bounds: "0, 0, parent.width, parent.height",
bg: "gray",
children:
[
{
kind: "image",
bounds: "parent.left+10, parent.top+10, left+128, top+128",
img: "8thlogo.png",
name: "logo"
},
{
kind: "label",
fg: "red",
font: 20,
label: "",
bounds: "logo.right+20, parent.top+10, parent.width-10, top+24 ",
justify: ["hcenter"],
name: "lbl0"
},
{
kind: "label",
label: "Username:",
bounds: "logo.right+20, lbl0.bottom+20, left+80, top+24 ",
name: "lbl1"
},
{
kind: "edit",
bounds: "lbl1.right+10, lbl1.top, parent.width-20, top+24",
name: "edit1",
max-text: 32,
return-pressed: ( "edit2" g:child g:focus drop ),
text-changed: ( "lbl0" g:child "" g:text drop )
},
{
kind: "label",
label: "Password:",
bounds: "lbl1.left, lbl1.bottom+10, left+80, top+24",
name: "lbl2"
},
{
kind: "edit",
bounds: "edit1.left, lbl2.top, parent.width-20, top+24",
name: "edit2",
max-text: 32,
password-char: "*",
return-pressed: ' authenticate ,
text-changed: ( "lbl0" g:child "" g:text drop )
},
{
kind: "toggle",
label: "Show password",
adjustwidth: true,
bounds: "edit1.left, lbl2.bottom+20, left+100, top+24",
name: "toggle",
click: ( "edit2" g:child g:text? >r "frame" g:child "edit2" g:-child next-state g:+child "edit2" g:child r> g:text drop )
},
{
kind: "btn",
label: "Login",
bg: "darkgray",
bounds: "lbl2.left, lbl2.bottom+60, edit1.right, top+30",
name: "button",
tooltip: "Login to account",
click: ' authenticate
}
]
}
]
} g:new var, gui
: app:main
( userdb @ db:close ) onexit ;
--- End code ---
I currently manually generate user database with the following piece of 8th code:
--- Code: ---"Creating a database and adding data:\n" .
\ remove previous db:
"mytest.db" f:rm
\ open a database:
"mytest.db" db:open "Could not create database" thrownull nip
\ create a table
"CREATE TABLE user (id PRIMARY KEY, key, salt)" db:exec
\ make a prepared statement
"INSERT INTO user VALUES (?, ?, ?);" "insert" db:prep-name
: build-params \ id passwd -- a
cr:uuid >r
r@ 10000 cr:genkey
r>
3 a:close ;
\ insert into table using a parameterized statement
\ You could use t:err? to get map of error information
"insert" "jalih" "pa$$w0rd!" build-params null db:bind-exec
"insert" "guest" "guest" build-params null db:bind-exec
"insert" "john" "hard-passwd" build-params null db:bind-exec
"insert" "air" "crack-this" build-params null db:bind-exec
\ and close the database
"All done!\n" .
db:close
bye
--- End code ---
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version