startAccountCreation static method
Returns the result of the operation and a process ID for the account request.
An account request is only created if the result
is
EmailAccountRequestResult.accountRequestCreated.
In all other cases accountRequestId
will be null
.
The caller should ensure that the actual result does not leak to the outside client. Instead clients generally should always see a message like "If this email was not registered already, a new account has been created and a verification email has been sent". This prevents the endpoint from being misused to scan for registered/valid email addresses.
The caller might decide to initiate a password reset (via email, not in the client response), to help users which try to register but already have an account.
In the success case of EmailAccountRequestResult.accountRequestCreated,
the caller may store additional information attached to the
accountRequestId
, which will be returned from verifyAccountCreation
later on.
Implementation
static Future<
({
EmailAccountRequestResult result,
UuidValue? accountRequestId,
})> startAccountCreation(
final Session session, {
required String email,
required final String password,
final Transaction? transaction,
}) async {
if (!EmailAccounts.config.passwordValidationFunction(password)) {
throw EmailAccountPasswordPolicyViolationException();
}
return DatabaseUtil.runInTransactionOrSavepoint(
session.db,
transaction,
(final transaction) async {
email = email.trim().toLowerCase();
final existingAccountCount = await EmailAccount.db.count(
session,
where: (final t) => t.email.equals(email),
transaction: transaction,
);
if (existingAccountCount > 0) {
return (
result: EmailAccountRequestResult.emailAlreadyRegistered,
accountRequestId: null,
);
}
final verificationCode =
EmailAccounts.config.registrationVerificationCodeGenerator();
final pendingAccountRequest = await EmailAccountRequest.db.findFirstRow(
session,
where: (final t) => t.email.equals(email),
transaction: transaction,
);
if (pendingAccountRequest != null) {
if (pendingAccountRequest.createdAt.isBefore(clock.now().subtract(
EmailAccounts.config.registrationVerificationCodeLifetime,
))) {
await EmailAccountRequest.db.deleteRow(
session,
pendingAccountRequest,
transaction: transaction,
);
} else {
return (
result: EmailAccountRequestResult.emailAlreadyRequested,
accountRequestId: null,
);
}
}
final passwordHash = await EmailAccountSecretHash.createHash(
value: password,
);
final verificationCodeHash = await EmailAccountSecretHash.createHash(
value: verificationCode,
);
final emailAccountRequest = await EmailAccountRequest.db.insertRow(
session,
EmailAccountRequest(
email: email,
passwordHash: passwordHash.hash.asByteData,
passwordSalt: passwordHash.salt.asByteData,
verificationCodeHash: verificationCodeHash.hash.asByteData,
verificationCodeSalt: verificationCodeHash.salt.asByteData,
),
transaction: transaction,
);
EmailAccounts.config.sendRegistrationVerificationCode?.call(
session,
email: email,
accountRequestId: emailAccountRequest.id!,
verificationCode: verificationCode,
transaction: transaction,
);
return (
result: EmailAccountRequestResult.accountRequestCreated,
accountRequestId: emailAccountRequest.id!,
);
},
);
}