mirror of
https://github.com/johrpan/musicus_mobile.git
synced 2025-10-26 10:47:25 +01:00
server: Run crypto methods in seperate isolate
This commit is contained in:
parent
fa2e9ebacd
commit
e897465fd7
3 changed files with 132 additions and 16 deletions
|
|
@ -1,12 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:aqueduct/aqueduct.dart';
|
||||
import 'package:corsac_jwt/corsac_jwt.dart';
|
||||
import 'package:steel_crypt/steel_crypt.dart';
|
||||
|
||||
import 'compute.dart';
|
||||
import 'crypt.dart';
|
||||
import 'database.dart';
|
||||
|
||||
/// Information on the rights of the user making the request.
|
||||
|
|
@ -54,9 +53,6 @@ class RequestUser {
|
|||
class RegisterController extends Controller {
|
||||
final ServerDatabase db;
|
||||
|
||||
final _crypt = PassCrypt();
|
||||
final _rand = Random.secure();
|
||||
|
||||
RegisterController(this.db);
|
||||
|
||||
@override
|
||||
|
|
@ -69,21 +65,20 @@ class RegisterController extends Controller {
|
|||
final existingUser = await db.getUser(requestUser.name);
|
||||
if (existingUser != null) {
|
||||
// Returning something different than 200 here has the security
|
||||
// implication that an attacker can check for existing user names. At the
|
||||
// moment, I don't see any alternatives, because we don't use email
|
||||
// implication that an attacker can check for existing user names. At
|
||||
// the moment, I don't see any alternatives, because we don't use email
|
||||
// addresses for identification. The client needs to know, whether the
|
||||
// user name is already given.
|
||||
return Response.conflict();
|
||||
} else {
|
||||
final bytes = List.generate(32, (i) => _rand.nextInt(256));
|
||||
final salt = base64UrlEncode(bytes);
|
||||
final hash = _crypt.hashPass(salt, requestUser.password);
|
||||
// This will take a long time, so we run it in a new isolate.
|
||||
final result = await compute(Crypt.hashPassword, requestUser.password);
|
||||
|
||||
db.updateUser(User(
|
||||
name: requestUser.name,
|
||||
email: requestUser.email,
|
||||
salt: salt,
|
||||
hash: hash,
|
||||
salt: result.salt,
|
||||
hash: result.hash,
|
||||
mayUpload: true,
|
||||
mayEdit: false,
|
||||
mayDelete: false,
|
||||
|
|
@ -106,7 +101,6 @@ class LoginController extends Controller {
|
|||
/// The secret that will be used for signing the token.
|
||||
final String secret;
|
||||
|
||||
final _crypt = PassCrypt();
|
||||
final JWTHmacSha256Signer _signer;
|
||||
|
||||
LoginController(this.db, this.secret) : _signer = JWTHmacSha256Signer(secret);
|
||||
|
|
@ -119,8 +113,16 @@ class LoginController extends Controller {
|
|||
|
||||
final realUser = await db.getUser(requestUser.name);
|
||||
if (realUser != null) {
|
||||
if (_crypt.checkPassKey(
|
||||
realUser.salt, requestUser.password, realUser.hash)) {
|
||||
// We check the password in a new isolate, because this can take a long
|
||||
// time.
|
||||
if (await compute(
|
||||
Crypt.checkPassword,
|
||||
CheckPasswordRequest(
|
||||
password: requestUser.password,
|
||||
salt: realUser.salt,
|
||||
hash: realUser.hash,
|
||||
),
|
||||
)) {
|
||||
final builder = JWTBuilder()
|
||||
..expiresAt = DateTime.now().add(Duration(minutes: 30))
|
||||
..setClaim('user', requestUser.name);
|
||||
|
|
|
|||
52
server/lib/src/compute.dart
Normal file
52
server/lib/src/compute.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import 'dart:isolate';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// This function will run within the new isolate.
|
||||
void _isolateEntrypoint<T, S>(_ComputeRequest<T, S> request) {
|
||||
final result = request.compute();
|
||||
request.sendPort.send(result);
|
||||
}
|
||||
|
||||
/// Bundle of information to pass to the isolate.
|
||||
class _ComputeRequest<T, S> {
|
||||
/// The function to call.
|
||||
T Function(S parameter) function;
|
||||
|
||||
/// The parameter to pass to the function.
|
||||
S parameter;
|
||||
|
||||
/// The port through which the result will be sent.
|
||||
SendPort sendPort;
|
||||
|
||||
_ComputeRequest({
|
||||
@required this.function,
|
||||
@required this.parameter,
|
||||
@required this.sendPort,
|
||||
});
|
||||
|
||||
/// Call [function] with [parameter] and return the result.
|
||||
///
|
||||
/// This function exists to avoid type errors within the isolate.
|
||||
T compute() => function(parameter);
|
||||
}
|
||||
|
||||
/// Call a function in a new isolate and await the result.
|
||||
///
|
||||
/// The function has to be a static function. If the result is not a primitive
|
||||
/// value or a list or map of such, this won't work
|
||||
/// (see https://api.dart.dev/stable/2.8.1/dart-isolate/SendPort/send.html).
|
||||
Future<T> compute<T, S>(T Function(S parameter) function, S parameter) async {
|
||||
final receivePort = ReceivePort();
|
||||
|
||||
Isolate.spawn(
|
||||
_isolateEntrypoint,
|
||||
_ComputeRequest<T, S>(
|
||||
function: function,
|
||||
parameter: parameter,
|
||||
sendPort: receivePort.sendPort,
|
||||
),
|
||||
);
|
||||
|
||||
return await receivePort.first as T;
|
||||
}
|
||||
62
server/lib/src/crypt.dart
Normal file
62
server/lib/src/crypt.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:steel_crypt/steel_crypt.dart';
|
||||
|
||||
/// Result of [hashPassword].
|
||||
class HashPasswordResult {
|
||||
/// The computed hash.
|
||||
final String hash;
|
||||
|
||||
/// A randomly generated string.
|
||||
final String salt;
|
||||
|
||||
HashPasswordResult({
|
||||
@required this.hash,
|
||||
@required this.salt,
|
||||
});
|
||||
}
|
||||
|
||||
/// Parameters for [checkPassword].
|
||||
class CheckPasswordRequest {
|
||||
/// The password to check.
|
||||
final String password;
|
||||
|
||||
/// The salt that was used for computing the hash.
|
||||
final String salt;
|
||||
|
||||
/// The hash value to check against.
|
||||
final String hash;
|
||||
|
||||
CheckPasswordRequest({
|
||||
@required this.password,
|
||||
@required this.salt,
|
||||
@required this.hash,
|
||||
});
|
||||
}
|
||||
|
||||
/// Methods for handling passwords.
|
||||
class Crypt {
|
||||
static final _crypt = PassCrypt();
|
||||
static final _rand = Random.secure();
|
||||
|
||||
/// Compute a hash for a password.
|
||||
///
|
||||
/// The result will contain the hash and a randomly generated salt.
|
||||
static HashPasswordResult hashPassword(String password) {
|
||||
final bytes = List.generate(32, (i) => _rand.nextInt(256));
|
||||
final salt = base64UrlEncode(bytes);
|
||||
final hash = _crypt.hashPass(salt, password);
|
||||
|
||||
return HashPasswordResult(
|
||||
hash: hash,
|
||||
salt: salt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Check whether a password matches a hash value.
|
||||
static bool checkPassword(CheckPasswordRequest request) {
|
||||
return _crypt.checkPassKey(request.salt, request.password, request.hash);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue