2016-09-05 16:52:13 +01:00
2017-01-31 15:15:46 +00:00
#!/usr/bin/env perl -w
2016-09-05 16:52:13 +01:00
# NOT READY FOR PRODUCTION
use Mojolicious::Lite ;
use Data::UUID ;
use Devel::Dwarn ;
use Mojo::JSON ;
2016-09-15 16:56:04 +01:00
use Data::Dumper ;
2017-01-31 15:15:46 +00:00
use Email::Valid ;
use ORM::Date ;
use Authen::Passphrase::BlowfishCrypt ;
2016-09-05 16:52:13 +01:00
# connect to database
use DBI ;
2017-01-31 15:15:46 +00:00
my $ config = plugin 'Config' ;
2016-09-05 16:52:13 +01:00
my $ dbh = DBI - > connect ( $ config - > { dsn } , $ config - > { user } , $ config - > { pass } ) or die "Could not connect" ;
2017-01-31 15:15:46 +00:00
$ dbh - > do ( "PRAGMA foreign_keys = ON" ) ;
$ dbh - > do ( "PRAGMA secure_delete = ON" ) ;
2016-09-05 16:52:13 +01:00
Dwarn $ config ;
# shortcut for use in template
helper db = > sub { $ dbh } ;
any '/' = > sub {
my $ self = shift ;
2016-09-19 12:51:22 +01:00
$ self - > render ( text = > 'If you are seeing this, then the server is running.' ) ;
2016-09-05 16:52:13 +01:00
} ;
post '/upload' = > sub {
my $ self = shift ;
# Fetch parameters to write to DB
2016-09-05 17:52:45 +01:00
my $ key = $ self - > param ( 'key' ) ;
2016-09-05 16:52:13 +01:00
# This will include an if function to see if key matches
2016-09-05 17:52:45 +01:00
unless ( $ key eq $ config - > { key } ) {
return $ self - > render ( json = > { success = > Mojo::JSON - > false } , status = > 403 ) ;
}
2016-09-05 16:52:13 +01:00
my $ username = $ self - > param ( 'username' ) ;
my $ company = $ self - > param ( 'company' ) ;
my $ currency = $ self - > param ( 'currency' ) ;
my $ file = $ self - > req - > upload ( 'file' ) ;
# Get image type and check extension
my $ headers = $ file - > headers - > content_type ;
# Is content type wrong?
if ( $ headers ne 'image/jpeg' ) {
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Wrong image extension!' ,
} ) ;
} ;
# Rewrite header data
my $ ext = '.jpg' ;
my $ uuid = Data::UUID - > new - > create_str ;
my $ filename = $ uuid . $ ext ;
# send photo to image folder on server
$ file - > move_to ( 'images/' . $ filename ) ;
# send data to foodloop db
my $ insert = $ self - > db - > prepare ( 'INSERT INTO foodloop (username, company, currency, filename) VALUES (?,?,?,?)' ) ;
$ insert - > execute ( $ username , $ company , $ currency , $ filename ) ;
2016-09-05 17:52:45 +01:00
$ self - > render ( json = > { success = > Mojo::JSON - > true } ) ;
2016-09-05 16:52:13 +01:00
$ self - > render ( text = > 'It did not kaboom!' ) ;
} ;
post '/register' = > sub {
my $ self = shift ;
my $ json = $ self - > req - > json ;
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( "\n\nStart of register" ) ;
2017-01-31 15:15:46 +00:00
$ self - > app - > log - > debug ( "JSON: " . Dumper $ json ) ;
2016-09-05 16:52:13 +01:00
2017-02-01 12:07:51 +00:00
if ( ! defined $ json ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No json sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2017-01-31 15:15:46 +00:00
my $ token = $ json - > { token } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ token ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
2017-02-01 12:07:51 +00:00
message = > 'No token sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( ! $ self - > is_token_unused ( $ token ) ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Token invalid or has been used.' ,
2017-01-31 15:15:46 +00:00
} ,
status = > 401 , ) ; #Unauthorized
}
2016-09-05 16:52:13 +01:00
2017-01-31 15:15:46 +00:00
my $ username = $ json - > { username } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ username ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No username sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( $ username eq '' ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Username cannot be blank.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( ! ( $ username =~ m/^[A-Za-z0-9]+$/ ) ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Username can only be A-Z, a-z and 0-9 characters.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( $ self - > does_username_exist ( $ username ) ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Username exists.' ,
} ,
status = > 403 , ) ; #Forbidden
}
2016-09-15 18:08:33 +01:00
2017-01-31 15:15:46 +00:00
my $ email = $ json - > { email } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ email ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No email sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( ! Email::Valid - > address ( $ email ) ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2016-09-05 16:52:13 +01:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
2017-01-31 15:15:46 +00:00
message = > 'Email is invalid.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( $ self - > does_email_exist ( $ email ) ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2016-09-05 16:52:13 +01:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
2017-01-31 15:15:46 +00:00
message = > 'Email exists.' ,
} ,
status = > 403 , ) ; #Forbidden
2016-09-05 16:52:13 +01:00
}
2017-01-31 15:15:46 +00:00
#TODO test to see if post code is valid.
my $ postcode = $ json - > { postcode } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ postcode ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No postcode sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2017-01-31 15:15:46 +00:00
#TODO should we enforce password requirements.
my $ password = $ json - > { password } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ password ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No password sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2017-01-31 15:15:46 +00:00
my $ hashedPassword = $ self - > generate_hashed_password ( $ password ) ;
my $ secondsTime = time ( ) ;
my $ date = ORM::Date - > new_epoch ( $ secondsTime ) - > mysql_date ;
my $ usertype = $ json - > { usertype } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ usertype ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No usertype sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
elsif ( $ usertype eq 'customer' ) {
$ self - > app - > log - > debug ( 'Path: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
my $ age = $ json - > { age } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ age ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No age sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2017-01-31 15:15:46 +00:00
my $ ageForeignKey = $ self - > get_age_foreign_key ( $ age ) ;
if ( ! defined $ ageForeignKey ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Age range is invalid.' ,
} ,
status = > 400 , ) ; #Malformed request
}
#TODO UNTESTED as it's hard to simulate.
#Token is no longer valid race condition.
if ( ! $ self - > set_token_as_used ( $ token ) ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Token no longer is accepted.' ,
} ,
status = > 500 , ) ; #Internal server error. Racecondition
}
my ( $ idToUse ) = $ self - > db - > selectrow_array ( "SELECT MAX(CustomerId) FROM Customers" ) ;
if ( defined $ idToUse ) {
$ idToUse + + ;
}
else {
$ idToUse = 1 ;
}
#TODO Race condition here.
my $ insertCustomer = $ self - > db - > prepare ( "INSERT INTO Customers (CustomerId, UserName, AgeRange_FK, PostCode) VALUES (?, ?, ?, ?)" ) ;
my $ rowsInsertedCustomer = $ insertCustomer - > execute ( $ idToUse , $ username , $ ageForeignKey , $ postcode ) ;
my $ insertUser = $ self - > db - > prepare ( "INSERT INTO Users (CustomerId_FK, Email, JoinDate, HashedPassword) VALUES (?, ?, ?, ?)" ) ;
my $ rowsInsertedUser = $ insertUser - > execute ( $ idToUse , $ email , $ date , $ hashedPassword ) ;
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Success: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > { success = > Mojo::JSON - > true } ) ;
}
elsif ( $ usertype eq 'organisation' ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
#TODO validation on the address. Or perhaps add the organisation to a "to be inspected" list then manually check them.
my $ fullAddress = $ json - > { fulladdress } ;
2017-02-01 12:07:51 +00:00
if ( ! defined $ fullAddress ) {
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'No fulladdress sent.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2017-01-31 15:15:46 +00:00
#TODO UNTESTED as it's hard to simulate.
#Token is no longer valid race condition.
if ( ! $ self - > set_token_as_used ( $ token ) ) {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Token no longer is accepted.' ,
} ,
status = > 500 , ) ; #Internal server error. Racecondition
}
my $ idToUse = $ self - > db - > selectrow_array ( "SELECT MAX(OrganisationalId) FROM Organisations" ) ;
if ( defined $ idToUse ) {
$ idToUse + + ;
}
else {
$ idToUse = 1 ;
}
#TODO Race condition here.
my $ insertOrganisation = $ self - > db - > prepare ( "INSERT INTO Organisations (OrganisationalId, Name, FullAddress, PostCode) VALUES (?, ?, ?, ?)" ) ;
my $ rowsInsertedOrganisation = $ insertOrganisation - > execute ( $ idToUse , $ username , $ fullAddress , $ postcode ) ;
my $ insertUser = $ self - > db - > prepare ( "INSERT INTO Users (OrganisationalId_FK, Email, JoinDate, HashedPassword) VALUES (?, ?, ?, ?)" ) ;
my $ rowsInsertedUser = $ insertUser - > execute ( $ idToUse , $ email , $ date , $ hashedPassword ) ;
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Success: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > { success = > Mojo::JSON - > true } ) ;
}
else {
2017-02-01 12:07:51 +00:00
$ self - > app - > log - > debug ( 'Path Error: file:' . __FILE__ . ', line: ' . __LINE__ ) ;
2017-01-31 15:15:46 +00:00
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > '"usertype" is invalid.' ,
} ,
status = > 400 , ) ; #Malformed request
}
2016-09-05 16:52:13 +01:00
} ;
post '/edit' = > sub {
my $ self = shift ;
my $ json = $ self - > req - > json ;
my $ account = $ self - > get_account_by_username ( $ json - > { username } ) ;
unless ( defined $ account ) {
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Username not recognised, has your token expired?' ,
} ) ;
# PLUG SECURITY HOLE
} elsif ( $ account - > { keyused } ne 't' ) {
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Token has not been used yet!' ,
} ) ;
}
2016-09-26 17:36:52 +01:00
my $ insert = $ self - > db - > prepare ( "UPDATE accounts SET fullname = ?, postcode = ?, age = ?, gender = ?, WHERE username = ?" ) ;
2016-09-05 16:52:13 +01:00
$ insert - > execute (
2016-09-26 17:36:52 +01:00
@ { $ json } { qw/ fullname postcode age gender / } , $ account - > { username } ,
2016-09-05 16:52:13 +01:00
) ;
$ self - > render ( json = > { success = > Mojo::JSON - > true } ) ;
} ;
2016-10-31 16:19:58 +00:00
post '/fetchuser' = > sub {
my $ self = shift ;
2016-09-05 16:52:13 +01:00
2016-10-31 16:19:58 +00:00
my $ json = $ self - > req - > json ;
my $ account = $ self - > get_account_by_username ( $ json - > { username } ) ;
unless ( defined $ account ) {
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Username not recognised, has your token expired?' ,
} ) ;
# PLUG SECURITY HOLE
} elsif ( $ account - > { keyused } ne 't' ) {
return $ self - > render ( json = > {
success = > Mojo::JSON - > false ,
message = > 'Token has not been used yet!' ,
} ) ;
}
# Add stuff to send back to user below here!
$ self - > render ( json = > {
success = > Mojo::JSON - > true ,
} ) ;
2016-09-08 17:29:56 +01:00
} ;
2016-09-05 16:52:13 +01:00
2016-09-08 17:29:56 +01:00
helper get_account_by_username = > sub {
2016-09-05 16:52:13 +01:00
my ( $ self , $ username ) = @ _ ;
return $ self - > db - > selectrow_hashref (
2016-09-12 12:27:07 +01:00
"SELECT keyused, username FROM accounts WHERE username = ?" ,
2016-09-05 16:52:13 +01:00
{ } ,
$ username ,
) ;
2016-09-08 17:29:56 +01:00
} ;
2016-09-05 16:52:13 +01:00
2017-01-31 15:15:46 +00:00
#Return true if and only if the token exists and has not been used.
helper is_token_unused = > sub {
my ( $ self , $ token ) = @ _ ;
my ( $ out ) = $ self - > db - > selectrow_array ( "SELECT COUNT(TokenId) FROM Tokens WHERE TokenName = ? AND Used = 0" , undef , ( $ token ) ) ;
return $ out != 0 ;
} ;
helper get_age_foreign_key = > sub {
my ( $ self , $ ageString ) = @ _ ;
my ( $ out ) = $ self - > db - > selectrow_array (
"SELECT AgeRangeId FROM AgeRanges WHERE AgeRangeString = ?" ,
{ } ,
$ ageString ,
) ;
return $ out ;
} ;
helper does_username_exist = > sub {
my ( $ self , $ username ) = @ _ ;
my ( $ out ) = $ self - > db - > selectrow_array ( "SELECT COUNT(UserName) FROM Customers WHERE UserName = ?" , { } , ( $ username ) ) ;
#print "-". Dumper($out) ."-";
return $ out != 0 ;
} ;
helper does_email_exist = > sub {
my ( $ self , $ email ) = @ _ ;
return defined ( $ self - > db - > selectrow_hashref (
"SELECT Email FROM Users WHERE Email = ?" ,
{ } ,
$ email ,
) ) ;
} ;
helper set_token_as_used = > sub {
my ( $ self , $ token ) = @ _ ;
#Return true if and only if the token exists and has not been used.
my $ statement = $ self - > db - > prepare ( "UPDATE Tokens SET Used = 1 WHERE TokenName = ? AND Used = 0 " ) ;
my $ rows = $ statement - > execute ( $ token ) ;
#print '-set_token_as_used-'.(Dumper($rows))."-\n";
return $ rows != 0 ;
} ;
helper generate_hashed_password = > sub {
my ( $ self , $ password ) = @ _ ;
my $ ppr = Authen::Passphrase::BlowfishCrypt - > new (
cost = > 8 , salt_random = > 1 ,
passphrase = > $ password ) ;
return $ ppr - > as_crypt ;
} ;
# We assume the user already exists.
helper check_password_email = > sub {
my ( $ self , $ email , $ password ) = @ _ ;
my $ statement = $ self - > db - > prepare ( "SELECT HashedPassword FROM Users WHERE Email = ?" ) ;
my $ result - > execute ( $ email ) ;
my ( $ hashedPassword ) = $ result - > fetchrow_array ;
my $ ppr = Authen::Passphrase::BlowfishCrypt - > from_crypt ( $ hashedPassword ) ;
return $ ppr - > match ( $ password ) ;
} ;
2017-02-01 12:07:51 +00:00
2016-09-05 16:52:13 +01:00
app - > start ;