This commit is contained in:
Drake Marino 2024-06-06 21:55:56 -05:00
parent ee8b419887
commit 68edb2c3a1
2 changed files with 69 additions and 94 deletions

View File

@ -8,8 +8,8 @@ services:
volumes: volumes:
- /var/jenkins_home/logos:/root/FBLA24/fbla-api/logos - /var/jenkins_home/logos:/root/FBLA24/fbla-api/logos
environment: environment:
- POSTGRES_USERNAME - JOBLINK_POSTGRES_USERNAME
- POSTGRES_PASSWORD - JOBLINK_POSTGRES_PASSWORD
- SECRET_KEY - JOBLINK_SECRET_KEY
- POSTGRES_ADDRESS - JOBLINK_POSTGRES_ADDRESS
- POSTGRES_PORT - JOBLINK_POSTGRES_PORT

View File

@ -2,14 +2,14 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:argon2/argon2.dart';
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:http/http.dart' as http;
import 'package:postgres/postgres.dart'; import 'package:postgres/postgres.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io; import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart'; import 'package:shelf_router/shelf_router.dart';
import 'package:http/http.dart' as http;
import 'package:argon2/argon2.dart';
SecretKey secretKey = SecretKey(Platform.environment['JOBLINK_SECRET_KEY']!); SecretKey secretKey = SecretKey(Platform.environment['JOBLINK_SECRET_KEY']!);
@ -75,8 +75,7 @@ class Business {
} }
Future<String> fetchBusinessData() async { Future<String> fetchBusinessData() async {
final result = await postgres.query( final result = await postgres.query('''
'''
SELECT json_agg( SELECT json_agg(
json_build_object( json_build_object(
'id', id, 'id', id,
@ -92,8 +91,7 @@ Future<String> fetchBusinessData() async {
'locationAddress', "locationAddress" 'locationAddress', "locationAddress"
) )
) FROM businesses ) FROM businesses
''' ''');
);
var encoded = json.encode(result); var encoded = json.encode(result);
var decoded = json.decode(encoded); var decoded = json.decode(encoded);
@ -105,7 +103,6 @@ Future<String> fetchBusinessData() async {
String _hostname = 'localhost'; String _hostname = 'localhost';
const _port = 8000; const _port = 8000;
final postgres = PostgreSQLConnection( final postgres = PostgreSQLConnection(
Platform.environment['JOBLINK_POSTGRES_ADDRESS']!, Platform.environment['JOBLINK_POSTGRES_ADDRESS']!,
int.parse(Platform.environment['JOBLINK_POSTGRES_PORT']!), int.parse(Platform.environment['JOBLINK_POSTGRES_PORT']!),
@ -114,8 +111,6 @@ final postgres = PostgreSQLConnection(
password: Platform.environment['JOBLINK_POSTGRES_PASSWORD'], password: Platform.environment['JOBLINK_POSTGRES_PASSWORD'],
); );
void main() async { void main() async {
await postgres.open(); await postgres.open();
@ -126,8 +121,8 @@ void main() async {
print('Hello received'); print('Hello received');
return Response.ok( return Response.ok(
'Hello, World!', 'Hello, World!',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.get('/fbla-api/businessdata', (Request request) async { app.get('/fbla-api/businessdata', (Request request) async {
@ -162,15 +157,12 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
Business business = Business.fromJson(json); Business business = Business.fromJson(json);
await postgres.query( await postgres.query('''
'''
INSERT INTO businesses (name, description, type, website, "contactName", "contactPhone", "contactEmail", notes, "locationName", "locationAddress") INSERT INTO businesses (name, description, type, website, "contactName", "contactPhone", "contactEmail", notes, "locationName", "locationAddress")
VALUES ('${business.name.replaceAll("'", "''")}', '${business.description.replaceAll("'", "''")}', '${business.type.name}', '${business.website}', '${business.contactName.replaceAll("'", "''")}', '${business.contactPhone}', '${business.contactEmail}', '${business.notes.replaceAll("'", "''")}', '${business.locationName.replaceAll("'", "''")}', '${business.locationAddress.replaceAll("'", "''")}') VALUES ('${business.name.replaceAll("'", "''")}', '${business.description.replaceAll("'", "''")}', '${business.type.name}', '${business.website}', '${business.contactName.replaceAll("'", "''")}', '${business.contactPhone}', '${business.contactEmail}', '${business.notes.replaceAll("'", "''")}', '${business.locationName.replaceAll("'", "''")}', '${business.locationAddress.replaceAll("'", "''")}')
''' ''');
);
final dbBusiness = await postgres.query( final dbBusiness = await postgres.query('''SELECT * FROM public.businesses
'''SELECT * FROM public.businesses
ORDER BY id DESC LIMIT 1'''); ORDER BY id DESC LIMIT 1''');
var id = dbBusiness[0][0]; var id = dbBusiness[0][0];
var logoResponse = await http.get( var logoResponse = await http.get(
@ -181,8 +173,8 @@ void main() async {
await File('logos/$id.png').writeAsBytes(logoResponse.bodyBytes); await File('logos/$id.png').writeAsBytes(logoResponse.bodyBytes);
} }
return Response.ok( return Response.ok(
id.toString(), id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
@ -191,14 +183,13 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/deletebusiness', (Request request) async { app.post('/fbla-api/deletebusiness', (Request request) async {
print('delete business request received'); print('delete business request received');
final payload = await request.readAsString(); final payload = await request.readAsString();
var auth = request.headers['Authorization']?.replaceAll('Bearer ', ''); var auth = request.headers['Authorization']?.replaceAll('Bearer ', '');
try { try {
@ -206,33 +197,31 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
var id = json['id']; var id = json['id'];
await postgres.query( await postgres.query('''
'''
DELETE FROM public.businesses DELETE FROM public.businesses
WHERE id IN WHERE id IN
($id); ($id);
''' ''');
);
try{ try {
await File('logos/$id.png').delete(); await File('logos/$id.png').delete();
} catch(e) { } catch (e) {
print('Failure to delete logo! $e'); print('Failure to delete logo! $e');
} }
return Response.ok( return Response.ok(
id.toString(), id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
print(e.message); print(e.message);
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/editbusiness', (Request request) async { app.post('/fbla-api/editbusiness', (Request request) async {
@ -246,13 +235,11 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
Business business = Business.fromJson(json); Business business = Business.fromJson(json);
await postgres.query( await postgres.query('''
'''
UPDATE businesses SET UPDATE businesses SET
name = '${business.name.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, description = '${business.description.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, website = '${business.website}'::text, type = '${business.type.name}'::text, "contactName" = '${business.contactName.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "contactPhone" = '${business.contactPhone}'::text, "contactEmail" = '${business.contactEmail}'::text, notes = '${business.notes.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationName" = '${business.locationName.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationAddress" = '${business.locationAddress.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text WHERE name = '${business.name.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, description = '${business.description.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, website = '${business.website}'::text, type = '${business.type.name}'::text, "contactName" = '${business.contactName.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "contactPhone" = '${business.contactPhone}'::text, "contactEmail" = '${business.contactEmail}'::text, notes = '${business.notes.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationName" = '${business.locationName.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text, "locationAddress" = '${business.locationAddress.replaceAll("'", "''").replaceAll("\"", "\"\"")}'::text WHERE
id = ${business.id}; id = ${business.id};
''' ''');
);
var logoResponse = await http.get( var logoResponse = await http.get(
Uri.http('logo.clearbit.com', '/${business.website}'), Uri.http('logo.clearbit.com', '/${business.website}'),
@ -260,18 +247,17 @@ void main() async {
try { try {
await File('logos/${business.id}.png').delete(); await File('logos/${business.id}.png').delete();
} catch(e) { } catch (e) {
print('Failure to delete logo! $e'); print('Failure to delete logo! $e');
} }
if (logoResponse.headers.toString().contains('image/png')) { if (logoResponse.headers.toString().contains('image/png')) {
await File('logos/${business.id}.png').writeAsBytes(logoResponse.bodyBytes); await File('logos/${business.id}.png')
.writeAsBytes(logoResponse.bodyBytes);
} }
return Response.ok( return Response.ok(
business.id.toString(), business.id.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
@ -280,8 +266,8 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/signin', (Request request) async { app.post('/fbla-api/signin', (Request request) async {
@ -292,13 +278,12 @@ void main() async {
var username = json['username']; var username = json['username'];
var password = json['password']; var password = json['password'];
var saltDb = await postgres.query( var saltDb = await postgres
'SELECT salt FROM users WHERE username=\'$username\'' .query('SELECT salt FROM users WHERE username=\'$username\'');
);
if (saltDb.isEmpty) { if (saltDb.isEmpty) {
return Response.unauthorized( return Response.unauthorized(
'invalid username', 'invalid username',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} }
@ -320,17 +305,13 @@ void main() async {
argon2.generateBytes(passwordBytes, result); argon2.generateBytes(passwordBytes, result);
var resultHex = result.toHexString(); var resultHex = result.toHexString();
var passwordHashDb = await postgres.query( var passwordHashDb = await postgres
'SELECT password_hash FROM users WHERE username=\'$username\'' .query('SELECT password_hash FROM users WHERE username=\'$username\'');
);
var passwordHash = passwordHashDb[0][0].toString(); var passwordHash = passwordHashDb[0][0].toString();
if (passwordHash == resultHex) { if (passwordHash == resultHex) {
final jwt = JWT( final jwt = JWT(
{ {'username': username},
'username': username
},
); );
final token = jwt.sign(secretKey); final token = jwt.sign(secretKey);
try { try {
@ -342,14 +323,13 @@ void main() async {
} }
return Response.ok( return Response.ok(
token.toString(), token.toString(),
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} else { } else {
return Response.unauthorized( return Response.unauthorized(
'invalid password', 'invalid password',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} }
}); });
@ -367,7 +347,8 @@ void main() async {
var password = json['password']; var password = json['password'];
var r = Random.secure(); var r = Random.secure();
String randomSalt = String.fromCharCodes(List.generate(32, (index) => r.nextInt(33) + 89)); String randomSalt = String.fromCharCodes(
List.generate(32, (index) => r.nextInt(33) + 89));
final salt = randomSalt.toBytesLatin1(); final salt = randomSalt.toBytesLatin1();
var parameters = Argon2Parameters( var parameters = Argon2Parameters(
@ -385,27 +366,24 @@ void main() async {
argon2.generateBytes(passwordBytes, result); argon2.generateBytes(passwordBytes, result);
var resultHex = result.toHexString(); var resultHex = result.toHexString();
postgres.query( postgres.query('''
'''
INSERT INTO public.users (username, password_hash, salt) INSERT INTO public.users (username, password_hash, salt)
VALUES ('$username', '$resultHex', '$randomSalt') VALUES ('$username', '$resultHex', '$randomSalt')
''' ''');
);
return Response.ok( return Response.ok(
username, username,
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
print(e.message); print(e.message);
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.post('/fbla-api/deleteuser', (Request request) async { app.post('/fbla-api/deleteuser', (Request request) async {
@ -420,18 +398,15 @@ void main() async {
var json = jsonDecode(payload); var json = jsonDecode(payload);
var username = json['username']; var username = json['username'];
postgres.query( postgres.query('''
'''
DELETE FROM public.users DELETE FROM public.users
WHERE username IN ('$username'); WHERE username IN ('$username');
''' ''');
);
return Response.ok( return Response.ok(
username, username,
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
} on JWTExpiredException { } on JWTExpiredException {
print('JWT Expired'); print('JWT Expired');
} on JWTException catch (e) { } on JWTException catch (e) {
@ -439,8 +414,8 @@ void main() async {
} }
return Response.unauthorized( return Response.unauthorized(
'unauthorized', 'unauthorized',
headers: {'Access-Control-Allow-Origin': '*'}, headers: {'Access-Control-Allow-Origin': '*'},
); );
}); });
app.get('/fbla-api/marinodev', (Request request) async { app.get('/fbla-api/marinodev', (Request request) async {
@ -450,11 +425,11 @@ void main() async {
List<int> content = logo.readAsBytesSync(); List<int> content = logo.readAsBytesSync();
return Response.ok( return Response.ok(
content, content,
headers: { headers: {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'image/svg+xml' 'Content-Type': 'image/svg+xml'
}, },
); );
}); });