Skip to content

Commit

Permalink
Merge pull request #164 from javad-zobeidi/dev
Browse files Browse the repository at this point in the history
Fixed bugs and added tags to the template
  • Loading branch information
javad-zobeidi authored Jan 20, 2025
2 parents fd66793 + d2e24a4 commit 2089cea
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 174 deletions.
6 changes: 3 additions & 3 deletions lib/src/authentication/authenticate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class Authenticate extends Middleware {
handle(Request req) async {
if (basic) {
bool loggedIn = await getSession<bool?>('logged_in') ?? false;
if (loggedIn) {
String guard = await getSession<String?>('auth_guard') ?? '';
String guard = await getSession<String?>('auth_guard') ?? '';
if (loggedIn && guard.isNotEmpty) {
Map<String, dynamic> user =
await getSession<Map<String, dynamic>?>('auth_user') ?? {};
Auth().guard(guard).login(user);
Auth().guard(guard).login(user[guard], true);
} else {
throw Unauthenticated(
message: loginPath,
Expand Down
23 changes: 12 additions & 11 deletions lib/src/authentication/authentication.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,18 @@ class Auth {
/// current guard.
///
/// Returns the current instance of the `Auth` class.
Auth login(Map<String, dynamic> user) {
Auth login(Map<String, dynamic> user, [bool basic = false]) {
_user[_userGuard] = user;
_updateSession();
if (basic) {
_updateSession();
}
return this;
}

Future<void> logout() async {
await setSession('logged_in', false);
await setSession('auth_user', null);
await setSession('auth_guard', null);
await deleteSession('logged_in');
await deleteSession('auth_guard');
await deleteSession('auth_user');
_loggedIn = false;
if (_currentToken.isNotEmpty) {
try {
Expand All @@ -88,15 +90,14 @@ class Auth {
/// function does not return anything.
Future<void> _updateSession() async {
await setSession('logged_in', true);

if (await getSession<Map?>('auth_user') != _user) {
await setSession('auth_guard', _userGuard);
Map<String, dynamic> user =
await getSession<Map<String, dynamic>?>('auth_user') ?? {};
if (_user != user) {
await deleteSession('auth_user');
await setSession('auth_user', _user);
}

if (await getSession<String?>('auth_guard') != _userGuard) {
await setSession('auth_guard', _userGuard);
}

_loggedIn = true;
}

Expand Down
105 changes: 37 additions & 68 deletions lib/src/cryptographic/vania_encryption.dart
Original file line number Diff line number Diff line change
@@ -1,84 +1,53 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';

class VaniaEncryption {
/// Encrypts the given [plainText] using the given [passphrase].
static IV iv = IV(Uint8List(16));

/// Encrypts the given [plainText] using the provided [passphrase].
///
/// The method generates a random salt and derives a key using the given
/// [passphrase] and the generated salt. The derived key is then used to
/// encrypt the given [plainText] using a simple XOR operation.
/// This method first encodes the [plainText] using Base64 and UTF-8 encoding.
/// Then, it creates a cryptographic key from the [passphrase] and uses the
/// AES encryption algorithm to encrypt the text with a predefined initialization
/// vector (IV). The result is an encrypted string returned in Base64 format.
///
/// The encrypted bytes are then combined with the salt bytes and encoded
/// using Base64.
/// Parameters:
/// - [plainText]: The text to be encrypted.
/// - [passphrase]: The passphrase used to generate the encryption key.
///
/// The resulting Base64 encoded string can be decrypted using the
/// [decryptString] method.
/// Returns:
/// A Base64 encoded string representing the encrypted text.
static String encryptString(String plainText, String passphrase) {
final saltBytes =
List<int>.generate(16, (_) => Random.secure().nextInt(256));

final plainBytes = utf8.encode(plainText);

final keyBytes = _deriveKey(passphrase, saltBytes, plainBytes.length);

final encryptedBytes = List<int>.generate(
plainBytes.length,
(i) => plainBytes[i] ^ keyBytes[i],
);

final combinedBytes = <int>[];
combinedBytes.addAll(saltBytes);
combinedBytes.addAll(encryptedBytes);

return base64.encode(combinedBytes);
plainText = base64.encode(utf8.encode(plainText));
Key key = Key.fromUtf8(passphrase.substring(0, 32));
Encrypter encrypter = Encrypter(AES(key));
Encrypted encrypted = encrypter.encrypt(plainText, iv: iv);
return encrypted.base64;
}

/// Decrypts the given [encryptedText] using the given [passphrase].
/// Decrypts the given [encryptedText] using the provided [passphrase].
///
/// The method first decodes the given [encryptedText] from Base64 and
/// splits the decoded bytes into a salt and the encrypted bytes.
/// This method first creates a cryptographic key from the [passphrase].
/// It then uses the AES encryption algorithm to decrypt the [encryptedText]
/// with a predefined initialization vector (IV). The decrypted text is
/// decoded from Base64 and UTF-8 encoding to return the original plain text.
///
/// The method then derives a key using the given [passphrase] and the
/// salt bytes. The derived key is then used to decrypt the encrypted
/// bytes using a simple XOR operation.
/// Parameters:
/// - [encryptedText]: The text to be decrypted, in Base64 format.
/// - [passphrase]: The passphrase used to generate the decryption key.
///
/// The decrypted bytes are then decoded from UTF-8 and returned as a string.
/// Returns:
/// The original plain text if decryption is successful, or an empty
/// string if decryption fails.
static String decryptString(String encryptedText, String passphrase) {
final allBytes = base64.decode(encryptedText);

final saltBytes = allBytes.sublist(0, 16);

final encryptedBytes = allBytes.sublist(16);

final keyBytes = _deriveKey(passphrase, saltBytes, encryptedBytes.length);

final plainBytes = List<int>.generate(
encryptedBytes.length,
(i) => encryptedBytes[i] ^ keyBytes[i],
);

return utf8.decode(plainBytes);
}

/// Derives a key from the given [passphrase] and [saltBytes] with the given [length].
///
/// The method works by first encoding the [passphrase] into bytes and then
/// combining the bytes with the given [saltBytes]. The combined bytes are then
/// repeated until their length is at least [length]. The resulting bytes are
/// then trimmed to [length] bytes and returned as the derived key.
static List<int> _deriveKey(
String passphrase, List<int> saltBytes, int length) {
final passBytes = utf8.encode(passphrase);

final combined = <int>[];
combined.addAll(passBytes);
combined.addAll(saltBytes);

final repeated = <int>[];
while (repeated.length < length) {
repeated.addAll(combined);
try {
Key key = Key.fromUtf8(passphrase.substring(0, 32));
Encrypter encrypter = Encrypter(AES(key));
String decrypted = encrypter.decrypt64(encryptedText, iv: iv);
return utf8.decode(base64.decode(decrypted));
} catch (error) {
return '';
}

return repeated.sublist(0, length);
}
}
66 changes: 33 additions & 33 deletions lib/src/http/request/request_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,52 @@ import '../session/session_manager.dart';
/// - [BaseHttpResponseException] if there is an issue with the HTTP response.
/// - [InvalidArgumentException] if an invalid argument is encountered.
Future httpRequestHandler(HttpRequest req) async {
await SessionManager().sessionStart(req, req.response);

/// Check the incoming request is web socket or not
if (env<bool>('APP_WEBSOCKET', false) &&
WebSocketTransformer.isUpgradeRequest(req)) {
await SessionManager().sessionStart(req, req.response);
WebSocketHandler().handler(req);
} else {
DateTime startTime = DateTime.now();
String requestUri = req.uri.path;
String starteRequest = startTime.format();

bool isHtml = req.headers.value('accept').toString().contains('html');

try {
/// Check if cors is enabled
HttpCors(req);
RouteData? route = httpRouteHandler(req);
Request request = Request.from(request: req, route: route);
await request.extractBody();
if (route == null) return;
DateTime startTime = DateTime.now();
String requestUri = req.uri.path;
String starteRequest = startTime.format();

RouteHistory().updateRouteHistory(req);
if (route != null) {
/// Check if cors is enabled
if (isHtml) {
TemplateEngine().formData.addAll(request.all());
}
Request request = Request.from(request: req, route: route);
await request.extractBody();

/// check if pre middleware exist and call it
if (route.preMiddleware.isNotEmpty) {
await middlewareHandler(route.preMiddleware, request);
}
if (isHtml) {
TemplateEngine().formData.addAll(request.all());
await SessionManager().sessionStart(req, req.response);
RouteHistory().updateRouteHistory(req);
}

/// Controller and method handler
ControllerHandler().create(
route: route,
request: request,
);
/// check if pre middleware exist and call it
if (route.preMiddleware.isNotEmpty) {
await middlewareHandler(route.preMiddleware, request);
}

/// Controller and method handler
ControllerHandler().create(
route: route,
request: request,
);

if (env<bool>('APP_DEBUG')) {
var endTime = DateTime.now();
var duration = endTime.difference(startTime).inMilliseconds;
var requestedPath = requestUri.isNotEmpty
? requestUri.padRight(118 - requestUri.length, '.')
: ''.padRight(118, '.');
print('$starteRequest $requestedPath ~ ${duration}ms');
}
}
} on BaseHttpResponseException catch (error) {
if (error is NotFoundException && isHtml) {
if (File('lib/view/template/errors/404.html').existsSync()) {
Expand Down Expand Up @@ -99,15 +108,6 @@ Future httpRequestHandler(HttpRequest req) async {
Logger.log(e.toString(), type: Logger.ERROR);
_response(req, e.toString());
}

if (env<bool>('APP_DEBUG')) {
var endTime = DateTime.now();
var duration = endTime.difference(startTime).inMilliseconds;
var requestedPath = requestUri.isNotEmpty
? requestUri.padRight(118 - requestUri.length, '.')
: ''.padRight(118, '.');
print('$starteRequest $requestedPath ~ ${duration}ms');
}
}
}

Expand Down
61 changes: 38 additions & 23 deletions lib/src/http/session/session_file_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,18 @@ class SessionFileStore {

int expiration =
DateTime.now().toUtc().millisecondsSinceEpoch + duration.inMilliseconds;
await file.writeAsString(VaniaEncryption.encryptString(
json.encode({"data": data, "expiration": expiration}), _secretKey));

await file.writeAsString(
VaniaEncryption.encryptString(
json.encode(
{
"data": data,
"expiration": expiration,
},
),
_secretKey,
),
);
}

/// Retrieves a session from the file system. The session is retrieved from the sessionPath directory,
Expand All @@ -60,13 +70,20 @@ class SessionFileStore {
return null;
}

Map<String, dynamic> data = json.decode(
VaniaEncryption.decryptString(await file.readAsString(), _secretKey));
final fileContent = VaniaEncryption.decryptString(
await file.readAsString(),
_secretKey,
);

Map<String, dynamic> data = fileContent.isEmpty
? {}
: json.decode(
fileContent,
);
int expiration = data['expiration'].toString().toInt() ?? 0;
if (!DateTime.now()
.toUtc()
.isBefore(DateTime.fromMillisecondsSinceEpoch(expiration))) {
file.deleteSync();
if (!DateTime.now().toUtc().isBefore(
DateTime.fromMillisecondsSinceEpoch(expiration),
)) {
return null;
}
return data['data'];
Expand All @@ -77,18 +94,8 @@ class SessionFileStore {
/// The method is synchronous and returns a boolean indicating if the session exists and is valid.
/// If the session has expired, the method deletes the file and returns false.
Future<bool> hasSession(String sessionId) async {
sessionId = _makeHash(sessionId).toString();
final file = File('$sessionPath/$sessionId');
if (!file.existsSync()) {
return false;
}
Map<String, dynamic> data = json.decode(
VaniaEncryption.decryptString(await file.readAsString(), _secretKey));
int expiration = data['expiration'].toString().toInt() ?? 0;
if (!DateTime.now()
.toUtc()
.isBefore(DateTime.fromMillisecondsSinceEpoch(expiration))) {
file.deleteSync();
Map<String, dynamic>? data = await retrieveSession(sessionId);
if (data == null) {
return false;
}
return true;
Expand All @@ -97,9 +104,17 @@ class SessionFileStore {
Future<void> deleteSession(String sessionId) async {
sessionId = _makeHash(sessionId).toString();
final file = File('$sessionPath/$sessionId');
if (file.existsSync()) {
await file.delete();
}
int expiration = DateTime.now().toUtc().microsecondsSinceEpoch -
Duration(seconds: 0).inMilliseconds;
await file.writeAsString(VaniaEncryption.encryptString(
json.encode(
{
"data": {},
"expiration": expiration,
},
),
_secretKey,
));
}

Digest _makeHash(String key) {
Expand Down
Loading

0 comments on commit 2089cea

Please sign in to comment.