From c3238cfc3dcd1aba533d71fc668f89447df2162b Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 27 Jun 2024 10:42:09 +0530 Subject: [PATCH 01/18] fix: tenant config --- .../multitenancy/EmailPasswordConfig.java | 22 +++ .../multitenancy/PasswordlessConfig.java | 30 +++ .../multitenancy/TenantConfig.java | 182 ++++++++---------- .../multitenancy/ThirdPartyConfig.java | 43 +++-- 4 files changed, 161 insertions(+), 116 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java index aac0c636..23a036ef 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java @@ -16,6 +16,11 @@ package io.supertokens.pluginInterface.multitenancy; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.List; + public class EmailPasswordConfig { public final boolean enabled; @@ -32,4 +37,21 @@ public boolean equals(Object other) { return false; } + public JsonElement toJson3_0(String[] firstFactors) { + JsonObject result = new JsonObject(); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || List.of(firstFactors).contains("emailpassword") + )); + return result; + } + + public JsonElement toJson5_0(String[] firstFactors) { + JsonObject result = new JsonObject(); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || firstFactors.length > 0 + )); + return result; + } } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java index 66db1977..0c00b4ab 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java @@ -16,6 +16,13 @@ package io.supertokens.pluginInterface.multitenancy; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + public class PasswordlessConfig { public boolean enabled; @@ -31,4 +38,27 @@ public boolean equals(Object other) { } return false; } + + public JsonElement toJson3_0(@Nullable String[] firstFactors) { + JsonObject result = new JsonObject(); + List firstFactorsList = firstFactors == null ? new ArrayList<>() : List.of(firstFactors); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || + firstFactorsList.contains("otp-phone") || + firstFactorsList.contains("otp-email") || + firstFactorsList.contains("link-phone") || + firstFactorsList.contains("link-email") + )); + return result; + } + + public JsonElement toJson5_0(String[] firstFactors) { + JsonObject result = new JsonObject(); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || firstFactors.length > 0 + )); + return result; + } } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java index 4de5119b..7c7b2812 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -25,6 +25,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.List; public class TenantConfig { @@ -115,131 +116,102 @@ public int hashCode() { return tenantIdentifier.hashCode(); } - public JsonObject toJson3_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { - JsonObject tenantConfigObject = toJson5_0(shouldProtectDbConfig, storage, protectedCoreConfigs); - - // as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371 - tenantConfigObject.get("emailPassword").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.emailPasswordConfig.enabled) || - (this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) - ); - tenantConfigObject.get("thirdParty").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.thirdPartyConfig.enabled) || - (this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) - ); - tenantConfigObject.get("passwordless").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.passwordlessConfig.enabled) || - (this.firstFactors != null && - (List.of(this.firstFactors).contains("otp-email") || - List.of(this.firstFactors).contains("otp-phone") || - List.of(this.firstFactors).contains("link-email") || - List.of(this.firstFactors).contains("link-phone"))) - ); - - tenantConfigObject.remove("firstFactors"); - tenantConfigObject.remove("requiredSecondaryFactors"); - - return tenantConfigObject; - } - - public JsonObject toJson5_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + private void protectConfigs(JsonObject tenantConfigObject, Storage storage, String[] protectedCoreConfigs) { + String[] protectedConfigs = storage.getProtectedConfigsFromSuperTokensSaaSUsers(); + for (String config : protectedConfigs) { + if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { + tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); + } + } - Gson gson = new Gson(); - JsonObject tenantConfigObject = gson.toJsonTree(this).getAsJsonObject(); + for (String config : protectedCoreConfigs) { + if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { + tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); + } + } + } - tenantConfigObject.add("thirdParty", this.thirdPartyConfig.toJson()); - tenantConfigObject.addProperty("tenantId", this.tenantIdentifier.getTenantId()); + public JsonObject toJson3_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + JsonObject result = new JsonObject(); + result.addProperty("tenantId", this.tenantIdentifier.getTenantId()); + result.add("emailPassword", this.emailPasswordConfig.toJson3_0(this.firstFactors)); + result.add("thirdParty", this.thirdPartyConfig.toJson3_0(this.firstFactors)); + result.add("passwordless", this.passwordlessConfig.toJson3_0(this.firstFactors)); + result.add("coreConfig", this.coreConfig); if (shouldProtectDbConfig) { - String[] protectedConfigs = storage.getProtectedConfigsFromSuperTokensSaaSUsers(); - for (String config : protectedConfigs) { - if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { - tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); - } - } - - for (String config : protectedCoreConfigs) { - if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { - tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); - } - } + this.protectConfigs(result, storage, protectedCoreConfigs); } - if (!tenantConfigObject.get("thirdParty").getAsJsonObject().has("providers")) { - tenantConfigObject.get("thirdParty").getAsJsonObject().add("providers", new JsonArray()); + return result; + } + + public JsonObject toJson5_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + JsonObject result = new JsonObject(); + result.addProperty("tenantId", this.tenantIdentifier.getTenantId()); + result.add("emailPassword", this.emailPasswordConfig.toJson5_0(this.firstFactors)); + result.add("thirdParty", this.thirdPartyConfig.toJson5_0(this.firstFactors)); + result.add("passwordless", this.passwordlessConfig.toJson5_0(this.firstFactors)); + + if (this.firstFactors != null && this.firstFactors.length > 0) { + result.add("firstFactors", new Gson().toJsonTree(this.firstFactors)); } + if (this.requiredSecondaryFactors != null) { + result.add("requiredSecondaryFactors", new Gson().toJsonTree(this.requiredSecondaryFactors)); + } + result.add("coreConfig", this.coreConfig); - // as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371 - tenantConfigObject.get("emailPassword").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.emailPasswordConfig.enabled) || - (this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) || - (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains("emailpassword")) - ); - tenantConfigObject.get("thirdParty").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.thirdPartyConfig.enabled) || - (this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) || - (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains("thirdparty")) - ); - tenantConfigObject.get("passwordless").getAsJsonObject().addProperty( - "enabled", - (this.firstFactors == null && this.passwordlessConfig.enabled) || - (this.firstFactors != null && - (List.of(this.firstFactors).contains("otp-email") || - List.of(this.firstFactors).contains("otp-phone") || - List.of(this.firstFactors).contains("link-email") || - List.of(this.firstFactors).contains("link-phone"))) || - (this.requiredSecondaryFactors != null && - (List.of(this.requiredSecondaryFactors).contains("otp-email") || - List.of(this.requiredSecondaryFactors).contains("otp-phone") || - List.of(this.requiredSecondaryFactors).contains("link-email") || - List.of(this.requiredSecondaryFactors).contains("link-phone"))) - ); - - if (tenantConfigObject.has("firstFactors") && tenantConfigObject.get("firstFactors").getAsJsonArray().size() == 0) { - tenantConfigObject.remove("firstFactors"); + if (shouldProtectDbConfig) { + this.protectConfigs(result, storage, protectedCoreConfigs); } - return tenantConfigObject; + return result; } - public JsonObject toJson_v2(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + public JsonObject toJson_v2_5_1(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + JsonObject result = new JsonObject(); + result.addProperty("tenantId", this.tenantIdentifier.getTenantId()); + result.add("thirdParty", this.thirdPartyConfig.toJson_v2_5_1()); - Gson gson = new Gson(); - JsonObject tenantConfigObject = gson.toJsonTree(this).getAsJsonObject(); - - tenantConfigObject.add("thirdParty", this.thirdPartyConfig.toJson()); - tenantConfigObject.addProperty("tenantId", this.tenantIdentifier.getTenantId()); + List firstFactors = this.firstFactors == null ? null : new ArrayList<>(List.of(this.firstFactors)); - if (shouldProtectDbConfig) { - String[] protectedConfigs = storage.getProtectedConfigsFromSuperTokensSaaSUsers(); - for (String config : protectedConfigs) { - if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { - tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); + if (firstFactors == null) { + if (!this.emailPasswordConfig.enabled || + !this.thirdPartyConfig.enabled || !this.passwordlessConfig.enabled) { + firstFactors = new ArrayList<>(); + if (this.emailPasswordConfig.enabled) { + firstFactors.add("emailpassword"); } - } - - for (String config : protectedCoreConfigs) { - if (tenantConfigObject.get("coreConfig").getAsJsonObject().has(config)) { - tenantConfigObject.get("coreConfig").getAsJsonObject().remove(config); + if (this.thirdPartyConfig.enabled) { + firstFactors.add("thirdparty"); + } + if (this.passwordlessConfig.enabled) { + firstFactors.add("otp-email"); + firstFactors.add("otp-phone"); + firstFactors.add("link-email"); + firstFactors.add("link-phone"); } } } - // as per https://github.com/supertokens/supertokens-core/issues/979#issuecomment-2099971371 - tenantConfigObject.remove("emailPassword"); - tenantConfigObject.remove("passwordless"); - tenantConfigObject.get("thirdParty").getAsJsonObject().remove("enabled"); + if (firstFactors != null) { + result.add("firstFactors", new Gson().toJsonTree(firstFactors)); + } + + if (this.requiredSecondaryFactors != null) { + result.add("requiredSecondaryFactors", new Gson().toJsonTree(this.requiredSecondaryFactors)); + } + + result.add("coreConfig", this.coreConfig); + if (shouldProtectDbConfig) { + this.protectConfigs(result, storage, protectedCoreConfigs); + } - return tenantConfigObject; + return result; } public boolean isEmailPasswordEnabled() { - return this.emailPasswordConfig.enabled || + return Boolean.TRUE.equals(this.emailPasswordConfig.enabled) || this.firstFactors == null || (this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) || (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains( @@ -247,7 +219,7 @@ public boolean isEmailPasswordEnabled() { } public boolean isThirdPartyEnabled() { - return this.thirdPartyConfig.enabled || + return Boolean.TRUE.equals(this.thirdPartyConfig.enabled) || this.firstFactors == null || (this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) || (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains( @@ -255,7 +227,7 @@ public boolean isThirdPartyEnabled() { } public boolean isPasswordlessEnabled() { - return this.passwordlessConfig.enabled || + return Boolean.TRUE.equals(this.passwordlessConfig.enabled) || this.firstFactors == null || (this.firstFactors != null && (List.of(this.firstFactors).contains("otp-email") || diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java index 8c0fd7f9..e85b4e5f 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java @@ -16,10 +16,7 @@ package io.supertokens.pluginInterface.multitenancy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.google.gson.*; import io.supertokens.pluginInterface.utils.Utils; import javax.annotation.Nonnull; @@ -37,18 +34,42 @@ public ThirdPartyConfig(boolean enabled, @Nullable Provider[] providers) { this.providers = providers; } - public JsonObject toJson() { + private void addProvidersToJson(JsonObject result) { + if (this.providers != null && this.providers.length > 0) { + JsonArray providersArray = new JsonArray(); + for (Provider provider : this.providers) { + providersArray.add(provider.toJson()); + } + result.add("providers", providersArray); + } + } + + public JsonElement toJson3_0(String[] firstFactors) { + JsonObject result = new JsonObject(); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || List.of(firstFactors).contains("thirdparty") + )); + this.addProvidersToJson(result); + return result; + } + + public JsonElement toJson5_0(String[] firstFactors) { JsonObject result = new JsonObject(); - result.addProperty("enabled", this.enabled); + result.addProperty("enabled", + this.enabled && ( + firstFactors == null || firstFactors.length > 0 + )); + this.addProvidersToJson(result); + return result; + } + public JsonElement toJson_v2_5_1() { + JsonObject result = new JsonObject(); if (this.providers != null) { result.add("providers", new JsonArray()); - - for (Provider provider : this.providers) { - result.getAsJsonArray("providers").add(provider.toJson()); - } } - + this.addProvidersToJson(result); return result; } From f5f9eacc509a27af76614e219f3b9060f3b1da71 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 27 Jun 2024 10:56:45 +0530 Subject: [PATCH 02/18] fix: thirdparty providers --- .../multitenancy/ThirdPartyConfig.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java index e85b4e5f..ef7802e9 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java @@ -35,13 +35,13 @@ public ThirdPartyConfig(boolean enabled, @Nullable Provider[] providers) { } private void addProvidersToJson(JsonObject result) { - if (this.providers != null && this.providers.length > 0) { - JsonArray providersArray = new JsonArray(); + JsonArray providersArray = new JsonArray(); + if (this.providers != null) { for (Provider provider : this.providers) { providersArray.add(provider.toJson()); } - result.add("providers", providersArray); } + result.add("providers", providersArray); } public JsonElement toJson3_0(String[] firstFactors) { @@ -49,6 +49,8 @@ public JsonElement toJson3_0(String[] firstFactors) { result.addProperty("enabled", this.enabled && ( firstFactors == null || List.of(firstFactors).contains("thirdparty") + ) && ( + providers == null || providers.length > 0 )); this.addProvidersToJson(result); return result; @@ -59,6 +61,8 @@ public JsonElement toJson5_0(String[] firstFactors) { result.addProperty("enabled", this.enabled && ( firstFactors == null || firstFactors.length > 0 + ) && ( + providers == null || providers.length > 0 )); this.addProvidersToJson(result); return result; @@ -66,10 +70,9 @@ public JsonElement toJson5_0(String[] firstFactors) { public JsonElement toJson_v2_5_1() { JsonObject result = new JsonObject(); - if (this.providers != null) { - result.add("providers", new JsonArray()); + if (providers != null) { + this.addProvidersToJson(result); } - this.addProvidersToJson(result); return result; } From 00be7952ce8925c296b4c56016c0fc92eb58bb0b Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 27 Jun 2024 15:34:56 +0530 Subject: [PATCH 03/18] fix: cleanup --- .../multitenancy/TenantConfig.java | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java index 7c7b2812..27effce6 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java @@ -209,35 +209,4 @@ public JsonObject toJson_v2_5_1(boolean shouldProtectDbConfig, Storage storage, return result; } - - public boolean isEmailPasswordEnabled() { - return Boolean.TRUE.equals(this.emailPasswordConfig.enabled) || - this.firstFactors == null || - (this.firstFactors != null && List.of(this.firstFactors).contains("emailpassword")) || - (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains( - "emailpassword")); - } - - public boolean isThirdPartyEnabled() { - return Boolean.TRUE.equals(this.thirdPartyConfig.enabled) || - this.firstFactors == null || - (this.firstFactors != null && List.of(this.firstFactors).contains("thirdparty")) || - (this.requiredSecondaryFactors != null && List.of(this.requiredSecondaryFactors).contains( - "thirdparty")); - } - - public boolean isPasswordlessEnabled() { - return Boolean.TRUE.equals(this.passwordlessConfig.enabled) || - this.firstFactors == null || - (this.firstFactors != null && - (List.of(this.firstFactors).contains("otp-email") || - List.of(this.firstFactors).contains("otp-phone") || - List.of(this.firstFactors).contains("link-email") || - List.of(this.firstFactors).contains("link-phone"))) || - (this.requiredSecondaryFactors != null && - (List.of(this.requiredSecondaryFactors).contains("otp-email") || - List.of(this.requiredSecondaryFactors).contains("otp-phone") || - List.of(this.requiredSecondaryFactors).contains("link-email") || - List.of(this.requiredSecondaryFactors).contains("link-phone"))); - } } From be4f84111bd8304ec805e978691645f193f141b9 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 2 Jul 2024 11:56:33 +0530 Subject: [PATCH 04/18] fix: providers non null --- .../multitenancy/ThirdPartyConfig.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java index ef7802e9..8748b95f 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java @@ -26,20 +26,18 @@ public class ThirdPartyConfig { public final boolean enabled; - @Nullable + @Nonnull public final Provider[] providers; public ThirdPartyConfig(boolean enabled, @Nullable Provider[] providers) { this.enabled = enabled; - this.providers = providers; + this.providers = providers == null ? new Provider[0] : providers; } private void addProvidersToJson(JsonObject result) { JsonArray providersArray = new JsonArray(); - if (this.providers != null) { - for (Provider provider : this.providers) { - providersArray.add(provider.toJson()); - } + for (Provider provider : this.providers) { + providersArray.add(provider.toJson()); } result.add("providers", providersArray); } @@ -49,8 +47,6 @@ public JsonElement toJson3_0(String[] firstFactors) { result.addProperty("enabled", this.enabled && ( firstFactors == null || List.of(firstFactors).contains("thirdparty") - ) && ( - providers == null || providers.length > 0 )); this.addProvidersToJson(result); return result; @@ -61,8 +57,6 @@ public JsonElement toJson5_0(String[] firstFactors) { result.addProperty("enabled", this.enabled && ( firstFactors == null || firstFactors.length > 0 - ) && ( - providers == null || providers.length > 0 )); this.addProvidersToJson(result); return result; @@ -70,9 +64,7 @@ public JsonElement toJson5_0(String[] firstFactors) { public JsonElement toJson_v2_5_1() { JsonObject result = new JsonObject(); - if (providers != null) { - this.addProvidersToJson(result); - } + this.addProvidersToJson(result); return result; } From 19d06a5a5fba516e6c4ca5c39a822a01e17f916e Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 2 Jul 2024 12:33:00 +0530 Subject: [PATCH 05/18] fix: changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1286525..502e0bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Adds new class `ConfigFieldInfo` that represents a core config field - Adds new method `getPluginConfigFieldsInfo` to fetch the plugin config as json in `DashboardStorage` - Updates `TenantConfig` to support `null` and empty array state for `firstFactors` -- Update `ThirdPartyConfig` to support `null` and empty array state for `providers` ## [6.1.0] - 2024-04-17 From 439730eb4f3bb18a52d566501f29cf3c82d2b67b Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 3 Jul 2024 13:05:02 +0530 Subject: [PATCH 06/18] fix: pr comments --- .../multitenancy/EmailPasswordConfig.java | 23 ++++++++----- .../multitenancy/PasswordlessConfig.java | 32 ++++++++++++------- .../multitenancy/TenantConfig.java | 9 +++--- .../multitenancy/ThirdPartyConfig.java | 22 +++++++++---- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java index 23a036ef..297647bb 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java @@ -37,21 +37,28 @@ public boolean equals(Object other) { return false; } - public JsonElement toJson3_0(String[] firstFactors) { + public boolean isEnabledInLesserThanOrEqualTo4_0(String[] firstFactors) { + return this.enabled && ( + firstFactors == null || List.of(firstFactors).contains("emailpassword") + ); + } + + public JsonElement toJsonLesserThanOrEqualTo4_0(String[] firstFactors) { JsonObject result = new JsonObject(); result.addProperty("enabled", - this.enabled && ( - firstFactors == null || List.of(firstFactors).contains("emailpassword") - )); + this.isEnabledInLesserThanOrEqualTo4_0(firstFactors)); return result; } + public boolean isEnabledIn5_0(String[] firstFactors) { + return this.enabled && ( + firstFactors == null || firstFactors.length > 0 + ); + } + public JsonElement toJson5_0(String[] firstFactors) { JsonObject result = new JsonObject(); - result.addProperty("enabled", - this.enabled && ( - firstFactors == null || firstFactors.length > 0 - )); + result.addProperty("enabled", this.isEnabledIn5_0(firstFactors)); return result; } } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java index 0c00b4ab..68686337 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/PasswordlessConfig.java @@ -39,26 +39,34 @@ public boolean equals(Object other) { return false; } - public JsonElement toJson3_0(@Nullable String[] firstFactors) { - JsonObject result = new JsonObject(); + public boolean isEnabledInLesserThanOrEqualTo4_0(@Nullable String[] firstFactors) { List firstFactorsList = firstFactors == null ? new ArrayList<>() : List.of(firstFactors); + return this.enabled && ( + firstFactors == null || + firstFactorsList.contains("otp-phone") || + firstFactorsList.contains("otp-email") || + firstFactorsList.contains("link-phone") || + firstFactorsList.contains("link-email") + ); + } + + public JsonElement toJsonLesserThanOrEqualTo4_0(@Nullable String[] firstFactors) { + JsonObject result = new JsonObject(); result.addProperty("enabled", - this.enabled && ( - firstFactors == null || - firstFactorsList.contains("otp-phone") || - firstFactorsList.contains("otp-email") || - firstFactorsList.contains("link-phone") || - firstFactorsList.contains("link-email") - )); + this.isEnabledInLesserThanOrEqualTo4_0(firstFactors)); return result; } + public boolean isEnabledIn5_0(@Nullable String[] firstFactors) { + return this.enabled && ( + firstFactors == null || firstFactors.length > 0 + ); + } + public JsonElement toJson5_0(String[] firstFactors) { JsonObject result = new JsonObject(); result.addProperty("enabled", - this.enabled && ( - firstFactors == null || firstFactors.length > 0 - )); + this.isEnabledIn5_0(firstFactors)); return result; } } diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java index 27effce6..d8375ebb 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java @@ -17,7 +17,6 @@ package io.supertokens.pluginInterface.multitenancy; import com.google.gson.Gson; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import io.supertokens.pluginInterface.Storage; @@ -131,12 +130,12 @@ private void protectConfigs(JsonObject tenantConfigObject, Storage storage, Stri } } - public JsonObject toJson3_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + public JsonObject toJsonLesserThanOrEqualTo4_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { JsonObject result = new JsonObject(); result.addProperty("tenantId", this.tenantIdentifier.getTenantId()); - result.add("emailPassword", this.emailPasswordConfig.toJson3_0(this.firstFactors)); - result.add("thirdParty", this.thirdPartyConfig.toJson3_0(this.firstFactors)); - result.add("passwordless", this.passwordlessConfig.toJson3_0(this.firstFactors)); + result.add("emailPassword", this.emailPasswordConfig.toJsonLesserThanOrEqualTo4_0(this.firstFactors)); + result.add("thirdParty", this.thirdPartyConfig.toJsonLesserThanOrEqualTo4_0(this.firstFactors)); + result.add("passwordless", this.passwordlessConfig.toJsonLesserThanOrEqualTo4_0(this.firstFactors)); result.add("coreConfig", this.coreConfig); if (shouldProtectDbConfig) { diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java index 8748b95f..06fcb31d 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java @@ -42,22 +42,30 @@ private void addProvidersToJson(JsonObject result) { result.add("providers", providersArray); } - public JsonElement toJson3_0(String[] firstFactors) { + public boolean isEnabledInLesserThanOrEqualTo4_0(String[] firstFactors) { + return this.enabled && ( + firstFactors == null || List.of(firstFactors).contains("thirdparty") + ); + } + + public JsonElement toJsonLesserThanOrEqualTo4_0(String[] firstFactors) { JsonObject result = new JsonObject(); result.addProperty("enabled", - this.enabled && ( - firstFactors == null || List.of(firstFactors).contains("thirdparty") - )); + this.isEnabledInLesserThanOrEqualTo4_0(firstFactors)); this.addProvidersToJson(result); return result; } + public boolean isEnabledIn5_0(String[] firstFactors) { + return this.enabled && ( + firstFactors == null || firstFactors.length > 0 + ); + } + public JsonElement toJson5_0(String[] firstFactors) { JsonObject result = new JsonObject(); result.addProperty("enabled", - this.enabled && ( - firstFactors == null || firstFactors.length > 0 - )); + this.isEnabledIn5_0(firstFactors)); this.addProvidersToJson(result); return result; } From f5cf449ac5d02966b4cf6640a3b64153df55f919 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 8 Jul 2024 21:47:52 +0530 Subject: [PATCH 07/18] reformats code --- CHANGELOG.md | 4 ++-- .../pluginInterface/multitenancy/EmailPasswordConfig.java | 4 ++-- .../pluginInterface/multitenancy/TenantConfig.java | 3 ++- .../pluginInterface/multitenancy/ThirdPartyConfig.java | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed742fb2..d6f79581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,8 +97,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - SessionSQLStorage interface changes - Adds `deleteSessionsOfUser_Transaction` - ThirdPartyStorage interface changes - - - Removes `deleteThirdPartyUser`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUsersByEmail` + - + Removes `deleteThirdPartyUser`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUsersByEmail` - Changes return type of `signUp` from `UserInfo` to `AuthRecipeUserInfo` - ThirdPartySQLStorage interface changes - Adds `deleteThirdPartyUser_Transaction` diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java index 297647bb..95fea0fb 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/EmailPasswordConfig.java @@ -40,7 +40,7 @@ public boolean equals(Object other) { public boolean isEnabledInLesserThanOrEqualTo4_0(String[] firstFactors) { return this.enabled && ( firstFactors == null || List.of(firstFactors).contains("emailpassword") - ); + ); } public JsonElement toJsonLesserThanOrEqualTo4_0(String[] firstFactors) { @@ -53,7 +53,7 @@ public JsonElement toJsonLesserThanOrEqualTo4_0(String[] firstFactors) { public boolean isEnabledIn5_0(String[] firstFactors) { return this.enabled && ( firstFactors == null || firstFactors.length > 0 - ); + ); } public JsonElement toJson5_0(String[] firstFactors) { diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java index b5a2ffa3..ca9a6ac9 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/TenantConfig.java @@ -131,7 +131,8 @@ private void protectConfigs(JsonObject tenantConfigObject, Storage storage, Stri } } - public JsonObject toJsonLesserThanOrEqualTo4_0(boolean shouldProtectDbConfig, Storage storage, String[] protectedCoreConfigs) { + public JsonObject toJsonLesserThanOrEqualTo4_0(boolean shouldProtectDbConfig, Storage storage, + String[] protectedCoreConfigs) { JsonObject result = new JsonObject(); result.addProperty("tenantId", this.tenantIdentifier.getTenantId()); result.add("emailPassword", this.emailPasswordConfig.toJsonLesserThanOrEqualTo4_0(this.firstFactors)); diff --git a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java index 30e0b592..b4bf7472 100644 --- a/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java +++ b/src/main/java/io/supertokens/pluginInterface/multitenancy/ThirdPartyConfig.java @@ -21,6 +21,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.List; import java.util.Objects; public class ThirdPartyConfig { From 8475fae82baceeb812894d424bd2af37574ed4ba Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 9 Jul 2024 16:21:38 +0530 Subject: [PATCH 08/18] adds function to check if client exists for the app --- .../pluginInterface/StorageUtils.java | 9 +++++++ .../pluginInterface/oauth/OAuthStorage.java | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java diff --git a/src/main/java/io/supertokens/pluginInterface/StorageUtils.java b/src/main/java/io/supertokens/pluginInterface/StorageUtils.java index 19bfb89a..9a18dbae 100644 --- a/src/main/java/io/supertokens/pluginInterface/StorageUtils.java +++ b/src/main/java/io/supertokens/pluginInterface/StorageUtils.java @@ -21,6 +21,7 @@ import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage; +import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage; import io.supertokens.pluginInterface.session.SessionStorage; import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; @@ -132,4 +133,12 @@ public static MultitenancyStorage getMultitenancyStorage(Storage storage) { } return (MultitenancyStorage) storage; } + + public static OAuthStorage getOAuthStorage(Storage storage) { + if (storage.getType() != STORAGE_TYPE.SQL) { + // we only support SQL for now + throw new UnsupportedOperationException(""); + } + return (OAuthStorage) storage; + } } diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java new file mode 100644 index 00000000..31c0089f --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.pluginInterface.oauth; + +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; + +public interface OAuthStorage extends NonAuthRecipeStorage { + public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId); +} From f9ebdd5cd0852ea2b1a47cfd24f167ba794ff0db Mon Sep 17 00:00:00 2001 From: Tamas Soltesz Date: Mon, 5 Aug 2024 21:47:40 +0200 Subject: [PATCH 09/18] feat: oauth - WIP (#158) * feat: oauth recipe id, sqlstoreage and response type * fix: review fixes * fix: review fixes * fix: review fix - renamed exception * feat: OAuthStorage isClientAlreadyExists * feat: delete from oauth table * fix: removing unused/unnecessary method, change return type of other --- .../pluginInterface/RECIPE_ID.java | 2 +- .../pluginInterface/oauth/OAuthStorage.java | 11 ++++++++- ...th2ClientAlreadyExistsForAppException.java | 24 +++++++++++++++++++ .../oauth/sqlStorage/OAuthSQLStorage.java | 24 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java diff --git a/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java b/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java index 6aee253f..d9d0848f 100644 --- a/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java +++ b/src/main/java/io/supertokens/pluginInterface/RECIPE_ID.java @@ -22,7 +22,7 @@ public enum RECIPE_ID { EMAIL_PASSWORD("emailpassword"), THIRD_PARTY("thirdparty"), SESSION("session"), EMAIL_VERIFICATION("emailverification"), JWT("jwt"), PASSWORDLESS("passwordless"), USER_METADATA("usermetadata"), USER_ROLES("userroles"), USER_ID_MAPPING("useridmapping"), DASHBOARD("dashboard"), TOTP("totp"), - MULTITENANCY("multitenancy"), ACCOUNT_LINKING("accountlinking"), MFA("mfa"); + MULTITENANCY("multitenancy"), ACCOUNT_LINKING("accountlinking"), MFA("mfa"), OAUTH("oauth"); private final String name; diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index 31c0089f..de6e8d86 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -16,9 +16,18 @@ package io.supertokens.pluginInterface.oauth; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.oauth.exceptions.OAuth2ClientAlreadyExistsForAppException; public interface OAuthStorage extends NonAuthRecipeStorage { - public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId); + + public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId) throws + StorageQueryException; + + public void addClientForApp(AppIdentifier appIdentifier, String clientId) throws StorageQueryException, + OAuth2ClientAlreadyExistsForAppException; + + public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java b/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java new file mode 100644 index 00000000..e7521a44 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.pluginInterface.oauth.exceptions; + +import java.io.Serial; + +public class OAuth2ClientAlreadyExistsForAppException extends Exception{ + @Serial + private static final long serialVersionUID = 2792232552559552544L; +} diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java new file mode 100644 index 00000000..df28bdef --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.pluginInterface.oauth.sqlStorage; + +import io.supertokens.pluginInterface.oauth.OAuthStorage; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; + +public interface OAuthSQLStorage extends OAuthStorage, SQLStorage { + +} From e6f111b23565c91270dd205bb7178f5a22f3fcaa Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 20 Aug 2024 13:09:45 +0530 Subject: [PATCH 10/18] fix: merge with latest (#159) * adding dev-v6.2.0 tag to this commit to ensure building * adding dev-v6.2.0 tag to this commit to ensure building --- jar/plugin-interface-6.2.0.jar | Bin 98212 -> 99448 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/jar/plugin-interface-6.2.0.jar b/jar/plugin-interface-6.2.0.jar index 4bcc23c2e64b8f349dfaf56b28df127ceb287ec4..fc86f02555158a0a94c5c87fa05e1416e1418624 100644 GIT binary patch delta 19844 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 delta 18454 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

-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 Date: Thu, 5 Sep 2024 16:02:14 +0530 Subject: [PATCH 11/18] fix: listClientsForApp --- .../io/supertokens/pluginInterface/oauth/OAuthStorage.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index de6e8d86..501d68bd 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -21,6 +21,8 @@ import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; import io.supertokens.pluginInterface.oauth.exceptions.OAuth2ClientAlreadyExistsForAppException; +import java.util.List; + public interface OAuthStorage extends NonAuthRecipeStorage { public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId) throws @@ -30,4 +32,6 @@ public void addClientForApp(AppIdentifier appIdentifier, String clientId) throws OAuth2ClientAlreadyExistsForAppException; public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; + + List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; } From 1224ca826f41e2d516b43a3c2a1c51a5cae36782 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 25 Sep 2024 16:13:43 +0530 Subject: [PATCH 12/18] fix: oauth plugin interface updates (#160) * fix: revoke * fix: pr comments * fix: oauth storage * fix: update * fix: add m2m token * fix: revoke and cleanup --- .../pluginInterface/oauth/OAuthStorage.java | 24 +++++++++++++++---- ...th2ClientAlreadyExistsForAppException.java | 24 ------------------- 2 files changed, 19 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index 501d68bd..94cb5e78 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -19,19 +19,33 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; -import io.supertokens.pluginInterface.oauth.exceptions.OAuth2ClientAlreadyExistsForAppException; import java.util.List; public interface OAuthStorage extends NonAuthRecipeStorage { - public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId) throws + public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; - public void addClientForApp(AppIdentifier appIdentifier, String clientId) throws StorageQueryException, - OAuth2ClientAlreadyExistsForAppException; + public void addOrUpdateClientForApp(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws StorageQueryException; public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; - List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + public List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + + public void revoke(AppIdentifier appIdentifier, String targetType, String targetValue, long exp) throws StorageQueryException; + + public boolean isRevoked(AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt) throws StorageQueryException; + + public void addM2MToken(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws StorageQueryException; + + public int countTotalNumberOfClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + + public int countTotalNumberOfClientCredentialsOnlyClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + + public int countTotalNumberOfM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) throws StorageQueryException; + + public int countTotalNumberOfM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException; + + public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException; } diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java b/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java deleted file mode 100644 index e7521a44..00000000 --- a/src/main/java/io/supertokens/pluginInterface/oauth/exceptions/OAuth2ClientAlreadyExistsForAppException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package io.supertokens.pluginInterface.oauth.exceptions; - -import java.io.Serial; - -public class OAuth2ClientAlreadyExistsForAppException extends Exception{ - @Serial - private static final long serialVersionUID = 2792232552559552544L; -} From 30e2bfe35aebbdafb5edb3959c4575b21031dfe5 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 27 Sep 2024 18:04:04 +0530 Subject: [PATCH 13/18] fix: logout (#161) * fix: logout * fix: session revoke in logout --- .../oauth/OAuthLogoutChallenge.java | 19 +++++++++++++++++++ .../pluginInterface/oauth/OAuthStorage.java | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/OAuthLogoutChallenge.java diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthLogoutChallenge.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthLogoutChallenge.java new file mode 100644 index 00000000..ca453367 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthLogoutChallenge.java @@ -0,0 +1,19 @@ +package io.supertokens.pluginInterface.oauth; + +public class OAuthLogoutChallenge { + public final String challenge; + public final String clientId; + public final String postLogoutRedirectionUri; + public final String sessionHandle; + public final String state; + public final long timeCreated; + + public OAuthLogoutChallenge(String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) { + this.challenge = challenge; + this.clientId = clientId; + this.postLogoutRedirectionUri = postLogoutRedirectionUri; + this.sessionHandle = sessionHandle; + this.state = state; + this.timeCreated = timeCreated; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index 94cb5e78..941eca86 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -48,4 +48,12 @@ public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clien public int countTotalNumberOfM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException; public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException; + + public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws StorageQueryException; + + public OAuthLogoutChallenge getLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; + + public void deleteLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; + + public void deleteLogoutChallengesBefore(AppIdentifier appIdentifier, long time) throws StorageQueryException; } From a2c0f9d1ecb2be3e7024e63a1af86c28a6fedb0a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 2 Oct 2024 15:35:53 +0530 Subject: [PATCH 14/18] fix: version and cleanup --- build.gradle | 2 +- .../pluginInterface/oauth/OAuthStorage.java | 4 +++- .../DuplicateOAuthLogoutChallengeException.java} | 11 ++++------- 3 files changed, 8 insertions(+), 9 deletions(-) rename src/main/java/io/supertokens/pluginInterface/oauth/{sqlStorage/OAuthSQLStorage.java => exception/DuplicateOAuthLogoutChallengeException.java} (68%) diff --git a/build.gradle b/build.gradle index 48fc30d8..b945eddd 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = "6.2.0" +version = "6.3.0" repositories { mavenCentral() diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index 941eca86..da6362d0 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -19,6 +19,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException; import java.util.List; @@ -49,7 +50,8 @@ public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clien public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException; - public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws StorageQueryException; + public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws + DuplicateOAuthLogoutChallengeException, StorageQueryException; public OAuthLogoutChallenge getLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/exception/DuplicateOAuthLogoutChallengeException.java similarity index 68% rename from src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java rename to src/main/java/io/supertokens/pluginInterface/oauth/exception/DuplicateOAuthLogoutChallengeException.java index df28bdef..0f1692ce 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/sqlStorage/OAuthSQLStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/exception/DuplicateOAuthLogoutChallengeException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the * "License") as published by the Apache Software Foundation. @@ -14,11 +14,8 @@ * under the License. */ -package io.supertokens.pluginInterface.oauth.sqlStorage; - -import io.supertokens.pluginInterface.oauth.OAuthStorage; -import io.supertokens.pluginInterface.sqlStorage.SQLStorage; - -public interface OAuthSQLStorage extends OAuthStorage, SQLStorage { +package io.supertokens.pluginInterface.oauth.exception; +public class DuplicateOAuthLogoutChallengeException extends Exception { + private static final long serialVersionUID = -7183235655606906540L; } From b04e4587d0b26c1f3081892a00d6a6fb22ffb07d Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 2 Oct 2024 16:46:05 +0530 Subject: [PATCH 15/18] fix: rename / refactor --- .../oauth/OAuthRevokeTargetType.java | 18 ++++++++++ .../pluginInterface/oauth/OAuthStorage.java | 34 +++++++++---------- 2 files changed, 35 insertions(+), 17 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/OAuthRevokeTargetType.java diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthRevokeTargetType.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthRevokeTargetType.java new file mode 100644 index 00000000..8ccdf975 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthRevokeTargetType.java @@ -0,0 +1,18 @@ +package io.supertokens.pluginInterface.oauth; + +public enum OAuthRevokeTargetType { + CLIENT_ID("client_id"), + GID("gid"), + JTI("jti"), + SESSION_HANDLE("session_handle"); + + private final String value; + + OAuthRevokeTargetType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index da6362d0..cfcec425 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -25,37 +25,37 @@ public interface OAuthStorage extends NonAuthRecipeStorage { - public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clientId) throws + public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; - public void addOrUpdateClientForApp(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws StorageQueryException; + public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws StorageQueryException; - public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; + public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; - public List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + public List listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException; - public void revoke(AppIdentifier appIdentifier, String targetType, String targetValue, long exp) throws StorageQueryException; + public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) throws StorageQueryException; - public boolean isRevoked(AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt) throws StorageQueryException; + public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) throws StorageQueryException; - public void addM2MToken(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws StorageQueryException; + public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws StorageQueryException; - public int countTotalNumberOfClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException; - public int countTotalNumberOfClientCredentialsOnlyClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException; + public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws + DuplicateOAuthLogoutChallengeException, StorageQueryException; - public int countTotalNumberOfM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) throws StorageQueryException; + public OAuthLogoutChallenge getOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; - public int countTotalNumberOfM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException; + public void deleteOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; - public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException; + public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryException; - public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws - DuplicateOAuthLogoutChallengeException, StorageQueryException; + public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException; - public OAuthLogoutChallenge getLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; + public int countTotalNumberOfClientCredentialsOnlyOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException; - public void deleteLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; + public int countTotalNumberOfOAuthM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) throws StorageQueryException; - public void deleteLogoutChallengesBefore(AppIdentifier appIdentifier, long time) throws StorageQueryException; + public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException; } From 112ea024687e1ddd6fcb51a3ed3f6bdffc55937c Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 2 Oct 2024 17:19:15 +0530 Subject: [PATCH 16/18] fix: changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f79581..77c1e08d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [6.3.0] - 2024-10-02 + +- Adds `OAuthStorage` interface + ## [6.2.0] - 2024-05-24 - Adds new class `ConfigFieldInfo` that represents a core config field From 19671e45c0b4a71ca5d6c89faae7492a0f9461f8 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 2 Oct 2024 17:20:12 +0530 Subject: [PATCH 17/18] fix: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c1e08d..423fea79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [6.3.0] - 2024-10-02 -- Adds `OAuthStorage` interface +- Adds `OAuthStorage` interface for OAuth Provider support ## [6.2.0] - 2024-05-24 From 1f99dced132e42330581042145316c164635261b Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 4 Oct 2024 08:58:32 +0530 Subject: [PATCH 18/18] fix: constraints --- .../pluginInterface/oauth/OAuthStorage.java | 10 ++++---- .../OAuthClientNotFoundException.java | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/supertokens/pluginInterface/oauth/exception/OAuthClientNotFoundException.java diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java index cfcec425..e0e5916a 100644 --- a/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java +++ b/src/main/java/io/supertokens/pluginInterface/oauth/OAuthStorage.java @@ -18,8 +18,10 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException; +import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException; import java.util.List; @@ -28,22 +30,22 @@ public interface OAuthStorage extends NonAuthRecipeStorage { public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; - public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws StorageQueryException; + public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws TenantOrAppNotFoundException, StorageQueryException; public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException; public List listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException; - public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) throws StorageQueryException; + public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) throws TenantOrAppNotFoundException, StorageQueryException; public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) throws StorageQueryException; - public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws StorageQueryException; + public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws OAuthClientNotFoundException, StorageQueryException; public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException; public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws - DuplicateOAuthLogoutChallengeException, StorageQueryException; + DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException, StorageQueryException; public OAuthLogoutChallenge getOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException; diff --git a/src/main/java/io/supertokens/pluginInterface/oauth/exception/OAuthClientNotFoundException.java b/src/main/java/io/supertokens/pluginInterface/oauth/exception/OAuthClientNotFoundException.java new file mode 100644 index 00000000..f14d7bf2 --- /dev/null +++ b/src/main/java/io/supertokens/pluginInterface/oauth/exception/OAuthClientNotFoundException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.pluginInterface.oauth.exception; + +import java.io.Serial; + +public class OAuthClientNotFoundException extends Exception { + @Serial + private static final long serialVersionUID = 1412853176388698991L; +}