Skip to content

Commit ce79c49

Browse files
committed
feat: webauthn support
1 parent 31c7c99 commit ce79c49

File tree

4 files changed

+1015
-19
lines changed

4 files changed

+1015
-19
lines changed

src/main/java/io/supertokens/storage/postgresql/Start.java

Lines changed: 311 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@
9191
import io.supertokens.pluginInterface.userroles.exception.DuplicateUserRoleMappingException;
9292
import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException;
9393
import io.supertokens.pluginInterface.userroles.sqlStorage.UserRolesSQLStorage;
94+
import io.supertokens.pluginInterface.webauthn.AccountRecoveryTokenInfo;
95+
import io.supertokens.pluginInterface.webauthn.WebAuthNOptions;
96+
import io.supertokens.pluginInterface.webauthn.WebAuthNStoredCredential;
97+
import io.supertokens.pluginInterface.webauthn.exceptions.DuplicateRecoverAccountTokenException;
98+
import io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserEmailException;
99+
import io.supertokens.pluginInterface.webauthn.exceptions.WebauthNCredentialNotExistsException;
100+
import io.supertokens.pluginInterface.webauthn.exceptions.WebauthNOptionsNotExistsException;
101+
import io.supertokens.pluginInterface.webauthn.slqStorage.WebAuthNSQLStorage;
94102
import io.supertokens.storage.postgresql.config.Config;
95103
import io.supertokens.storage.postgresql.config.PostgreSQLConfig;
96104
import io.supertokens.storage.postgresql.output.Logging;
@@ -117,7 +125,7 @@ public class Start
117125
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
118126
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
119127
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage,
120-
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage {
128+
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage, WebAuthNSQLStorage {
121129

122130
// these configs are protected from being modified / viewed by the dev using the SuperTokens
123131
// SaaS. If the core is not running in SuperTokens SaaS, this array has no effect.
@@ -1423,6 +1431,17 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(TenantIdentifier tenan
14231431
}
14241432
}
14251433

1434+
@Override
1435+
public AuthRecipeUserInfo getPrimaryUserByWebauthNCredentialId(TenantIdentifier tenantIdentifier,
1436+
String webauthNCredentialId)
1437+
throws StorageQueryException {
1438+
try {
1439+
return GeneralQueries.getPrimaryUserByWebauthNCredentialId(this, tenantIdentifier, webauthNCredentialId);
1440+
} catch (SQLException | StorageTransactionLogicException e) {
1441+
throw new StorageQueryException(e);
1442+
}
1443+
}
1444+
14261445
@Override
14271446
public AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(TenantIdentifier tenantIdentifier, String thirdPartyId,
14281447
String thirdPartyUserId) throws StorageQueryException {
@@ -3381,4 +3400,295 @@ public boolean isOAuthTokenRevokedByJTI(AppIdentifier appIdentifier, String gid,
33813400
throw new StorageQueryException(e);
33823401
}
33833402
}
3403+
3404+
@Override
3405+
public WebAuthNStoredCredential saveCredentials(TenantIdentifier tenantIdentifier, WebAuthNStoredCredential credential)
3406+
throws StorageQueryException {
3407+
try {
3408+
return WebAuthNQueries.saveCredential(this, tenantIdentifier, credential);
3409+
} catch (SQLException e) {
3410+
throw new StorageQueryException(e);
3411+
}
3412+
}
3413+
3414+
@Override
3415+
public WebAuthNOptions saveGeneratedOptions(TenantIdentifier tenantIdentifier, WebAuthNOptions optionsToSave) throws StorageQueryException {
3416+
try {
3417+
return WebAuthNQueries.saveOptions(this, tenantIdentifier, optionsToSave);
3418+
} catch (SQLException e) {
3419+
throw new StorageQueryException(e);
3420+
}
3421+
}
3422+
3423+
@Override
3424+
public WebAuthNOptions loadOptionsById(TenantIdentifier tenantIdentifier, String optionsId)
3425+
throws StorageQueryException {
3426+
try {
3427+
return WebAuthNQueries.loadOptionsById(this, tenantIdentifier, optionsId);
3428+
} catch (SQLException e){
3429+
throw new StorageQueryException(e);
3430+
}
3431+
}
3432+
3433+
@Override
3434+
public WebAuthNStoredCredential loadCredentialByIdForUser(TenantIdentifier tenantIdentifier, String credentialId, String recipeUserId)
3435+
throws StorageQueryException {
3436+
try {
3437+
return WebAuthNQueries.loadCredentialByIdForUser(this, tenantIdentifier, credentialId, recipeUserId);
3438+
} catch (SQLException e) {
3439+
throw new StorageQueryException(e);
3440+
}
3441+
}
3442+
3443+
3444+
@Override
3445+
public WebAuthNStoredCredential saveCredentials_Transaction(TenantIdentifier tenantIdentifier,
3446+
TransactionConnection con,
3447+
WebAuthNStoredCredential credential)
3448+
throws StorageQueryException {
3449+
3450+
Connection sqlCon = (Connection) con.getConnection();
3451+
try {
3452+
return WebAuthNQueries.saveCredential_Transaction(this, sqlCon, tenantIdentifier, credential);
3453+
} catch (SQLException e) {
3454+
throw new StorageQueryException(e);
3455+
}
3456+
}
3457+
3458+
@Override
3459+
public WebAuthNOptions loadOptionsById_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
3460+
String optionsId) throws StorageQueryException {
3461+
try {
3462+
Connection sqlCon = (Connection) con.getConnection();
3463+
return WebAuthNQueries.loadOptionsById_Transaction(this, sqlCon, tenantIdentifier, optionsId);
3464+
} catch (SQLException e) {
3465+
throw new StorageQueryException(e);
3466+
}
3467+
}
3468+
3469+
@Override
3470+
public WebAuthNStoredCredential loadCredentialById_Transaction(TenantIdentifier tenantIdentifier,
3471+
TransactionConnection con, String credentialId)
3472+
throws StorageQueryException {
3473+
try {
3474+
Connection sqlCon = (Connection) con.getConnection();
3475+
return WebAuthNQueries.loadCredentialById_Transaction(this, sqlCon, tenantIdentifier, credentialId);
3476+
} catch (SQLException e) {
3477+
throw new StorageQueryException(e);
3478+
}
3479+
}
3480+
3481+
@Override
3482+
public AuthRecipeUserInfo signUpWithCredentialsRegister_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
3483+
String userId, String email, String relyingPartyId, WebAuthNStoredCredential credential)
3484+
throws StorageQueryException, io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException, TenantOrAppNotFoundException,
3485+
DuplicateUserEmailException {
3486+
Connection sqlCon = (Connection) con.getConnection();
3487+
try {
3488+
return WebAuthNQueries.signUpWithCredentialRegister_Transaction(this, sqlCon, tenantIdentifier, userId, email, relyingPartyId, credential);
3489+
} catch (StorageTransactionLogicException stle) {
3490+
if (stle.actualException instanceof SQLException) {
3491+
ServerErrorMessage errorMessage = ((PSQLException) stle.actualException).getServerErrorMessage();
3492+
PostgreSQLConfig config = Config.getConfig(this);
3493+
3494+
if (isUniqueConstraintError(errorMessage, config.getWebAuthNUserToTenantTable(),"email")) {
3495+
throw new DuplicateUserEmailException();
3496+
} else if (isPrimaryKeyError(errorMessage, config.getWebAuthNUsersTable())
3497+
|| isPrimaryKeyError(errorMessage, config.getUsersTable())
3498+
|| isPrimaryKeyError(errorMessage, config.getWebAuthNUserToTenantTable())
3499+
|| isPrimaryKeyError(errorMessage, config.getAppIdToUserIdTable())) {
3500+
throw new io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException();
3501+
} else if (isForeignKeyConstraintError(
3502+
errorMessage,
3503+
config.getAppsTable(),
3504+
"app_id")) {
3505+
throw new TenantOrAppNotFoundException(tenantIdentifier.toAppIdentifier());
3506+
} else if (isForeignKeyConstraintError(
3507+
errorMessage,
3508+
config.getTenantsTable(),
3509+
"tenant_id")) {
3510+
throw new TenantOrAppNotFoundException(tenantIdentifier);
3511+
}
3512+
}
3513+
3514+
throw new StorageQueryException(stle.actualException);
3515+
} catch (SQLException e) {
3516+
throw new StorageQueryException(e);
3517+
}
3518+
}
3519+
3520+
@Override
3521+
public AuthRecipeUserInfo signUp_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
3522+
String userId, String email, String relyingPartyId)
3523+
throws StorageQueryException, TenantOrAppNotFoundException, DuplicateUserEmailException,
3524+
io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException {
3525+
Connection sqlCon = (Connection) con.getConnection();
3526+
try {
3527+
return WebAuthNQueries.signUp_Transaction(this, sqlCon, tenantIdentifier, userId, email, relyingPartyId);
3528+
} catch (StorageTransactionLogicException stle) {
3529+
if (stle.actualException instanceof SQLException) {
3530+
ServerErrorMessage errorMessage = ((PSQLException) stle.actualException).getServerErrorMessage();
3531+
PostgreSQLConfig config = Config.getConfig(this);
3532+
3533+
if (isUniqueConstraintError(errorMessage, config.getWebAuthNUserToTenantTable(),"email")) {
3534+
throw new DuplicateUserEmailException();
3535+
} else if (isPrimaryKeyError(errorMessage, config.getWebAuthNUsersTable())
3536+
|| isPrimaryKeyError(errorMessage, config.getUsersTable())
3537+
|| isPrimaryKeyError(errorMessage, config.getWebAuthNUserToTenantTable())
3538+
|| isPrimaryKeyError(errorMessage, config.getAppIdToUserIdTable())) {
3539+
throw new io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException();
3540+
} else if (isForeignKeyConstraintError(
3541+
errorMessage,
3542+
config.getAppsTable(),
3543+
"app_id")) {
3544+
throw new TenantOrAppNotFoundException(tenantIdentifier.toAppIdentifier());
3545+
} else if (isForeignKeyConstraintError(
3546+
errorMessage,
3547+
config.getTenantsTable(),
3548+
"tenant_id")) {
3549+
throw new TenantOrAppNotFoundException(tenantIdentifier);
3550+
}
3551+
}
3552+
3553+
throw new StorageQueryException(stle.actualException);
3554+
} catch (SQLException e) {
3555+
throw new StorageQueryException(e);
3556+
}
3557+
}
3558+
3559+
@Override
3560+
public AuthRecipeUserInfo getUserInfoByCredentialId_Transaction(TenantIdentifier tenantIdentifier,
3561+
TransactionConnection con, String credentialId)
3562+
throws StorageQueryException {
3563+
try {
3564+
Connection sqlCon = (Connection) con.getConnection();
3565+
return WebAuthNQueries.getUserInfoByCredentialId_Transaction(this, sqlCon, tenantIdentifier, credentialId);
3566+
} catch (SQLException e) {
3567+
throw new StorageQueryException(e);
3568+
}
3569+
}
3570+
3571+
@Override
3572+
public void updateCounter_Transaction(TenantIdentifier tenantIdentifier,
3573+
TransactionConnection con, String credentialId,
3574+
long counter) throws StorageQueryException {
3575+
try {
3576+
Connection sqlCon = (Connection) con.getConnection();
3577+
WebAuthNQueries.updateCounter_Transaction(this, sqlCon, tenantIdentifier, credentialId, counter);
3578+
} catch (SQLException e) {
3579+
throw new StorageQueryException(e);
3580+
}
3581+
}
3582+
3583+
@Override
3584+
public void addRecoverAccountToken(TenantIdentifier tenantIdentifier, AccountRecoveryTokenInfo accountRecoveryTokenInfo)
3585+
throws DuplicateRecoverAccountTokenException, StorageQueryException {
3586+
try {
3587+
WebAuthNQueries.addRecoverAccountToken(this, tenantIdentifier, accountRecoveryTokenInfo);
3588+
} catch (SQLException e) {
3589+
throw new StorageQueryException(e);
3590+
}
3591+
}
3592+
3593+
@Override
3594+
public void removeCredential(TenantIdentifier tenantIdentifier, String userId, String credentialId)
3595+
throws StorageQueryException, WebauthNCredentialNotExistsException {
3596+
try {
3597+
int rowsUpdated = WebAuthNQueries.removeCredential(this, tenantIdentifier, userId, credentialId);
3598+
if(rowsUpdated < 1) {
3599+
throw new WebauthNCredentialNotExistsException();
3600+
}
3601+
} catch (SQLException e) {
3602+
throw new StorageQueryException(e);
3603+
}
3604+
}
3605+
3606+
@Override
3607+
public void removeOptions(TenantIdentifier tenantIdentifier, String optionsId)
3608+
throws StorageQueryException, WebauthNOptionsNotExistsException {
3609+
try {
3610+
int rowsUpdated = WebAuthNQueries.removeOptions(this, tenantIdentifier, optionsId);
3611+
if(rowsUpdated < 1) {
3612+
throw new WebauthNOptionsNotExistsException();
3613+
}
3614+
} catch (SQLException e) {
3615+
throw new StorageQueryException(e);
3616+
}
3617+
}
3618+
3619+
@Override
3620+
public List<WebAuthNStoredCredential> listCredentialsForUser(TenantIdentifier tenantIdentifier, String userId)
3621+
throws StorageQueryException {
3622+
try {
3623+
return WebAuthNQueries.listCredentials(this, tenantIdentifier, userId);
3624+
} catch (SQLException e) {
3625+
throw new StorageQueryException(e);
3626+
}
3627+
}
3628+
3629+
@Override
3630+
public void updateUserEmail(TenantIdentifier tenantIdentifier, String userId, String newEmail)
3631+
throws StorageQueryException, io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException,
3632+
DuplicateUserEmailException {
3633+
try {
3634+
WebAuthNQueries.updateUserEmail(this, tenantIdentifier, userId, newEmail);
3635+
} catch (StorageQueryException e) {
3636+
if (e.getCause() instanceof SQLException){
3637+
ServerErrorMessage errorMessage = ((PSQLException) e.getCause()).getServerErrorMessage();
3638+
PostgreSQLConfig config = Config.getConfig(this);
3639+
3640+
if (isUniqueConstraintError(errorMessage, config.getWebAuthNUserToTenantTable(),
3641+
"email")) {
3642+
throw new DuplicateUserEmailException();
3643+
} else if (isForeignKeyConstraintError(errorMessage,config.getWebAuthNUserToTenantTable(),"user_id")) {
3644+
throw new io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException();
3645+
}
3646+
}
3647+
throw new StorageQueryException(e);
3648+
}
3649+
}
3650+
3651+
@Override
3652+
public AccountRecoveryTokenInfo getAccountRecoveryTokenInfoByToken_Transaction(TenantIdentifier tenantIdentifier,
3653+
TransactionConnection con,
3654+
String token)
3655+
throws StorageQueryException {
3656+
3657+
Connection sqlCon = (Connection) con.getConnection();
3658+
try {
3659+
return WebAuthNQueries.getAccountRecoveryTokenInfoByToken_Transaction(this, tenantIdentifier, sqlCon, token);
3660+
} catch (SQLException e) {
3661+
throw new StorageQueryException(e);
3662+
}
3663+
}
3664+
3665+
@Override
3666+
public void deleteAccountRecoveryTokenByEmail_Transaction(TenantIdentifier tenantIdentifier,
3667+
TransactionConnection con, String email)
3668+
throws StorageQueryException {
3669+
Connection sqlCon = (Connection) con.getConnection();
3670+
try {
3671+
WebAuthNQueries.deleteAccountRecoveryTokenByEmail_Transaction(this, sqlCon, tenantIdentifier, email);
3672+
} catch (SQLException e) {
3673+
throw new StorageQueryException(e);
3674+
}
3675+
}
3676+
3677+
@Override
3678+
public void deleteExpiredAccountRecoveryTokens() throws StorageQueryException {
3679+
try {
3680+
WebAuthNQueries.deleteExpiredAccountRecoveryTokens(this);
3681+
} catch (SQLException e) {
3682+
throw new StorageQueryException(e);
3683+
}
3684+
}
3685+
3686+
@Override
3687+
public void deleteExpiredGeneratedOptions() throws StorageQueryException {
3688+
try {
3689+
WebAuthNQueries.deleteExpiredGeneratedOptions(this);
3690+
} catch (SQLException e) {
3691+
throw new StorageQueryException(e);
3692+
}
3693+
}
33843694
}

src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ public String getOAuthLogoutChallengesTable() {
470470

471471
public String getWebAuthNCredentialsTable() { return addSchemaAndPrefixToTableName("webauthn_credentials"); }
472472

473+
public String getWebAuthNAccountRecoveryTokenTable() { return addSchemaAndPrefixToTableName("webauthn_account_recovery_tokens"); }
474+
473475
private String addSchemaAndPrefixToTableName(String tableName) {
474476
return addSchemaToTableName(postgresql_table_names_prefix + tableName);
475477
}

0 commit comments

Comments
 (0)