Skip to content

Commit

Permalink
fix: emailverified in tp & pless (#774)
Browse files Browse the repository at this point in the history
* fix: email verification in thirdparty and pless

* fix: email verification

* fix: more test for passwordless

* fix: thirdparty and tests
  • Loading branch information
sattvikc authored Aug 28, 2023
1 parent 3ce68da commit 446d282
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 5 deletions.
67 changes: 66 additions & 1 deletion src/main/java/io/supertokens/passwordless/Passwordless.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,19 +251,31 @@ public static ConsumeCodeResponse consumeCode(Main main,
Storage storage = StorageLayer.getStorage(main);
return consumeCode(
new TenantIdentifierWithStorage(null, null, null, storage),
main, deviceId, deviceIdHashFromUser, userInputCode, linkCode);
main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false);
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
throw new IllegalStateException(e);
}
}

@TestOnly
public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main,
String deviceId, String deviceIdHashFromUser,
String userInputCode, String linkCode)
throws RestartFlowException, ExpiredUserInputCodeException,
IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException,
StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException,
TenantOrAppNotFoundException, BadPermissionException {
return consumeCode(tenantIdentifierWithStorage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode,
false);
}

public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main,
String deviceId, String deviceIdHashFromUser,
String userInputCode, String linkCode, boolean setEmailVerified)
throws RestartFlowException, ExpiredUserInputCodeException,
IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException,
StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException,
TenantOrAppNotFoundException, BadPermissionException {

TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage);
if (config == null) {
Expand Down Expand Up @@ -417,6 +429,33 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant
long timeJoined = System.currentTimeMillis();
user = passwordlessStorage.createUser(tenantIdentifierWithStorage, userId, consumedDevice.email,
consumedDevice.phoneNumber, timeJoined);

// Set email as verified, if using email
if (setEmailVerified && consumedDevice.email != null) {
try {
AuthRecipeUserInfo finalUser = user;
tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> {
try {
tenantIdentifierWithStorage.getEmailVerificationStorage()
.updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con,
finalUser.getSupertokensUserId(), consumedDevice.email, true);
tenantIdentifierWithStorage.getEmailVerificationStorage()
.commitTransaction(con);

return null;
} catch (TenantOrAppNotFoundException e) {
throw new StorageTransactionLogicException(e);
}
});
user.loginMethods[0].setVerified(); // newly created user has only one loginMethod
} catch (StorageTransactionLogicException e) {
if (e.actualException instanceof TenantOrAppNotFoundException) {
throw (TenantOrAppNotFoundException) e.actualException;
}
throw new StorageQueryException(e);
}
}

return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber);
} catch (DuplicateEmailException | DuplicatePhoneNumberException e) {
// Getting these would mean that between getting the user and trying creating it:
Expand All @@ -433,6 +472,32 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant
} else {
// We do not need this cleanup if we are creating the user, since it uses the email/phoneNumber of the
// device, which has already been cleaned up
if (setEmailVerified && consumedDevice.email != null) {
// Set email verification
try {
LoginMethod finalLoginMethod = loginMethod;
tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> {
try {
tenantIdentifierWithStorage.getEmailVerificationStorage()
.updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con,
finalLoginMethod.getSupertokensUserId(), consumedDevice.email, true);
tenantIdentifierWithStorage.getEmailVerificationStorage()
.commitTransaction(con);

return null;
} catch (TenantOrAppNotFoundException e) {
throw new StorageTransactionLogicException(e);
}
});
loginMethod.setVerified();
} catch (StorageTransactionLogicException e) {
if (e.actualException instanceof TenantOrAppNotFoundException) {
throw (TenantOrAppNotFoundException) e.actualException;
}
throw new StorageQueryException(e);
}
}

if (loginMethod.email != null && !loginMethod.email.equals(consumedDevice.email)) {
removeCodesByEmail(tenantIdentifierWithStorage, loginMethod.email);
}
Expand Down
45 changes: 43 additions & 2 deletions src/main/java/io/supertokens/thirdparty/ThirdParty.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,26 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t
Storage storage = StorageLayer.getStorage(main);
return signInUp(
new TenantIdentifierWithStorage(null, null, null, storage), main,
thirdPartyId, thirdPartyUserId, email);
thirdPartyId, thirdPartyUserId, email, false);
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
throw new IllegalStateException(e);
}
}

@TestOnly
public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main,
String thirdPartyId,
String thirdPartyUserId, String email)
throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException,
EmailChangeNotAllowedException {
return signInUp(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email, false);
}

public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main,
String thirdPartyId,
String thirdPartyUserId, String email, boolean isEmailVerified)
throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException,
EmailChangeNotAllowedException {

TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage);
if (config == null) {
Expand All @@ -140,7 +149,39 @@ public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdenti
throw new BadPermissionException("Third Party login not enabled for tenant");
}

return signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email);
SignInUpResponse response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId,
email);

if (isEmailVerified) {
for (LoginMethod lM : response.user.loginMethods) {
if (lM.thirdParty != null && lM.thirdParty.id.equals(thirdPartyId) && lM.thirdParty.userId.equals(thirdPartyUserId)) {
try {
tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> {
try {
tenantIdentifierWithStorage.getEmailVerificationStorage()
.updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con,
lM.getSupertokensUserId(), lM.email, true);
tenantIdentifierWithStorage.getEmailVerificationStorage()
.commitTransaction(con);

return null;
} catch (TenantOrAppNotFoundException e) {
throw new StorageTransactionLogicException(e);
}
});
lM.setVerified();
} catch (StorageTransactionLogicException e) {
if (e.actualException instanceof TenantOrAppNotFoundException) {
throw (TenantOrAppNotFoundException) e.actualException;
}
throw new StorageQueryException(e);
}
break;
}
}
}

return response;
}

private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenantIdentifierWithStorage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(
this.getTenantIdentifierWithStorageFromRequest(req), main,
deviceId, deviceIdHash,
userInputCode, linkCode);
userInputCode, linkCode,
// From CDI version 4.0 onwards, the email verification will be set
getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0));
io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user});

ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.getSupertokensUserId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
JsonObject emailObject = InputParser.parseJsonObjectOrThrowError(input, "email", false);
String email = InputParser.parseStringOrThrowError(emailObject, "id", false);

// setting email verified behaviour is to be done only for CDI 4.0 onwards. version 3.0 and earlier
// do not have this field
Boolean isEmailVerified = false;
if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) {
isEmailVerified = InputParser.parseBooleanOrThrowError(emailObject, "isVerified", false);
}

assert thirdPartyId != null;
assert thirdPartyUserId != null;
assert email != null;
Expand All @@ -129,7 +136,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
try {
ThirdParty.SignInUpResponse response = ThirdParty.signInUp(
this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId,
email);
email, isEmailVerified);
UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user});

ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId());
Expand Down
Loading

0 comments on commit 446d282

Please sign in to comment.