Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
41c22f589b |
|
|
@ -1,64 +0,0 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update -y
|
||||
#&& apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y --fix-missing
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
|
||||
|
||||
RUN apt install mysql-server -y
|
||||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN [ -d /var/run/mysqld ] || mkdir -p /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
RUN chmod +x /runMySQL.sh
|
||||
|
||||
RUN apt-get install -y git-core
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz -C /usr/java/
|
||||
RUN mv /usr/java/jdk-21.0.7+6 /usr/java/jdk-21.0.7
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'JRE_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
#install postgres 13
|
||||
# Import Repository Signing Key
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
RUN apt install curl gpg gnupg2 software-properties-common apt-transport-https lsb-release ca-certificates sudo -y
|
||||
# Add PostgreSQL repository
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
# Update again
|
||||
RUN apt update
|
||||
# Install PostgreSQL 13
|
||||
RUN apt install -y postgresql-13
|
||||
# Verify PostgreSQL 13 Installation on Ubuntu 22.04|20.04|18.04
|
||||
RUN psql --version
|
||||
# Manage PostgreSQL 13 service
|
||||
#you can manage with `service postgresql start`
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
|
||||
|
||||
RUN apt install mysql-server -y
|
||||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN mkdir /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
RUN chmod +x /runMySQL.sh
|
||||
|
||||
RUN apt-get install -y git-core
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Install OpenJDK 12
|
||||
RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://download.java.net/java/GA/jdk21.0.7/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-21.0.7_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mv openjdk-21.0.7_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-21.0.7_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
|
||||
|
||||
RUN apt install mysql-server -y
|
||||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN mkdir /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
RUN chmod +x /runMySQL.sh
|
||||
|
||||
RUN apt-get install -y git-core
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Install OpenJDK 12
|
||||
RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 15.0.1
|
||||
RUN wget https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mv openjdk-15.0.1_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-15.0.1' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
|
||||
|
||||
RUN apt install mysql-server -y
|
||||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN [ -d /var/run/mysqld ] || mkdir -p /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
RUN chmod +x /runMySQL.sh
|
||||
|
||||
RUN apt-get install -y git-core
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN mkdir -p /usr/java/jdk-21.0.7
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz -C /usr/java/jdk-21.0.7
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
#install postgres 13
|
||||
# Import Repository Signing Key
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
RUN apt install curl gpg gnupg2 software-properties-common apt-transport-https lsb-release ca-certificates sudo -y
|
||||
# Add PostgreSQL repository
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
# Update again
|
||||
RUN apt update
|
||||
# Install PostgreSQL 13
|
||||
RUN apt install -y postgresql-13
|
||||
# Verify PostgreSQL 13 Installation on Ubuntu 22.04|20.04|18.04
|
||||
RUN psql --version
|
||||
# Manage PostgreSQL 13 service
|
||||
#you can manage with `service postgresql start`
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
version: 2.1
|
||||
orbs:
|
||||
slack: circleci/slack@3.4.2
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: tamassupertokens/supertokens_core_testing
|
||||
- image: rishabhpoddar/oauth-server-cicd
|
||||
- image: mongo
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: root
|
||||
resource_class: large
|
||||
parallelism: 4
|
||||
parameters:
|
||||
plugin:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir ~/junit
|
||||
- run: echo $'\n[mysqld]\ncharacter_set_server=utf8mb4\nmax_connections=10000' >> /etc/mysql/mysql.cnf
|
||||
- run: echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/13/main/pg_hba.conf
|
||||
- run: echo "listen_addresses='*'" >> /etc/postgresql/13/main/postgresql.conf
|
||||
- run: sed -i 's/^#*\s*max_connections\s*=.*/max_connections = 10000/' /etc/postgresql/13/main/postgresql.conf
|
||||
- run: (cd .circleci/ && ./doTests.sh << parameters.plugin >>)
|
||||
- store_test_results:
|
||||
path: ~/junit
|
||||
- slack/status
|
||||
|
||||
mark-passed:
|
||||
docker:
|
||||
- image: tamassupertokens/supertokens_core_testing
|
||||
steps:
|
||||
- checkout
|
||||
- run: (cd .circleci && ./markPassed.sh)
|
||||
- slack/status
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
tagged-build:
|
||||
jobs:
|
||||
- test:
|
||||
plugin: sqlite
|
||||
name: test-sqlite
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
only: /test-cicd\/.*/
|
||||
- test:
|
||||
plugin: mongodb
|
||||
name: test-mongodb
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
only: /test-cicd\/.*/
|
||||
- test:
|
||||
plugin: postgresql
|
||||
name: test-postgresql
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
only: /test-cicd\/.*/
|
||||
- test:
|
||||
plugin: mysql
|
||||
name: test-mysql
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
only: /test-cicd\/.*/
|
||||
- mark-passed:
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
requires:
|
||||
- test-sqlite
|
||||
- test-mongodb
|
||||
- test-postgresql
|
||||
- test-mysql
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
function cleanup {
|
||||
if test -f "pluginInterfaceExactVersionsOutput"; then
|
||||
rm pluginInterfaceExactVersionsOutput
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
cleanup
|
||||
|
||||
pluginToTest=$1
|
||||
|
||||
pinnedDBJson=$(curl -s -X GET \
|
||||
'https://api.supertokens.io/0/plugin/pinned?planType=FREE' \
|
||||
-H 'api-version: 0')
|
||||
pinnedDBLength=$(echo "$pinnedDBJson" | jq ".plugins | length")
|
||||
pinnedDBArray=$(echo "$pinnedDBJson" | jq ".plugins")
|
||||
echo "got pinned dbs..."
|
||||
|
||||
pluginInterfaceJson=$(cat ../pluginInterfaceSupported.json)
|
||||
pluginInterfaceLength=$(echo "$pluginInterfaceJson" | jq ".versions | length")
|
||||
pluginInterfaceArray=$(echo "$pluginInterfaceJson" | jq ".versions")
|
||||
echo "got plugin interface relations"
|
||||
|
||||
coreDriverJson=$(cat ../coreDriverInterfaceSupported.json)
|
||||
coreDriverArray=$(echo "$coreDriverJson" | jq ".versions")
|
||||
echo "got core driver relations"
|
||||
|
||||
./getPluginInterfaceExactVersions.sh "$pluginInterfaceLength" "$pluginInterfaceArray"
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "all plugin interfaces found... failed. exiting!"
|
||||
exit 1
|
||||
else
|
||||
echo "all plugin interfaces found..."
|
||||
fi
|
||||
|
||||
# get core version
|
||||
coreVersion=$(cat ../build.gradle | grep -e "version =" -e "version=")
|
||||
while IFS='"' read -ra ADDR; do
|
||||
counter=0
|
||||
for i in "${ADDR[@]}"; do
|
||||
if [ $counter == 1 ]
|
||||
then
|
||||
coreVersion=$i
|
||||
fi
|
||||
counter=$(($counter+1))
|
||||
done
|
||||
done <<< "$coreVersion"
|
||||
|
||||
responseStatus=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \
|
||||
https://api.supertokens.io/0/core \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'api-version: 0' \
|
||||
-d "{
|
||||
\"password\": \"$SUPERTOKENS_API_KEY\",
|
||||
\"planType\":\"FREE\",
|
||||
\"version\":\"$coreVersion\",
|
||||
\"pluginInterfaces\": $pluginInterfaceArray,
|
||||
\"coreDriverInterfaces\": $coreDriverArray
|
||||
}")
|
||||
if [ "$responseStatus" -ne "200" ]
|
||||
then
|
||||
echo "failed core PUT API status code: $responseStatus. Exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p ~/junit
|
||||
|
||||
someTestsRan=false
|
||||
while read -u 10 line
|
||||
do
|
||||
if [[ $line = "" ]]; then
|
||||
continue
|
||||
fi
|
||||
i=0
|
||||
currTag=$(echo "$line" | jq .tag)
|
||||
currTag=$(echo "$currTag" | tr -d '"')
|
||||
|
||||
currVersion=$(echo "$line" | jq .version)
|
||||
currVersion=$(echo "$currVersion" | tr -d '"')
|
||||
piX=$(cut -d'.' -f1 <<<"$currVersion")
|
||||
piY=$(cut -d'.' -f2 <<<"$currVersion")
|
||||
piVersion="$piX.$piY"
|
||||
|
||||
while [ $i -lt "$pinnedDBLength" ]; do
|
||||
someTestsRan=true
|
||||
currPinnedDb=$(echo "$pinnedDBArray" | jq ".[$i]")
|
||||
currPinnedDb=$(echo "$currPinnedDb" | tr -d '"')
|
||||
|
||||
i=$((i+1))
|
||||
|
||||
if [[ $currPinnedDb == $pluginToTest ]]
|
||||
then
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo "===== testing $currPinnedDb with plugin-interface $currVersion ====="
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
if [[ $currPinnedDb == "sqlite" ]]
|
||||
then
|
||||
# shellcheck disable=SC2034
|
||||
continue=1
|
||||
else
|
||||
response=$(curl -s -X GET \
|
||||
"https://api.supertokens.io/0/plugin-interface/dependency/plugin/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$piVersion&pluginName=$currPinnedDb" \
|
||||
-H 'api-version: 0')
|
||||
if [[ $(echo "$response" | jq .plugin) == "null" ]]
|
||||
then
|
||||
echo "fetching latest X.Y version for $currPinnedDb given plugin-interface X.Y version: $piVersion gave response: $response"
|
||||
exit 1
|
||||
fi
|
||||
pinnedDbVersionX2=$(echo $response | jq .plugin | tr -d '"')
|
||||
|
||||
response=$(curl -s -X GET \
|
||||
"https://api.supertokens.io/0/plugin/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$pinnedDbVersionX2&name=$currPinnedDb" \
|
||||
-H 'api-version: 0')
|
||||
if [[ $(echo "$response" | jq .tag) == "null" ]]
|
||||
then
|
||||
echo "fetching latest X.Y.Z version for $currPinnedDb, X.Y version: $pinnedDbVersionX2 gave response: $response"
|
||||
exit 1
|
||||
fi
|
||||
pinnedDbVersionTag=$(echo "$response" | jq .tag | tr -d '"')
|
||||
pinnedDbVersion=$(echo "$response" | jq .version | tr -d '"')
|
||||
./startDb.sh "$currPinnedDb"
|
||||
fi
|
||||
|
||||
cd ../../
|
||||
git clone git@github.com:supertokens/supertokens-root.git
|
||||
cd supertokens-root
|
||||
rm gradle.properties
|
||||
|
||||
update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 2
|
||||
update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 2
|
||||
|
||||
coreX=$(cut -d'.' -f1 <<<"$coreVersion")
|
||||
coreY=$(cut -d'.' -f2 <<<"$coreVersion")
|
||||
if [[ $currPinnedDb == "sqlite" ]]
|
||||
then
|
||||
echo -e "core,$coreX.$coreY\nplugin-interface,$piVersion" > modules.txt
|
||||
else
|
||||
echo -e "core,$coreX.$coreY\nplugin-interface,$piVersion\n$currPinnedDb-plugin,$pinnedDbVersionX2" > modules.txt
|
||||
fi
|
||||
./loadModules
|
||||
cd supertokens-core
|
||||
git checkout dev-v$coreVersion
|
||||
cd ../supertokens-plugin-interface
|
||||
git checkout $currTag
|
||||
if [[ $currPinnedDb == "sqlite" ]]
|
||||
then
|
||||
# shellcheck disable=SC2034
|
||||
continue=1
|
||||
else
|
||||
cd ../supertokens-$currPinnedDb-plugin
|
||||
git checkout $pinnedDbVersionTag
|
||||
fi
|
||||
cd ../
|
||||
echo $SUPERTOKENS_API_KEY > apiPassword
|
||||
|
||||
./startTestingEnv --cicd
|
||||
|
||||
TEST_EXIT_CODE=$?
|
||||
|
||||
if [ -d ~/junit ]
|
||||
then
|
||||
echo "Copying output from core"
|
||||
cp ~/supertokens-root/supertokens-core/build/test-results/test/*.xml ~/junit/
|
||||
|
||||
if [[ $pluginToTest != "sqlite" ]]
|
||||
then
|
||||
echo "Copying output from plugin"
|
||||
cp ~/supertokens-root/supertokens-$pluginToTest-plugin/build/test-results/test/*.xml ~/junit/
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $TEST_EXIT_CODE -ne 0 ]]
|
||||
then
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo "===== testing $currPinnedDb with plugin-interface $currVersion FAILED ====="
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
cat logs/*
|
||||
cd ../project/
|
||||
echo "test failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo "===== testing $currPinnedDb with plugin-interface $currVersion SUCCEEDED ====="
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
cd ..
|
||||
rm -rf supertokens-root
|
||||
|
||||
if [[ $currPinnedDb == "sqlite" ]]
|
||||
then
|
||||
# shellcheck disable=SC2034
|
||||
continue=1
|
||||
else
|
||||
curl -o supertokens.zip -s -X GET \
|
||||
"https://api.supertokens.io/0/app/download?pluginName=$currPinnedDb&os=linux&mode=DEV&binary=FREE&targetCore=$coreVersion&targetPlugin=$pinnedDbVersion" \
|
||||
-H 'api-version: 0'
|
||||
unzip supertokens.zip -d .
|
||||
rm supertokens.zip
|
||||
cd supertokens
|
||||
../project/.circleci/testCli.sh
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
cd ../
|
||||
fi
|
||||
|
||||
rm -rf supertokens
|
||||
cd project/.circleci
|
||||
if [[ $currPinnedDb == "sqlite" ]]
|
||||
then
|
||||
# shellcheck disable=SC2034
|
||||
continue=1
|
||||
else
|
||||
./stopDb.sh $currPinnedDb
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
done 10<pluginInterfaceExactVersionsOutput
|
||||
|
||||
if [[ $someTestsRan = "true" ]]
|
||||
then
|
||||
echo "tests ran successfully"
|
||||
else
|
||||
echo "no test ran"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# args: <length of array> <array like ["0.0", "0.1"]>
|
||||
touch pluginInterfaceExactVersionsOutput
|
||||
i=0
|
||||
while [ $i -lt $1 ]; do
|
||||
currVersion=`echo $2 | jq ".[$i]"`
|
||||
currVersion=`echo $currVersion | tr -d '"'`
|
||||
i=$((i+1))
|
||||
# now we have the current version like 0.0.
|
||||
# We now have to find something that matches dev-v0.0.* or v0.0.*
|
||||
response=`curl -s -X GET \
|
||||
"https://api.supertokens.io/0/plugin-interface/latest?password=$SUPERTOKENS_API_KEY&planType=FREE&mode=DEV&version=$currVersion" \
|
||||
-H 'api-version: 0'`
|
||||
if [[ `echo $response | jq .tag` == "null" ]]
|
||||
then
|
||||
echo $response
|
||||
exit 1
|
||||
fi
|
||||
echo $response >> pluginInterfaceExactVersionsOutput
|
||||
done
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
coreVersion=$(cat ../build.gradle | grep -e "version =" -e "version=")
|
||||
|
||||
while IFS='"' read -ra ADDR; do
|
||||
counter=0
|
||||
for i in "${ADDR[@]}"; do
|
||||
if [ $counter == 1 ]
|
||||
then
|
||||
coreVersion=$i
|
||||
fi
|
||||
counter=$(($counter+1))
|
||||
done
|
||||
done <<< "$coreVersion"
|
||||
|
||||
echo "calling /core PATCH to make testing passed"
|
||||
responseStatus=$(curl -s -o /dev/null -w "%{http_code}" -X PATCH \
|
||||
https://api.supertokens.io/0/core \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'api-version: 0' \
|
||||
-d "{
|
||||
\"password\": \"$SUPERTOKENS_API_KEY\",
|
||||
\"planType\":\"FREE\",
|
||||
\"version\":\"$coreVersion\",
|
||||
\"testPassed\": true
|
||||
}")
|
||||
if [ "$responseStatus" -ne "200" ]
|
||||
then
|
||||
echo "patch api failed"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -1 +0,0 @@
|
|||
chown -R mysql:mysql /var/lib/mysql /var/run/mysqld && service mysql start
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
case $1 in
|
||||
mysql)
|
||||
(cd / && ./runMySQL.sh)
|
||||
mysql -u root --password=root -e "CREATE DATABASE supertokens;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st0;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st1;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st2;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st3;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st4;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st5;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st6;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st7;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st8;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st9;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st10;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st11;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st12;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st13;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st14;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st15;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st16;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st17;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st18;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st19;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st20;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st21;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st22;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st23;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st24;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st25;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st26;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st27;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st28;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st29;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st30;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st31;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st32;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st33;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st34;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st35;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st36;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st37;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st38;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st39;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st40;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st41;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st42;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st43;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st44;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st45;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st46;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st47;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st48;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st49;"
|
||||
mysql -u root --password=root -e "CREATE DATABASE st50;"
|
||||
;;
|
||||
postgresql)
|
||||
service postgresql start
|
||||
sudo -u postgres psql --command "CREATE USER root WITH SUPERUSER PASSWORD 'root';"
|
||||
createdb
|
||||
psql -c "create database supertokens;"
|
||||
psql -c "create database st0;"
|
||||
psql -c "create database st1;"
|
||||
psql -c "create database st2;"
|
||||
psql -c "create database st3;"
|
||||
psql -c "create database st4;"
|
||||
psql -c "create database st5;"
|
||||
psql -c "create database st6;"
|
||||
psql -c "create database st7;"
|
||||
psql -c "create database st8;"
|
||||
psql -c "create database st9;"
|
||||
psql -c "create database st10;"
|
||||
psql -c "create database st11;"
|
||||
psql -c "create database st12;"
|
||||
psql -c "create database st13;"
|
||||
psql -c "create database st14;"
|
||||
psql -c "create database st15;"
|
||||
psql -c "create database st16;"
|
||||
psql -c "create database st17;"
|
||||
psql -c "create database st18;"
|
||||
psql -c "create database st19;"
|
||||
psql -c "create database st20;"
|
||||
psql -c "create database st21;"
|
||||
psql -c "create database st22;"
|
||||
psql -c "create database st23;"
|
||||
psql -c "create database st24;"
|
||||
psql -c "create database st25;"
|
||||
psql -c "create database st26;"
|
||||
psql -c "create database st27;"
|
||||
psql -c "create database st28;"
|
||||
psql -c "create database st29;"
|
||||
psql -c "create database st30;"
|
||||
psql -c "create database st31;"
|
||||
psql -c "create database st32;"
|
||||
psql -c "create database st33;"
|
||||
psql -c "create database st34;"
|
||||
psql -c "create database st35;"
|
||||
psql -c "create database st36;"
|
||||
psql -c "create database st37;"
|
||||
psql -c "create database st38;"
|
||||
psql -c "create database st39;"
|
||||
psql -c "create database st40;"
|
||||
psql -c "create database st41;"
|
||||
psql -c "create database st42;"
|
||||
psql -c "create database st43;"
|
||||
psql -c "create database st44;"
|
||||
psql -c "create database st45;"
|
||||
psql -c "create database st46;"
|
||||
psql -c "create database st47;"
|
||||
psql -c "create database st48;"
|
||||
psql -c "create database st49;"
|
||||
psql -c "create database st50;"
|
||||
esac
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
case $1 in
|
||||
mysql)
|
||||
service mysql stop
|
||||
;;
|
||||
postgresql)
|
||||
service postgresql stop
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
# inside supertokens downloaded zip
|
||||
|
||||
./install
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
supertokens start --port=8888
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
supertokens list
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -i 's/# mysql_connection_uri:/mysql_connection_uri: "mysql:\/\/root:root@localhost:3306?rewriteBatchedStatements=true"/g' /usr/lib/supertokens/config.yaml
|
||||
sed -i 's/# mongodb_connection_uri:/mongodb_connection_uri: mongodb:\/\/root:root@localhost:27017/g' /usr/lib/supertokens/config.yaml
|
||||
sed -i 's/# disable_telemetry:/disable_telemetry: true/g' /usr/lib/supertokens/config.yaml
|
||||
|
||||
supertokens start --port=8889
|
||||
|
||||
supertokens list
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl http://localhost:8889/hello
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl http://localhost:8888/hello
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
supertokens stop
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
supertokens uninstall
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
echo "cli testing failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -37,7 +37,6 @@ highlighting the necessary changes)
|
|||
- If no such branch exists, then create one from the latest released branch.
|
||||
- [ ] If added a foreign key constraint on `app_id_to_user_id` table, make sure to delete from this table when deleting
|
||||
the user as well if `deleteUserIdMappingToo` is false.
|
||||
- [ ] If added a new recipe, then make sure to update the bulk import API to include the new recipe.
|
||||
|
||||
## Remaining TODOs for this PR
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
FROM ubuntu:22.04 AS tmp
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
RUN apt-get install build-essential -y
|
||||
RUN apt-get install -y git-core wget unzip jq curl
|
||||
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN mkdir -p /usr/java/
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
RUN mv /usr/java/jdk-21.0.7+6 /usr/java/jdk-21.0.7
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
RUN wget -O docker-entrypoint.sh https://raw.githubusercontent.com/supertokens/supertokens-docker-postgresql/master/docker-entrypoint.sh
|
||||
|
||||
# RUN wget https://services.gradle.org/distributions/gradle-7.0-all.zip
|
||||
# RUN unzip gradle-7.0-all.zip
|
||||
# ENV GRADLE_HOME=/gradle-7.0
|
||||
# ENV PATH=$PATH:$GRADLE_HOME/bin
|
||||
|
||||
RUN git clone https://github.com/supertokens/supertokens-root.git
|
||||
WORKDIR /supertokens-root
|
||||
COPY ./pluginInterfaceSupported.json pluginInterfaceSupported.json
|
||||
RUN git clone --single-branch --branch "$(cat pluginInterfaceSupported.json | jq '.versions[-1]' | tr -d '"')" "https://github.com/supertokens/supertokens-plugin-interface.git"
|
||||
RUN mkdir -p supertokens-core
|
||||
COPY ./ supertokens-core
|
||||
RUN echo "org.gradle.vfs.watch=false" >> ./gradle.properties
|
||||
RUN ./loadModules
|
||||
RUN ./utils/setupTestEnv --local
|
||||
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN groupadd supertokens && useradd -m -s /bin/bash -g supertokens supertokens
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr curl unzip && rm -rf /var/lib/apt/lists/*
|
||||
ENV GOSU_VERSION=1.7
|
||||
RUN set -x \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* \
|
||||
&& wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
|
||||
&& wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
|
||||
&& export GNUPGHOME="$(mktemp -d)" \
|
||||
&& gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
|
||||
&& gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
|
||||
&& gpgconf --kill all \
|
||||
&& rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
&& wget -O jre.zip "https://raw.githubusercontent.com/supertokens/jre/master/jre-21.0.7-linux.zip" \
|
||||
&& mkdir -p /usr/lib/supertokens/jre \
|
||||
&& unzip jre.zip \
|
||||
&& mv jre-*/* /usr/lib/supertokens/jre \
|
||||
&& apt-get purge -y --auto-remove ca-certificates wget unzip \
|
||||
&& rm -rf jre.zip
|
||||
COPY --from=tmp --chown=supertokens /supertokens-root/core /usr/lib/supertokens/core
|
||||
COPY --from=tmp --chown=supertokens /supertokens-root/plugin-interface /usr/lib/supertokens/plugin-interface
|
||||
COPY --from=tmp --chown=supertokens /supertokens-root/ee /usr/lib/supertokens/ee
|
||||
COPY --from=tmp --chown=supertokens /supertokens-root/temp/config.yaml /usr/lib/supertokens/config.yaml
|
||||
COPY --from=tmp --chown=supertokens /supertokens-root/version.yaml /usr/lib/supertokens/version.yaml
|
||||
COPY --from=tmp --chown=supertokens /docker-entrypoint.sh /usr/local/bin/
|
||||
RUN mkdir -p /lib/supertokens
|
||||
RUN chown -R supertokens:supertokens /lib/supertokens
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
RUN echo "$(md5sum /usr/lib/supertokens/config.yaml | awk '{ print $1 }')" >> /CONFIG_HASH
|
||||
RUN ln -s /usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
|
||||
EXPOSE 3567
|
||||
USER "supertokens"
|
||||
CMD ["/usr/lib/supertokens/jre/bin/java", "-classpath", "/usr/lib/supertokens/core/*:/usr/lib/supertokens/plugin-interface/*:/usr/lib/supertokens/ee/*", "io.supertokens.Main", "/usr/lib/supertokens", "DEV", "host=0.0.0.0", "test_mode", "tempDirLocation=/usr/lib/supertokens/temp", "configFile=/usr/lib/supertokens/temp/config.yaml"]
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
|
@ -65,4 +65,4 @@ register_plugin_version(
|
|||
plugin_version=plugin_version,
|
||||
plugin_interface_array=plugin_interface_array,
|
||||
plugin_name=os.environ.get("PLUGIN_NAME")
|
||||
)
|
||||
)
|
||||
|
|
@ -45,16 +45,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
ref: for_jdk_15_releases
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
name: Container Security Scan
|
||||
|
||||
on:
|
||||
# Allow manual triggering
|
||||
workflow_dispatch:
|
||||
|
||||
# Run automatically once a day at 2 AM UTC
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
jobs:
|
||||
container-scan:
|
||||
name: Scan SuperTokens PostgreSQL Container
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run Azure Container Scan
|
||||
id: container-scan
|
||||
uses: Azure/container-scan@v0
|
||||
continue-on-error: true
|
||||
with:
|
||||
image-name: supertokens/supertokens-postgresql:latest
|
||||
severity-threshold: LOW
|
||||
run-quality-checks: false
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
|
||||
- name: Upload scan results
|
||||
id: upload-scan-results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: container-scan-results
|
||||
path: |
|
||||
${{ steps.container-scan.outputs.scan-report-path }}
|
||||
retention-days: 30
|
||||
|
||||
- name: Generate Security Summary
|
||||
id: security-summary
|
||||
run: |
|
||||
echo "summary<<EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "**Image:** \`supertokens/supertokens-postgresql:latest\`\n" >> $GITHUB_OUTPUT
|
||||
echo "**Scan Date:** \`$(date -u)\`\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get the scan report path from the container scan output
|
||||
SCAN_REPORT_PATH="${{ steps.container-scan.outputs.scan-report-path }}"
|
||||
|
||||
if [ -f "$SCAN_REPORT_PATH" ]; then
|
||||
# Count vulnerabilities by severity using the correct JSON structure
|
||||
critical=$(jq '[.vulnerabilities[]? | select(.severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
high=$(jq '[.vulnerabilities[]? | select(.severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
medium=$(jq '[.vulnerabilities[]? | select(.severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
low=$(jq '[.vulnerabilities[]? | select(.severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
total_vulns=$(jq '[.vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
echo "**Total Vulnerabilities:** $total_vulns\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "- 🔴 **Critical**: $critical\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟠 **High**: $high\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟡 **Medium**: $medium\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟢 **Low**: $low\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ **Scan results not found or scan failed**" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "[📃 Download the full report](${{ steps.upload-scan-results.outputs.artifact-url }})\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Add to Action Summary
|
||||
run: |
|
||||
echo "**Image:** \`supertokens/supertokens-postgresql:latest\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Scan Date:** \`$(date -u)\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Get the scan report path from the container scan output
|
||||
SCAN_REPORT_PATH="${{ steps.container-scan.outputs.scan-report-path }}"
|
||||
|
||||
if [ -f "$SCAN_REPORT_PATH" ]; then
|
||||
# Count vulnerabilities by severity using the correct JSON structure
|
||||
critical=$(jq '[.vulnerabilities[]? | select(.severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
high=$(jq '[.vulnerabilities[]? | select(.severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
medium=$(jq '[.vulnerabilities[]? | select(.severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
low=$(jq '[.vulnerabilities[]? | select(.severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
total_vulns=$(jq '[.vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
echo "**Total Vulnerabilities:** $total_vulns" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "- 🔴 **Critical**: $critical" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟠 **High**: $high" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟡 **Medium**: $medium" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟢 **Low**: $low" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "**Vulnerabilities:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| ID | Package | Severity | | Description |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----|---------|----------|-|-------------|" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Extract and format vulnerabilities into a table with colored severity indicators, excluding LOW severity
|
||||
jq -r '.vulnerabilities[]? | select(.severity != "LOW") | "| \(.vulnerabilityId // "N/A") | \(.packageName // "N/A") | \(.severity // "UNKNOWN") | \(if .severity == "CRITICAL" then "🔴" elif .severity == "HIGH" then "🟠" elif .severity == "MEDIUM" then "🟡" else "🟢" end) | \((.description // "No description available") | gsub("\n"; " ")) |"' "$SCAN_REPORT_PATH" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Scan results not found or scan failed**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[📃 Download the full report](${{ steps.upload-scan-results.outputs.artifact-url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Post notification on Slack channel
|
||||
id: deployment_message
|
||||
uses: slackapi/slack-github-action@v2.1.0
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
text: ""
|
||||
blocks:
|
||||
- type: "header"
|
||||
text:
|
||||
type: "plain_text"
|
||||
text: "${{ steps.container-scan.outcome == 'success' && '✅' || '❌' }} Vulnerability Report: ${{ steps.container-scan.outcome == 'success' && 'All okay' || 'Needs attention' }}"
|
||||
- type: "markdown"
|
||||
text: "${{ steps.security-summary.outputs.summary }}"
|
||||
|
|
@ -53,10 +53,10 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 21.0.7
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
|
|
@ -81,16 +81,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: [dependency-branches, release-docker]
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
ref: for_jdk_15_releases
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ jobs:
|
|||
name: Lint PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
validateSingleCommit: true
|
||||
- uses: amannn/action-semantic-pull-request@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
validateSingleCommit: true
|
||||
changelog:
|
||||
name: Enforce Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dangoslen/changelog-enforcer@v2
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'Skip-Changelog'
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dangoslen/changelog-enforcer@v2
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'Skip-Changelog'
|
||||
unit-tests:
|
||||
name: Run unit tests
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
|
|
@ -19,8 +19,6 @@ jobs:
|
|||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
core-branch: ${{ github.ref_name }}
|
||||
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -36,16 +34,16 @@ jobs:
|
|||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
ref: for_jdk_15_releases
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
|
|
@ -101,4 +99,4 @@ jobs:
|
|||
context: ./supertokens-root
|
||||
tags: supertokens/supertokens-dev-${{ matrix.plugin }}:${{ steps.set_tag.outputs.TAG }}
|
||||
file: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin/.github/helpers/docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
@ -10,7 +10,7 @@ on:
|
|||
|
||||
jobs:
|
||||
stress-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
|
|
@ -44,4 +44,4 @@ jobs:
|
|||
echo "## Stress Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Test | Duration |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||
jq -r '.measurements[] | "| \(.title) | \(.formatted) |"' stress-tests/stats.json >> $GITHUB_STEP_SUMMARY
|
||||
jq -r '.measurements[] | "| \(.title) | \(.formatted) |"' stress-tests/stats.json >> $GITHUB_STEP_SUMMARY
|
||||
|
|
@ -3,10 +3,13 @@ name: Unit Tests
|
|||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
total-runners: 12
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
|
|
@ -16,33 +19,45 @@ jobs:
|
|||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
core-branch: ${{ github.head_ref }}
|
||||
|
||||
test:
|
||||
name: Unit tests
|
||||
runner-indexes:
|
||||
runs-on: ubuntu-latest
|
||||
name: Generate runner indexes
|
||||
needs: dependency-branches
|
||||
outputs:
|
||||
json: ${{ steps.generate-index-list.outputs.json }}
|
||||
steps:
|
||||
- id: generate-index-list
|
||||
run: |
|
||||
MAX_INDEX=$((${{ env.total-runners }}-1))
|
||||
INDEX_LIST=$(seq 0 ${MAX_INDEX})
|
||||
INDEX_JSON=$(jq --null-input --compact-output '. |= [inputs]' <<< ${INDEX_LIST})
|
||||
echo "::set-output name=json::${INDEX_JSON}"
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Unit tests: ${{ matrix.plugin }} plugin, runner #${{ matrix.runner-index }}"
|
||||
needs:
|
||||
- dependency-branches
|
||||
- runner-indexes
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner-index: ${{ fromjson(needs.runner-indexes.outputs.json) }}
|
||||
plugin:
|
||||
- sqlite
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
ref: for_jdk_15_releases
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
|
|
@ -71,12 +86,36 @@ jobs:
|
|||
- name: Start ${{ matrix.plugin }} server
|
||||
if: matrix.plugin != 'sqlite'
|
||||
run: cd supertokens-root/supertokens-${{ matrix.plugin }}-plugin && ./startDb.sh
|
||||
- name: Start oauth provider
|
||||
run: |
|
||||
docker run -d -p 4444:4444 -p 4445:4445 rishabhpoddar/oauth-server-cicd
|
||||
- uses: chaosaffe/split-tests@v1-alpha.1
|
||||
id: split-tests
|
||||
name: Split tests
|
||||
with:
|
||||
glob: 'supertokens-root/*/src/test/java/**/*.java'
|
||||
split-total: ${{ env.total-runners }}
|
||||
split-index: ${{ matrix.runner-index }}
|
||||
- run: 'echo "This runner will execute the following tests: ${{ steps.split-tests.outputs.test-suite }}"'
|
||||
- name: Run tests
|
||||
env:
|
||||
ST_PLUGIN_NAME: ${{ matrix.plugin }}
|
||||
run: |
|
||||
cd supertokens-root
|
||||
./gradlew test
|
||||
echo "./gradlew test \\" > test.sh
|
||||
chmod +x test.sh
|
||||
IFS=' ' read -ra TESTS <<< "${{ steps.split-tests.outputs.test-suite }}"
|
||||
for test in "${TESTS[@]}"; do
|
||||
test_name="${test%.java}"
|
||||
test_name="${test_name#supertokens-root/supertokens-core/src/test/java/}"
|
||||
test_name="${test_name//\//.}"
|
||||
echo " --tests $test_name \\" >> test.sh
|
||||
done
|
||||
echo "" >> test.sh
|
||||
echo "this is the test command:"
|
||||
cat test.sh
|
||||
echo "--------------------------------"
|
||||
./test.sh
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always()
|
||||
|
|
@ -84,4 +123,4 @@ jobs:
|
|||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
detailed_summary: true
|
||||
include_passed: false
|
||||
annotate_notice: true
|
||||
annotate_notice: true
|
||||
|
|
@ -12,7 +12,6 @@ gradle-app.setting
|
|||
!cli/jar/**/*.jar
|
||||
!downloader/jar/**/*.jar
|
||||
!ee/jar/**/*.jar
|
||||
!src/main/resources/**/*.jar
|
||||
|
||||
*target*
|
||||
*.war
|
||||
|
|
@ -48,7 +47,4 @@ local.properties
|
|||
*.iml
|
||||
ee/bin
|
||||
addDevTag
|
||||
addReleaseTag
|
||||
|
||||
install-linux.sh
|
||||
install-windows.bat
|
||||
addReleaseTag
|
||||
459
CHANGELOG.md
459
CHANGELOG.md
|
|
@ -7,469 +7,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [11.3.0]
|
||||
## [9.3.2]
|
||||
|
||||
- Adds SAML features
|
||||
- Fixes potential deadlock issue with `TelemetryProvider`
|
||||
- Adds DeadlockLogger as an utility for discovering deadlock issues
|
||||
|
||||
### Migration
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS saml_clients (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
client_secret TEXT,
|
||||
sso_login_url TEXT NOT NULL,
|
||||
redirect_uris TEXT NOT NULL,
|
||||
default_redirect_uri TEXT NOT NULL,
|
||||
idp_entity_id VARCHAR(256) NOT NULL,
|
||||
idp_signing_certificate TEXT NOT NULL,
|
||||
allow_idp_initiated_login BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
enable_request_signing BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_clients_pkey PRIMARY KEY(app_id, tenant_id, client_id),
|
||||
CONSTRAINT saml_clients_idp_entity_id_key UNIQUE (app_id, tenant_id, idp_entity_id),
|
||||
CONSTRAINT saml_clients_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_clients_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_clients_app_id_tenant_id_index ON saml_clients (app_id, tenant_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saml_relay_state (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
relay_state VARCHAR(256) NOT NULL,
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
redirect_uri TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_relay_state_pkey PRIMARY KEY(app_id, tenant_id, relay_state),
|
||||
CONSTRAINT saml_relay_state_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_relay_state_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_relay_state_app_id_tenant_id_index ON saml_relay_state (app_id, tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS saml_relay_state_expires_at_index ON saml_relay_state (expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saml_claims (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
code VARCHAR(256) NOT NULL,
|
||||
claims TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_claims_pkey PRIMARY KEY(app_id, tenant_id, code),
|
||||
CONSTRAINT saml_claims_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_claims_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_claims_app_id_tenant_id_index ON saml_claims (app_id, tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS saml_claims_expires_at_index ON saml_claims (expires_at);
|
||||
```
|
||||
|
||||
## [11.2.1]
|
||||
|
||||
- Fixes deadlock issue with `ResourceDistributor`
|
||||
- Fixes race issues with Refreshing OAuth token
|
||||
|
||||
## [11.2.0]
|
||||
|
||||
- Adds opentelemetry-javaagent to the core distribution
|
||||
|
||||
## [11.1.1]
|
||||
|
||||
- Updates tomcat-embed to 11.0.12 because of security vulnerabilities
|
||||
|
||||
## [11.1.0]
|
||||
|
||||
- Adds hikari logs to opentelemetry
|
||||
- Fetches core and plugin config from env
|
||||
- Open Telemetry configuration is now optional
|
||||
- Migrates API calls from supertokens.io to supertokens.com
|
||||
|
||||
## [11.0.5]
|
||||
|
||||
- Adds all logs to telemetry which were logged with `io/supertokens/output/Logging.java`
|
||||
- Upgrades the embedded tomcat to 11.0.8 because of security vulnerabilities
|
||||
- Adds back previously removed `implementationDependencies.json`, but now it is generated by the build process
|
||||
|
||||
## [11.0.4]
|
||||
|
||||
- Fixes user to roles association in bulk import users when the user is not a primary user
|
||||
|
||||
## [11.0.3]
|
||||
|
||||
- Fixes BatchUpdateException checks and error handling to prevent bulk import users stuck in `PROCESSING` state
|
||||
- Adds more DEBUG logging to the bulk import users process
|
||||
|
||||
## [11.0.2]
|
||||
|
||||
- Fixes `AuthRecipe#getUserByAccountInfo` to consider the tenantId instead of the appId when fetching the webauthn user
|
||||
|
||||
## [11.0.1]
|
||||
|
||||
- Upgrades the embedded tomcat 11.0.6 and logback classic to 1.5.13 because of security vulnerabilities
|
||||
|
||||
## [11.0.0]
|
||||
|
||||
- Migrates tests to Github Actions
|
||||
- Updates JRE to 21.
|
||||
|
||||
## [10.1.4]
|
||||
|
||||
- Fixes bulk migration user roles association when there is no external userId assigned to the user
|
||||
- Bulk migration now actually uses the `isVerified` field's value in the loginMethod input
|
||||
- Fixes nullpointer exception in bulk migration error handling in case of null external user id
|
||||
|
||||
## [10.1.3]
|
||||
|
||||
- Version bumped for re-release
|
||||
|
||||
## [10.1.2]
|
||||
|
||||
- Adds user_id index to the user roles table
|
||||
- Adds more debug logging to bulk migration
|
||||
- Adds more tests to bulk migration
|
||||
|
||||
### Migration
|
||||
|
||||
If using PostgreSQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE INDEX IF NOT EXISTS user_roles_app_id_user_id_index ON user_roles (app_id, user_id);
|
||||
```
|
||||
|
||||
If using MySQL, run the following SQL script:
|
||||
```sql
|
||||
CREATE INDEX user_roles_app_id_user_id_index ON user_roles (app_id, user_id);
|
||||
```
|
||||
|
||||
|
||||
## [10.1.1]
|
||||
|
||||
- Adds debug logging for the bulk migration process
|
||||
- Bulk migration users upload now returns the ids of the users.
|
||||
- Bulk Migration now requires Account Linking to be enabled only if the input data justifies it
|
||||
- Speed up Bulk Migration's account linking and primary user making
|
||||
|
||||
## [10.1.0]
|
||||
|
||||
- Adds Webauthn (Passkeys) support to core
|
||||
- Adds APIs:
|
||||
- GET `/recipe/webauthn/user/credential/`
|
||||
- GET `/recipe/webauthn/user/credential/list`
|
||||
- GET `/recipe/webauthn/options`
|
||||
- GET `/recipe/webauthn/user/recover`
|
||||
- POST `/recipe/webauthn/options/register`
|
||||
- POST `/recipe/webauthn/options/signin`
|
||||
- POST `/recipe/webauthn/user/credential/register`
|
||||
- POST `/recipe/webauthn/signup`
|
||||
- POST `/recipe/webauthn/signin`
|
||||
- POST `/recipe/webauthn/user/recover/token`
|
||||
- POST `/recipe/webauthn/user/recover/token/consume`
|
||||
- PUT `/recipe/webauthn/user/email`
|
||||
- DELETE `/recipe/webauthn/user/credential/remove`
|
||||
- DELETE `/recipe/webauthn/options/remove`
|
||||
- Adds additional indexing for `emailverification_verified_emails`
|
||||
- Introduces `bulk_migration_batch_size` core config
|
||||
- Introduces `BULK_MIGRATION_CRON_ENABLED` environment variable to control the bulk migration cron job
|
||||
|
||||
### Migration
|
||||
|
||||
If using PostgreSQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
|
||||
CREATE INDEX IF NOT EXISTS emailverification_verified_emails_app_id_email_index ON emailverification_verified_emails
|
||||
(app_id, email);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_account_recovery_tokens (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
token VARCHAR(256) NOT NULL,
|
||||
expires_at BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_account_recovery_token_pkey PRIMARY KEY (app_id, tenant_id, user_id, token),
|
||||
CONSTRAINT webauthn_account_recovery_token_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES
|
||||
all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_credentials (
|
||||
id VARCHAR(256) NOT NULL,
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
user_id CHAR(36),
|
||||
counter BIGINT NOT NULL,
|
||||
public_key BYTEA NOT NULL,
|
||||
transports TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_credentials_pkey PRIMARY KEY (app_id, rp_id, id),
|
||||
CONSTRAINT webauthn_credentials_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES webauthn_users
|
||||
(app_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_generated_options (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public'NOT NULL,
|
||||
id CHAR(36) NOT NULL,
|
||||
challenge VARCHAR(256) NOT NULL,
|
||||
email VARCHAR(256),
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
rp_name VARCHAR(256) NOT NULL,
|
||||
origin VARCHAR(256) NOT NULL,
|
||||
expires_at BIGINT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
user_presence_required BOOLEAN DEFAULT false NOT NULL,
|
||||
user_verification VARCHAR(12) DEFAULT 'preferred' NOT NULL,
|
||||
CONSTRAINT webauthn_generated_options_pkey PRIMARY KEY (app_id, tenant_id, id),
|
||||
CONSTRAINT webauthn_generated_options_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES tenants
|
||||
(app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_user_to_tenant (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
CONSTRAINT webauthn_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email),
|
||||
CONSTRAINT webauthn_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id),
|
||||
CONSTRAINT webauthn_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES
|
||||
all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_users (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
time_joined BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_users_pkey PRIMARY KEY (app_id, user_id),
|
||||
CONSTRAINT webauthn_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id(app_id,
|
||||
user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS webauthn_user_to_tenant_email_index ON webauthn_user_to_tenant (app_id, email);
|
||||
CREATE INDEX IF NOT EXISTS webauthn_user_challenges_expires_at_index ON webauthn_generated_options (app_id, tenant_id, expires_at);
|
||||
CREATE INDEX IF NOT EXISTS webauthn_credentials_user_id_index ON webauthn_credentials (user_id);
|
||||
CREATE INDEX IF NOT EXISTS webauthn_account_recovery_token_token_index ON webauthn_account_recovery_tokens (app_id, tenant_id, token);
|
||||
CREATE INDEX IF NOT EXISTS webauthn_account_recovery_token_expires_at_index ON webauthn_account_recovery_tokens (expires_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS webauthn_account_recovery_token_email_index ON webauthn_account_recovery_tokens (app_id, tenant_id, email);
|
||||
```
|
||||
|
||||
If using MySQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE INDEX emailverification_verified_emails_app_id_email_index ON emailverification_verified_emails
|
||||
(app_id, email);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_account_recovery_tokens (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
token VARCHAR(256) NOT NULL,
|
||||
expires_at BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_account_recovery_token_pkey PRIMARY KEY (app_id, tenant_id, user_id, token),
|
||||
CONSTRAINT webauthn_account_recovery_token_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES
|
||||
all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_credentials (
|
||||
id VARCHAR(256) NOT NULL,
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
user_id CHAR(36),
|
||||
counter BIGINT NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
transports TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_credentials_pkey PRIMARY KEY (app_id, rp_id, id),
|
||||
CONSTRAINT webauthn_credentials_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES webauthn_users
|
||||
(app_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_generated_options (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public'NOT NULL,
|
||||
id CHAR(36) NOT NULL,
|
||||
challenge VARCHAR(256) NOT NULL,
|
||||
email VARCHAR(256),
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
rp_name VARCHAR(256) NOT NULL,
|
||||
origin VARCHAR(256) NOT NULL,
|
||||
expires_at BIGINT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
user_presence_required BOOLEAN DEFAULT false NOT NULL,
|
||||
user_verification VARCHAR(12) DEFAULT 'preferred' NOT NULL,
|
||||
CONSTRAINT webauthn_generated_options_pkey PRIMARY KEY (app_id, tenant_id, id),
|
||||
CONSTRAINT webauthn_generated_options_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) REFERENCES tenants
|
||||
(app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_user_to_tenant (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
CONSTRAINT webauthn_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email),
|
||||
CONSTRAINT webauthn_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id),
|
||||
CONSTRAINT webauthn_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES
|
||||
all_auth_recipe_users(app_id, tenant_id, user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webauthn_users (
|
||||
app_id VARCHAR(64) DEFAULT 'public' NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
email VARCHAR(256) NOT NULL,
|
||||
rp_id VARCHAR(256) NOT NULL,
|
||||
time_joined BIGINT NOT NULL,
|
||||
CONSTRAINT webauthn_users_pkey PRIMARY KEY (app_id, user_id),
|
||||
CONSTRAINT webauthn_users_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id,
|
||||
user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX webauthn_user_to_tenant_email_index ON webauthn_user_to_tenant (app_id, email);
|
||||
CREATE INDEX webauthn_user_challenges_expires_at_index ON webauthn_generated_options (app_id, tenant_id, expires_at);
|
||||
CREATE INDEX webauthn_credentials_user_id_index ON webauthn_credentials (user_id);
|
||||
CREATE INDEX webauthn_account_recovery_token_token_index ON webauthn_account_recovery_tokens (app_id, tenant_id, token);
|
||||
CREATE INDEX webauthn_account_recovery_token_expires_at_index ON webauthn_account_recovery_tokens (expires_at DESC);
|
||||
CREATE INDEX webauthn_account_recovery_token_email_index ON webauthn_account_recovery_tokens (app_id, tenant_id, email);
|
||||
```
|
||||
|
||||
## [10.0.3]
|
||||
|
||||
- Fixes `StorageTransactionLogicException` in bulk import when not using userRoles and totpDevices in import json.
|
||||
- MFA only required in Bulk Import if it's used in input data
|
||||
- Fixes issue with reloading all resources when exception occurs while loading a resource, other valid resources were offloaded from the memory. Now we log the exception and continue loading other resources.
|
||||
- Adds `USE_STRUCTURED_LOGGING` environment variable to control the logging format.
|
||||
|
||||
## [10.0.2]
|
||||
|
||||
- Fixes `NullPointerException` in user search API.
|
||||
|
||||
## [10.0.1]
|
||||
|
||||
- Fixes slow queries for account linking
|
||||
- Masks db password in 500 response
|
||||
|
||||
### Migration
|
||||
|
||||
If using PostgreSQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE INDEX IF NOT EXISTS emailpassword_users_email_index ON emailpassword_users (app_id, email);
|
||||
CREATE INDEX IF NOT EXISTS emailpassword_user_to_tenant_email_index ON emailpassword_user_to_tenant (app_id, tenant_id, email);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS passwordless_users_email_index ON passwordless_users (app_id, email);
|
||||
CREATE INDEX IF NOT EXISTS passwordless_users_phone_number_index ON passwordless_users (app_id, phone_number);
|
||||
CREATE INDEX IF NOT EXISTS passwordless_user_to_tenant_email_index ON passwordless_user_to_tenant (app_id, tenant_id, email);
|
||||
CREATE INDEX IF NOT EXISTS passwordless_user_to_tenant_phone_number_index ON passwordless_user_to_tenant (app_id, tenant_id, phone_number);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS thirdparty_user_to_tenant_third_party_user_id_index ON thirdparty_user_to_tenant (app_id, tenant_id, third_party_id, third_party_user_id);
|
||||
```
|
||||
|
||||
If using MySQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE INDEX emailpassword_users_email_index ON emailpassword_users (app_id, email);
|
||||
CREATE INDEX emailpassword_user_to_tenant_email_index ON emailpassword_user_to_tenant (app_id, tenant_id, email);
|
||||
|
||||
CREATE INDEX passwordless_users_email_index ON passwordless_users (app_id, email);
|
||||
CREATE INDEX passwordless_users_phone_number_index ON passwordless_users (app_id, phone_number);
|
||||
CREATE INDEX passwordless_user_to_tenant_email_index ON passwordless_user_to_tenant (app_id, tenant_id, email);
|
||||
CREATE INDEX passwordless_user_to_tenant_phone_number_index ON passwordless_user_to_tenant (app_id, tenant_id, phone_number);
|
||||
|
||||
CREATE INDEX thirdparty_user_to_tenant_third_party_user_id_index ON thirdparty_user_to_tenant (app_id, tenant_id, third_party_id, third_party_user_id);
|
||||
```
|
||||
|
||||
## [10.0.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Optimize getUserIdMappingWithEitherSuperTokensUserIdOrExternalUserId query
|
||||
- Adds property `bulk_migration_parallelism` for fine-tuning the worker threads number
|
||||
- Adds APIs to bulk import users
|
||||
- GET `/bulk-import/users`
|
||||
- POST `/bulk-import/users`
|
||||
- GET `/bulk-import/users/count`
|
||||
- POST `/bulk-import/users/remove`
|
||||
- POST `/bulk-import/users/import`
|
||||
- Adds `ProcessBulkImportUsers` cron job to process bulk import users
|
||||
- Adds multithreaded worker support for the `ProcessBulkImportUsers` cron job for faster bulk imports
|
||||
- Adds support for lazy importing users
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Includes CUD in the owner field for OAuth clients
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixes issue with user id mapping while refreshing session
|
||||
- Adds indexing for `session_info` table on `user_id, app_id` columns
|
||||
|
||||
### Migrations
|
||||
|
||||
For PostgreSQL, run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS bulk_import_users (
|
||||
id CHAR(36),
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
primary_user_id VARCHAR(36),
|
||||
raw_data TEXT NOT NULL,
|
||||
status VARCHAR(128) DEFAULT 'NEW',
|
||||
error_msg TEXT,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT bulk_import_users_pkey PRIMARY KEY(app_id, id),
|
||||
CONSTRAINT bulk_import_users__app_id_fkey FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS bulk_import_users_status_updated_at_index ON bulk_import_users (app_id, status, updated_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS bulk_import_users_pagination_index1 ON bulk_import_users (app_id, status, created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS bulk_import_users_pagination_index2 ON bulk_import_users (app_id, created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS session_info_user_id_app_id_index ON session_info (user_id, app_id);
|
||||
```
|
||||
|
||||
For MySQL run the following SQL script:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS bulk_import_users (
|
||||
id CHAR(36),
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
primary_user_id VARCHAR(36),
|
||||
raw_data TEXT NOT NULL,
|
||||
status VARCHAR(128) DEFAULT 'NEW',
|
||||
error_msg TEXT,
|
||||
created_at BIGINT UNSIGNED NOT NULL,
|
||||
updated_at BIGINT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (app_id, id),
|
||||
FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX bulk_import_users_status_updated_at_index ON bulk_import_users (app_id, status, updated_at);
|
||||
|
||||
CREATE INDEX bulk_import_users_pagination_index1 ON bulk_import_users (app_id, status, created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX bulk_import_users_pagination_index2 ON bulk_import_users (app_id, created_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX session_info_user_id_app_id_index ON session_info (user_id, app_id);
|
||||
```
|
||||
- Adds internal opentelemetry support for logging
|
||||
|
||||
## [9.3.1]
|
||||
|
||||
- Includes exception class name in 500 error message
|
||||
|
||||
|
||||
## [9.3.0]
|
||||
|
||||
### Changes
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ We're happy to help!:raised_hands:
|
|||
### Local Setup Prerequisites
|
||||
|
||||
- OS: Linux or macOS. Or if using Windows, you need to use [wsl2](https://docs.microsoft.com/en-us/windows/wsl/about).
|
||||
- JDK: openjdk 21.0.7. Installation instructions for Mac and Linux can be found
|
||||
- JDK: openjdk 15.0.1. Installation instructions for Mac and Linux can be found
|
||||
in [our wiki](https://github.com/supertokens/supertokens-core/wiki/Installing-OpenJDK-for-Mac-and-Linux)
|
||||
- IDE: [IntelliJ](https://www.jetbrains.com/idea/download/)(recommended) or equivalent IDE
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ If you think this is a project you could use in the future, please :star2: this
|
|||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Lehoczky"><img src="https://avatars.githubusercontent.com/u/31937175?v=4" width="100px;" alt=""/><br /><sub><b>Lehoczky Zoltán</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/mavwolverine"><img src="https://avatars.githubusercontent.com/u/316111?v=4" width="100px;" alt=""/><br /><sub><b>Viraj Kanwade</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/virajkanwade"><img src="https://avatars.githubusercontent.com/u/316111?v=4" width="100px;" alt=""/><br /><sub><b>Viraj Kanwade</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/anuragmerndev"><img src="https://avatars.githubusercontent.com/u/144275260?v=4" width="100px;" alt=""/><br /><sub><b>Anurag Srivastava</b></sub></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
71
build.gradle
71
build.gradle
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java-library'
|
||||
id "io.freefair.aspectj" version "8.13" //same as gradle version!
|
||||
}
|
||||
compileJava { options.encoding = "UTF-8" }
|
||||
compileTestJava { options.encoding = "UTF-8" }
|
||||
|
|
@ -21,37 +19,26 @@ compileTestJava { options.encoding = "UTF-8" }
|
|||
// }
|
||||
//}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
version = "11.3.0"
|
||||
version = "9.3.2"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
// if this changes, remember to also change in the ee folder's build.gradle
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-cbor
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-cbor', version: '2.18.2'
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.2'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
|
||||
api group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.0.12'
|
||||
implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
|
|
@ -83,18 +70,8 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/com.googlecode.libphonenumber/libphonenumber/
|
||||
implementation group: 'com.googlecode.libphonenumber', name: 'libphonenumber', version: '8.13.25'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.webauthn4j/webauthn4j-core
|
||||
implementation group: 'com.webauthn4j', name: 'webauthn4j-core', version: '0.28.6.RELEASE'
|
||||
|
||||
implementation platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.17.0-alpha")
|
||||
|
||||
// Open SAML
|
||||
implementation group: 'org.opensaml', name: 'opensaml-core', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-saml-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-security-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-profile-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-xmlsec-impl', version: '4.3.1'
|
||||
|
||||
implementation("ch.qos.logback:logback-core:1.5.18")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.18")
|
||||
|
||||
|
|
@ -106,13 +83,10 @@ dependencies {
|
|||
|
||||
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
|
||||
|
||||
implementation('org.aspectj:aspectjrt:1.9.24')
|
||||
|
||||
compileOnly project(":supertokens-plugin-interface")
|
||||
testImplementation project(":supertokens-plugin-interface")
|
||||
|
||||
// this is so that we can find plugin-interface jar while testing
|
||||
testImplementation project(":supertokens-plugin-interface")
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.mockito/mockito-core
|
||||
|
|
@ -123,9 +97,8 @@ dependencies {
|
|||
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit4:0.22.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.webauthn4j/webauthn4j-test
|
||||
testImplementation group: 'com.webauthn4j', name: 'webauthn4j-test', version: '0.28.6.RELEASE'
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.supertokens.Main")
|
||||
}
|
||||
|
|
@ -135,47 +108,43 @@ jar {
|
|||
}
|
||||
|
||||
|
||||
tasks.register('copyJars', Copy) {
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs = ['-Djava.security.egd=file:/dev/urandom',
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED"]
|
||||
jvmArgs '-Djava.security.egd=file:/dev/urandom'
|
||||
testLogging {
|
||||
outputs.upToDateWhen { false }
|
||||
showStandardStreams = true
|
||||
}
|
||||
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||
}
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
tasks.withType(Test) {
|
||||
testLogging {
|
||||
// set options for log level LIFECYCLE
|
||||
events = [TestLogEvent.FAILED,
|
||||
events TestLogEvent.FAILED,
|
||||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT]
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
events = [TestLogEvent.STARTED,
|
||||
events TestLogEvent.STARTED,
|
||||
TestLogEvent.FAILED,
|
||||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT]
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ plugins {
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
@ -18,13 +16,13 @@ jar {
|
|||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.2'
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.2'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/de.mkammerer/argon2-jvm
|
||||
implementation group: 'de.mkammerer', name: 'argon2-jvm', version: '2.11'
|
||||
|
|
@ -35,9 +33,9 @@ dependencies {
|
|||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
tasks.register('copyJars', Copy) {
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
@ -57,10 +55,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -70,7 +68,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
|
|
@ -1,40 +1,55 @@
|
|||
{
|
||||
"_comment": "Contains list of implementation dependencies URL for this project. This is a generated file, don't modify the contents by hand.",
|
||||
"list": [
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar",
|
||||
"name":"gson 2.13.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar",
|
||||
"name":"error_prone_annotations 2.38.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2.jar",
|
||||
"name":"jackson-dataformat-yaml 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar",
|
||||
"name":"snakeyaml 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar",
|
||||
"name":"jackson-databind 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name":"argon2-jvm 2.11",
|
||||
"src":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name":"jbcrypt 0.4",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
}
|
||||
]
|
||||
"_comment": "Contains list of implementation dependencies URL for this project",
|
||||
"list": [
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1.jar",
|
||||
"name": "Gson 2.3.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1.jar",
|
||||
"name": "Jackson Dataformat 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar",
|
||||
"name": "SnakeYAML 2.2",
|
||||
"src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar",
|
||||
"name": "Jackson core 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar",
|
||||
"name": "Jackson databind 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1.jar",
|
||||
"name": "Jackson annotation 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name": "Argon2-jvm 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11.jar",
|
||||
"name": "Argon2-jvm no libs 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name": "SQLite JDBC Driver 3.30.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar",
|
||||
"name": "JNA 5.8.0",
|
||||
"src": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
cli/jar/cli.jar
BIN
cli/jar/cli.jar
Binary file not shown.
|
|
@ -43,38 +43,12 @@ public class StartHandler extends CommandHandler {
|
|||
String host = CLIOptionsParser.parseOption("--host", args);
|
||||
boolean foreground = CLIOptionsParser.hasKey("--foreground", args);
|
||||
boolean forceNoInMemDB = CLIOptionsParser.hasKey("--no-in-mem-db", args);
|
||||
boolean javaagentEnabled = CLIOptionsParser.hasKey("--javaagent", args);
|
||||
boolean jmxEnabled = CLIOptionsParser.hasKey("--jmx", args);
|
||||
String jmxPort = CLIOptionsParser.parseOption("--jmx-port", args);
|
||||
String jmxAuthenticate = CLIOptionsParser.parseOption("--jmx-authenticate", args);
|
||||
String jmxSSL = CLIOptionsParser.parseOption("--jmx-ssl", args);
|
||||
|
||||
List<String> commands = new ArrayList<>();
|
||||
if (OperatingSystem.getOS() == OperatingSystem.OS.WINDOWS) {
|
||||
commands.add(installationDir + "jre\\bin\\java.exe");
|
||||
commands.add("-classpath");
|
||||
commands.add("\"" + installationDir + "core\\*\";\"" + installationDir + "plugin-interface\\*\"");
|
||||
if (javaagentEnabled) {
|
||||
commands.add("-javaagent:\"" + installationDir + "agent\\opentelemetry-javaagent.jar\"");
|
||||
}
|
||||
if (jmxEnabled) {
|
||||
commands.add("-Dcom.sun.management.jmxremote");
|
||||
if (jmxPort != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=9010");
|
||||
}
|
||||
if (jmxAuthenticate != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
}
|
||||
if (jmxSSL != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
|
||||
}
|
||||
}
|
||||
if (space != null) {
|
||||
commands.add("-Xmx" + space + "M");
|
||||
}
|
||||
|
|
@ -103,27 +77,6 @@ public class StartHandler extends CommandHandler {
|
|||
commands.add("-classpath");
|
||||
commands.add(
|
||||
installationDir + "core/*:" + installationDir + "plugin-interface/*:" + installationDir + "ee/*");
|
||||
if (javaagentEnabled) {
|
||||
commands.add("-javaagent:" + installationDir + "agent/opentelemetry-javaagent.jar");
|
||||
}
|
||||
if (jmxEnabled) {
|
||||
commands.add("-Dcom.sun.management.jmxremote");
|
||||
if (jmxPort != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=9010");
|
||||
}
|
||||
if (jmxAuthenticate != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
}
|
||||
if (jmxSSL != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
|
||||
}
|
||||
}
|
||||
if (space != null) {
|
||||
commands.add("-Xmx" + space + "M");
|
||||
}
|
||||
|
|
@ -148,7 +101,6 @@ public class StartHandler extends CommandHandler {
|
|||
if (!foreground) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(commands);
|
||||
Logging.info("Command to be run: " + String.join(" ", pb.command()));
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
try (InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
|
|
@ -229,13 +181,6 @@ public class StartHandler extends CommandHandler {
|
|||
new Option("--foreground", "Runs this instance of SuperTokens in the foreground (not as a daemon)"));
|
||||
options.add(
|
||||
new Option("--with-temp-dir", "Uses the passed dir as temp dir, instead of the internal default."));
|
||||
options.add(new Option("--javaagent", "Enables the OpenTelemetry Javaagent for tracing and metrics."));
|
||||
options.add(new Option("--jmx", "Enables JMX management and monitoring."));
|
||||
options.add(new Option("--jmx-port", "Sets the port for JMX. Defaults to 9010 if --jmx is passed."));
|
||||
options.add(new Option("--jmx-authenticate",
|
||||
"Sets whether JMX authentication is enabled or not. Defaults to false if --jmx is passed."));
|
||||
options.add(new Option("--jmx-ssl",
|
||||
"Sets whether JMX SSL is enabled or not. Defaults to false if --jmx is passed."));
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
|||
29
config.yaml
29
config.yaml
|
|
@ -171,33 +171,6 @@ core_config_version: 0
|
|||
# (Optional | Default: null) string value. The encryption key used for saving OAuth client secret on the database.
|
||||
# oauth_client_secret_encryption_key:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: number of available processor cores) int value. If specified,
|
||||
# the supertokens core will use the specified number of threads to complete the migration of users.
|
||||
# bulk_migration_parallelism:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 8000) int value. If specified, the supertokens core will load the
|
||||
# specified number of users for migrating in one single batch.
|
||||
# bulk_migration_batch_size:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 3600000) long value. Time in milliseconds for how long a webauthn
|
||||
# account recovery token is valid for.
|
||||
# webauthn_recover_account_token_lifetime:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# (OPTIONAL | Default: http://localhost:4317) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# will send telemetry data. This should be in the format http://<host>:<port> or https://<host>:<port>.
|
||||
# otel_collector_connection_uri:
|
||||
|
||||
# (OPTIONAL | Default: false) boolean value. Enables or disables the deadlock logger.
|
||||
# deadlock_logger_enable:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, uses this URL as ACS URL for handling legacy SAML clients
|
||||
# saml_legacy_acs_url:
|
||||
|
||||
# (OPTIONAL | Default: https://saml.supertokens.com) string value. Service provider's entity ID.
|
||||
# saml_sp_entity_id:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML claims will be valid before it is consumed
|
||||
# saml_claims_validity:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML relay state will be valid before it is consumed
|
||||
# saml_relay_state_validity:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@
|
|||
"4.0",
|
||||
"5.0",
|
||||
"5.1",
|
||||
"5.2",
|
||||
"5.3",
|
||||
"5.4"
|
||||
"5.2"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,33 +171,6 @@ disable_telemetry: true
|
|||
# (Optional | Default: null) string value. The encryption key used for saving OAuth client secret on the database.
|
||||
# oauth_client_secret_encryption_key:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: number of available processor cores) int value. If specified,
|
||||
# the supertokens core will use the specified number of threads to complete the migration of users.
|
||||
# bulk_migration_parallelism:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 8000) int value. If specified, the supertokens core will load the
|
||||
# specified number of users for migrating in one single batch.
|
||||
# bulk_migration_batch_size:
|
||||
|
||||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 3600000) long value. Time in milliseconds for how long a webauthn
|
||||
# account recovery token is valid for.
|
||||
# webauthn_recover_account_token_lifetime:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# (OPTIONAL | Default: http://localhost:4317) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# will send telemetry data. This should be in the format http://<host>:<port> or https://<host>:<port>.
|
||||
# otel_collector_connection_uri:
|
||||
|
||||
# (OPTIONAL | Default: false) boolean value. Enables or disables the deadlock logger.
|
||||
# deadlock_logger_enable:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, uses this URL as ACS URL for handling legacy SAML clients
|
||||
saml_legacy_acs_url: "http://localhost:5225/api/oauth/saml"
|
||||
|
||||
# (OPTIONAL | Default: https://saml.supertokens.com) string value. Service provider's entity ID.
|
||||
# saml_sp_entity_id:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML claims will be valid before it is consumed
|
||||
# saml_claims_validity:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML relay state will be valid before it is consumed
|
||||
# saml_relay_state_validity:
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ dependencies {
|
|||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
tasks.register('copyJars', Copy) {
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
@ -56,10 +56,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -69,7 +69,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -14,7 +14,6 @@ exitIfNeeded
|
|||
|
||||
exitIfNeeded
|
||||
|
||||
|
||||
(cd ../../ && ./gradlew :$prefix-core:downloader:copyJars < /dev/null)
|
||||
|
||||
exitIfNeeded
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ plugins {
|
|||
id 'java-library'
|
||||
}
|
||||
|
||||
version = 'unspecified'
|
||||
version 'unspecified'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
jar {
|
||||
|
|
@ -15,7 +13,7 @@ jar {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
|
||||
compileOnly project(":supertokens-plugin-interface")
|
||||
testImplementation project(":supertokens-plugin-interface")
|
||||
|
|
@ -37,13 +35,13 @@ dependencies {
|
|||
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.1.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
|
||||
testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.0.5'
|
||||
testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
|
||||
|
||||
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.13'
|
||||
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit4:0.22.0'
|
||||
|
||||
|
|
@ -54,18 +52,17 @@ dependencies {
|
|||
testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
testImplementation group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
|
||||
}
|
||||
|
||||
tasks.register('copyJars', Copy) {
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
def interfaceName = "io.supertokens.featureflag.EEFeatureFlagInterface"
|
||||
def className = "io.supertokens.ee.EEFeatureFlag"
|
||||
|
||||
tasks.register('generateMetaInf') {
|
||||
task generateMetaInf {
|
||||
doFirst {
|
||||
mkdir "src/main/resources/META-INF/services"
|
||||
file("src/main/resources/META-INF/services/${interfaceName}").text = "${className}"
|
||||
|
|
@ -92,10 +89,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -105,7 +102,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
BIN
ee/jar/ee.jar
BIN
ee/jar/ee.jar
Binary file not shown.
|
|
@ -14,7 +14,6 @@ exitIfNeeded
|
|||
|
||||
exitIfNeeded
|
||||
|
||||
|
||||
(cd ../../ && ./gradlew :$prefix-core:ee:copyJars < /dev/null)
|
||||
|
||||
exitIfNeeded
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
|||
import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.oauth.OAuthStorage;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.utils.Utils;
|
||||
|
|
@ -387,34 +386,6 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
return mauArr;
|
||||
}
|
||||
|
||||
private JsonObject getSAMLStats() throws TenantOrAppNotFoundException, StorageQueryException {
|
||||
JsonObject stats = new JsonObject();
|
||||
|
||||
stats.addProperty("connectionUriDomain", this.appIdentifier.getConnectionUriDomain());
|
||||
stats.addProperty("appId", this.appIdentifier.getAppId());
|
||||
|
||||
JsonArray tenantStats = new JsonArray();
|
||||
|
||||
TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(this.appIdentifier, main);
|
||||
for (TenantConfig tenantConfig : tenantConfigs) {
|
||||
JsonObject tenantStat = new JsonObject();
|
||||
tenantStat.addProperty("tenantId", tenantConfig.tenantIdentifier.getTenantId());
|
||||
|
||||
{
|
||||
Storage storage = StorageLayer.getStorage(tenantConfig.tenantIdentifier, main);
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
|
||||
JsonObject stat = new JsonObject();
|
||||
stat.addProperty("numberOfSAMLClients", samlStorage.countSAMLClients(tenantConfig.tenantIdentifier));
|
||||
stat.add(tenantConfig.tenantIdentifier.getTenantId(), stat);
|
||||
}
|
||||
}
|
||||
|
||||
stats.add("tenants", tenantStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonObject usageStats = new JsonObject();
|
||||
|
|
@ -462,10 +433,6 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
if (feature == EE_FEATURES.OAUTH) {
|
||||
usageStats.add(EE_FEATURES.OAUTH.toString(), getOAuthStats());
|
||||
}
|
||||
|
||||
if (feature == EE_FEATURES.SAML) {
|
||||
usageStats.add(EE_FEATURES.SAML.toString(), getSAMLStats());
|
||||
}
|
||||
}
|
||||
|
||||
usageStats.add("maus", getMAUs());
|
||||
|
|
@ -556,7 +523,7 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
ProcessState.getInstance(main)
|
||||
.addState(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL, null, json);
|
||||
JsonObject licenseCheckResponse = HttpRequest.sendJsonPOSTRequest(this.main, REQUEST_ID,
|
||||
"https://api.supertokens.com/0/st/license/check",
|
||||
"https://api.supertokens.io/0/st/license/check",
|
||||
json, 10000, 10000, 0);
|
||||
if (licenseCheckResponse.get("status").getAsString().equalsIgnoreCase("OK")) {
|
||||
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "API returned OK");
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ public class TestMultitenancyStats {
|
|||
String[] args = {"../../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
CronTaskTest.getInstance(process.getProcess()).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1);
|
||||
CronTaskTest.getInstance(process.main).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class TestingProcessManager {
|
|||
String[] args = {"../../"};
|
||||
TestingProcess process = TestingProcessManager.start(args);
|
||||
process.checkOrWaitForEvent(PROCESS_STATE.STARTED);
|
||||
process.getProcess().deleteAllInformationForTesting();
|
||||
process.main.deleteAllInformationForTesting();
|
||||
process.kill();
|
||||
System.out.println("----------DELETE ALL INFORMATION----------");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ public abstract class Utils extends Mockito {
|
|||
try {
|
||||
|
||||
// remove config.yaml file
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
ProcessBuilder pb = new ProcessBuilder("rm", "config" + workerId + ".yaml");
|
||||
ProcessBuilder pb = new ProcessBuilder("rm", "config.yaml");
|
||||
pb.directory(new File(installDir));
|
||||
Process process = pb.start();
|
||||
process.waitFor();
|
||||
|
|
@ -59,8 +58,7 @@ public abstract class Utils extends Mockito {
|
|||
|
||||
// if the default config is not the same as the current config, we must reset the storage layer
|
||||
File ogConfig = new File("../../temp/config.yaml");
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
File currentConfig = new File("../../config" + workerId + ".yaml");
|
||||
File currentConfig = new File("../../config.yaml");
|
||||
if (currentConfig.isFile()) {
|
||||
byte[] ogConfigContent = Files.readAllBytes(ogConfig.toPath());
|
||||
byte[] currentConfigContent = Files.readAllBytes(currentConfig.toPath());
|
||||
|
|
@ -69,7 +67,7 @@ public abstract class Utils extends Mockito {
|
|||
}
|
||||
}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("cp", "temp/config.yaml", "./config" + workerId + ".yaml");
|
||||
ProcessBuilder pb = new ProcessBuilder("cp", "temp/config.yaml", "./config.yaml");
|
||||
pb.directory(new File(installDir));
|
||||
Process process = pb.start();
|
||||
process.waitFor();
|
||||
|
|
@ -98,15 +96,14 @@ public abstract class Utils extends Mockito {
|
|||
String newStr = "\n# " + key + ":";
|
||||
|
||||
StringBuilder originalFileContent = new StringBuilder();
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config" + workerId + ".yaml"))) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config.yaml"))) {
|
||||
String currentReadingLine = reader.readLine();
|
||||
while (currentReadingLine != null) {
|
||||
originalFileContent.append(currentReadingLine).append(System.lineSeparator());
|
||||
currentReadingLine = reader.readLine();
|
||||
}
|
||||
String modifiedFileContent = originalFileContent.toString().replaceAll(oldStr, newStr);
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config" + workerId + ".yaml"))) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config.yaml"))) {
|
||||
writer.write(modifiedFileContent);
|
||||
}
|
||||
}
|
||||
|
|
@ -120,15 +117,14 @@ public abstract class Utils extends Mockito {
|
|||
String oldStr = "\n((#\\s)?)" + key + "(:|((:\\s).+))\n";
|
||||
String newStr = "\n" + key + ": " + value + "\n";
|
||||
StringBuilder originalFileContent = new StringBuilder();
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config" + workerId + ".yaml"))) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config.yaml"))) {
|
||||
String currentReadingLine = reader.readLine();
|
||||
while (currentReadingLine != null) {
|
||||
originalFileContent.append(currentReadingLine).append(System.lineSeparator());
|
||||
currentReadingLine = reader.readLine();
|
||||
}
|
||||
String modifiedFileContent = originalFileContent.toString().replaceAll(oldStr, newStr);
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config" + workerId + ".yaml"))) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config.yaml"))) {
|
||||
writer.write(modifiedFileContent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class GetFeatureFlagAPITest {
|
|||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ public class GetFeatureFlagAPITest {
|
|||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public class GetLicenseKeyAPITest {
|
|||
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.getProcess()).getEeFeatureFlagInstance());
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.main).getEeFeatureFlagInstance());
|
||||
|
||||
Assert.assertEquals(FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ public class SetLicenseKeyAPITest {
|
|||
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.getProcess()).getEeFeatureFlagInstance());
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.main).getEeFeatureFlagInstance());
|
||||
|
||||
Assert.assertEquals(0, FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length);
|
||||
Assert.assertEquals(FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length, 0);
|
||||
|
||||
// set license key when ee folder does not exist
|
||||
JsonObject requestBody = new JsonObject();
|
||||
|
|
|
|||
|
|
@ -2,44 +2,34 @@
|
|||
"_comment": "Contains list of implementation dependencies URL for this project. This is a generated file, don't modify the contents by hand.",
|
||||
"list": [
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/11.0.12/tomcat-embed-core-11.0.12.jar",
|
||||
"name":"tomcat-embed-core 11.0.12",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/11.0.12/tomcat-embed-core-11.0.12-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1.jar",
|
||||
"name":"gson 2.3.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/11.0.12/tomcat-annotations-api-11.0.12.jar",
|
||||
"name":"tomcat-annotations-api 11.0.12",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/11.0.12/tomcat-annotations-api-11.0.12-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1.jar",
|
||||
"name":"jackson-dataformat-yaml 2.16.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar",
|
||||
"name":"gson 2.13.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar",
|
||||
"name":"snakeyaml 2.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar",
|
||||
"name":"error_prone_annotations 2.38.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar",
|
||||
"name":"jackson-databind 2.16.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2.jar",
|
||||
"name":"jackson-dataformat-yaml 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18.jar",
|
||||
"name":"tomcat-embed-core 10.1.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar",
|
||||
"name":"snakeyaml 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2.jar",
|
||||
"name":"jackson-dataformat-cbor 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar",
|
||||
"name":"jackson-databind 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2-sources.jar"
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18.jar",
|
||||
"name":"tomcat-annotations-api 10.1.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
|
||||
|
|
@ -96,151 +86,6 @@
|
|||
"name":"libphonenumber 8.13.25",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE.jar",
|
||||
"name":"webauthn4j-core 0.28.6.RELEASE",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-core/4.3.1/opensaml-core-4.3.1.jar",
|
||||
"name":"opensaml-core 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-core/4.3.1/opensaml-core-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/net/shibboleth/utilities/java-support/8.4.1/java-support-8.4.1.jar",
|
||||
"name":"java-support 8.4.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/net/shibboleth/utilities/java-support/8.4.1/java-support-8.4.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/guava/31.1-jre/guava-31.1-jre.jar",
|
||||
"name":"guava 31.1-jre",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/guava/31.1-jre/guava-31.1-jre-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar",
|
||||
"name":"failureaccess 1.0.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar",
|
||||
"name":"listenablefuture 9999.0-empty-to-avoid-conflict-with-guava",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar",
|
||||
"name":"j2objc-annotations 1.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/dropwizard/metrics/metrics-core/4.2.25/metrics-core-4.2.25.jar",
|
||||
"name":"metrics-core 4.2.25",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/dropwizard/metrics/metrics-core/4.2.25/metrics-core-4.2.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-impl/4.3.1/opensaml-saml-impl-4.3.1.jar",
|
||||
"name":"opensaml-saml-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-impl/4.3.1/opensaml-saml-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-impl/4.3.1/opensaml-xmlsec-impl-4.3.1.jar",
|
||||
"name":"opensaml-xmlsec-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-impl/4.3.1/opensaml-xmlsec-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-impl/4.3.1/opensaml-security-impl-4.3.1.jar",
|
||||
"name":"opensaml-security-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-impl/4.3.1/opensaml-security-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-api/4.3.1/opensaml-security-api-4.3.1.jar",
|
||||
"name":"opensaml-security-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-api/4.3.1/opensaml-security-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-messaging-api/4.3.1/opensaml-messaging-api-4.3.1.jar",
|
||||
"name":"opensaml-messaging-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-messaging-api/4.3.1/opensaml-messaging-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar",
|
||||
"name":"httpclient 4.5.14",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar",
|
||||
"name":"httpcore 4.4.16",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/cryptacular/cryptacular/1.2.5/cryptacular-1.2.5.jar",
|
||||
"name":"cryptacular 1.2.5",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/cryptacular/cryptacular/1.2.5/cryptacular-1.2.5-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcprov-jdk18on/1.72/bcprov-jdk18on-1.72.jar",
|
||||
"name":"bcprov-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcprov-jdk18on/1.72/bcprov-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcpkix-jdk18on/1.72/bcpkix-jdk18on-1.72.jar",
|
||||
"name":"bcpkix-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcpkix-jdk18on/1.72/bcpkix-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcutil-jdk18on/1.72/bcutil-jdk18on-1.72.jar",
|
||||
"name":"bcutil-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcutil-jdk18on/1.72/bcutil-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-api/4.3.1/opensaml-xmlsec-api-4.3.1.jar",
|
||||
"name":"opensaml-xmlsec-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-api/4.3.1/opensaml-xmlsec-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/santuario/xmlsec/2.3.4/xmlsec-2.3.4.jar",
|
||||
"name":"xmlsec 2.3.4",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/santuario/xmlsec/2.3.4/xmlsec-2.3.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-api/4.3.1/opensaml-saml-api-4.3.1.jar",
|
||||
"name":"opensaml-saml-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-api/4.3.1/opensaml-saml-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-api/4.3.1/opensaml-profile-api-4.3.1.jar",
|
||||
"name":"opensaml-profile-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-api/4.3.1/opensaml-profile-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-api/4.3.1/opensaml-soap-api-4.3.1.jar",
|
||||
"name":"opensaml-soap-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-api/4.3.1/opensaml-soap-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-impl/4.3.1/opensaml-soap-impl-4.3.1.jar",
|
||||
"name":"opensaml-soap-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-impl/4.3.1/opensaml-soap-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-storage-api/4.3.1/opensaml-storage-api-4.3.1.jar",
|
||||
"name":"opensaml-storage-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-storage-api/4.3.1/opensaml-storage-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/velocity/velocity-engine-core/2.3/velocity-engine-core-2.3.jar",
|
||||
"name":"velocity-engine-core 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/velocity/velocity-engine-core/2.3/velocity-engine-core-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11.jar",
|
||||
"name":"commons-lang3 3.11",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-impl/4.3.1/opensaml-profile-impl-4.3.1.jar",
|
||||
"name":"opensaml-profile-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-impl/4.3.1/opensaml-profile-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar",
|
||||
"name":"logback-core 1.5.18",
|
||||
|
|
@ -251,11 +96,6 @@
|
|||
"name":"logback-classic 1.5.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/aspectj/aspectjrt/1.9.24/aspectjrt-1.9.24.jar",
|
||||
"name":"aspectjrt 1.9.24",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/aspectj/aspectjrt/1.9.24/aspectjrt-1.9.24-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-api/1.51.0/opentelemetry-api-1.51.0.jar",
|
||||
"name":"opentelemetry-api 1.51.0",
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"_comment": "contains a list of plugin interfaces branch names that this core supports",
|
||||
"versions": [
|
||||
"8.3"
|
||||
"6.3"
|
||||
]
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class ActiveUsers {
|
|||
@TestOnly
|
||||
public static void updateLastActive(Main main, String userId) {
|
||||
try {
|
||||
ActiveUsers.updateLastActive(ResourceDistributor.getAppForTesting().toAppIdentifier(),
|
||||
ActiveUsers.updateLastActive(new AppIdentifier(null, null),
|
||||
main, userId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -55,6 +55,6 @@ public class ActiveUsers {
|
|||
@TestOnly
|
||||
public static int countUsersActiveSince(Main main, long time)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
return countUsersActiveSince(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), time);
|
||||
return countUsersActiveSince(main, new AppIdentifier(null, null), time);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,7 @@ import io.supertokens.cliOptions.CLIOptions;
|
|||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.cronjobs.Cronjobs;
|
||||
import io.supertokens.cronjobs.bulkimport.ProcessBulkImportUsers;
|
||||
import io.supertokens.cronjobs.cleanupOAuthSessionsAndChallenges.CleanupOAuthSessionsAndChallenges;
|
||||
import io.supertokens.cronjobs.deleteExpiredSAMLData.DeleteExpiredSAMLData;
|
||||
import io.supertokens.cronjobs.cleanupWebauthnExpiredData.CleanUpWebauthNExpiredDataCron;
|
||||
import io.supertokens.cronjobs.deadlocklogger.DeadlockLogger;
|
||||
import io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys;
|
||||
import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions;
|
||||
import io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens;
|
||||
|
|
@ -44,7 +40,6 @@ import io.supertokens.pluginInterface.exceptions.DbInitException;
|
|||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.saml.SAMLBootstrap;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.version.Version;
|
||||
|
|
@ -67,8 +62,6 @@ public class Main {
|
|||
|
||||
// this is a special variable that will be set to true by TestingProcessManager
|
||||
public static boolean isTesting = false;
|
||||
// this flag is used in ProcessBulkImportUsersCronJobTest to skip the user validation
|
||||
public static boolean isTesting_skipBulkImportUserValidationInCronJob = false;
|
||||
|
||||
// this is a special variable that will be set to true by TestingProcessManager
|
||||
public static boolean makeConsolePrintSilent = false;
|
||||
|
|
@ -95,9 +88,6 @@ public class Main {
|
|||
private boolean waitToEnableFeatureFlag = false;
|
||||
private final Object waitToEnableFeatureFlagLock = new Object();
|
||||
|
||||
//setting to true by default
|
||||
private final Boolean bulkMigrationCronEnabled = System.getenv("BULK_MIGRATION_CRON_ENABLED") == null || Boolean.parseBoolean(System.getenv("BULK_MIGRATION_CRON_ENABLED"));
|
||||
|
||||
|
||||
private boolean forceInMemoryDB = false;
|
||||
|
||||
|
|
@ -124,8 +114,6 @@ public class Main {
|
|||
CLIOptions.load(this, args);
|
||||
init();
|
||||
} catch (Exception e) {
|
||||
Logging.error(this, TenantIdentifier.BASE_TENANT, "What caused the crash: " + e.getMessage(), true,
|
||||
e);
|
||||
ProcessState.getInstance(this).addState(ProcessState.PROCESS_STATE.INIT_FAILURE, e);
|
||||
throw e;
|
||||
}
|
||||
|
|
@ -160,12 +148,9 @@ public class Main {
|
|||
// Handle kill signal gracefully
|
||||
handleKillSignalForWhenItHappens();
|
||||
|
||||
StorageLayer.loadStorageUCL(CLIOptions.get(this).getInstallationPath() + "plugin/");
|
||||
|
||||
// loading configs for core from config.yaml file.
|
||||
try {
|
||||
Config.loadBaseConfig(this);
|
||||
Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new QuitProgramException(e);
|
||||
}
|
||||
|
|
@ -173,11 +158,14 @@ public class Main {
|
|||
// loading version file
|
||||
Version.loadVersion(this, CLIOptions.get(this).getInstallationPath() + "version.yaml");
|
||||
|
||||
Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);
|
||||
|
||||
TelemetryProvider.initialize(this);
|
||||
|
||||
// loading storage layer
|
||||
try {
|
||||
StorageLayer.initPrimary(this, Config.getBaseConfigAsJsonObject(this));
|
||||
StorageLayer.initPrimary(this, CLIOptions.get(this).getInstallationPath() + "plugin/",
|
||||
Config.getBaseConfigAsJsonObject(this));
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new QuitProgramException(e);
|
||||
}
|
||||
|
|
@ -185,9 +173,6 @@ public class Main {
|
|||
// init file logging
|
||||
Logging.initFileLogging(this);
|
||||
|
||||
// Required for SAML related stuff
|
||||
SAMLBootstrap.initialize();
|
||||
|
||||
// initialise cron job handler
|
||||
Cronjobs.init(this);
|
||||
|
||||
|
|
@ -275,22 +260,8 @@ public class Main {
|
|||
// starts DeleteExpiredAccessTokenSigningKeys cronjob if the access token signing keys can change
|
||||
Cronjobs.addCronjob(this, DeleteExpiredAccessTokenSigningKeys.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// initializes ProcessBulkImportUsers cronjob to process bulk import users
|
||||
if(bulkMigrationCronEnabled) {
|
||||
Cronjobs.addCronjob(this, ProcessBulkImportUsers.init(this, uniqueUserPoolIdsTenants));
|
||||
}
|
||||
|
||||
Cronjobs.addCronjob(this, CleanupOAuthSessionsAndChallenges.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
Cronjobs.addCronjob(this, CleanUpWebauthNExpiredDataCron.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// starts the DeadlockLogger if
|
||||
if (Config.getBaseConfig(this).isDeadlockLoggerEnabled()) {
|
||||
DeadlockLogger.getInstance().start();
|
||||
}
|
||||
|
||||
Cronjobs.addCronjob(this, DeleteExpiredSAMLData.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// this is to ensure tenantInfos are in sync for the new cron job as well
|
||||
MultitenancyHelper.getInstance(this).refreshCronjobs();
|
||||
|
||||
|
|
@ -380,16 +351,12 @@ public class Main {
|
|||
}
|
||||
|
||||
private void createDotStartedFileForThisProcess() throws IOException {
|
||||
String startedDir = ".started";
|
||||
if (isTesting) {
|
||||
startedDir = ".started" + System.getProperty("org.gradle.test.worker", "");
|
||||
}
|
||||
CoreConfig config = Config.getBaseConfig(this);
|
||||
String fileLocation = CLIOptions.get(this).getTempDirLocation() == null ? CLIOptions.get(this).getInstallationPath() : CLIOptions.get(this).getTempDirLocation();
|
||||
String fileName = OperatingSystem.getOS() == OperatingSystem.OS.WINDOWS
|
||||
? fileLocation + startedDir + "\\" + config.getHost(this) + "-"
|
||||
? fileLocation + ".started\\" + config.getHost(this) + "-"
|
||||
+ config.getPort(this)
|
||||
: fileLocation + startedDir + "/" + config.getHost(this) + "-"
|
||||
: fileLocation + ".started/" + config.getHost(this) + "-"
|
||||
+ config.getPort(this);
|
||||
File dotStarted = new File(fileName);
|
||||
if (!dotStarted.exists()) {
|
||||
|
|
@ -432,10 +399,9 @@ public class Main {
|
|||
|
||||
@TestOnly
|
||||
public void killForTestingAndWaitForShutdown() throws InterruptedException {
|
||||
// Do not kill for now
|
||||
assertIsTesting();
|
||||
wakeUpMainThreadToShutdown();
|
||||
mainThread.join();
|
||||
assertIsTesting();
|
||||
wakeUpMainThreadToShutdown();
|
||||
mainThread.join();
|
||||
}
|
||||
|
||||
// must not throw any error
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public class ProcessState extends ResourceDistributor.SingletonResource {
|
|||
public static class EventAndException {
|
||||
public Exception exception;
|
||||
public JsonObject data;
|
||||
public PROCESS_STATE state;
|
||||
PROCESS_STATE state;
|
||||
|
||||
public EventAndException(PROCESS_STATE state, Exception e) {
|
||||
this.state = state;
|
||||
|
|
|
|||
|
|
@ -35,28 +35,16 @@ public class ResourceDistributor {
|
|||
private final Map<KeyClass, SingletonResource> resources = new HashMap<>(1);
|
||||
private final Main main;
|
||||
|
||||
private static TenantIdentifier appUsedForTesting = TenantIdentifier.BASE_TENANT;
|
||||
|
||||
public ResourceDistributor(Main main) {
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void setAppForTesting(TenantIdentifier app) {
|
||||
appUsedForTesting = app;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static TenantIdentifier getAppForTesting() {
|
||||
return appUsedForTesting;
|
||||
}
|
||||
|
||||
public SingletonResource getResource(AppIdentifier appIdentifier, @Nonnull String key)
|
||||
public synchronized SingletonResource getResource(AppIdentifier appIdentifier, @Nonnull String key)
|
||||
throws TenantOrAppNotFoundException {
|
||||
return getResource(appIdentifier.getAsPublicTenantIdentifier(), key);
|
||||
}
|
||||
|
||||
public SingletonResource getResource(TenantIdentifier tenantIdentifier, @Nonnull String key)
|
||||
public synchronized SingletonResource getResource(TenantIdentifier tenantIdentifier, @Nonnull String key)
|
||||
throws TenantOrAppNotFoundException {
|
||||
// first we do exact match
|
||||
SingletonResource resource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
|
|
@ -70,6 +58,14 @@ public class ResourceDistributor {
|
|||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
|
||||
MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true);
|
||||
|
||||
// we try again..
|
||||
resource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
// then we see if the user has configured anything to do with connectionUriDomain, and if they have,
|
||||
// then we must return null cause the user has not specifically added tenantId to it
|
||||
for (KeyClass currKey : resources.keySet()) {
|
||||
|
|
@ -93,11 +89,11 @@ public class ResourceDistributor {
|
|||
}
|
||||
|
||||
@TestOnly
|
||||
public SingletonResource getResource(@Nonnull String key) {
|
||||
return resources.get(new KeyClass(appUsedForTesting, key));
|
||||
public synchronized SingletonResource getResource(@Nonnull String key) {
|
||||
return resources.get(new KeyClass(new TenantIdentifier(null, null, null), key));
|
||||
}
|
||||
|
||||
public SingletonResource setResource(TenantIdentifier tenantIdentifier,
|
||||
public synchronized SingletonResource setResource(TenantIdentifier tenantIdentifier,
|
||||
@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
SingletonResource alreadyExists = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
|
|
@ -108,7 +104,7 @@ public class ResourceDistributor {
|
|||
return resource;
|
||||
}
|
||||
|
||||
public SingletonResource removeResource(TenantIdentifier tenantIdentifier,
|
||||
public synchronized SingletonResource removeResource(TenantIdentifier tenantIdentifier,
|
||||
@Nonnull String key) {
|
||||
SingletonResource singletonResource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
if (singletonResource == null) {
|
||||
|
|
@ -118,18 +114,18 @@ public class ResourceDistributor {
|
|||
return singletonResource;
|
||||
}
|
||||
|
||||
public SingletonResource setResource(AppIdentifier appIdentifier,
|
||||
public synchronized SingletonResource setResource(AppIdentifier appIdentifier,
|
||||
@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
return setResource(appIdentifier.getAsPublicTenantIdentifier(), key, resource);
|
||||
}
|
||||
|
||||
public SingletonResource removeResource(AppIdentifier appIdentifier,
|
||||
public synchronized SingletonResource removeResource(AppIdentifier appIdentifier,
|
||||
@Nonnull String key) {
|
||||
return removeResource(appIdentifier.getAsPublicTenantIdentifier(), key);
|
||||
}
|
||||
|
||||
public void clearAllResourcesWithResourceKey(String inputKey) {
|
||||
public synchronized void clearAllResourcesWithResourceKey(String inputKey) {
|
||||
List<KeyClass> toRemove = new ArrayList<>();
|
||||
resources.forEach((key, value) -> {
|
||||
if (key.key.equals(inputKey)) {
|
||||
|
|
@ -141,7 +137,7 @@ public class ResourceDistributor {
|
|||
}
|
||||
}
|
||||
|
||||
public Map<KeyClass, SingletonResource> getAllResourcesWithResourceKey(String inputKey) {
|
||||
public synchronized Map<KeyClass, SingletonResource> getAllResourcesWithResourceKey(String inputKey) {
|
||||
Map<KeyClass, SingletonResource> result = new HashMap<>();
|
||||
resources.forEach((key, value) -> {
|
||||
if (key.key.equals(inputKey)) {
|
||||
|
|
@ -152,9 +148,9 @@ public class ResourceDistributor {
|
|||
}
|
||||
|
||||
@TestOnly
|
||||
public SingletonResource setResource(@Nonnull String key,
|
||||
public synchronized SingletonResource setResource(@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
return setResource(appUsedForTesting, key, resource);
|
||||
return setResource(new TenantIdentifier(null, null, null), key, resource);
|
||||
}
|
||||
|
||||
public interface Func<T> {
|
||||
|
|
|
|||
|
|
@ -1,31 +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;
|
||||
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
|
||||
|
||||
public class StorageAndUserIdMappingForBulkImport extends StorageAndUserIdMapping {
|
||||
|
||||
public String userIdInQuestion;
|
||||
|
||||
public StorageAndUserIdMappingForBulkImport(Storage storage,
|
||||
UserIdMapping userIdMapping, String userIdInQuestion) {
|
||||
super(storage, userIdMapping);
|
||||
this.userIdInQuestion = userIdInQuestion;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,19 +17,18 @@
|
|||
package io.supertokens.authRecipe;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.exception.*;
|
||||
import io.supertokens.bulkimport.BulkImportUserUtils;
|
||||
import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException;
|
||||
import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException;
|
||||
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException;
|
||||
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlag;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
import io.supertokens.multitenancy.exception.BadPermissionException;
|
||||
import io.supertokens.pluginInterface.RECIPE_ID;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.*;
|
||||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
|
||||
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
|
|
@ -43,13 +42,10 @@ import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
|
|||
import io.supertokens.session.Session;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.useridmapping.UserIdType;
|
||||
import io.supertokens.utils.Utils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/*This files contains functions that are common for all auth recipes*/
|
||||
|
||||
|
|
@ -60,7 +56,7 @@ public class AuthRecipe {
|
|||
@TestOnly
|
||||
public static boolean unlinkAccounts(Main main, String recipeUserId)
|
||||
throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException {
|
||||
return unlinkAccounts(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
return unlinkAccounts(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -125,7 +121,7 @@ public class AuthRecipe {
|
|||
@TestOnly
|
||||
public static AuthRecipeUserInfo getUserById(Main main, String userId)
|
||||
throws StorageQueryException {
|
||||
return getUserById(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), userId);
|
||||
return getUserById(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId);
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo getUserById(AppIdentifier appIdentifier, Storage storage, String userId)
|
||||
|
|
@ -133,18 +129,6 @@ public class AuthRecipe {
|
|||
return StorageUtils.getAuthRecipeStorage(storage).getPrimaryUserById(appIdentifier, userId);
|
||||
}
|
||||
|
||||
public static List<AuthRecipeUserInfo> getUsersById(AppIdentifier appIdentifier, Storage storage, List<String> userIds)
|
||||
throws StorageQueryException {
|
||||
AuthRecipeSQLStorage authStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
try {
|
||||
return authStorage.startTransaction(con -> {
|
||||
return authStorage.getPrimaryUsersByIds_Transaction(appIdentifier, con, userIds);
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CreatePrimaryUserResult {
|
||||
public AuthRecipeUserInfo user;
|
||||
public boolean wasAlreadyAPrimaryUser;
|
||||
|
|
@ -155,24 +139,10 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
public static class CreatePrimaryUserBulkResult {
|
||||
public BulkImportUser user;
|
||||
public BulkImportUser.LoginMethod primaryLoginMethod;
|
||||
public boolean wasAlreadyAPrimaryUser;
|
||||
public Exception error;
|
||||
|
||||
public CreatePrimaryUserBulkResult(BulkImportUser user, BulkImportUser.LoginMethod primaryLoginMethod,
|
||||
boolean wasAlreadyAPrimaryUser, Exception error) {
|
||||
this.user = user;
|
||||
this.primaryLoginMethod = primaryLoginMethod;
|
||||
this.wasAlreadyAPrimaryUser = wasAlreadyAPrimaryUser;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CanLinkAccountsResult {
|
||||
public String recipeUserId;
|
||||
public String primaryUserId;
|
||||
|
||||
public boolean alreadyLinked;
|
||||
|
||||
public CanLinkAccountsResult(String recipeUserId, String primaryUserId, boolean alreadyLinked) {
|
||||
|
|
@ -182,29 +152,12 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
public static class CanLinkAccountsBulkResult {
|
||||
public String recipeUserId;
|
||||
public String primaryUserId;
|
||||
public Exception error;
|
||||
public BulkImportUser bulkImportUser;
|
||||
public boolean alreadyLinked;
|
||||
|
||||
public CanLinkAccountsBulkResult(String recipeUserId, String primaryUserId, boolean alreadyLinked, Exception error,
|
||||
BulkImportUser bulkImportUser) {
|
||||
this.recipeUserId = recipeUserId;
|
||||
this.primaryUserId = primaryUserId;
|
||||
this.alreadyLinked = alreadyLinked;
|
||||
this.error = error;
|
||||
this.bulkImportUser = bulkImportUser;
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static CanLinkAccountsResult canLinkAccounts(Main main, String recipeUserId, String primaryUserId)
|
||||
throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException,
|
||||
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException,
|
||||
AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException {
|
||||
return canLinkAccounts(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId,
|
||||
return canLinkAccounts(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId,
|
||||
primaryUserId);
|
||||
}
|
||||
|
||||
|
|
@ -295,65 +248,10 @@ public class AuthRecipe {
|
|||
return new CanLinkAccountsResult(recipeUser.getSupertokensUserId(), primaryUser.getSupertokensUserId(), false);
|
||||
}
|
||||
|
||||
private static List<CanLinkAccountsBulkResult> canLinkMultipleAccountsHelperForBulkImport(TransactionConnection con,
|
||||
AppIdentifier appIdentifier,
|
||||
Storage storage,
|
||||
List<BulkImportUser> users,
|
||||
List<AuthRecipeUserInfo> allUsersWithExtraData)
|
||||
throws StorageQueryException {
|
||||
AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
|
||||
List<CanLinkAccountsBulkResult> results = new ArrayList<>();
|
||||
|
||||
Map<String, String> recipeUserIdByPrimaryUserId = BulkImportUserUtils.collectRecipeIdsToPrimaryIds(users);
|
||||
|
||||
if(recipeUserIdByPrimaryUserId != null && !recipeUserIdByPrimaryUserId.isEmpty()) {
|
||||
|
||||
for(Map.Entry<String, String> recipeUserByPrimaryUser : recipeUserIdByPrimaryUserId.entrySet()) {
|
||||
String recipeUserId = recipeUserByPrimaryUser.getKey();
|
||||
String primaryUserId = recipeUserByPrimaryUser.getValue();
|
||||
BulkImportUser.LoginMethod primaryUser = BulkImportUserUtils.findLoginMethodByRecipeUserId(users, primaryUserId);
|
||||
BulkImportUser.LoginMethod recipeUser = BulkImportUserUtils.findLoginMethodByRecipeUserId(users, recipeUserId);
|
||||
if(primaryUser == null || recipeUser == null) {
|
||||
results.add(new CanLinkAccountsBulkResult(recipeUserId, primaryUserId, false, new UnknownUserIdException(), null));
|
||||
} else if(recipeUser.isPrimary) {
|
||||
if (recipeUser.superTokensUserId.equals(primaryUser.superTokensUserId)) {
|
||||
results.add(new CanLinkAccountsBulkResult(recipeUserId, primaryUserId, true, null, null));
|
||||
} else {
|
||||
results.add(new CanLinkAccountsBulkResult(recipeUserId, primaryUserId, false,
|
||||
new BulkImportRecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUserId), null));
|
||||
}
|
||||
} else {
|
||||
|
||||
Set<String> tenantIds = new HashSet<>();
|
||||
tenantIds.addAll(recipeUser.tenantIds);
|
||||
tenantIds.addAll(primaryUser.tenantIds);
|
||||
|
||||
try {
|
||||
bulkCheckIfLoginMethodCanBeLinkedOnTenant(con, appIdentifier, authRecipeStorage, tenantIds,
|
||||
recipeUser, primaryUserId, allUsersWithExtraData);
|
||||
BulkImportUser currentPrimaryUser = BulkImportUserUtils.findUserByPrimaryId(users, primaryUserId);
|
||||
for (BulkImportUser.LoginMethod currLoginMethod : currentPrimaryUser.loginMethods) {
|
||||
bulkCheckIfLoginMethodCanBeLinkedOnTenant(con, appIdentifier, authRecipeStorage, tenantIds,
|
||||
currLoginMethod, primaryUserId, allUsersWithExtraData);
|
||||
}
|
||||
|
||||
results.add(new CanLinkAccountsBulkResult(recipeUserId, primaryUserId, false, null, currentPrimaryUser));
|
||||
|
||||
} catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException exception) {
|
||||
results.add(new CanLinkAccountsBulkResult(recipeUserId, primaryUserId, false, exception, null));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void checkIfLoginMethodCanBeLinkedOnTenant(TransactionConnection con, AppIdentifier appIdentifier,
|
||||
AuthRecipeSQLStorage authRecipeStorage,
|
||||
Set<String> tenantIds, LoginMethod currLoginMethod,
|
||||
AuthRecipeUserInfo primaryUser)
|
||||
AuthRecipeSQLStorage authRecipeStorage,
|
||||
Set<String> tenantIds, LoginMethod currLoginMethod,
|
||||
AuthRecipeUserInfo primaryUser)
|
||||
throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException {
|
||||
// we loop through the union of both the user's tenantIds and check that the criteria for
|
||||
// linking accounts is not violated in any of them. We do a union and not an intersection
|
||||
|
|
@ -371,8 +269,9 @@ public class AuthRecipe {
|
|||
// tenants of the same storage - therefore, the storage will be the same.
|
||||
|
||||
if (currLoginMethod.email != null) {
|
||||
AuthRecipeUserInfo[] usersWithSameEmail =
|
||||
authRecipeStorage.listPrimaryUsersByEmail_Transaction(appIdentifier, con, currLoginMethod.email);
|
||||
AuthRecipeUserInfo[] usersWithSameEmail = authRecipeStorage
|
||||
.listPrimaryUsersByEmail_Transaction(appIdentifier, con,
|
||||
currLoginMethod.email);
|
||||
for (AuthRecipeUserInfo user : usersWithSameEmail) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
continue;
|
||||
|
|
@ -386,8 +285,8 @@ public class AuthRecipe {
|
|||
}
|
||||
|
||||
if (currLoginMethod.phoneNumber != null) {
|
||||
AuthRecipeUserInfo[] usersWithSamePhoneNumber =
|
||||
authRecipeStorage.listPrimaryUsersByPhoneNumber_Transaction(appIdentifier, con,
|
||||
AuthRecipeUserInfo[] usersWithSamePhoneNumber = authRecipeStorage
|
||||
.listPrimaryUsersByPhoneNumber_Transaction(appIdentifier, con,
|
||||
currLoginMethod.phoneNumber);
|
||||
for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
|
|
@ -416,88 +315,9 @@ public class AuthRecipe {
|
|||
userWithSameThirdParty.getSupertokensUserId(),
|
||||
"This user's third party login is already associated with another" +
|
||||
" user ID");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void bulkCheckIfLoginMethodCanBeLinkedOnTenant(TransactionConnection con, AppIdentifier appIdentifier,
|
||||
AuthRecipeSQLStorage authRecipeStorage,
|
||||
Set<String> tenantIds, BulkImportUser.LoginMethod currLoginMethod,
|
||||
String primaryUserId,
|
||||
List<AuthRecipeUserInfo> allUsersWithExtraData)
|
||||
throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException {
|
||||
// we loop through the union of both the user's tenantIds and check that the criteria for
|
||||
// linking accounts is not violated in any of them. We do a union and not an intersection
|
||||
// cause if we did an intersection, and that yields that account linking is allowed, it could
|
||||
// result in one tenant having two primary users with the same email. For example:
|
||||
// - tenant1 has u1 with email e, and u2 with email e, primary user (one is ep, one is tp)
|
||||
// - tenant2 has u3 with email e, primary user (passwordless)
|
||||
// now if we want to link u3 with u1, we have to deny it cause if we don't, it will result in
|
||||
// u1 and u2 to be primary users with the same email in the same tenant. If we do an
|
||||
// intersection, we will get an empty set, but if we do a union, we will get both the tenants and
|
||||
// do the checks in both.
|
||||
for (String tenantId : tenantIds) {
|
||||
// we do not bother with getting the storage for each tenant here because
|
||||
// we get the tenants from the user itself, and the user can only be shared across
|
||||
// tenants of the same storage - therefore, the storage will be the same.
|
||||
|
||||
if (currLoginMethod.email != null) {
|
||||
List<AuthRecipeUserInfo> usersWithSameEmail =
|
||||
allUsersWithExtraData.stream().filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods).map(loginMethod -> loginMethod.email).collect(
|
||||
Collectors.toList()).contains(currLoginMethod.email)).collect(Collectors.toList());
|
||||
for (AuthRecipeUserInfo user : usersWithSameEmail) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
continue;
|
||||
}
|
||||
if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUserId)) {
|
||||
throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
user.getSupertokensUserId(),
|
||||
"This user's email is already associated with another user ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currLoginMethod.phoneNumber != null) {
|
||||
List<AuthRecipeUserInfo> usersWithSamePhoneNumber =
|
||||
allUsersWithExtraData.stream().filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods).map(loginMethod -> loginMethod.phoneNumber).collect(
|
||||
Collectors.toList()).contains(currLoginMethod.phoneNumber)).collect(Collectors.toList());
|
||||
for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
continue;
|
||||
}
|
||||
if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUserId)) {
|
||||
throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
user.getSupertokensUserId(),
|
||||
"This user's phone number is already associated with another user" +
|
||||
" ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currLoginMethod.thirdPartyId != null) {
|
||||
List<AuthRecipeUserInfo> extraUsersWithThirdParty = allUsersWithExtraData.stream().filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods).anyMatch(loginMethod1 -> loginMethod1.thirdParty != null)).collect(Collectors.toList());
|
||||
for(AuthRecipeUserInfo extraUser : extraUsersWithThirdParty) {
|
||||
if(extraUser.isPrimaryUser && extraUser.tenantIds.contains(tenantId)
|
||||
&& !extraUser.getSupertokensUserId().equals(primaryUserId)) {
|
||||
for (LoginMethod loginMethodExtra : extraUser.loginMethods) {
|
||||
if (loginMethodExtra.thirdParty != null &&
|
||||
loginMethodExtra.thirdParty.userId.equals(currLoginMethod.thirdPartyUserId)
|
||||
&& loginMethodExtra.thirdParty.id.equals(currLoginMethod.thirdPartyId)) {
|
||||
|
||||
throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
extraUser.getSupertokensUserId(),
|
||||
"This user's third party login is already associated with another" +
|
||||
" user ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -509,7 +329,7 @@ public class AuthRecipe {
|
|||
FeatureNotEnabledException, InputUserIdIsNotAPrimaryUserException,
|
||||
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException {
|
||||
try {
|
||||
return linkAccounts(main, ResourceDistributor.getAppForTesting().toAppIdentifier(),
|
||||
return linkAccounts(main, new AppIdentifier(null, null),
|
||||
StorageLayer.getStorage(main), recipeUserId, primaryUserId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
@ -523,7 +343,8 @@ public class AuthRecipe {
|
|||
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException,
|
||||
UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException {
|
||||
|
||||
if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) {
|
||||
if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
|
||||
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA)) {
|
||||
throw new FeatureNotEnabledException(
|
||||
"Account linking feature is not enabled for this app. Please contact support to enable it.");
|
||||
}
|
||||
|
|
@ -580,48 +401,6 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
public static void linkMultipleAccountsForBulkImport(Main main, AppIdentifier appIdentifier,
|
||||
Storage storage,
|
||||
List<BulkImportUser> users,
|
||||
List<AuthRecipeUserInfo> usersWithSameExtraData)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException, FeatureNotEnabledException {
|
||||
|
||||
if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) {
|
||||
throw new FeatureNotEnabledException(
|
||||
"Account linking feature is not enabled for this app. Please contact support to enable it.");
|
||||
}
|
||||
|
||||
AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
Map<String, Exception> errorByUserId = new HashMap<>();
|
||||
try {
|
||||
|
||||
authRecipeStorage.startTransaction(con -> {
|
||||
List<CanLinkAccountsBulkResult> canLinkAccounts = canLinkMultipleAccountsHelperForBulkImport(con, appIdentifier,
|
||||
authRecipeStorage, users, usersWithSameExtraData);
|
||||
Map<String, String> recipeUserByPrimaryUserNeedsLinking = new HashMap<>();
|
||||
if(!canLinkAccounts.isEmpty()){
|
||||
for(CanLinkAccountsBulkResult canLinkAccountsBulkResult : canLinkAccounts) {
|
||||
if(!canLinkAccountsBulkResult.alreadyLinked && canLinkAccountsBulkResult.error != null) {
|
||||
errorByUserId.put(canLinkAccountsBulkResult.recipeUserId, canLinkAccountsBulkResult.error);
|
||||
} else {
|
||||
recipeUserByPrimaryUserNeedsLinking.put(canLinkAccountsBulkResult.recipeUserId, canLinkAccountsBulkResult.primaryUserId);
|
||||
}
|
||||
}
|
||||
// link the remaining
|
||||
authRecipeStorage.linkMultipleAccounts_Transaction(appIdentifier, con, recipeUserByPrimaryUserNeedsLinking);
|
||||
|
||||
authRecipeStorage.commitTransaction(con);
|
||||
}
|
||||
if(!errorByUserId.isEmpty()) {
|
||||
throw new StorageQueryException(new BulkImportBatchInsertException("link accounts errors", errorByUserId));
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class LinkAccountsResult {
|
||||
public final AuthRecipeUserInfo user;
|
||||
public final boolean wasAlreadyLinked;
|
||||
|
|
@ -632,24 +411,12 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
public static class LinkAccountsBulkResult {
|
||||
public final BulkImportUser user;
|
||||
public final boolean wasAlreadyLinked;
|
||||
public final Exception error;
|
||||
|
||||
public LinkAccountsBulkResult(BulkImportUser user, boolean wasAlreadyLinked, Exception error) {
|
||||
this.user = user;
|
||||
this.wasAlreadyLinked = wasAlreadyLinked;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static CreatePrimaryUserResult canCreatePrimaryUser(Main main,
|
||||
String recipeUserId)
|
||||
throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException,
|
||||
RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException {
|
||||
return canCreatePrimaryUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
return canCreatePrimaryUser(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
}
|
||||
|
||||
public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifier appIdentifier,
|
||||
|
|
@ -761,159 +528,9 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return new CreatePrimaryUserResult(targetUser, false);
|
||||
}
|
||||
|
||||
private static CreatePrimaryUsersResultHolder canCreatePrimaryUsersHelperForBulkImport(TransactionConnection con,
|
||||
AppIdentifier appIdentifier,
|
||||
Storage storage,
|
||||
List<BulkImportUser> bulkImportUsers)
|
||||
throws StorageQueryException, UnknownUserIdException{
|
||||
|
||||
AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
|
||||
if (bulkImportUsers == null || bulkImportUsers.isEmpty()) {
|
||||
throw new UnknownUserIdException();
|
||||
}
|
||||
|
||||
DistinctAuthIdentifiers mailPhoneThirdParty = getDistinctAuthIdentifiers(bulkImportUsers);
|
||||
|
||||
List<CreatePrimaryUserBulkResult> results = new ArrayList<>();
|
||||
List<AuthRecipeUserInfo> allUsersWithProvidedExtraData =
|
||||
List.of(authRecipeStorage.
|
||||
listPrimaryUsersByMultipleEmailsOrPhoneNumbersOrThirdparty_Transaction(appIdentifier, con,
|
||||
new ArrayList<>(mailPhoneThirdParty.allEmails), new ArrayList<>(mailPhoneThirdParty.allPhoneNumber),
|
||||
mailPhoneThirdParty.allThirdParty)); // this is multiple - not so cheap DB query, but we need to do it
|
||||
|
||||
for (BulkImportUser targetUser : bulkImportUsers) {
|
||||
BulkImportUser.LoginMethod primaryLoginMethod = BulkImportUserUtils.getPrimaryLoginMethod(targetUser);
|
||||
|
||||
for (BulkImportUser.LoginMethod loginMethod : targetUser.loginMethods) {
|
||||
// note here: account takeover risk checks are done in the sdk. The situation in which someone registers
|
||||
// for example with a thirparty which also verifies email address and later someone else tries to register
|
||||
// with the same email address but with emailpassword is not handled here. This is because the sdk
|
||||
// will handle this. In the bulk import we have no means to check this.
|
||||
boolean errorFound = false;
|
||||
for (String tenantId : loginMethod.tenantIds) {
|
||||
if (loginMethod.email != null) {
|
||||
List<AuthRecipeUserInfo> usersWithSameEmail = allUsersWithProvidedExtraData.stream()
|
||||
.filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods).map(loginMethod1 -> loginMethod1.email)
|
||||
.collect(Collectors.toList()).contains(loginMethod.email)).collect(
|
||||
Collectors.toList());
|
||||
for (AuthRecipeUserInfo user : usersWithSameEmail) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
continue;
|
||||
}
|
||||
if (user.isPrimaryUser) {
|
||||
results.add(new CreatePrimaryUserBulkResult(targetUser, primaryLoginMethod, false,
|
||||
new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
user.getSupertokensUserId(),
|
||||
"This user's email is already associated with another user ID")));
|
||||
errorFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loginMethod.phoneNumber != null) {
|
||||
List<AuthRecipeUserInfo> usersWithSamePhoneNumber = allUsersWithProvidedExtraData.stream()
|
||||
.filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods).map(loginMethod1 -> loginMethod1.phoneNumber)
|
||||
.collect(Collectors.toList()).contains(loginMethod.phoneNumber)).collect(
|
||||
Collectors.toList());
|
||||
for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) {
|
||||
if (!user.tenantIds.contains(tenantId)) {
|
||||
continue;
|
||||
}
|
||||
if (user.isPrimaryUser) {
|
||||
results.add(new CreatePrimaryUserBulkResult(targetUser, primaryLoginMethod, false,
|
||||
new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
user.getSupertokensUserId(),
|
||||
"This user's phone number is already associated with another user" +
|
||||
" ID")));
|
||||
errorFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loginMethod.thirdPartyId != null && loginMethod.thirdPartyUserId != null) {
|
||||
List<AuthRecipeUserInfo> extraUsersWithThirdParty = allUsersWithProvidedExtraData.stream()
|
||||
.filter(authRecipeUserInfo -> Arrays.stream(
|
||||
authRecipeUserInfo.loginMethods)
|
||||
.anyMatch(loginMethod1 -> loginMethod1.thirdParty != null))
|
||||
.collect(Collectors.toList());
|
||||
for (AuthRecipeUserInfo extraUser : extraUsersWithThirdParty) {
|
||||
if (extraUser.isPrimaryUser && extraUser.tenantIds.contains(tenantId)) {
|
||||
for (LoginMethod loginMethodExtra : extraUser.loginMethods) {
|
||||
if (loginMethodExtra.thirdParty != null &&
|
||||
loginMethodExtra.thirdParty.userId.equals(loginMethod.thirdPartyUserId)
|
||||
&& loginMethodExtra.thirdParty.id.equals(loginMethod.thirdPartyId)) {
|
||||
|
||||
results.add(
|
||||
new CreatePrimaryUserBulkResult(targetUser, primaryLoginMethod, false,
|
||||
new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(
|
||||
extraUser.getSupertokensUserId(),
|
||||
"This user's third party login is already associated with another" +
|
||||
" user ID")));
|
||||
errorFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!errorFound) {
|
||||
results.add(new CreatePrimaryUserBulkResult(targetUser, primaryLoginMethod, false, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CreatePrimaryUsersResultHolder resultHolder = new CreatePrimaryUsersResultHolder();
|
||||
resultHolder.createPrimaryUserBulkResults = results;
|
||||
resultHolder.usersWithSameExtraData = allUsersWithProvidedExtraData;
|
||||
return resultHolder;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static DistinctAuthIdentifiers getDistinctAuthIdentifiers(List<BulkImportUser> bulkImportUsers) {
|
||||
Set<String> allEmails = new HashSet<>();
|
||||
Set<String> allPhoneNumber = new HashSet<>();
|
||||
Map<String, String> allThirdParty = new HashMap<>();
|
||||
for (BulkImportUser user : bulkImportUsers) {
|
||||
for (BulkImportUser.LoginMethod loginMethod : user.loginMethods) {
|
||||
if (loginMethod.email != null) {
|
||||
allEmails.add(loginMethod.email);
|
||||
}
|
||||
if (loginMethod.phoneNumber != null) {
|
||||
allPhoneNumber.add(loginMethod.phoneNumber);
|
||||
}
|
||||
if (loginMethod.thirdPartyId != null && loginMethod.thirdPartyUserId != null) {
|
||||
allThirdParty.put(loginMethod.thirdPartyUserId, loginMethod.thirdPartyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
DistinctAuthIdentifiers mailPhoneThirdparty = new DistinctAuthIdentifiers(allEmails, allPhoneNumber, allThirdParty);
|
||||
return mailPhoneThirdparty;
|
||||
}
|
||||
|
||||
private static class DistinctAuthIdentifiers {
|
||||
public final Set<String> allEmails;
|
||||
public final Set<String> allPhoneNumber;
|
||||
public final Map<String, String> allThirdParty;
|
||||
|
||||
public DistinctAuthIdentifiers(Set<String> allEmails, Set<String> allPhoneNumber, Map<String, String> allThirdParty) {
|
||||
this.allEmails = allEmails;
|
||||
this.allPhoneNumber = allPhoneNumber;
|
||||
this.allThirdParty = allThirdParty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestOnly
|
||||
public static CreatePrimaryUserResult createPrimaryUser(Main main,
|
||||
String recipeUserId)
|
||||
|
|
@ -921,7 +538,7 @@ public class AuthRecipe {
|
|||
RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException,
|
||||
FeatureNotEnabledException {
|
||||
try {
|
||||
return createPrimaryUser(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
return createPrimaryUser(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
@ -935,7 +552,8 @@ public class AuthRecipe {
|
|||
RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, TenantOrAppNotFoundException,
|
||||
FeatureNotEnabledException {
|
||||
|
||||
if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) {
|
||||
if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
|
||||
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA)) {
|
||||
throw new FeatureNotEnabledException(
|
||||
"Account linking feature is not enabled for this app. Please contact support to enable it.");
|
||||
}
|
||||
|
|
@ -945,7 +563,7 @@ public class AuthRecipe {
|
|||
return authRecipeStorage.startTransaction(con -> {
|
||||
|
||||
try {
|
||||
CreatePrimaryUserResult result = canCreatePrimaryUserHelper(con, appIdentifier, authRecipeStorage,
|
||||
CreatePrimaryUserResult result = canCreatePrimaryUserHelper(con, appIdentifier, authRecipeStorage,
|
||||
recipeUserId);
|
||||
if (result.wasAlreadyAPrimaryUser) {
|
||||
return result;
|
||||
|
|
@ -975,182 +593,75 @@ public class AuthRecipe {
|
|||
}
|
||||
}
|
||||
|
||||
//helper class to return together the results of primary user creation and the users with the same extradata (email, phone, etc)
|
||||
public static class CreatePrimaryUsersResultHolder {
|
||||
public List<CreatePrimaryUserBulkResult> createPrimaryUserBulkResults;
|
||||
public List<AuthRecipeUserInfo> usersWithSameExtraData;
|
||||
}
|
||||
|
||||
public static CreatePrimaryUsersResultHolder createPrimaryUsersForBulkImport(Main main,
|
||||
AppIdentifier appIdentifier,
|
||||
Storage storage,
|
||||
List<BulkImportUser> bulkImportUsers)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException,
|
||||
FeatureNotEnabledException {
|
||||
|
||||
if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) {
|
||||
throw new FeatureNotEnabledException(
|
||||
"Account linking feature is not enabled for this app. Please contact support to enable it.");
|
||||
}
|
||||
|
||||
AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
Map<String, Exception> errorsByUserId = new HashMap<>();
|
||||
try {
|
||||
return authRecipeStorage.startTransaction(con -> {
|
||||
|
||||
try {
|
||||
CreatePrimaryUsersResultHolder resultHolder = canCreatePrimaryUsersHelperForBulkImport(con, appIdentifier, authRecipeStorage,
|
||||
bulkImportUsers);
|
||||
List<CreatePrimaryUserBulkResult> results = resultHolder.createPrimaryUserBulkResults;
|
||||
List<CreatePrimaryUserBulkResult> canMakePrimaryUsers = new ArrayList<>();
|
||||
for(CreatePrimaryUserBulkResult result : results) {
|
||||
if (result.wasAlreadyAPrimaryUser) {
|
||||
continue;
|
||||
}
|
||||
if(result.error != null) {
|
||||
errorsByUserId.put(result.user.id, result.error);
|
||||
continue;
|
||||
}
|
||||
canMakePrimaryUsers.add(result);
|
||||
}
|
||||
authRecipeStorage.makePrimaryUsers_Transaction(appIdentifier, con,
|
||||
canMakePrimaryUsers.stream().map(canMakePrimaryUser -> canMakePrimaryUser.user.id).collect(
|
||||
Collectors.toList()));
|
||||
|
||||
authRecipeStorage.commitTransaction(con);
|
||||
|
||||
for(CreatePrimaryUserBulkResult result : results) {
|
||||
if (result.wasAlreadyAPrimaryUser) {
|
||||
continue;
|
||||
}
|
||||
if(result.error != null) {
|
||||
errorsByUserId.put(result.user.id, result.error);
|
||||
continue;
|
||||
}
|
||||
result.primaryLoginMethod.isPrimary = true;
|
||||
result.user.primaryUserId = result.primaryLoginMethod.superTokensUserId;
|
||||
}
|
||||
|
||||
if(!errorsByUserId.isEmpty()) {
|
||||
throw new StorageTransactionLogicException(new BulkImportBatchInsertException("create primary users errors", errorsByUserId));
|
||||
}
|
||||
|
||||
return resultHolder;
|
||||
} catch (UnknownUserIdException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e.actualException);
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifier tenantIdentifier,
|
||||
Storage storage,
|
||||
boolean doUnionOfAccountInfo, String email,
|
||||
String phoneNumber, String thirdPartyId,
|
||||
String thirdPartyUserId,
|
||||
String webauthnCredentialId)
|
||||
throws StorageQueryException {
|
||||
Set<AuthRecipeUserInfo> result = loadAuthRecipeUserInfosByVariousIds(
|
||||
tenantIdentifier, storage, email, phoneNumber, thirdPartyId, thirdPartyUserId, webauthnCredentialId);
|
||||
|
||||
if (doUnionOfAccountInfo) {
|
||||
return mergeAuthRecipeUserInfosResultWithORMatch(result); // matches any of the provided: email, thirdparty, phone number, webauthnCredential
|
||||
} else {
|
||||
return mergeAuthRecipeUserInfosResultWithANDMatch(email, phoneNumber, thirdPartyId, thirdPartyUserId, webauthnCredentialId,
|
||||
result); // matches all the provided: email, thirdparty, phone number, webauthnCredential
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static AuthRecipeUserInfo[] mergeAuthRecipeUserInfosResultWithANDMatch(String email, String phoneNumber,
|
||||
String thirdPartyId, String thirdPartyUserId,
|
||||
String webauthnCredentialId,
|
||||
Set<AuthRecipeUserInfo> result) {
|
||||
List<AuthRecipeUserInfo> finalList = new ArrayList<>();
|
||||
for (AuthRecipeUserInfo user : result) {
|
||||
boolean emailMatch = email == null;
|
||||
boolean phoneNumberMatch = phoneNumber == null;
|
||||
boolean thirdPartyMatch = thirdPartyId == null;
|
||||
boolean webauthnCredentialIdMatch = webauthnCredentialId == null;
|
||||
for (LoginMethod lM : user.loginMethods) {
|
||||
if (email != null && email.equals(lM.email)) {
|
||||
emailMatch = true;
|
||||
}
|
||||
if (phoneNumber != null && phoneNumber.equals(lM.phoneNumber)) {
|
||||
phoneNumberMatch = true;
|
||||
}
|
||||
if (thirdPartyId != null &&
|
||||
(new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId)).equals(lM.thirdParty)) {
|
||||
thirdPartyMatch = true;
|
||||
}
|
||||
if(webauthnCredentialId != null
|
||||
&& lM.webauthN != null
|
||||
&& lM.webauthN.credentialIds.contains(webauthnCredentialId)){
|
||||
webauthnCredentialIdMatch = true;
|
||||
}
|
||||
}
|
||||
if (emailMatch && phoneNumberMatch && thirdPartyMatch && webauthnCredentialIdMatch) {
|
||||
finalList.add(user);
|
||||
}
|
||||
}
|
||||
finalList.sort((o1, o2) -> {
|
||||
if (o1.timeJoined < o2.timeJoined) {
|
||||
return -1;
|
||||
} else if (o1.timeJoined > o2.timeJoined) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return finalList.toArray(new AuthRecipeUserInfo[0]);
|
||||
}
|
||||
|
||||
private static AuthRecipeUserInfo[] mergeAuthRecipeUserInfosResultWithORMatch(Set<AuthRecipeUserInfo> result) {
|
||||
AuthRecipeUserInfo[] finalResult = result.toArray(new AuthRecipeUserInfo[0]);
|
||||
return Arrays.stream(finalResult).sorted((o1, o2) -> {
|
||||
if (o1.timeJoined < o2.timeJoined) {
|
||||
return -1;
|
||||
} else if (o1.timeJoined > o2.timeJoined) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).toArray(AuthRecipeUserInfo[]::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Set<AuthRecipeUserInfo> loadAuthRecipeUserInfosByVariousIds(TenantIdentifier tenantIdentifier, Storage storage,
|
||||
String email, String phoneNumber, String thirdPartyId,
|
||||
String thirdPartyUserId, String webauthnCredentialId)
|
||||
String thirdPartyUserId)
|
||||
throws StorageQueryException {
|
||||
Set<AuthRecipeUserInfo> result = new HashSet<>();
|
||||
|
||||
AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
|
||||
if (email != null) {
|
||||
AuthRecipeUserInfo[] users = authRecipeStorage
|
||||
AuthRecipeUserInfo[] users = StorageUtils.getAuthRecipeStorage(storage)
|
||||
.listPrimaryUsersByEmail(tenantIdentifier, email);
|
||||
result.addAll(List.of(users));
|
||||
}
|
||||
if (phoneNumber != null) {
|
||||
AuthRecipeUserInfo[] users = authRecipeStorage
|
||||
AuthRecipeUserInfo[] users = StorageUtils.getAuthRecipeStorage(storage)
|
||||
.listPrimaryUsersByPhoneNumber(tenantIdentifier, phoneNumber);
|
||||
result.addAll(List.of(users));
|
||||
}
|
||||
if (thirdPartyId != null && thirdPartyUserId != null) {
|
||||
AuthRecipeUserInfo user = authRecipeStorage
|
||||
AuthRecipeUserInfo user = StorageUtils.getAuthRecipeStorage(storage)
|
||||
.getPrimaryUserByThirdPartyInfo(tenantIdentifier, thirdPartyId, thirdPartyUserId);
|
||||
if (user != null) {
|
||||
result.add(user);
|
||||
}
|
||||
}
|
||||
if(webauthnCredentialId != null){
|
||||
AuthRecipeUserInfo user = authRecipeStorage
|
||||
.getPrimaryUserByWebauthNCredentialId(tenantIdentifier, webauthnCredentialId);
|
||||
if (user != null) {
|
||||
result.add(user);
|
||||
|
||||
if (doUnionOfAccountInfo) {
|
||||
AuthRecipeUserInfo[] finalResult = result.toArray(new AuthRecipeUserInfo[0]);
|
||||
return Arrays.stream(finalResult).sorted((o1, o2) -> {
|
||||
if (o1.timeJoined < o2.timeJoined) {
|
||||
return -1;
|
||||
} else if (o1.timeJoined > o2.timeJoined) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).toArray(AuthRecipeUserInfo[]::new);
|
||||
} else {
|
||||
List<AuthRecipeUserInfo> finalList = new ArrayList<>();
|
||||
for (AuthRecipeUserInfo user : result) {
|
||||
boolean emailMatch = email == null;
|
||||
boolean phoneNumberMatch = phoneNumber == null;
|
||||
boolean thirdPartyMatch = thirdPartyId == null;
|
||||
for (LoginMethod lM : user.loginMethods) {
|
||||
if (email != null && email.equals(lM.email)) {
|
||||
emailMatch = true;
|
||||
}
|
||||
if (phoneNumber != null && phoneNumber.equals(lM.phoneNumber)) {
|
||||
phoneNumberMatch = true;
|
||||
}
|
||||
if (thirdPartyId != null &&
|
||||
(new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId)).equals(lM.thirdParty)) {
|
||||
thirdPartyMatch = true;
|
||||
}
|
||||
}
|
||||
if (emailMatch && phoneNumberMatch && thirdPartyMatch) {
|
||||
finalList.add(user);
|
||||
}
|
||||
}
|
||||
finalList.sort((o1, o2) -> {
|
||||
if (o1.timeJoined < o2.timeJoined) {
|
||||
return -1;
|
||||
} else if (o1.timeJoined > o2.timeJoined) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return finalList.toArray(new AuthRecipeUserInfo[0]);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public static long getUsersCountForTenant(TenantIdentifier tenantIdentifier,
|
||||
|
|
@ -1182,7 +693,7 @@ public class AuthRecipe {
|
|||
RECIPE_ID[] includeRecipeIds) throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUsersCountForTenant(ResourceDistributor.getAppForTesting(), storage, includeRecipeIds);
|
||||
return getUsersCountForTenant(TenantIdentifier.BASE_TENANT, storage, includeRecipeIds);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -1232,7 +743,7 @@ public class AuthRecipe {
|
|||
throws StorageQueryException, UserPaginationToken.InvalidTokenException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUsers(ResourceDistributor.getAppForTesting(), storage,
|
||||
return getUsers(TenantIdentifier.BASE_TENANT, storage,
|
||||
limit, timeJoinedOrder, paginationToken, includeRecipeIds, dashboardSearchTags);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -1390,7 +901,7 @@ public class AuthRecipe {
|
|||
public static void deleteUser(Main main, String userId, boolean removeAllLinkedAccounts)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
AppIdentifier appIdentifier = ResourceDistributor.getAppForTesting().toAppIdentifier();
|
||||
AppIdentifier appIdentifier = new AppIdentifier(null, null);
|
||||
UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier,
|
||||
storage, userId, UserIdType.ANY);
|
||||
|
||||
|
|
@ -1401,7 +912,7 @@ public class AuthRecipe {
|
|||
public static void deleteUser(Main main, String userId)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
AppIdentifier appIdentifier = ResourceDistributor.getAppForTesting().toAppIdentifier();
|
||||
AppIdentifier appIdentifier = new AppIdentifier(null, null);
|
||||
UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier,
|
||||
storage, userId, UserIdType.ANY);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 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.authRecipe.exception;
|
||||
|
||||
public class BulkImportRecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException extends Exception {
|
||||
public final String recipeUserId;
|
||||
|
||||
public BulkImportRecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(String recipeUserId) {
|
||||
super("The recipe user id '" + recipeUserId + "' is already linked with another primary user id");
|
||||
this.recipeUserId = recipeUserId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,845 +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.bulkimport;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException;
|
||||
import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException;
|
||||
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException;
|
||||
import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.emailpassword.EmailPassword;
|
||||
import io.supertokens.emailpassword.PasswordHashing;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithEmailAlreadyExistsException;
|
||||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithPhoneNumberAlreadyExistsException;
|
||||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.passwordless.Passwordless;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportStorage.BULK_IMPORT_USER_STATUS;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole;
|
||||
import io.supertokens.pluginInterface.bulkimport.ImportUserBase;
|
||||
import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException;
|
||||
import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage;
|
||||
import io.supertokens.pluginInterface.emailpassword.EmailPasswordImportUser;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
|
||||
import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage;
|
||||
import io.supertokens.pluginInterface.exceptions.DbInitException;
|
||||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser;
|
||||
import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException;
|
||||
import io.supertokens.pluginInterface.sqlStorage.SQLStorage;
|
||||
import io.supertokens.pluginInterface.thirdparty.ThirdPartyImportUser;
|
||||
import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException;
|
||||
import io.supertokens.pluginInterface.totp.TOTPDevice;
|
||||
import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException;
|
||||
import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException;
|
||||
import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.thirdparty.ThirdParty;
|
||||
import io.supertokens.totp.Totp;
|
||||
import io.supertokens.useridmapping.UserIdMapping;
|
||||
import io.supertokens.usermetadata.UserMetadata;
|
||||
import io.supertokens.userroles.UserRoles;
|
||||
import io.supertokens.utils.Utils;
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// Error codes ensure globally unique and identifiable errors in Bulk Import.
|
||||
// Current range: E001 to E046.
|
||||
|
||||
public class BulkImport {
|
||||
|
||||
// Maximum number of users that can be added in a single /bulk-import/users POST request
|
||||
public static final int MAX_USERS_TO_ADD = 10000;
|
||||
// Maximum number of users to return in a single page when calling /bulk-import/users GET
|
||||
public static final int GET_USERS_PAGINATION_MAX_LIMIT = 500;
|
||||
// Default number of users to return when no specific limit is given in /bulk-import/users GET
|
||||
public static final int GET_USERS_DEFAULT_LIMIT = 100;
|
||||
// Maximum number of users that can be deleted in a single operation
|
||||
public static final int DELETE_USERS_MAX_LIMIT = 500;
|
||||
// Time interval in seconds between two consecutive runs of ProcessBulkImportUsers Cron Job
|
||||
public static final int PROCESS_USERS_INTERVAL_SECONDS = 5*60; // 5 minutes
|
||||
private static final Logger log = LoggerFactory.getLogger(BulkImport.class);
|
||||
|
||||
// This map allows reusing proxy storage for all tenants in the app and closing connections after import.
|
||||
private static Map<String, SQLStorage> userPoolToStorageMap = new HashMap<>();
|
||||
|
||||
public static void addUsers(AppIdentifier appIdentifier, Storage storage, List<BulkImportUser> users)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
while (true) {
|
||||
try {
|
||||
StorageUtils.getBulkImportStorage(storage).addBulkImportUsers(appIdentifier, users);
|
||||
break;
|
||||
} catch (StorageQueryException sqe) {
|
||||
if (sqe.getCause() instanceof io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException) {
|
||||
// We re-generate the user id for every user and retry
|
||||
for (BulkImportUser user : users) {
|
||||
user.id = Utils.getUUID();
|
||||
}
|
||||
} else {
|
||||
throw sqe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BulkImportUserPaginationContainer getUsers(AppIdentifier appIdentifier, Storage storage,
|
||||
int limit, @Nullable BULK_IMPORT_USER_STATUS status, @Nullable String paginationToken)
|
||||
throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException {
|
||||
List<BulkImportUser> users;
|
||||
|
||||
BulkImportSQLStorage bulkImportStorage = StorageUtils.getBulkImportStorage(storage);
|
||||
|
||||
if (paginationToken == null) {
|
||||
users = bulkImportStorage
|
||||
.getBulkImportUsers(appIdentifier, limit + 1, status, null, null);
|
||||
} else {
|
||||
BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken);
|
||||
users = bulkImportStorage
|
||||
.getBulkImportUsers(appIdentifier, limit + 1, status, tokenInfo.bulkImportUserId,
|
||||
tokenInfo.createdAt);
|
||||
}
|
||||
|
||||
String nextPaginationToken = null;
|
||||
int maxLoop = users.size();
|
||||
if (users.size() == limit + 1) {
|
||||
maxLoop = limit;
|
||||
BulkImportUser user = users.get(limit);
|
||||
nextPaginationToken = new BulkImportUserPaginationToken(user.id, user.createdAt).generateToken();
|
||||
}
|
||||
|
||||
List<BulkImportUser> resultUsers = users.subList(0, maxLoop);
|
||||
return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken);
|
||||
}
|
||||
|
||||
public static List<String> deleteUsers(AppIdentifier appIdentifier, Storage storage, String[] userIds)
|
||||
throws StorageQueryException {
|
||||
return StorageUtils.getBulkImportStorage(storage).deleteBulkImportUsers(appIdentifier, userIds);
|
||||
}
|
||||
|
||||
public static long getBulkImportUsersCount(AppIdentifier appIdentifier, Storage storage,
|
||||
@Nullable BULK_IMPORT_USER_STATUS status)
|
||||
throws StorageQueryException {
|
||||
return StorageUtils.getBulkImportStorage(storage).getBulkImportUsersCount(appIdentifier, status);
|
||||
}
|
||||
|
||||
public static synchronized AuthRecipeUserInfo importUser(Main main, AppIdentifier appIdentifier,
|
||||
BulkImportUser user)
|
||||
throws StorageQueryException, InvalidConfigException, IOException, TenantOrAppNotFoundException,
|
||||
DbInitException, BulkImportBatchInsertException {
|
||||
// Since all the tenants of a user must share the storage, we will just use the
|
||||
// storage of the first tenantId of the first loginMethod
|
||||
TenantIdentifier firstTenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), user.loginMethods.get(0).tenantIds.get(0));
|
||||
|
||||
SQLStorage bulkImportProxyStorage = (SQLStorage) getBulkImportProxyStorage(main, firstTenantIdentifier);
|
||||
|
||||
LoginMethod primaryLM = BulkImportUserUtils.getPrimaryLoginMethod(user);
|
||||
|
||||
try {
|
||||
return bulkImportProxyStorage.startTransaction(con -> {
|
||||
try {
|
||||
Storage[] allStoragesForApp = getAllProxyStoragesForApp(main, appIdentifier);
|
||||
|
||||
processUsersImportSteps(main, appIdentifier, bulkImportProxyStorage, List.of(user), allStoragesForApp);
|
||||
|
||||
bulkImportProxyStorage.commitTransactionForBulkImportProxyStorage();
|
||||
|
||||
AuthRecipeUserInfo importedUser = AuthRecipe.getUserById(appIdentifier, bulkImportProxyStorage,
|
||||
primaryLM.superTokensUserId);
|
||||
io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(appIdentifier,
|
||||
bulkImportProxyStorage, new AuthRecipeUserInfo[] { importedUser });
|
||||
|
||||
return importedUser;
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
// We need to rollback the transaction manually because we have overridden that in the proxy storage
|
||||
bulkImportProxyStorage.rollbackTransactionForBulkImportProxyStorage();
|
||||
throw e;
|
||||
} finally {
|
||||
closeAllProxyStorages();
|
||||
}
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
if(e.actualException instanceof BulkImportBatchInsertException){
|
||||
throw (BulkImportBatchInsertException) e.actualException;
|
||||
}
|
||||
throw new StorageQueryException(e.actualException);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processUsersImportSteps(Main main, AppIdentifier appIdentifier,
|
||||
Storage bulkImportProxyStorage, List<BulkImportUser> users, Storage[] allStoragesForApp)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing login methods..");
|
||||
processUsersLoginMethods(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing login methods DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating Primary users and linking accounts..");
|
||||
createPrimaryUsersAndLinkAccounts(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating Primary users and linking accounts DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user id mappings..");
|
||||
createMultipleUserIdMapping(appIdentifier, users, allStoragesForApp);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user id mappings DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Verifying email addresses..");
|
||||
verifyMultipleEmailForAllLoginMethods(appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Verifying email addresses DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating TOTP devices..");
|
||||
createMultipleTotpDevices(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating TOTP devices DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user metadata..");
|
||||
createMultipleUserMetadata(appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user metadata DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user roles..");
|
||||
createMultipleUserRoles(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user roles DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Effective processUsersImportSteps DONE");
|
||||
} catch ( StorageQueryException | FeatureNotEnabledException |
|
||||
TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processUsersLoginMethods(Main main, AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users) throws StorageTransactionLogicException {
|
||||
//sort login methods together
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Sorting login methods by recipeId..");
|
||||
Map<String, List<LoginMethod>> sortedLoginMethods = new HashMap<>();
|
||||
for (BulkImportUser user: users) {
|
||||
for(LoginMethod loginMethod : user.loginMethods){
|
||||
if(!sortedLoginMethods.containsKey(loginMethod.recipeId)) {
|
||||
sortedLoginMethods.put(loginMethod.recipeId, new ArrayList<>());
|
||||
}
|
||||
sortedLoginMethods.get(loginMethod.recipeId).add(loginMethod);
|
||||
}
|
||||
}
|
||||
|
||||
List<ImportUserBase> importedUsers = new ArrayList<>();
|
||||
if (sortedLoginMethods.containsKey("emailpassword")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing emailpassword login methods..");
|
||||
importedUsers.addAll(
|
||||
processEmailPasswordLoginMethods(main, storage, sortedLoginMethods.get("emailpassword"),
|
||||
appIdentifier));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing emailpassword login methods DONE");
|
||||
}
|
||||
if (sortedLoginMethods.containsKey("thirdparty")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing thirdparty login methods..");
|
||||
importedUsers.addAll(
|
||||
processThirdpartyLoginMethods(main, storage, sortedLoginMethods.get("thirdparty"),
|
||||
appIdentifier));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing thirdparty login methods DONE");
|
||||
}
|
||||
if (sortedLoginMethods.containsKey("passwordless")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing passwordless login methods..");
|
||||
importedUsers.addAll(processPasswordlessLoginMethods(main, appIdentifier, storage,
|
||||
sortedLoginMethods.get("passwordless")));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing passwordless login methods DONE");
|
||||
}
|
||||
Set<String> actualKeys = new HashSet<>(sortedLoginMethods.keySet());
|
||||
List.of("emailpassword", "thirdparty", "passwordless").forEach(actualKeys::remove);
|
||||
if(!actualKeys.isEmpty()){
|
||||
throw new StorageTransactionLogicException(
|
||||
new IllegalArgumentException("E001: Unknown recipeId(s) [" +
|
||||
actualKeys.stream().map(s -> s+" ") + "] for loginMethod."));
|
||||
}
|
||||
|
||||
Map<String, Exception> errorsById = new HashMap<>();
|
||||
for (Map.Entry<String, List<LoginMethod>> loginMethodEntries : sortedLoginMethods.entrySet()) {
|
||||
for (LoginMethod loginMethod : loginMethodEntries.getValue()) {
|
||||
try {
|
||||
associateUserToTenants(main, appIdentifier, storage, loginMethod, loginMethod.tenantIds.get(0));
|
||||
} catch (StorageTransactionLogicException e){
|
||||
errorsById.put(loginMethod.superTokensUserId, e.actualException);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!errorsById.isEmpty()){
|
||||
throw new StorageTransactionLogicException(new BulkImportBatchInsertException("tenant association errors", errorsById));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<? extends ImportUserBase> processPasswordlessLoginMethods(Main main, AppIdentifier appIdentifier, Storage storage,
|
||||
List<LoginMethod> loginMethods)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
List<PasswordlessImportUser> usersToImport = new ArrayList<>();
|
||||
for (LoginMethod loginMethod : loginMethods) {
|
||||
TenantIdentifier tenantIdentifierForLoginMethod = new TenantIdentifier(
|
||||
appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), loginMethod.tenantIds.get(
|
||||
0)); // the cron runs per app. The app stays the same, the tenant can change
|
||||
usersToImport.add(new PasswordlessImportUser(loginMethod.superTokensUserId, loginMethod.phoneNumber,
|
||||
loginMethod.email, tenantIdentifierForLoginMethod, loginMethod.timeJoinedInMSSinceEpoch));
|
||||
}
|
||||
|
||||
Passwordless.createPasswordlessUsers(storage, usersToImport);
|
||||
return usersToImport;
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "exception: " + e.getMessage());
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof DuplicateEmailException) {
|
||||
String message = "E006: A user with email "
|
||||
+ loginMethods.stream()
|
||||
.filter(loginMethod -> loginMethod.superTokensUserId.equals(userid))
|
||||
.findFirst().get().email + " already exists in passwordless loginMethod.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof DuplicatePhoneNumberException) {
|
||||
String message = "E007: A user with phoneNumber "
|
||||
+ loginMethods.stream()
|
||||
.filter(loginMethod -> loginMethod.superTokensUserId.equals(userid))
|
||||
.findFirst().get().phoneNumber + " already exists in passwordless loginMethod.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E008: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<? extends ImportUserBase> processThirdpartyLoginMethods(Main main, Storage storage, List<LoginMethod> loginMethods,
|
||||
AppIdentifier appIdentifier)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
List<ThirdPartyImportUser> usersToImport = new ArrayList<>();
|
||||
for (LoginMethod loginMethod: loginMethods){
|
||||
TenantIdentifier tenantIdentifierForLoginMethod = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), loginMethod.tenantIds.get(0)); // the cron runs per app. The app stays the same, the tenant can change
|
||||
|
||||
usersToImport.add(new ThirdPartyImportUser(loginMethod.email, loginMethod.superTokensUserId, loginMethod.thirdPartyId,
|
||||
loginMethod.thirdPartyUserId, tenantIdentifierForLoginMethod, loginMethod.timeJoinedInMSSinceEpoch));
|
||||
}
|
||||
ThirdParty.createMultipleThirdPartyUsers(storage, usersToImport);
|
||||
|
||||
return usersToImport;
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof DuplicateThirdPartyUserException) {
|
||||
LoginMethod loginMethodForError = loginMethods.stream()
|
||||
.filter(loginMethod -> loginMethod.superTokensUserId.equals(userid))
|
||||
.findFirst().get();
|
||||
String message = "E005: A user with thirdPartyId " + loginMethodForError.thirdPartyId
|
||||
+ " and thirdPartyUserId " + loginMethodForError.thirdPartyUserId
|
||||
+ " already exists in thirdparty loginMethod.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E004: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<? extends ImportUserBase> processEmailPasswordLoginMethods(Main main, Storage storage, List<LoginMethod> loginMethods,
|
||||
AppIdentifier appIdentifier)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
|
||||
//prepare data for batch import
|
||||
List<EmailPasswordImportUser> usersToImport = new ArrayList<>();
|
||||
for(LoginMethod emailPasswordLoginMethod : loginMethods) {
|
||||
|
||||
TenantIdentifier tenantIdentifierForLoginMethod = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), emailPasswordLoginMethod.tenantIds.get(0)); // the cron runs per app. The app stays the same, the tenant can change
|
||||
|
||||
String passwordHash = emailPasswordLoginMethod.passwordHash;
|
||||
if (passwordHash == null && emailPasswordLoginMethod.plainTextPassword != null) {
|
||||
passwordHash = PasswordHashing.getInstance(main)
|
||||
.createHashWithSalt(tenantIdentifierForLoginMethod.toAppIdentifier(), emailPasswordLoginMethod.plainTextPassword);
|
||||
}
|
||||
emailPasswordLoginMethod.passwordHash = passwordHash;
|
||||
usersToImport.add(new EmailPasswordImportUser(emailPasswordLoginMethod.superTokensUserId, emailPasswordLoginMethod.email,
|
||||
emailPasswordLoginMethod.passwordHash, tenantIdentifierForLoginMethod, emailPasswordLoginMethod.timeJoinedInMSSinceEpoch));
|
||||
}
|
||||
|
||||
EmailPassword.createMultipleUsersWithPasswordHash(storage, usersToImport);
|
||||
|
||||
return usersToImport;
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
if(e.getCause() instanceof BulkImportBatchInsertException){
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for(String userid : errorsByPosition.keySet()){
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if(exception instanceof DuplicateEmailException){
|
||||
String message = "E003: A user with email "
|
||||
+ loginMethods.stream().filter(loginMethod -> loginMethod.superTokensUserId.equals(userid))
|
||||
.findFirst().get().email + " already exists in emailpassword loginMethod.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E002: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void associateUserToTenants(Main main, AppIdentifier appIdentifier, Storage storage, LoginMethod lm,
|
||||
String firstTenant) throws StorageTransactionLogicException {
|
||||
for (String tenantId : lm.tenantIds) {
|
||||
try {
|
||||
if (tenantId.equals(firstTenant)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), tenantId);
|
||||
Multitenancy.addUserIdToTenant(main, tenantIdentifier, storage, lm.superTokensUserId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E009: " + e.getMessage()));
|
||||
} catch (StorageQueryException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
} catch (UnknownUserIdException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E010: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but it doesn't exist. This should not happen. Please contact support."));
|
||||
} catch (AnotherPrimaryUserWithEmailAlreadyExistsException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E011: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another primary user with email " + lm.email + " already exists."));
|
||||
} catch (AnotherPrimaryUserWithPhoneNumberAlreadyExistsException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E012: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another primary user with phoneNumber " + lm.phoneNumber + " already exists."));
|
||||
} catch (AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E013: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another primary user with thirdPartyId " + lm.thirdPartyId + " and thirdPartyUserId "
|
||||
+ lm.thirdPartyUserId + " already exists."));
|
||||
} catch (DuplicateEmailException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E014: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another user with email " + lm.email + " already exists."));
|
||||
} catch (DuplicatePhoneNumberException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E015: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another user with phoneNumber " + lm.phoneNumber + " already exists."));
|
||||
} catch (DuplicateThirdPartyUserException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E016: " + "We tried to add the userId "
|
||||
+ lm.getSuperTokenOrExternalUserId() + " to the tenantId " + tenantId
|
||||
+ " but another user with thirdPartyId " + lm.thirdPartyId + " and thirdPartyUserId "
|
||||
+ lm.thirdPartyUserId + " already exists."));
|
||||
} catch (FeatureNotEnabledException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E017: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createPrimaryUsersAndLinkAccounts(Main main,
|
||||
AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users)
|
||||
throws StorageTransactionLogicException, StorageQueryException, FeatureNotEnabledException,
|
||||
TenantOrAppNotFoundException {
|
||||
|
||||
List<BulkImportUser> usersForAccountLinking = filterUsersInNeedOfAccountLinking(users);
|
||||
|
||||
if(usersForAccountLinking.isEmpty()){
|
||||
return;
|
||||
}
|
||||
AuthRecipe.CreatePrimaryUsersResultHolder resultHolder;
|
||||
try {
|
||||
resultHolder = AuthRecipe.createPrimaryUsersForBulkImport(main, appIdentifier, storage, usersForAccountLinking);
|
||||
} catch (StorageQueryException e) {
|
||||
if(e.getCause() instanceof BulkImportBatchInsertException){
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof UnknownUserIdException) {
|
||||
String message = "E020: We tried to create the primary user for the userId "
|
||||
+ userid
|
||||
+ " but it doesn't exist. This should not happen. Please contact support.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) {
|
||||
String message = "E021: We tried to create the primary user for the userId "
|
||||
+ userid
|
||||
+ " but it is already linked with another primary user.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) {
|
||||
String message = "E022: We tried to create the primary user for the userId "
|
||||
+ userid
|
||||
+ " but the account info is already associated with another primary user.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E018: " + e.getMessage()));
|
||||
} catch (FeatureNotEnabledException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E019: " + e.getMessage()));
|
||||
}
|
||||
if(resultHolder != null && resultHolder.usersWithSameExtraData != null){
|
||||
linkAccountsForMultipleUser(main, appIdentifier, storage, usersForAccountLinking, resultHolder.usersWithSameExtraData);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<BulkImportUser> filterUsersInNeedOfAccountLinking(List<BulkImportUser> allUsers) {
|
||||
if (allUsers == null || allUsers.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return allUsers.stream().filter(bulkImportUser -> bulkImportUser.loginMethods.stream()
|
||||
.anyMatch(loginMethod -> loginMethod.isPrimary) || bulkImportUser.loginMethods.size() > 1)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static void linkAccountsForMultipleUser(Main main, AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users, List<AuthRecipeUserInfo> allUsersWithSameExtraData)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
AuthRecipe.linkMultipleAccountsForBulkImport(main, appIdentifier, storage,
|
||||
users, allUsersWithSameExtraData);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E023: " + e.getMessage()));
|
||||
} catch (FeatureNotEnabledException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E024: " + e.getMessage()));
|
||||
} catch (StorageQueryException e) {
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, String> recipeUserIdByPrimaryUserId = BulkImportUserUtils.collectRecipeIdsToPrimaryIds(users);
|
||||
|
||||
Map<String, Exception> errorByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userId : errorByPosition.keySet()) {
|
||||
Exception currentException = errorByPosition.get(userId);
|
||||
String recipeUID = recipeUserIdByPrimaryUserId.get(userId);
|
||||
if (currentException instanceof UnknownUserIdException) {
|
||||
String message = "E025: We tried to link the userId " + recipeUID
|
||||
+ " to the primary userId " + userId
|
||||
+ " but it doesn't exist.";
|
||||
errorByPosition.put(userId, new Exception(message));
|
||||
} else if (currentException instanceof InputUserIdIsNotAPrimaryUserException) {
|
||||
String message = "E026: We tried to link the userId " + recipeUID
|
||||
+ " to the primary userId " + userId
|
||||
+ " but it is not a primary user.";
|
||||
errorByPosition.put(userId, new Exception(message));
|
||||
} else if (currentException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) {
|
||||
String message = "E027: We tried to link the userId " + userId
|
||||
+ " to the primary userId " + recipeUID
|
||||
+ " but the account info is already associated with another primary user.";
|
||||
errorByPosition.put(userId, new Exception(message));
|
||||
} else if (currentException instanceof RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) {
|
||||
String message = "E028: We tried to link the userId " + recipeUID
|
||||
+ " to the primary userId " + userId
|
||||
+ " but it is already linked with another primary user.";
|
||||
errorByPosition.put(userId, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("link accounts translated", errorByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createMultipleUserIdMapping(AppIdentifier appIdentifier,
|
||||
List<BulkImportUser> users, Storage[] storages) throws StorageTransactionLogicException {
|
||||
Map<String, String> superTokensUserIdToExternalUserId = new HashMap<>();
|
||||
for(BulkImportUser user: users) {
|
||||
if(user.externalUserId != null) {
|
||||
LoginMethod primaryLoginMethod = BulkImportUserUtils.getPrimaryLoginMethod(user);
|
||||
superTokensUserIdToExternalUserId.put(primaryLoginMethod.superTokensUserId, user.externalUserId);
|
||||
primaryLoginMethod.externalUserId = user.externalUserId;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if(!superTokensUserIdToExternalUserId.isEmpty()) {
|
||||
List<UserIdMapping.UserIdBulkMappingResult> mappingResults = UserIdMapping.createMultipleUserIdMappings(
|
||||
appIdentifier, storages,
|
||||
superTokensUserIdToExternalUserId,
|
||||
false, true);
|
||||
}
|
||||
} catch (StorageQueryException e) {
|
||||
if(e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof ServletException) {
|
||||
String message = "E030: " + e.getMessage();
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof UserIdMappingAlreadyExistsException) {
|
||||
String message = "E031: A user with externalId " + superTokensUserIdToExternalUserId.get(userid) + " already exists";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof UnknownSuperTokensUserIdException) {
|
||||
String message = "E032: We tried to create the externalUserId mapping for the superTokenUserId "
|
||||
+ userid
|
||||
+ " but it doesn't exist. This should not happen. Please contact support.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createMultipleUserMetadata(AppIdentifier appIdentifier, Storage storage, List<BulkImportUser> users)
|
||||
throws StorageTransactionLogicException {
|
||||
|
||||
Map<String, JsonObject> usersMetadata = new HashMap<>();
|
||||
for(BulkImportUser user: users) {
|
||||
if (user.userMetadata != null) {
|
||||
usersMetadata.put(BulkImportUserUtils.getPrimaryLoginMethod(user).getSuperTokenOrExternalUserId(), user.userMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if(!usersMetadata.isEmpty()) {
|
||||
UserMetadata.updateMultipleUsersMetadata(appIdentifier, storage, usersMetadata);
|
||||
}
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E040: " + e.getMessage()));
|
||||
} catch (StorageQueryException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void createMultipleUserRoles(Main main, AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users) throws StorageTransactionLogicException {
|
||||
Map<TenantIdentifier, Map<String, List<String>>> rolesToUserByTenant = gatherRolesForUsersByTenant(appIdentifier, users);
|
||||
try {
|
||||
if(!rolesToUserByTenant.isEmpty()){
|
||||
UserRoles.addMultipleRolesToMultipleUsers(main, appIdentifier, storage, rolesToUserByTenant);
|
||||
}
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E033: " + e.getMessage()));
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
if(e.actualException instanceof BulkImportBatchInsertException){
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof UnknownRoleException) {
|
||||
String message = "E034: Role does not exist! You need to pre-create the role before " +
|
||||
"assigning it to the user.";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(new BulkImportBatchInsertException("roles errors translated", errorsByPosition));
|
||||
} else {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Map<TenantIdentifier, Map<String, List<String>>> gatherRolesForUsersByTenant(AppIdentifier appIdentifier, List<BulkImportUser> users) {
|
||||
Map<TenantIdentifier, Map<String, List<String>>> rolesToUserByTenant = new HashMap<>();
|
||||
for (BulkImportUser user : users) {
|
||||
if (user.userRoles != null) {
|
||||
for (UserRole userRole : user.userRoles) {
|
||||
for (String tenantId : userRole.tenantIds) {
|
||||
TenantIdentifier tenantIdentifier = new TenantIdentifier(
|
||||
appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(),
|
||||
tenantId);
|
||||
if(!rolesToUserByTenant.containsKey(tenantIdentifier)){
|
||||
|
||||
rolesToUserByTenant.put(tenantIdentifier, new HashMap<>());
|
||||
}
|
||||
String userIdToUse = user.externalUserId != null ?
|
||||
user.externalUserId : user.id;
|
||||
if(!rolesToUserByTenant.get(tenantIdentifier).containsKey(userIdToUse)){
|
||||
rolesToUserByTenant.get(tenantIdentifier).put(userIdToUse, new ArrayList<>());
|
||||
}
|
||||
rolesToUserByTenant.get(tenantIdentifier).get(userIdToUse).add(userRole.role);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rolesToUserByTenant;
|
||||
}
|
||||
|
||||
public static void verifyMultipleEmailForAllLoginMethods(AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users)
|
||||
throws StorageTransactionLogicException {
|
||||
|
||||
Map<String, String> emailToUserId = collectVerifiedEmailAddressesByUserIds(users);
|
||||
try {
|
||||
verifyCollectedEmailAddressesForUsers(appIdentifier, storage, emailToUserId);
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition =
|
||||
((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof DuplicateEmailException) {
|
||||
String message =
|
||||
"E043: Email " + errorsByPosition.get(userid) + " is already verified for the user";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof NullPointerException) {
|
||||
String message = "E044: null email address was found for the userId " + userid +
|
||||
" while verifying the email";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyCollectedEmailAddressesForUsers(AppIdentifier appIdentifier, Storage storage,
|
||||
Map<String, String> emailToUserId)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
if(!emailToUserId.isEmpty()) {
|
||||
EmailVerificationSQLStorage emailVerificationSQLStorage = StorageUtils
|
||||
.getEmailVerificationStorage(storage);
|
||||
emailVerificationSQLStorage.startTransaction(con -> {
|
||||
emailVerificationSQLStorage
|
||||
.updateMultipleIsEmailVerified_Transaction(appIdentifier, con,
|
||||
emailToUserId, true); //only the verified email addresses are expected to be in the map
|
||||
|
||||
emailVerificationSQLStorage.commitTransaction(con);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, String> collectVerifiedEmailAddressesByUserIds(List<BulkImportUser> users) {
|
||||
Map<String, String> emailToUserId = new LinkedHashMap<>();
|
||||
for (BulkImportUser user : users) {
|
||||
for (LoginMethod lm : user.loginMethods) {
|
||||
//we skip passwordless` 'null' email addresses
|
||||
if (lm.isVerified && !(lm.recipeId.equals("passwordless") && lm.email == null)) {
|
||||
//collect the verified email addresses for the userId
|
||||
emailToUserId.put(lm.getSuperTokenOrExternalUserId(), lm.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
return emailToUserId;
|
||||
}
|
||||
|
||||
public static void createMultipleTotpDevices(Main main, AppIdentifier appIdentifier,
|
||||
Storage storage, List<BulkImportUser> users)
|
||||
throws StorageTransactionLogicException {
|
||||
List<TOTPDevice> devices = new ArrayList<>();
|
||||
for (BulkImportUser user : users) {
|
||||
if (user.totpDevices != null) {
|
||||
for(TotpDevice device : user.totpDevices){
|
||||
TOTPDevice totpDevice = new TOTPDevice(BulkImportUserUtils.getPrimaryLoginMethod(user).getSuperTokenOrExternalUserId(),
|
||||
device.deviceName, device.secretKey, device.period, device.skew, true,
|
||||
System.currentTimeMillis());
|
||||
devices.add(totpDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
if(!devices.isEmpty()){
|
||||
Totp.createDevices(main, appIdentifier, storage, devices);
|
||||
}
|
||||
} catch (StorageQueryException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E036: " + e.getMessage()));
|
||||
} catch (FeatureNotEnabledException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E037: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static synchronized Storage getBulkImportProxyStorage(Main main, TenantIdentifier tenantIdentifier)
|
||||
throws InvalidConfigException, IOException, TenantOrAppNotFoundException, DbInitException {
|
||||
String userPoolId = StorageLayer.getStorage(tenantIdentifier, main).getUserPoolId();
|
||||
if (userPoolToStorageMap.containsKey(userPoolId)) {
|
||||
return userPoolToStorageMap.get(userPoolId);
|
||||
}
|
||||
|
||||
TenantConfig[] allTenants = Multitenancy.getAllTenants(main);
|
||||
|
||||
Map<ResourceDistributor.KeyClass, JsonObject> normalisedConfigs = Config.getNormalisedConfigsForAllTenants(
|
||||
allTenants,
|
||||
Config.getBaseConfigAsJsonObject(main));
|
||||
|
||||
for (ResourceDistributor.KeyClass key : normalisedConfigs.keySet()) {
|
||||
if (key.getTenantIdentifier().equals(tenantIdentifier)) {
|
||||
SQLStorage bulkImportProxyStorage = (SQLStorage) StorageLayer.getNewBulkImportProxyStorageInstance(main,
|
||||
normalisedConfigs.get(key), tenantIdentifier, true);
|
||||
|
||||
userPoolToStorageMap.put(userPoolId, bulkImportProxyStorage);
|
||||
bulkImportProxyStorage.initStorage(false, new ArrayList<>());
|
||||
return bulkImportProxyStorage;
|
||||
}
|
||||
}
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
|
||||
private static Storage[] getAllProxyStoragesForApp(Main main, AppIdentifier appIdentifier)
|
||||
throws StorageTransactionLogicException {
|
||||
|
||||
try {
|
||||
List<Storage> allProxyStorages = new ArrayList<>();
|
||||
|
||||
TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main);
|
||||
for (TenantConfig tenantConfig : tenantConfigs) {
|
||||
allProxyStorages.add(getBulkImportProxyStorage(main, tenantConfig.tenantIdentifier));
|
||||
}
|
||||
return allProxyStorages.toArray(new Storage[0]);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E039: " + e.getMessage()));
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new StorageTransactionLogicException(new InvalidConfigException("E040: " + e.getMessage()));
|
||||
} catch (DbInitException e) {
|
||||
throw new StorageTransactionLogicException(new DbInitException("E041: " + e.getMessage()));
|
||||
} catch (IOException e) {
|
||||
throw new StorageTransactionLogicException(new IOException("E042: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void closeAllProxyStorages() throws StorageQueryException {
|
||||
for (SQLStorage storage : userPoolToStorageMap.values()) {
|
||||
storage.closeConnectionForBulkImportProxyStorage();
|
||||
storage.close();
|
||||
}
|
||||
userPoolToStorageMap.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +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.bulkimport;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
|
||||
public class BulkImportUserPaginationContainer {
|
||||
public final List<BulkImportUser> users;
|
||||
public final String nextPaginationToken;
|
||||
|
||||
public BulkImportUserPaginationContainer(@Nonnull List<BulkImportUser> users, @Nullable String nextPaginationToken) {
|
||||
this.users = users;
|
||||
this.nextPaginationToken = nextPaginationToken;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +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.bulkimport;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class BulkImportUserPaginationToken {
|
||||
public final String bulkImportUserId;
|
||||
public final long createdAt;
|
||||
|
||||
public BulkImportUserPaginationToken(String bulkImportUserId, long createdAt) {
|
||||
this.bulkImportUserId = bulkImportUserId;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException {
|
||||
try {
|
||||
String decodedPaginationToken = new String(Base64.getDecoder().decode(token));
|
||||
String[] splitDecodedToken = decodedPaginationToken.split(";");
|
||||
if (splitDecodedToken.length != 2) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
String bulkImportUserId = splitDecodedToken[0];
|
||||
long createdAt = Long.parseLong(splitDecodedToken[1]);
|
||||
return new BulkImportUserPaginationToken(bulkImportUserId, createdAt);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
}
|
||||
|
||||
public String generateToken() {
|
||||
return new String(Base64.getEncoder().encode((this.bulkImportUserId + ";" + this.createdAt).getBytes()));
|
||||
}
|
||||
|
||||
public static class InvalidTokenException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 6289026174830695478L;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,654 +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.bulkimport;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.emailpassword.PasswordHashingUtils;
|
||||
import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlag;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.LoginMethod;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.TotpDevice;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser.UserRole;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.utils.JsonValidatorUtils.ValueType;
|
||||
import io.supertokens.utils.Utils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static io.supertokens.utils.JsonValidatorUtils.parseAndValidateFieldType;
|
||||
import static io.supertokens.utils.JsonValidatorUtils.validateJsonFieldType;
|
||||
|
||||
public class BulkImportUserUtils {
|
||||
private String[] allUserRoles;
|
||||
private Set<String> allExternalUserIds;
|
||||
|
||||
public BulkImportUserUtils(String[] allUserRoles) {
|
||||
this.allUserRoles = allUserRoles;
|
||||
this.allExternalUserIds = new HashSet<>();
|
||||
}
|
||||
|
||||
public BulkImportUser createBulkImportUserFromJSON(Main main, AppIdentifier appIdentifier, JsonObject userData, IDMode idMode)
|
||||
throws InvalidBulkImportDataException, StorageQueryException, TenantOrAppNotFoundException {
|
||||
List<String> errors = new ArrayList<>();
|
||||
|
||||
String externalUserId = parseAndValidateFieldType(userData, "externalUserId", ValueType.STRING, false,
|
||||
String.class,
|
||||
errors, ".");
|
||||
JsonObject userMetadata = parseAndValidateFieldType(userData, "userMetadata", ValueType.OBJECT, false,
|
||||
JsonObject.class, errors, ".");
|
||||
List<UserRole> userRoles = getParsedUserRoles(main, appIdentifier, userData, errors);
|
||||
List<TotpDevice> totpDevices = getParsedTotpDevices(main, appIdentifier, userData, errors);
|
||||
List<LoginMethod> loginMethods = getParsedLoginMethods(main, appIdentifier, userData, errors, idMode);
|
||||
|
||||
externalUserId = validateAndNormaliseExternalUserId(externalUserId, errors);
|
||||
|
||||
validateTenantIdsForRoleAndLoginMethods(main, appIdentifier, userRoles, loginMethods, errors);
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new InvalidBulkImportDataException(errors);
|
||||
}
|
||||
String id = getPrimaryLoginMethod(loginMethods).superTokensUserId;
|
||||
return new BulkImportUser(id, externalUserId, userMetadata, userRoles, totpDevices, loginMethods);
|
||||
}
|
||||
|
||||
private List<UserRole> getParsedUserRoles(Main main, AppIdentifier appIdentifier, JsonObject userData,
|
||||
List<String> errors) throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonArray jsonUserRoles = parseAndValidateFieldType(userData, "userRoles", ValueType.ARRAY_OF_OBJECT, false,
|
||||
JsonArray.class, errors, ".");
|
||||
|
||||
if (jsonUserRoles == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<UserRole> userRoles = new ArrayList<>();
|
||||
|
||||
for (JsonElement jsonUserRoleEl : jsonUserRoles) {
|
||||
JsonObject jsonUserRole = jsonUserRoleEl.getAsJsonObject();
|
||||
|
||||
String role = parseAndValidateFieldType(jsonUserRole, "role", ValueType.STRING, true, String.class, errors,
|
||||
" for a user role.");
|
||||
JsonArray jsonTenantIds = parseAndValidateFieldType(jsonUserRole, "tenantIds", ValueType.ARRAY_OF_STRING,
|
||||
true, JsonArray.class, errors, " for a user role.");
|
||||
|
||||
role = validateAndNormaliseUserRole(role, errors);
|
||||
List<String> normalisedTenantIds = validateAndNormaliseTenantIds(main, appIdentifier, jsonTenantIds, errors,
|
||||
" for a user role.");
|
||||
|
||||
if (role != null && normalisedTenantIds != null) {
|
||||
userRoles.add(new UserRole(role, normalisedTenantIds));
|
||||
}
|
||||
}
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
private List<TotpDevice> getParsedTotpDevices(Main main, AppIdentifier appIdentifier, JsonObject userData,
|
||||
List<String> errors) throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonArray jsonTotpDevices = parseAndValidateFieldType(userData, "totpDevices", ValueType.ARRAY_OF_OBJECT, false,
|
||||
JsonArray.class, errors, ".");
|
||||
|
||||
if (jsonTotpDevices == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
|
||||
.noneMatch(t -> t == EE_FEATURES.MFA)) {
|
||||
errors.add("MFA must be enabled to import totp devices.");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<TotpDevice> totpDevices = new ArrayList<>();
|
||||
for (JsonElement jsonTotpDeviceEl : jsonTotpDevices) {
|
||||
JsonObject jsonTotpDevice = jsonTotpDeviceEl.getAsJsonObject();
|
||||
|
||||
String secretKey = parseAndValidateFieldType(jsonTotpDevice, "secretKey", ValueType.STRING, true,
|
||||
String.class, errors, " for a totp device.");
|
||||
Integer period = parseAndValidateFieldType(jsonTotpDevice, "period", ValueType.INTEGER, false,
|
||||
Integer.class, errors, " for a totp device.");
|
||||
Integer skew = parseAndValidateFieldType(jsonTotpDevice, "skew", ValueType.INTEGER, false, Integer.class,
|
||||
errors, " for a totp device.");
|
||||
String deviceName = parseAndValidateFieldType(jsonTotpDevice, "deviceName", ValueType.STRING, false,
|
||||
String.class, errors, " for a totp device.");
|
||||
|
||||
secretKey = validateAndNormaliseTotpSecretKey(secretKey, errors);
|
||||
period = validateAndNormaliseTotpPeriod(period, errors);
|
||||
skew = validateAndNormaliseTotpSkew(skew, errors);
|
||||
deviceName = validateAndNormaliseTotpDeviceName(deviceName, errors);
|
||||
|
||||
if (secretKey != null && period != null && skew != null) {
|
||||
totpDevices.add(new TotpDevice(secretKey, period, skew, deviceName));
|
||||
}
|
||||
}
|
||||
return totpDevices;
|
||||
}
|
||||
|
||||
private List<LoginMethod> getParsedLoginMethods(Main main, AppIdentifier appIdentifier, JsonObject userData,
|
||||
List<String> errors, IDMode idMode)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonArray jsonLoginMethods = parseAndValidateFieldType(userData, "loginMethods", ValueType.ARRAY_OF_OBJECT,
|
||||
true, JsonArray.class, errors, ".");
|
||||
|
||||
if (jsonLoginMethods == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (jsonLoginMethods.size() == 0) {
|
||||
errors.add("At least one loginMethod is required.");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (jsonLoginMethods.size() > 1) {
|
||||
if (!Utils.isAccountLinkingEnabled(main, appIdentifier)) {
|
||||
errors.add("Account linking must be enabled to import multiple loginMethods.");
|
||||
}
|
||||
}
|
||||
|
||||
validateAndNormaliseIsPrimaryField(jsonLoginMethods, errors);
|
||||
|
||||
List<LoginMethod> loginMethods = new ArrayList<>();
|
||||
|
||||
for (JsonElement jsonLoginMethod : jsonLoginMethods) {
|
||||
JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject();
|
||||
|
||||
String recipeId = parseAndValidateFieldType(jsonLoginMethodObj, "recipeId", ValueType.STRING, true,
|
||||
String.class, errors, " for a loginMethod.");
|
||||
JsonArray tenantIds = parseAndValidateFieldType(jsonLoginMethodObj, "tenantIds", ValueType.ARRAY_OF_STRING,
|
||||
false, JsonArray.class, errors, " for a loginMethod.");
|
||||
Boolean isVerified = parseAndValidateFieldType(jsonLoginMethodObj, "isVerified", ValueType.BOOLEAN, false,
|
||||
Boolean.class, errors, " for a loginMethod.");
|
||||
Boolean isPrimary = parseAndValidateFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN, false,
|
||||
Boolean.class, errors, " for a loginMethod.");
|
||||
Long timeJoined = parseAndValidateFieldType(jsonLoginMethodObj, "timeJoinedInMSSinceEpoch", ValueType.LONG,
|
||||
false, Long.class, errors, " for a loginMethod");
|
||||
|
||||
|
||||
recipeId = validateAndNormaliseRecipeId(recipeId, errors);
|
||||
List<String> normalisedTenantIds = validateAndNormaliseTenantIds(main, appIdentifier, tenantIds, errors,
|
||||
" for " + recipeId + " recipe.");
|
||||
isPrimary = validateAndNormaliseIsPrimary(isPrimary);
|
||||
isVerified = validateAndNormaliseIsVerified(isVerified);
|
||||
|
||||
long timeJoinedInMSSinceEpoch = validateAndNormaliseTimeJoined(timeJoined, errors);
|
||||
|
||||
String supertokensUserId = switch (idMode) {
|
||||
case READ_STORED -> parseAndValidateFieldType(jsonLoginMethodObj, "superTokensUserId", ValueType.STRING,
|
||||
true, String.class, errors, " for a loginMethod");
|
||||
case GENERATE -> Utils.getUUID();
|
||||
};
|
||||
|
||||
if ("emailpassword".equals(recipeId)) {
|
||||
String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true,
|
||||
String.class, errors, " for an emailpassword recipe.");
|
||||
String passwordHash = parseAndValidateFieldType(jsonLoginMethodObj, "passwordHash", ValueType.STRING,
|
||||
false, String.class, errors, " for an emailpassword recipe.");
|
||||
String hashingAlgorithm = parseAndValidateFieldType(jsonLoginMethodObj, "hashingAlgorithm",
|
||||
ValueType.STRING, false, String.class, errors, " for an emailpassword recipe.");
|
||||
String plainTextPassword = parseAndValidateFieldType(jsonLoginMethodObj, "plainTextPassword",
|
||||
ValueType.STRING, false, String.class, errors, " for an emailpassword recipe.");
|
||||
|
||||
if ((passwordHash == null || hashingAlgorithm == null) && plainTextPassword == null) {
|
||||
errors.add("Either (passwordHash, hashingAlgorithm) or plainTextPassword is required for an emailpassword recipe.");
|
||||
}
|
||||
|
||||
email = validateAndNormaliseEmail(email, errors);
|
||||
CoreConfig.PASSWORD_HASHING_ALG normalisedHashingAlgorithm = validateAndNormaliseHashingAlgorithm(
|
||||
hashingAlgorithm, errors);
|
||||
hashingAlgorithm = normalisedHashingAlgorithm != null ? normalisedHashingAlgorithm.toString()
|
||||
: hashingAlgorithm;
|
||||
passwordHash = validateAndNormalisePasswordHash(main, appIdentifier, normalisedHashingAlgorithm,
|
||||
passwordHash, errors);
|
||||
|
||||
loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary,
|
||||
timeJoinedInMSSinceEpoch, email, passwordHash, hashingAlgorithm, plainTextPassword,
|
||||
null, null, null, supertokensUserId));
|
||||
} else if ("thirdparty".equals(recipeId)) {
|
||||
String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, true,
|
||||
String.class, errors, " for a thirdparty recipe.");
|
||||
String thirdPartyId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyId", ValueType.STRING,
|
||||
true, String.class, errors, " for a thirdparty recipe.");
|
||||
String thirdPartyUserId = parseAndValidateFieldType(jsonLoginMethodObj, "thirdPartyUserId",
|
||||
ValueType.STRING, true, String.class, errors, " for a thirdparty recipe.");
|
||||
|
||||
email = validateAndNormaliseEmail(email, errors);
|
||||
thirdPartyId = validateAndNormaliseThirdPartyId(thirdPartyId, errors);
|
||||
thirdPartyUserId = validateAndNormaliseThirdPartyUserId(thirdPartyUserId, errors);
|
||||
|
||||
loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary,
|
||||
timeJoinedInMSSinceEpoch, email, null, null, null,
|
||||
thirdPartyId, thirdPartyUserId, null, supertokensUserId));
|
||||
} else if ("passwordless".equals(recipeId)) {
|
||||
String email = parseAndValidateFieldType(jsonLoginMethodObj, "email", ValueType.STRING, false,
|
||||
String.class, errors, " for a passwordless recipe.");
|
||||
String phoneNumber = parseAndValidateFieldType(jsonLoginMethodObj, "phoneNumber", ValueType.STRING,
|
||||
false, String.class, errors, " for a passwordless recipe.");
|
||||
|
||||
email = validateAndNormaliseEmail(email, errors);
|
||||
phoneNumber = validateAndNormalisePhoneNumber(phoneNumber, errors);
|
||||
|
||||
if (email == null && phoneNumber == null) {
|
||||
errors.add("Either email or phoneNumber is required for a passwordless recipe.");
|
||||
}
|
||||
|
||||
loginMethods.add(new LoginMethod(normalisedTenantIds, recipeId, isVerified, isPrimary,
|
||||
timeJoinedInMSSinceEpoch, email, null, null, null,
|
||||
null, null, phoneNumber, supertokensUserId));
|
||||
}
|
||||
}
|
||||
return loginMethods;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseExternalUserId(String externalUserId, List<String> errors) {
|
||||
if (externalUserId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (externalUserId.length() > 128) {
|
||||
errors.add("externalUserId " + externalUserId + " is too long. Max length is 128.");
|
||||
}
|
||||
|
||||
if (!allExternalUserIds.add(externalUserId)) {
|
||||
errors.add("externalUserId " + externalUserId + " is not unique. It is already used by another user.");
|
||||
}
|
||||
|
||||
// We just trim the externalUserId as per the UpdateExternalUserIdInfoAPI.java
|
||||
return externalUserId.trim();
|
||||
}
|
||||
|
||||
private String validateAndNormaliseUserRole(String role, List<String> errors) {
|
||||
if (role.length() > 255) {
|
||||
errors.add("role " + role + " is too long. Max length is 255.");
|
||||
}
|
||||
|
||||
// We just trim the role as per the CreateRoleAPI.java
|
||||
String normalisedRole = role.trim();
|
||||
|
||||
if (!Arrays.asList(allUserRoles).contains(normalisedRole)) {
|
||||
errors.add("Role " + normalisedRole + " does not exist.");
|
||||
}
|
||||
|
||||
return normalisedRole;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseTotpSecretKey(String secretKey, List<String> errors) {
|
||||
if (secretKey == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (secretKey.length() > 256) {
|
||||
errors.add("TOTP secretKey " + secretKey + " is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We don't perform any normalisation on the secretKey in ImportTotpDeviceAPI.java
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
private Integer validateAndNormaliseTotpPeriod(Integer period, List<String> errors) {
|
||||
// We default to 30 if period is null
|
||||
if (period == null) {
|
||||
return 30;
|
||||
}
|
||||
|
||||
if (period.intValue() < 1) {
|
||||
errors.add("period should be > 0 for a totp device.");
|
||||
return null;
|
||||
}
|
||||
return period;
|
||||
}
|
||||
|
||||
private Integer validateAndNormaliseTotpSkew(Integer skew, List<String> errors) {
|
||||
// We default to 1 if skew is null
|
||||
if (skew == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (skew.intValue() < 0) {
|
||||
errors.add("skew should be >= 0 for a totp device.");
|
||||
return null;
|
||||
}
|
||||
return skew;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseTotpDeviceName(String deviceName, List<String> errors) {
|
||||
if (deviceName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (deviceName.length() > 256) {
|
||||
errors.add("TOTP deviceName " + deviceName + " is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We normalise the deviceName as per the ImportTotpDeviceAPI.java
|
||||
return deviceName.trim();
|
||||
}
|
||||
|
||||
private void validateAndNormaliseIsPrimaryField(JsonArray jsonLoginMethods, List<String> errors) {
|
||||
// We are validating that only one loginMethod has isPrimary as true
|
||||
boolean hasPrimaryLoginMethod = false;
|
||||
for (JsonElement jsonLoginMethod : jsonLoginMethods) {
|
||||
JsonObject jsonLoginMethodObj = jsonLoginMethod.getAsJsonObject();
|
||||
if (validateJsonFieldType(jsonLoginMethodObj, "isPrimary", ValueType.BOOLEAN)) {
|
||||
if (jsonLoginMethodObj.get("isPrimary").getAsBoolean()) {
|
||||
if (hasPrimaryLoginMethod) {
|
||||
errors.add("No two loginMethods can have isPrimary as true.");
|
||||
}
|
||||
hasPrimaryLoginMethod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String validateAndNormaliseRecipeId(String recipeId, List<String> errors) {
|
||||
if (recipeId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We don't perform any normalisation on the recipeId after reading it from request header.
|
||||
// We will validate it as is.
|
||||
if (!Arrays.asList("emailpassword", "thirdparty", "passwordless").contains(recipeId)) {
|
||||
errors.add("Invalid recipeId for loginMethod. Pass one of emailpassword, thirdparty or, passwordless!");
|
||||
}
|
||||
return recipeId;
|
||||
}
|
||||
|
||||
private List<String> validateAndNormaliseTenantIds(Main main, AppIdentifier appIdentifier,
|
||||
JsonArray tenantIds, List<String> errors, String errorSuffix)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
if (tenantIds == null) {
|
||||
return List.of(TenantIdentifier.DEFAULT_TENANT_ID); // Default to DEFAULT_TENANT_ID ("public")
|
||||
}
|
||||
|
||||
List<String> normalisedTenantIds = new ArrayList<>();
|
||||
|
||||
for (JsonElement tenantIdEl : tenantIds) {
|
||||
String tenantId = tenantIdEl.getAsString();
|
||||
tenantId = validateAndNormaliseTenantId(main, appIdentifier, tenantId, errors, errorSuffix);
|
||||
|
||||
if (tenantId != null) {
|
||||
normalisedTenantIds.add(tenantId);
|
||||
}
|
||||
}
|
||||
return normalisedTenantIds;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseTenantId(Main main, AppIdentifier appIdentifier, String tenantId,
|
||||
List<String> errors, String errorSuffix)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
|
||||
.noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) {
|
||||
errors.add("Multitenancy must be enabled before importing users to a different tenant.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// We make the tenantId lowercase while parsing from the request in WebserverAPI.java
|
||||
String normalisedTenantId = tenantId.trim().toLowerCase();
|
||||
TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main);
|
||||
Set<String> validTenantIds = new HashSet<>();
|
||||
Arrays.stream(allTenantConfigs)
|
||||
.forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId()));
|
||||
|
||||
if (!validTenantIds.contains(normalisedTenantId)) {
|
||||
errors.add("Invalid tenantId: " + tenantId + errorSuffix);
|
||||
return null;
|
||||
}
|
||||
return normalisedTenantId;
|
||||
}
|
||||
|
||||
private Boolean validateAndNormaliseIsPrimary(Boolean isPrimary) {
|
||||
// We set the default value as false
|
||||
return isPrimary == null ? false : isPrimary;
|
||||
}
|
||||
|
||||
private Boolean validateAndNormaliseIsVerified(Boolean isVerified) {
|
||||
// We set the default value as false
|
||||
return isVerified == null ? false : isVerified;
|
||||
}
|
||||
|
||||
private long validateAndNormaliseTimeJoined(Long timeJoined, List<String> errors) {
|
||||
// We default timeJoined to currentTime if it is null
|
||||
if (timeJoined == null) {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (timeJoined > System.currentTimeMillis()) {
|
||||
errors.add("timeJoined cannot be in future for a loginMethod.");
|
||||
}
|
||||
|
||||
if (timeJoined < 0) {
|
||||
errors.add("timeJoined cannot be < 0 for a loginMethod.");
|
||||
}
|
||||
|
||||
return timeJoined.longValue();
|
||||
}
|
||||
|
||||
private String validateAndNormaliseEmail(String email, List<String> errors) {
|
||||
if (email == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (email.length() > 255) {
|
||||
errors.add("email " + email + " is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We normalise the email as per the SignUpAPI.java
|
||||
return Utils.normaliseEmail(email);
|
||||
}
|
||||
|
||||
private CoreConfig.PASSWORD_HASHING_ALG validateAndNormaliseHashingAlgorithm(String hashingAlgorithm,
|
||||
List<String> errors) {
|
||||
if (hashingAlgorithm == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// We trim the hashingAlgorithm and make it uppercase as per the ImportUserWithPasswordHashAPI.java
|
||||
return CoreConfig.PASSWORD_HASHING_ALG.valueOf(hashingAlgorithm.trim().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
errors.add(
|
||||
"Invalid hashingAlgorithm for emailpassword recipe. Pass one of bcrypt, argon2 or, firebase_scrypt!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String validateAndNormalisePasswordHash(Main main, AppIdentifier appIdentifier,
|
||||
CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm, String passwordHash, List<String> errors)
|
||||
throws TenantOrAppNotFoundException {
|
||||
if (hashingAlgorithm == null || passwordHash == null) {
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
if (passwordHash.length() > 256) {
|
||||
errors.add("passwordHash is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We trim the passwordHash and validate it as per ImportUserWithPasswordHashAPI.java
|
||||
passwordHash = passwordHash.trim();
|
||||
|
||||
try {
|
||||
PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(appIdentifier, main, passwordHash,
|
||||
hashingAlgorithm);
|
||||
} catch (UnsupportedPasswordHashingFormatException e) {
|
||||
errors.add(e.getMessage());
|
||||
}
|
||||
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseThirdPartyId(String thirdPartyId, List<String> errors) {
|
||||
if (thirdPartyId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (thirdPartyId.length() > 28) {
|
||||
errors.add("thirdPartyId " + thirdPartyId + " is too long. Max length is 28.");
|
||||
}
|
||||
|
||||
// We don't perform any normalisation on the thirdPartyId in SignInUpAPI.java
|
||||
return thirdPartyId;
|
||||
}
|
||||
|
||||
private String validateAndNormaliseThirdPartyUserId(String thirdPartyUserId, List<String> errors) {
|
||||
if (thirdPartyUserId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (thirdPartyUserId.length() > 256) {
|
||||
errors.add("thirdPartyUserId " + thirdPartyUserId + " is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We don't perform any normalisation on the thirdPartyUserId in SignInUpAPI.java
|
||||
return thirdPartyUserId;
|
||||
}
|
||||
|
||||
private String validateAndNormalisePhoneNumber(String phoneNumber, List<String> errors) {
|
||||
if (phoneNumber == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (phoneNumber.length() > 256) {
|
||||
errors.add("phoneNumber " + phoneNumber + " is too long. Max length is 256.");
|
||||
}
|
||||
|
||||
// We normalise the phoneNumber as per the CreateCodeAPI.java
|
||||
return Utils.normalizeIfPhoneNumber(phoneNumber);
|
||||
}
|
||||
|
||||
private void validateTenantIdsForRoleAndLoginMethods(Main main, AppIdentifier appIdentifier,
|
||||
List<UserRole> userRoles, List<LoginMethod> loginMethods, List<String> errors)
|
||||
throws TenantOrAppNotFoundException {
|
||||
if (loginMethods == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First validate that tenantIds provided for userRoles also exist in the loginMethods
|
||||
if (userRoles != null) {
|
||||
for (UserRole userRole : userRoles) {
|
||||
for (String tenantId : userRole.tenantIds) {
|
||||
if (!tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID) && loginMethods.stream()
|
||||
.noneMatch(loginMethod -> loginMethod.tenantIds.contains(tenantId))) {
|
||||
errors.add("TenantId " + tenantId + " for a user role does not exist in loginMethods.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now validate that all the tenants share the same storage
|
||||
String commonTenantUserPoolId = null;
|
||||
for (LoginMethod loginMethod : loginMethods) {
|
||||
for (String tenantId : loginMethod.tenantIds) {
|
||||
TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), tenantId);
|
||||
Storage storage = StorageLayer.getStorage(tenantIdentifier, main);
|
||||
String tenantUserPoolId = storage.getUserPoolId();
|
||||
|
||||
if (commonTenantUserPoolId == null) {
|
||||
commonTenantUserPoolId = tenantUserPoolId;
|
||||
} else if (!commonTenantUserPoolId.equals(tenantUserPoolId)) {
|
||||
errors.add("All tenants for a user must share the same database for " + loginMethod.recipeId
|
||||
+ " recipe.");
|
||||
break; // Break to avoid adding the same error multiple times for the same loginMethod
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static BulkImportUser.LoginMethod getPrimaryLoginMethod(BulkImportUser user) {
|
||||
return getPrimaryLoginMethod(user.loginMethods);
|
||||
}
|
||||
|
||||
// Returns the primary loginMethod of the user. If no loginMethod is marked as
|
||||
// primary, then the oldest loginMethod is returned.
|
||||
public static BulkImportUser.LoginMethod getPrimaryLoginMethod(List<LoginMethod> loginMethods) {
|
||||
BulkImportUser.LoginMethod oldestLM = loginMethods.get(0);
|
||||
for (BulkImportUser.LoginMethod lm : loginMethods) {
|
||||
if (lm.isPrimary) {
|
||||
return lm;
|
||||
}
|
||||
|
||||
if (lm.timeJoinedInMSSinceEpoch < oldestLM.timeJoinedInMSSinceEpoch) {
|
||||
oldestLM = lm;
|
||||
}
|
||||
}
|
||||
return oldestLM;
|
||||
}
|
||||
|
||||
public enum IDMode {
|
||||
GENERATE,
|
||||
READ_STORED;
|
||||
}
|
||||
|
||||
// Returns a map of recipe user ids -> primary user ids
|
||||
public static Map<String, String> collectRecipeIdsToPrimaryIds(List<BulkImportUser> users) {
|
||||
Map<String, String> recipeUserIdByPrimaryUserId = new HashMap<>();
|
||||
if(users == null){
|
||||
return recipeUserIdByPrimaryUserId;
|
||||
}
|
||||
for(BulkImportUser user: users){
|
||||
LoginMethod primaryLM = BulkImportUserUtils.getPrimaryLoginMethod(user);
|
||||
for (LoginMethod lm : user.loginMethods) {
|
||||
if (lm.getSuperTokenOrExternalUserId().equals(primaryLM.getSuperTokenOrExternalUserId())) {
|
||||
continue;
|
||||
}
|
||||
recipeUserIdByPrimaryUserId.put(lm.getSuperTokenOrExternalUserId(),
|
||||
primaryLM.getSuperTokenOrExternalUserId());
|
||||
}
|
||||
}
|
||||
return recipeUserIdByPrimaryUserId;
|
||||
}
|
||||
|
||||
public static LoginMethod findLoginMethodByRecipeUserId(List<BulkImportUser> users, String recipeUserId) {
|
||||
if(users == null || users.isEmpty() || recipeUserId == null){
|
||||
return null;
|
||||
}
|
||||
for(BulkImportUser user: users) {
|
||||
for (LoginMethod loginMethod : user.loginMethods) {
|
||||
if (recipeUserId.equals(loginMethod.superTokensUserId)) {
|
||||
return loginMethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static BulkImportUser findUserByPrimaryId(List<BulkImportUser> users, String primaryUserId) {
|
||||
if(users == null || users.isEmpty() || primaryUserId == null){
|
||||
return null;
|
||||
}
|
||||
for(BulkImportUser user: users) {
|
||||
if(primaryUserId.equals(user.primaryUserId)){
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +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.bulkimport.exceptions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class InvalidBulkImportDataException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public List<String> errors;
|
||||
|
||||
public InvalidBulkImportDataException(List<String> errors) {
|
||||
super("Data has missing or invalid fields. Please check the errors field for more details.");
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public void addError(String error) {
|
||||
this.errors.add(error);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,8 +52,6 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
|
||||
Object configObj = mapper.readValue(new File(configFilePath), Object.class);
|
||||
JsonObject jsonConfig = new GsonBuilder().serializeNulls().create().toJsonTree(configObj).getAsJsonObject();
|
||||
CoreConfig.updateConfigJsonFromEnv(jsonConfig);
|
||||
StorageLayer.updateConfigJsonFromEnv(main, jsonConfig);
|
||||
CoreConfig config = ConfigMapper.mapConfig(jsonConfig, CoreConfig.class);
|
||||
config.normalizeAndValidate(main, true);
|
||||
this.core = config;
|
||||
|
|
@ -93,20 +91,12 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
// omit them from the output json.
|
||||
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
|
||||
Object obj = yamlReader.readValue(new File(getConfigFilePath(main)), Object.class);
|
||||
JsonObject configJson = new GsonBuilder().serializeNulls().create().toJsonTree(obj).getAsJsonObject();
|
||||
CoreConfig.updateConfigJsonFromEnv(configJson);
|
||||
StorageLayer.updateConfigJsonFromEnv(main, configJson);
|
||||
return configJson;
|
||||
return new GsonBuilder().serializeNulls().create().toJsonTree(obj).getAsJsonObject();
|
||||
}
|
||||
|
||||
private static String getConfigFilePath(Main main) {
|
||||
String configFile = "config.yaml";
|
||||
if (Main.isTesting) {
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
configFile = "config" + workerId + ".yaml";
|
||||
}
|
||||
return CLIOptions.get(main).getConfigFilePath() == null
|
||||
? CLIOptions.get(main).getInstallationPath() + configFile
|
||||
? CLIOptions.get(main).getInstallationPath() + "config.yaml"
|
||||
: CLIOptions.get(main).getConfigFilePath();
|
||||
}
|
||||
|
||||
|
|
@ -128,12 +118,12 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
// At this point, we know that all configs are valid.
|
||||
try {
|
||||
main.getResourceDistributor().withResourceDistributorLock(() -> {
|
||||
Map<ResourceDistributor.KeyClass, ResourceDistributor.SingletonResource> existingResources =
|
||||
main.getResourceDistributor()
|
||||
.getAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
for (ResourceDistributor.KeyClass key : normalisedConfigs.keySet()) {
|
||||
try {
|
||||
try {
|
||||
Map<ResourceDistributor.KeyClass, ResourceDistributor.SingletonResource> existingResources =
|
||||
main.getResourceDistributor()
|
||||
.getAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
for (ResourceDistributor.KeyClass key : normalisedConfigs.keySet()) {
|
||||
ResourceDistributor.SingletonResource resource = existingResources.get(
|
||||
new ResourceDistributor.KeyClass(
|
||||
key.getTenantIdentifier(),
|
||||
|
|
@ -147,16 +137,19 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
main.getResourceDistributor()
|
||||
.setResource(key.getTenantIdentifier(), RESOURCE_KEY,
|
||||
new Config(main, normalisedConfigs.get(key)));
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logging.error(main, key.getTenantIdentifier(), e.getMessage(), false);
|
||||
// continue loading other resources
|
||||
}
|
||||
} catch (InvalidConfigException | IOException e) {
|
||||
throw new ResourceDistributor.FuncException(e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (ResourceDistributor.FuncException e) {
|
||||
throw new IllegalStateException("should never happen", e);
|
||||
if (e.getCause() instanceof InvalidConfigException) {
|
||||
throw (InvalidConfigException) e.getCause();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +308,7 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
@TestOnly
|
||||
public static CoreConfig getConfig(Main main) {
|
||||
try {
|
||||
return getConfig(ResourceDistributor.getAppForTesting(), main);
|
||||
return getConfig(new TenantIdentifier(null, null, null), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,7 @@ public class CoreConfig {
|
|||
"oauth_provider_public_service_url",
|
||||
"oauth_provider_admin_service_url",
|
||||
"oauth_provider_consent_login_base_url",
|
||||
"oauth_provider_url_configured_in_oauth_provider",
|
||||
"saml_legacy_acs_url"
|
||||
"oauth_provider_url_configured_in_oauth_provider"
|
||||
};
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
|
|
@ -76,13 +75,11 @@ public class CoreConfig {
|
|||
@ConfigDescription("The version of the core config.")
|
||||
private int core_config_version = -1;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_VALIDITY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in seconds for how long an access token is valid for. [Default: 3600 (1 hour)]")
|
||||
private long access_token_validity = 3600; // in seconds
|
||||
|
||||
@EnvName("ACCESS_TOKEN_BLACKLISTING")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -91,20 +88,17 @@ public class CoreConfig {
|
|||
"call that requires authentication. (Default: false)")
|
||||
private boolean access_token_blacklisting = false;
|
||||
|
||||
@EnvName("REFRESH_TOKEN_VALIDITY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in mins for how long a refresh token is valid for. [Default: 60 * 2400 (100 days)]")
|
||||
private double refresh_token_validity = 60 * 2400; // in mins
|
||||
|
||||
@EnvName("PASSWORD_RESET_TOKEN_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Time in milliseconds for how long a password reset token / link is valid for. [Default: 3600000 (1 hour)]")
|
||||
private long password_reset_token_lifetime = 3600000; // in MS
|
||||
|
||||
@EnvName("EMAIL_VERIFICATION_TOKEN_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -112,27 +106,23 @@ public class CoreConfig {
|
|||
" 1000 (1 day)]")
|
||||
private long email_verification_token_lifetime = 24 * 3600 * 1000; // in MS
|
||||
|
||||
@EnvName("PASSWORDLESS_MAX_CODE_INPUT_ATTEMPTS")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"The maximum number of code input attempts per login before the user needs to restart. (Default: 5)")
|
||||
private int passwordless_max_code_input_attempts = 5;
|
||||
|
||||
@EnvName("PASSWORDLESS_CODE_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Time in milliseconds for how long a passwordless code is valid for. [Default: 900000 (15 mins)]")
|
||||
private long passwordless_code_lifetime = 900000; // in MS
|
||||
|
||||
@EnvName("TOTP_MAX_ATTEMPTS")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription("The maximum number of invalid TOTP attempts that will trigger rate limiting. (Default: 5)")
|
||||
private int totp_max_attempts = 5;
|
||||
|
||||
@EnvName("TOTP_RATE_LIMIT_COOLDOWN_SEC")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -143,7 +133,6 @@ public class CoreConfig {
|
|||
@IgnoreForAnnotationCheck
|
||||
private final String logDefault = "asdkfahbdfk3kjHS";
|
||||
|
||||
@EnvName("INFO_LOG_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -152,7 +141,6 @@ public class CoreConfig {
|
|||
"directory/logs/info.log)")
|
||||
private String info_log_path = logDefault;
|
||||
|
||||
@EnvName("ERROR_LOG_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -161,7 +149,6 @@ public class CoreConfig {
|
|||
"directory/logs/error.log)")
|
||||
private String error_log_path = logDefault;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_SIGNING_KEY_DYNAMIC")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -169,20 +156,17 @@ public class CoreConfig {
|
|||
" be signed using a static signing key. (Default: true)")
|
||||
private boolean access_token_signing_key_dynamic = true;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_DYNAMIC_SIGNING_KEY_UPDATE_INTERVAL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty("access_token_dynamic_signing_key_update_interval")
|
||||
@JsonAlias({"access_token_dynamic_signing_key_update_interval", "access_token_signing_key_update_interval"})
|
||||
@ConfigDescription("Time in hours for how frequently the dynamic signing key will change. [Default: 168 (1 week)]")
|
||||
private double access_token_dynamic_signing_key_update_interval = 168; // in hours
|
||||
|
||||
@EnvName("SUPERTOKENS_PORT")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("The port at which SuperTokens service runs. (Default: 3567)")
|
||||
private int port = 3567;
|
||||
|
||||
@EnvName("SUPERTOKENS_HOST")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -190,13 +174,11 @@ public class CoreConfig {
|
|||
" address associated with your machine. (Default: localhost)")
|
||||
private String host = "localhost";
|
||||
|
||||
@EnvName("MAX_SERVER_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Sets the max thread pool size for incoming http server requests. (Default: 10)")
|
||||
private int max_server_pool_size = 10;
|
||||
|
||||
@EnvName("API_KEYS")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -206,7 +188,6 @@ public class CoreConfig {
|
|||
"length of 20 chars. (Default: null)")
|
||||
private String api_keys = null;
|
||||
|
||||
@EnvName("DISABLE_TELEMETRY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -214,32 +195,27 @@ public class CoreConfig {
|
|||
"(Default: false)")
|
||||
private boolean disable_telemetry = false;
|
||||
|
||||
@EnvName("PASSWORD_HASHING_ALG")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("The password hashing algorithm to use. Values are \"ARGON2\" | \"BCRYPT\". (Default: BCRYPT)")
|
||||
@EnumProperty({"ARGON2", "BCRYPT"})
|
||||
private String password_hashing_alg = "BCRYPT";
|
||||
|
||||
@EnvName("ARGON2_ITERATIONS")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Number of iterations for argon2 password hashing. (Default: 1)")
|
||||
private int argon2_iterations = 1;
|
||||
|
||||
@EnvName("ARGON2_MEMORY_KB")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Amount of memory in kb for argon2 password hashing. [Default: 87795 (85 mb)]")
|
||||
private int argon2_memory_kb = 87795; // 85 mb
|
||||
|
||||
@EnvName("ARGON2_PARALLELISM")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Amount of parallelism for argon2 password hashing. (Default: 2)")
|
||||
private int argon2_parallelism = 2;
|
||||
|
||||
@EnvName("ARGON2_HASHING_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -247,7 +223,6 @@ public class CoreConfig {
|
|||
"(Default: 1)")
|
||||
private int argon2_hashing_pool_size = 1;
|
||||
|
||||
@EnvName("FIREBASE_PASSWORD_HASHING_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -255,7 +230,6 @@ public class CoreConfig {
|
|||
"(Default: 1)")
|
||||
private int firebase_password_hashing_pool_size = 1;
|
||||
|
||||
@EnvName("BCRYPT_LOG_ROUNDS")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Number of rounds to set for bcrypt password hashing. (Default: 11)")
|
||||
|
|
@ -271,16 +245,13 @@ public class CoreConfig {
|
|||
// # webserver_https_enabled:
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
private boolean webserver_https_enabled = false;
|
||||
|
||||
@EnvName("BASE_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Used to prepend a base path to all APIs when querying the core.")
|
||||
private String base_path = "";
|
||||
|
||||
@EnvName("LOG_LEVEL")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -289,13 +260,11 @@ public class CoreConfig {
|
|||
@EnumProperty({"DEBUG", "INFO", "WARN", "ERROR", "NONE"})
|
||||
private String log_level = "INFO";
|
||||
|
||||
@EnvName("FIREBASE_PASSWORD_HASHING_SIGNER_KEY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("The signer key used for firebase scrypt password hashing. (Default: null)")
|
||||
private String firebase_password_hashing_signer_key = null;
|
||||
|
||||
@EnvName("IP_ALLOW_REGEX")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -303,7 +272,6 @@ public class CoreConfig {
|
|||
"127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1 to allow only localhost to query the core")
|
||||
private String ip_allow_regex = null;
|
||||
|
||||
@EnvName("IP_DENY_REGEX")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -311,7 +279,6 @@ public class CoreConfig {
|
|||
" address.")
|
||||
private String ip_deny_regex = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_PUBLIC_SERVICE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -319,7 +286,6 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to connect to the OAuth provider public service.")
|
||||
private String oauth_provider_public_service_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_ADMIN_SERVICE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -327,7 +293,6 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to connect to the OAuth provider admin service.")
|
||||
private String oauth_provider_admin_service_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_CONSENT_LOGIN_BASE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -335,7 +300,6 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to replace the default consent and login URLs to {apiDomain}.")
|
||||
private String oauth_provider_consent_login_base_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_URL_CONFIGURED_IN_OAUTH_PROVIDER")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -343,14 +307,12 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to parse responses from the oauth provider when the oauth provider's internal address differs from the known public provider address.")
|
||||
private String oauth_provider_url_configured_in_oauth_provider = null;
|
||||
|
||||
@EnvName("OAUTH_CLIENT_SECRET_ENCRYPTION_KEY")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
@ConfigDescription("The encryption key used for saving OAuth client secret on the database.")
|
||||
private String oauth_client_secret_encryption_key = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_SAAS_SECRET")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -360,7 +322,6 @@ public class CoreConfig {
|
|||
"regular api_keys config.")
|
||||
private String supertokens_saas_secret = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_MAX_CDI_VERSION")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -370,7 +331,6 @@ public class CoreConfig {
|
|||
"null)")
|
||||
private String supertokens_max_cdi_version = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_SAAS_LOAD_ONLY_CUD")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -378,73 +338,20 @@ public class CoreConfig {
|
|||
"the database and block all other CUDs from being used from this instance.")
|
||||
private String supertokens_saas_load_only_cud = null;
|
||||
|
||||
@EnvName("SAML_LEGACY_ACS_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, uses this URL as ACS URL for handling legacy SAML clients")
|
||||
@HideFromDashboard
|
||||
private String saml_legacy_acs_url = null;
|
||||
|
||||
@EnvName("SAML_SP_ENTITY_ID")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Service provider's entity ID")
|
||||
private String saml_sp_entity_id = null;
|
||||
|
||||
@EnvName("SAML_CLAIMS_VALIDITY")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Duration for which SAML claims will be valid before it is consumed")
|
||||
private long saml_claims_validity = 300000;
|
||||
|
||||
@EnvName("SAML_RELAY_STATE_VALIDITY")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Duration for which SAML relay state will be valid before it is consumed")
|
||||
private long saml_relay_state_validity = 300000;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private Set<LOG_LEVEL> allowedLogLevels = null;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private boolean isNormalizedAndValid = false;
|
||||
|
||||
@EnvName("BULK_MIGRATION_PARALLELISM")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, the supertokens core will use the specified number of threads to complete the " +
|
||||
"migration of users. (Default: number of available processor cores).")
|
||||
private int bulk_migration_parallelism = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@EnvName("BULK_MIGRATION_BATCH_SIZE")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, the supertokens core will load the specified number of users for migrating in " +
|
||||
"one single batch. (Default: 8000)")
|
||||
private int bulk_migration_batch_size = 8000;
|
||||
|
||||
@EnvName("WEBAUTHN_RECOVER_ACCOUNT_TOKEN_LIFETIME")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in milliseconds for how long a webauthn account recovery token is valid for. [Default: 3600000 (1 hour)]")
|
||||
private long webauthn_recover_account_token_lifetime = 3600000; // in MS;
|
||||
|
||||
@EnvName("OTEL_COLLECTOR_CONNECTION_URI")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"The URL of the OpenTelemetry collector to which the core will send telemetry data. " +
|
||||
"This should be in the format http://<host>:<port> or https://<host>:<port>. (Default: " +
|
||||
"null)")
|
||||
private String otel_collector_connection_uri = null;
|
||||
|
||||
@EnvName("DEADLOCK_LOGGER_ENABLE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Enables or disables the deadlock logger. (Default: false)")
|
||||
private boolean deadlock_logger_enable = false;
|
||||
|
||||
"http://localhost:4317)")
|
||||
private String otel_collector_connection_uri = "http://localhost:4317";
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private static boolean disableOAuthValidationForTest = false;
|
||||
|
||||
|
|
@ -513,10 +420,6 @@ public class CoreConfig {
|
|||
return ip_deny_regex;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return log_level;
|
||||
}
|
||||
|
||||
public Set<LOG_LEVEL> getLogLevels(Main main) {
|
||||
if (allowedLogLevels != null) {
|
||||
return allowedLogLevels;
|
||||
|
|
@ -684,88 +587,16 @@ public class CoreConfig {
|
|||
return webserver_https_enabled;
|
||||
}
|
||||
|
||||
public int getBulkMigrationParallelism() {
|
||||
return bulk_migration_parallelism;
|
||||
}
|
||||
|
||||
public long getWebauthnRecoverAccountTokenLifetime() {
|
||||
return webauthn_recover_account_token_lifetime;
|
||||
}
|
||||
|
||||
public int getBulkMigrationBatchSize() {
|
||||
return bulk_migration_batch_size;
|
||||
}
|
||||
|
||||
public String getOtelCollectorConnectionURI() {
|
||||
return otel_collector_connection_uri;
|
||||
}
|
||||
|
||||
public boolean isDeadlockLoggerEnabled() {
|
||||
return deadlock_logger_enable;
|
||||
}
|
||||
|
||||
public String getSAMLLegacyACSURL() {
|
||||
return saml_legacy_acs_url;
|
||||
}
|
||||
|
||||
public String getSAMLSPEntityID() {
|
||||
return saml_sp_entity_id;
|
||||
}
|
||||
|
||||
public long getSAMLClaimsValidity() {
|
||||
return saml_claims_validity;
|
||||
}
|
||||
|
||||
public long getSAMLRelayStateValidity() {
|
||||
return saml_relay_state_validity;
|
||||
}
|
||||
|
||||
private String getConfigFileLocation(Main main) {
|
||||
return new File(CLIOptions.get(main).getConfigFilePath() == null
|
||||
? CLIOptions.get(main).getInstallationPath() + "config.yaml"
|
||||
: CLIOptions.get(main).getConfigFilePath()).getAbsolutePath();
|
||||
}
|
||||
|
||||
public static void updateConfigJsonFromEnv(JsonObject configJson) {
|
||||
Map<String, String> env = System.getenv();
|
||||
|
||||
for (Field field : CoreConfig.class.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(EnvName.class)) {
|
||||
String envName = field.getAnnotation(EnvName.class).value();
|
||||
String stringValue = env.get(envName);
|
||||
|
||||
if (stringValue == null || stringValue.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stringValue.startsWith("\"") && stringValue.endsWith("\"")) {
|
||||
stringValue = stringValue.substring(1, stringValue.length() - 1);
|
||||
stringValue = stringValue
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\'", "'")
|
||||
.replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
if (field.getType().equals(String.class)) {
|
||||
configJson.addProperty(field.getName(), stringValue);
|
||||
} else if (field.getType().equals(int.class)) {
|
||||
configJson.addProperty(field.getName(), Integer.parseInt(stringValue));
|
||||
} else if (field.getType().equals(long.class)) {
|
||||
configJson.addProperty(field.getName(), Long.parseLong(stringValue));
|
||||
} else if (field.getType().equals(boolean.class)) {
|
||||
configJson.addProperty(field.getName(), Boolean.parseBoolean(stringValue));
|
||||
} else if (field.getType().equals(float.class)) {
|
||||
configJson.addProperty(field.getName(), Float.parseFloat(stringValue));
|
||||
} else if (field.getType().equals(double.class)) {
|
||||
configJson.addProperty(field.getName(), Double.parseDouble(stringValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void normalizeAndValidate(Main main, boolean includeConfigFilePath) throws InvalidConfigException {
|
||||
if (isNormalizedAndValid) {
|
||||
return;
|
||||
|
|
@ -953,18 +784,6 @@ public class CoreConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if (bulk_migration_parallelism < 1) {
|
||||
throw new InvalidConfigException("Provided bulk_migration_parallelism must be >= 1");
|
||||
}
|
||||
|
||||
if (bulk_migration_batch_size < 1) {
|
||||
throw new InvalidConfigException("Provided bulk_migration_batch_size must be >= 1");
|
||||
}
|
||||
|
||||
if (webauthn_recover_account_token_lifetime <= 0) {
|
||||
throw new InvalidConfigException("Provided webauthn_recover_account_token_lifetime must be > 0");
|
||||
}
|
||||
|
||||
for (String fieldId : CoreConfig.getValidFields()) {
|
||||
try {
|
||||
Field field = CoreConfig.class.getDeclaredField(fieldId);
|
||||
|
|
@ -988,10 +807,6 @@ public class CoreConfig {
|
|||
}
|
||||
|
||||
// Normalize
|
||||
if (saml_sp_entity_id == null) {
|
||||
saml_sp_entity_id = "https://saml.supertokens.com";
|
||||
}
|
||||
|
||||
if (ip_allow_regex != null) {
|
||||
ip_allow_regex = ip_allow_regex.trim();
|
||||
if (ip_allow_regex.equals("")) {
|
||||
|
|
@ -1129,24 +944,6 @@ public class CoreConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if (Main.isTesting) {
|
||||
if (oauth_provider_public_service_url == null) {
|
||||
oauth_provider_public_service_url = "http://localhost:" + System.getProperty("ST_OAUTH_PROVIDER_SERVICE_PORT");
|
||||
}
|
||||
if (oauth_provider_admin_service_url == null) {
|
||||
oauth_provider_admin_service_url = "http://localhost:" + System.getProperty("ST_OAUTH_PROVIDER_ADMIN_PORT");
|
||||
}
|
||||
if (oauth_provider_url_configured_in_oauth_provider == null) {
|
||||
oauth_provider_url_configured_in_oauth_provider = "http://localhost:4444";
|
||||
}
|
||||
if (oauth_client_secret_encryption_key == null) {
|
||||
oauth_client_secret_encryption_key = "clientsecretencryptionkey";
|
||||
}
|
||||
if (oauth_provider_consent_login_base_url == null) {
|
||||
oauth_provider_consent_login_base_url = "http://localhost:3001/auth";
|
||||
}
|
||||
}
|
||||
|
||||
isNormalizedAndValid = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 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.config.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// Make annotation accessible at runtime so that config can be read from env
|
||||
@Target(ElementType.FIELD) // Annotation can only be applied to fields
|
||||
public @interface EnvName {
|
||||
String value(); // String value that provides a env var name for the field
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import java.util.Map;
|
|||
public class CronTaskTest extends SingletonResource {
|
||||
private static final String RESOURCE_ID = "io.supertokens.cronjobs.CronTaskTest";
|
||||
private Map<String, Integer> cronTaskToInterval = new HashMap<String, Integer>();
|
||||
private Map<String, Integer> cronTaskToWaitTime = new HashMap<String, Integer>();
|
||||
|
||||
private CronTaskTest() {
|
||||
|
||||
|
|
@ -52,13 +51,4 @@ public class CronTaskTest extends SingletonResource {
|
|||
public Integer getIntervalInSeconds(String resourceId) {
|
||||
return cronTaskToInterval.get(resourceId);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setInitialWaitTimeInSeconds(String resourceId, int interval) {
|
||||
cronTaskToWaitTime.put(resourceId, interval);
|
||||
}
|
||||
|
||||
public Integer getInitialWaitTimeInSeconds(String resourceId) {
|
||||
return cronTaskToWaitTime.get(resourceId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.supertokens.cronjobs;
|
|||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.multitenancy.MultitenancyHelper;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -99,16 +100,6 @@ public class Cronjobs extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isCronjobLoaded(Main main, CronTask task) {
|
||||
if (getInstance(main) == null) {
|
||||
init(main);
|
||||
}
|
||||
Cronjobs instance = getInstance(main);
|
||||
synchronized (instance.lock) {
|
||||
return instance.tasks.contains(task);
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public List<CronTask> getTasks() {
|
||||
return this.tasks;
|
||||
|
|
|
|||
|
|
@ -1,194 +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.cronjobs.bulkimport;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.bulkimport.BulkImport;
|
||||
import io.supertokens.bulkimport.BulkImportUserUtils;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportStorage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ProcessBulkImportUsers extends CronTask {
|
||||
|
||||
public static final String RESOURCE_KEY = "io.supertokens.cronjobs.ProcessBulkImportUsers";
|
||||
|
||||
private ExecutorService executorService;
|
||||
|
||||
private ProcessBulkImportUsers(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("ProcessBulkImportUsers", main, tenantsInfo, true);
|
||||
}
|
||||
|
||||
public static ProcessBulkImportUsers init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
return (ProcessBulkImportUsers) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
|
||||
new ProcessBulkImportUsers(main, tenantsInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerApp(AppIdentifier app)
|
||||
throws TenantOrAppNotFoundException, StorageQueryException {
|
||||
|
||||
if (StorageLayer.getBaseStorage(main).getType() != STORAGE_TYPE.SQL || StorageLayer.isInMemDb(main)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BulkImportSQLStorage bulkImportSQLStorage = (BulkImportSQLStorage) StorageLayer
|
||||
.getStorage(app.getAsPublicTenantIdentifier(), main);
|
||||
|
||||
//split the loaded users list into smaller chunks
|
||||
int numberOfBatchChunks = Config.getConfig(app.getAsPublicTenantIdentifier(), main)
|
||||
.getBulkMigrationParallelism();
|
||||
int bulkMigrationBatchSize = Config.getConfig(app.getAsPublicTenantIdentifier(), main)
|
||||
.getBulkMigrationBatchSize();
|
||||
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "CronTask starts. Instance: " + this);
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "CronTask starts. Processing bulk import users with " + bulkMigrationBatchSize
|
||||
+ " batch size, one batch split into " + numberOfBatchChunks + " chunks");
|
||||
|
||||
executorService = Executors.newFixedThreadPool(numberOfBatchChunks);
|
||||
String[] allUserRoles = StorageUtils.getUserRolesStorage(bulkImportSQLStorage).getRoles(app);
|
||||
BulkImportUserUtils bulkImportUserUtils = new BulkImportUserUtils(allUserRoles);
|
||||
|
||||
long newUsers = bulkImportSQLStorage.getBulkImportUsersCount(app, BulkImportStorage.BULK_IMPORT_USER_STATUS.NEW);
|
||||
long processingUsers = bulkImportSQLStorage.getBulkImportUsersCount(app, BulkImportStorage.BULK_IMPORT_USER_STATUS.PROCESSING);
|
||||
long failedUsers = 0;
|
||||
//taking a "snapshot" here and processing in this round as many users as there are uploaded now. After this the processing will go on
|
||||
//with another app and gets back here when all the apps had a chance.
|
||||
long usersProcessed = 0;
|
||||
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Found " + (newUsers + processingUsers) + " waiting for processing"
|
||||
+ " (" + newUsers + " new, " + processingUsers + " processing)");;
|
||||
|
||||
while(usersProcessed < (newUsers + processingUsers)) {
|
||||
|
||||
List<BulkImportUser> users = bulkImportSQLStorage.getBulkImportUsersAndChangeStatusToProcessing(app,
|
||||
bulkMigrationBatchSize);
|
||||
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Loaded " + users.size() + " users to process");
|
||||
|
||||
if (users == null || users.isEmpty()) {
|
||||
// "No more users to process!"
|
||||
break;
|
||||
}
|
||||
|
||||
List<List<BulkImportUser>> loadedUsersChunks = makeChunksOf(users, numberOfBatchChunks);
|
||||
for (List<BulkImportUser> chunk : loadedUsersChunks) {
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Chunk size: " + chunk.size());
|
||||
}
|
||||
|
||||
try {
|
||||
List<Future<?>> tasks = new ArrayList<>();
|
||||
for (int i = 0; i < numberOfBatchChunks && i < loadedUsersChunks.size(); i++) {
|
||||
tasks.add(
|
||||
executorService.submit(new ProcessBulkUsersImportWorker(main, app, loadedUsersChunks.get(i),
|
||||
bulkImportSQLStorage, bulkImportUserUtils)));
|
||||
}
|
||||
|
||||
for (Future<?> task : tasks) {
|
||||
while (!task.isDone()) {
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Waiting for task " + task + " to finish");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Task " + task + " finished");
|
||||
try {
|
||||
Void result = (Void) task.get(); //to know if there were any errors while executing and for
|
||||
// waiting in this thread for all the other threads to finish up
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(),
|
||||
"Task " + task + " finished with result: " + result);
|
||||
} catch (ExecutionException executionException) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users", true,
|
||||
executionException);
|
||||
throw new RuntimeException(executionException);
|
||||
}
|
||||
usersProcessed += loadedUsersChunks.get(tasks.indexOf(task)).size();
|
||||
failedUsers = bulkImportSQLStorage.getBulkImportUsersCount(app, BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED);
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Chunk " + tasks.indexOf(task) + " finished processing, all chunks processed: "
|
||||
+ usersProcessed + " users (" + failedUsers + " failed)");
|
||||
}
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Processing round finished");
|
||||
} catch (InterruptedException e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(), "Error while processing bulk import users", true,
|
||||
e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
if (Main.isTesting) {
|
||||
Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY);
|
||||
if (interval != null) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
return BulkImport.PROCESS_USERS_INTERVAL_SECONDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
if (Main.isTesting) {
|
||||
Integer waitTime = CronTaskTest.getInstance(main).getInitialWaitTimeInSeconds(RESOURCE_KEY);
|
||||
if (waitTime != null) {
|
||||
return waitTime;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private List<List<BulkImportUser>> makeChunksOf(List<BulkImportUser> users, int numberOfChunks) {
|
||||
List<List<BulkImportUser>> chunks = new ArrayList<>();
|
||||
if (users != null && !users.isEmpty() && numberOfChunks > 0) {
|
||||
AtomicInteger index = new AtomicInteger(0);
|
||||
int chunkSize = users.size() / numberOfChunks + 1;
|
||||
Stream<List<BulkImportUser>> listStream = users.stream()
|
||||
.collect(Collectors.groupingBy(x -> index.getAndIncrement() / chunkSize))
|
||||
.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue);
|
||||
|
||||
listStream.forEach(chunks::add);
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,327 +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.cronjobs.bulkimport;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.bulkimport.BulkImport;
|
||||
import io.supertokens.bulkimport.BulkImportUserUtils;
|
||||
import io.supertokens.bulkimport.exceptions.InvalidBulkImportDataException;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportUser;
|
||||
import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportBatchInsertException;
|
||||
import io.supertokens.pluginInterface.bulkimport.exceptions.BulkImportTransactionRolledBackException;
|
||||
import io.supertokens.pluginInterface.bulkimport.sqlStorage.BulkImportSQLStorage;
|
||||
import io.supertokens.pluginInterface.exceptions.DbInitException;
|
||||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.sqlStorage.SQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class ProcessBulkUsersImportWorker implements Runnable {
|
||||
|
||||
private final Map<String, SQLStorage> userPoolToStorageMap = new HashMap<>();
|
||||
private final Main main;
|
||||
private final AppIdentifier app;
|
||||
private final BulkImportSQLStorage bulkImportSQLStorage;
|
||||
private final BulkImportUserUtils bulkImportUserUtils;
|
||||
private final List<BulkImportUser> usersToProcess;
|
||||
|
||||
ProcessBulkUsersImportWorker(Main main, AppIdentifier app, List<BulkImportUser> usersToProcess, BulkImportSQLStorage bulkImportSQLStorage, BulkImportUserUtils bulkImportUserUtils){
|
||||
this.main = main;
|
||||
this.app = app;
|
||||
this.usersToProcess = usersToProcess;
|
||||
this.bulkImportSQLStorage = bulkImportSQLStorage;
|
||||
this.bulkImportUserUtils = bulkImportUserUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
processMultipleUsers(app, usersToProcess, bulkImportUserUtils, bulkImportSQLStorage);
|
||||
} catch (TenantOrAppNotFoundException | DbInitException | IOException | StorageQueryException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processMultipleUsers(AppIdentifier appIdentifier, List<BulkImportUser> users,
|
||||
BulkImportUserUtils bulkImportUserUtils,
|
||||
BulkImportSQLStorage baseTenantStorage)
|
||||
throws TenantOrAppNotFoundException, StorageQueryException, IOException,
|
||||
DbInitException {
|
||||
BulkImportUser user = null;
|
||||
try {
|
||||
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(),
|
||||
"Processing bulk import users: " + users.size());
|
||||
final Storage[] allStoragesForApp = getAllProxyStoragesForApp(main, appIdentifier);
|
||||
int userIndexPointer = 0;
|
||||
List<BulkImportUser> validUsers = new ArrayList<>();
|
||||
Map<String, Exception> validationErrorsBeforeActualProcessing = new HashMap<>();
|
||||
while(userIndexPointer < users.size()) {
|
||||
user = users.get(userIndexPointer);
|
||||
if (Main.isTesting && Main.isTesting_skipBulkImportUserValidationInCronJob) {
|
||||
// Skip validation when the flag is enabled during testing
|
||||
// Skip validation if it's a retry run. This already passed validation. A revalidation triggers
|
||||
// an invalid external user id already exists validation error - which is not true!
|
||||
validUsers.add(user);
|
||||
} else {
|
||||
// Validate the user
|
||||
try {
|
||||
validUsers.add(bulkImportUserUtils.createBulkImportUserFromJSON(main, appIdentifier,
|
||||
user.toJsonObject(), BulkImportUserUtils.IDMode.READ_STORED));
|
||||
} catch (InvalidBulkImportDataException exception) {
|
||||
validationErrorsBeforeActualProcessing.put(user.id, new Exception(
|
||||
String.valueOf(exception.errors)));
|
||||
}
|
||||
}
|
||||
userIndexPointer+=1;
|
||||
}
|
||||
|
||||
if(!validationErrorsBeforeActualProcessing.isEmpty()) {
|
||||
throw new BulkImportBatchInsertException("Invalid input data", validationErrorsBeforeActualProcessing);
|
||||
}
|
||||
// Since all the tenants of a user must share the storage, we will just use the
|
||||
// storage of the first tenantId of the first loginMethod
|
||||
Map<SQLStorage, List<BulkImportUser>> partitionedUsers = partitionUsersByStorage(appIdentifier, validUsers);
|
||||
|
||||
for(SQLStorage bulkImportProxyStorage : partitionedUsers.keySet()) {
|
||||
boolean shouldRetryImmediatley = true;
|
||||
while (shouldRetryImmediatley) {
|
||||
shouldRetryImmediatley = bulkImportProxyStorage.startTransaction(con -> {
|
||||
try {
|
||||
BulkImport.processUsersImportSteps(main, appIdentifier, bulkImportProxyStorage,
|
||||
partitionedUsers.get(bulkImportProxyStorage),
|
||||
allStoragesForApp);
|
||||
|
||||
bulkImportProxyStorage.commitTransactionForBulkImportProxyStorage();
|
||||
|
||||
String[] toDelete = new String[validUsers.size()];
|
||||
for (int i = 0; i < validUsers.size(); i++) {
|
||||
toDelete[i] = validUsers.get(i).id;
|
||||
}
|
||||
|
||||
while (true){
|
||||
try {
|
||||
List<String> deletedIds = baseTenantStorage.deleteBulkImportUsers(appIdentifier,
|
||||
toDelete);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
// ignore and retry delete. The import transaction is already committed, the delete should happen no matter what
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(),
|
||||
"Exception while deleting bulk import users: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (StorageTransactionLogicException | StorageQueryException e) {
|
||||
// We need to rollback the transaction manually because we have overridden that in the proxy
|
||||
// storage
|
||||
bulkImportProxyStorage.rollbackTransactionForBulkImportProxyStorage();
|
||||
if (isBulkImportTransactionRolledBackIsTheRealCause(e)) {
|
||||
return true;
|
||||
//@see BulkImportTransactionRolledBackException for explanation
|
||||
}
|
||||
handleProcessUserExceptions(app, validUsers, e, baseTenantStorage);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (StorageTransactionLogicException | InvalidConfigException e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users: " + e.getMessage(), true, e);
|
||||
throw new RuntimeException(e);
|
||||
} catch (BulkImportBatchInsertException insertException) {
|
||||
handleProcessUserExceptions(app, users, insertException, baseTenantStorage);
|
||||
} catch (Exception e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users: " + e.getMessage(), true, e);
|
||||
throw e;
|
||||
} finally {
|
||||
closeAllProxyStorages(); //closing it here to reuse the existing connection with all the users
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBulkImportTransactionRolledBackIsTheRealCause(Throwable exception) {
|
||||
if(exception instanceof BulkImportTransactionRolledBackException){
|
||||
return true;
|
||||
} else if(exception.getCause()!=null){
|
||||
return isBulkImportTransactionRolledBackIsTheRealCause(exception.getCause());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleProcessUserExceptions(AppIdentifier appIdentifier, List<BulkImportUser> usersBatch, Exception e,
|
||||
BulkImportSQLStorage baseTenantStorage)
|
||||
throws StorageQueryException {
|
||||
// Java doesn't allow us to reassign local variables inside a lambda expression
|
||||
// so we have to use an array.
|
||||
String[] errorMessage = { e.getMessage() };
|
||||
Map<String, String> bulkImportUserIdToErrorMessage = new HashMap<>();
|
||||
|
||||
switch (e) {
|
||||
case StorageTransactionLogicException exception -> {
|
||||
// If the exception is due to a StorageQueryException, we want to retry the entry after sometime instead
|
||||
// of marking it as FAILED. We will return early in that case.
|
||||
if (exception.actualException instanceof StorageQueryException) {
|
||||
Logging.error(main, null,
|
||||
"We got an StorageQueryException while processing a bulk import user entry. It will be " +
|
||||
"retried again. Error Message: " +
|
||||
e.getMessage(), true);
|
||||
return;
|
||||
}
|
||||
if (exception.actualException instanceof BulkImportBatchInsertException) {
|
||||
handleBulkImportException(usersBatch, (BulkImportBatchInsertException) exception.actualException,
|
||||
bulkImportUserIdToErrorMessage);
|
||||
} else {
|
||||
//fail the whole batch
|
||||
errorMessage[0] = exception.actualException.getMessage();
|
||||
for (BulkImportUser user : usersBatch) {
|
||||
bulkImportUserIdToErrorMessage.put(user.id, errorMessage[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
case InvalidBulkImportDataException invalidBulkImportDataException ->
|
||||
errorMessage[0] = invalidBulkImportDataException.errors.toString();
|
||||
case InvalidConfigException invalidConfigException -> errorMessage[0] = e.getMessage();
|
||||
case BulkImportBatchInsertException bulkImportBatchInsertException ->
|
||||
handleBulkImportException(usersBatch, bulkImportBatchInsertException,
|
||||
bulkImportUserIdToErrorMessage);
|
||||
default -> {
|
||||
Logging.error(main, null,
|
||||
"We got an error while processing a bulk import user entry. It will be " +
|
||||
"retried again. Error Message: " +
|
||||
e.getMessage(), true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
baseTenantStorage.startTransaction(con -> {
|
||||
baseTenantStorage.updateMultipleBulkImportUsersStatusToError_Transaction(appIdentifier, con,
|
||||
bulkImportUserIdToErrorMessage);
|
||||
return null;
|
||||
});
|
||||
} catch (StorageTransactionLogicException e1) {
|
||||
throw new StorageQueryException(e1.actualException);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleBulkImportException(List<BulkImportUser> usersBatch, BulkImportBatchInsertException exception,
|
||||
Map<String, String> bulkImportUserIdToErrorMessage) {
|
||||
Map<String, Exception> userIndexToError = exception.exceptionByUserId;
|
||||
for(String userid : userIndexToError.keySet()){
|
||||
Optional<BulkImportUser> userWithId = usersBatch.stream()
|
||||
.filter(bulkImportUser -> userid.equals(bulkImportUser.id) || userid.equals(bulkImportUser.externalUserId)).findFirst();
|
||||
String id = null;
|
||||
if(userWithId.isPresent()){
|
||||
id = userWithId.get().id;
|
||||
}
|
||||
|
||||
if(id == null) {
|
||||
userWithId = usersBatch.stream()
|
||||
.filter(bulkImportUser ->
|
||||
bulkImportUser.loginMethods.stream()
|
||||
.map(loginMethod -> loginMethod.superTokensUserId)
|
||||
.anyMatch(s -> s!= null && s.equals(userid))).findFirst();
|
||||
if(userWithId.isPresent()){
|
||||
id = userWithId.get().id;
|
||||
}
|
||||
}
|
||||
bulkImportUserIdToErrorMessage.put(id, userIndexToError.get(userid).getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized Storage getBulkImportProxyStorage(TenantIdentifier tenantIdentifier)
|
||||
throws InvalidConfigException, IOException, TenantOrAppNotFoundException, DbInitException {
|
||||
String userPoolId = StorageLayer.getStorage(tenantIdentifier, main).getUserPoolId();
|
||||
if (userPoolToStorageMap.containsKey(userPoolId)) {
|
||||
return userPoolToStorageMap.get(userPoolId);
|
||||
}
|
||||
|
||||
TenantConfig[] allTenants = Multitenancy.getAllTenants(main);
|
||||
|
||||
Map<ResourceDistributor.KeyClass, JsonObject> normalisedConfigs = Config.getNormalisedConfigsForAllTenants(
|
||||
allTenants,
|
||||
Config.getBaseConfigAsJsonObject(main));
|
||||
|
||||
for (ResourceDistributor.KeyClass key : normalisedConfigs.keySet()) {
|
||||
if (key.getTenantIdentifier().equals(tenantIdentifier)) {
|
||||
SQLStorage bulkImportProxyStorage = (SQLStorage) StorageLayer.getNewBulkImportProxyStorageInstance(main,
|
||||
normalisedConfigs.get(key), tenantIdentifier, true);
|
||||
|
||||
userPoolToStorageMap.put(userPoolId, bulkImportProxyStorage);
|
||||
bulkImportProxyStorage.initStorage(false, new ArrayList<>());
|
||||
return bulkImportProxyStorage;
|
||||
}
|
||||
}
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
|
||||
private synchronized Storage[] getAllProxyStoragesForApp(Main main, AppIdentifier appIdentifier)
|
||||
throws StorageTransactionLogicException {
|
||||
|
||||
try {
|
||||
List<Storage> allProxyStorages = new ArrayList<>();
|
||||
TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main);
|
||||
for (TenantConfig tenantConfig : tenantConfigs) {
|
||||
allProxyStorages.add(getBulkImportProxyStorage(tenantConfig.tenantIdentifier));
|
||||
}
|
||||
return allProxyStorages.toArray(new Storage[0]);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(new Exception("E043: " + e.getMessage()));
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new StorageTransactionLogicException(new InvalidConfigException("E044: " + e.getMessage()));
|
||||
} catch (DbInitException e) {
|
||||
throw new StorageTransactionLogicException(new DbInitException("E045: " + e.getMessage()));
|
||||
} catch (IOException e) {
|
||||
throw new StorageTransactionLogicException(new IOException("E046: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAllProxyStorages() throws StorageQueryException {
|
||||
for (SQLStorage storage : userPoolToStorageMap.values()) {
|
||||
storage.closeConnectionForBulkImportProxyStorage();
|
||||
}
|
||||
userPoolToStorageMap.clear();
|
||||
}
|
||||
|
||||
private Map<SQLStorage, List<BulkImportUser>> partitionUsersByStorage(AppIdentifier appIdentifier, List<BulkImportUser> users)
|
||||
throws DbInitException, TenantOrAppNotFoundException, InvalidConfigException, IOException {
|
||||
Map<SQLStorage, List<BulkImportUser>> result = new HashMap<>();
|
||||
for(BulkImportUser user: users) {
|
||||
TenantIdentifier firstTenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), user.loginMethods.getFirst().tenantIds.getFirst());
|
||||
|
||||
SQLStorage bulkImportProxyStorage = (SQLStorage) getBulkImportProxyStorage(firstTenantIdentifier);
|
||||
if(!result.containsKey(bulkImportProxyStorage)){
|
||||
result.put(bulkImportProxyStorage, new ArrayList<>());
|
||||
}
|
||||
result.get(bulkImportProxyStorage).add(user);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 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.cronjobs.cleanupWebauthnExpiredData;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.webauthn.WebAuthNStorage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CleanUpWebauthNExpiredDataCron extends CronTask {
|
||||
|
||||
public static final String RESOURCE_KEY = "io.supertokens.cronjobs.cleanupWebauthnExpiredData" +
|
||||
".CleanUpWebauthnExpiredDataCron";
|
||||
|
||||
private CleanUpWebauthNExpiredDataCron(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("CleanUpWebauthnExpiredDataCron", main, tenantsInfo, true);
|
||||
}
|
||||
|
||||
public static CleanUpWebauthNExpiredDataCron init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
return (CleanUpWebauthNExpiredDataCron) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
|
||||
new CleanUpWebauthNExpiredDataCron(main, tenantsInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerStorage(Storage storage) throws Exception {
|
||||
if (storage.getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
WebAuthNStorage webAuthNStorage = StorageUtils.getWebAuthNStorage(storage);
|
||||
webAuthNStorage.deleteExpiredAccountRecoveryTokens();
|
||||
webAuthNStorage.deleteExpiredGeneratedOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
if (Main.isTesting) {
|
||||
Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY);
|
||||
if (interval != null) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
// Every 24 hours.
|
||||
return 24 * 3600;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
if (!Main.isTesting) {
|
||||
return getIntervalTimeSeconds();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 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.cronjobs.deadlocklogger;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DeadlockLogger {
|
||||
|
||||
private static final DeadlockLogger INSTANCE = new DeadlockLogger();
|
||||
|
||||
private DeadlockLogger() {
|
||||
}
|
||||
|
||||
public static DeadlockLogger getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void start(){
|
||||
Thread deadlockLoggerThread = new Thread(deadlockDetector, "DeadlockLoggerThread");
|
||||
deadlockLoggerThread.setDaemon(true);
|
||||
deadlockLoggerThread.start();
|
||||
}
|
||||
|
||||
private final Runnable deadlockDetector = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("DeadlockLogger started!");
|
||||
while (true) {
|
||||
System.out.println("DeadlockLogger - checking");
|
||||
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
|
||||
long[] threadIds = bean.findDeadlockedThreads(); // Returns null if no threads are deadlocked.
|
||||
System.out.println("DeadlockLogger - DeadlockedThreads: " + Arrays.toString(threadIds));
|
||||
if (threadIds != null) {
|
||||
ThreadInfo[] infos = bean.getThreadInfo(threadIds);
|
||||
boolean deadlockFound = false;
|
||||
System.out.println("DEADLOCK found!");
|
||||
for (ThreadInfo info : infos) {
|
||||
System.out.println("ThreadName: " + info.getThreadName());
|
||||
System.out.println("Thread ID: " + info.getThreadId());
|
||||
System.out.println("LockName: " + info.getLockName());
|
||||
System.out.println("LockOwnerName: " + info.getLockOwnerName());
|
||||
System.out.println("LockedMonitors: " + Arrays.toString(info.getLockedMonitors()));
|
||||
System.out.println("LockInfo: " + info.getLockInfo());
|
||||
System.out.println("Stack: " + Arrays.toString(info.getStackTrace()));
|
||||
System.out.println();
|
||||
deadlockFound = true;
|
||||
}
|
||||
System.out.println("*******************************");
|
||||
if(deadlockFound) {
|
||||
System.out.println(" ==== ALL THREAD INFO ===");
|
||||
ThreadInfo[] allThreads = bean.dumpAllThreads(true, true, 100);
|
||||
for (ThreadInfo threadInfo : allThreads) {
|
||||
System.out.println("THREAD: " + threadInfo.getThreadName());
|
||||
System.out.println("StackTrace: " + Arrays.toString(threadInfo.getStackTrace()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package io.supertokens.cronjobs.deleteExpiredSAMLData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
|
||||
public class DeleteExpiredSAMLData extends CronTask {
|
||||
public static final String RESOURCE_KEY = "io.supertokens.cronjobs.deleteExpiredSAMLData" +
|
||||
".DeleteExpiredSAMLData";
|
||||
|
||||
private DeleteExpiredSAMLData(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("DeleteExpiredSAMLData", main, tenantsInfo, false);
|
||||
}
|
||||
|
||||
public static DeleteExpiredSAMLData init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
return (DeleteExpiredSAMLData) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
|
||||
new DeleteExpiredSAMLData(main, tenantsInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerStorage(Storage storage) throws Exception {
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
samlStorage.removeExpiredSAMLCodesAndRelayStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
if (Main.isTesting) {
|
||||
Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY);
|
||||
if (interval != null) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
// Every hour
|
||||
return 3600;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
if (!Main.isTesting) {
|
||||
return getIntervalTimeSeconds();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import io.supertokens.cronjobs.CronTaskTest;
|
|||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -31,11 +30,7 @@ public class DeleteExpiredTotpTokens extends CronTask {
|
|||
|
||||
@TestOnly
|
||||
public static DeleteExpiredTotpTokens getInstance(Main main) {
|
||||
try {
|
||||
return (DeleteExpiredTotpTokens) main.getResourceDistributor().getResource(TenantIdentifier.BASE_TENANT, RESOURCE_KEY);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return (DeleteExpiredTotpTokens) main.getResourceDistributor().getResource(RESOURCE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ package io.supertokens.cronjobs.syncCoreConfigWithDb;
|
|||
import io.supertokens.Main;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.multitenancy.MultitenancyHelper;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SyncCoreConfigWithDb extends CronTask {
|
||||
|
||||
|
|
@ -59,7 +62,6 @@ public class SyncCoreConfigWithDb extends CronTask {
|
|||
return 60;
|
||||
}
|
||||
|
||||
@WithinOtelSpan
|
||||
@Override
|
||||
protected void doTaskForTargetTenant(TenantIdentifier targetTenant) throws Exception {
|
||||
MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public class Telemetry extends CronTask {
|
|||
json.add("maus", new JsonArray());
|
||||
}
|
||||
|
||||
String url = "https://api.supertokens.com/0/st/telemetry";
|
||||
String url = "https://api.supertokens.io/0/st/telemetry";
|
||||
|
||||
// we call the API only if we are not testing the core, of if the request can be mocked (in case a test
|
||||
// wants
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.supertokens.dashboard;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.dashboard.exceptions.UserSuspendedException;
|
||||
import io.supertokens.emailpassword.PasswordHashing;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
|
|
@ -56,7 +55,7 @@ public class Dashboard {
|
|||
throws StorageQueryException, DuplicateEmailException, FeatureNotEnabledException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signUpDashboardUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
return signUpDashboardUser(new AppIdentifier(null, null), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -104,7 +103,7 @@ public class Dashboard {
|
|||
public static DashboardUser[] getAllDashboardUsers(Main main)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getAllDashboardUsers(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main);
|
||||
return getAllDashboardUsers(new AppIdentifier(null, null), storage, main);
|
||||
}
|
||||
|
||||
public static DashboardUser[] getAllDashboardUsers(AppIdentifier appIdentifier, Storage storage, Main main)
|
||||
|
|
@ -128,7 +127,7 @@ public class Dashboard {
|
|||
throws StorageQueryException, UserSuspendedException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signInDashboardUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
return signInDashboardUser(new AppIdentifier(null, null), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -160,7 +159,7 @@ public class Dashboard {
|
|||
public static boolean deleteUserWithUserId(Main main, String userId)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return deleteUserWithUserId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
return deleteUserWithUserId(new AppIdentifier(null, null), storage, userId);
|
||||
}
|
||||
|
||||
public static boolean deleteUserWithUserId(AppIdentifier appIdentifier, Storage storage, String userId)
|
||||
|
|
@ -202,7 +201,7 @@ public class Dashboard {
|
|||
public static boolean deleteUserWithEmail(Main main, String email)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return deleteUserWithEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, email);
|
||||
return deleteUserWithEmail(new AppIdentifier(null, null), storage, email);
|
||||
}
|
||||
|
||||
public static boolean deleteUserWithEmail(AppIdentifier appIdentifier, Storage storage, String email)
|
||||
|
|
@ -224,7 +223,7 @@ public class Dashboard {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return updateUsersCredentialsWithUserId(
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main, userId,
|
||||
new AppIdentifier(null, null), storage, main, userId,
|
||||
newEmail, newPassword);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -292,7 +291,7 @@ public class Dashboard {
|
|||
public static DashboardUser getDashboardUserByEmail(Main main, String email)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDashboardUserByEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, email);
|
||||
return getDashboardUserByEmail(new AppIdentifier(null, null), storage, email);
|
||||
}
|
||||
|
||||
public static DashboardUser getDashboardUserByEmail(AppIdentifier appIdentifier, Storage storage, String email)
|
||||
|
|
@ -306,7 +305,7 @@ public class Dashboard {
|
|||
public static boolean revokeSessionWithSessionId(Main main, String sessionId)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return revokeSessionWithSessionId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, sessionId);
|
||||
return revokeSessionWithSessionId(new AppIdentifier(null, null), storage, sessionId);
|
||||
}
|
||||
|
||||
public static boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, Storage storage, String sessionId)
|
||||
|
|
@ -321,7 +320,7 @@ public class Dashboard {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getAllDashboardSessionsForUser(
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
new AppIdentifier(null, null), storage, userId);
|
||||
}
|
||||
|
||||
public static DashboardSessionInfo[] getAllDashboardSessionsForUser(AppIdentifier appIdentifier, Storage storage,
|
||||
|
|
@ -391,7 +390,7 @@ public class Dashboard {
|
|||
public static boolean isValidUserSession(Main main, String sessionId)
|
||||
throws StorageQueryException, UserSuspendedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return isValidUserSession(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main, sessionId);
|
||||
return isValidUserSession(new AppIdentifier(null, null), storage, main, sessionId);
|
||||
}
|
||||
|
||||
public static boolean isValidUserSession(AppIdentifier appIdentifier, Storage storage, Main main, String sessionId)
|
||||
|
|
|
|||
|
|
@ -16,18 +16,7 @@
|
|||
|
||||
package io.supertokens.emailpassword;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
|
|
@ -43,8 +32,6 @@ import io.supertokens.pluginInterface.StorageUtils;
|
|||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
|
||||
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportStorage;
|
||||
import io.supertokens.pluginInterface.emailpassword.EmailPasswordImportUser;
|
||||
import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException;
|
||||
|
|
@ -61,6 +48,13 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoun
|
|||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.utils.Utils;
|
||||
import io.supertokens.webserver.WebserverAPI;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
public class EmailPassword {
|
||||
|
||||
|
|
@ -77,7 +71,7 @@ public class EmailPassword {
|
|||
@TestOnly
|
||||
public static long getPasswordResetTokenLifetimeForTests(Main main) {
|
||||
try {
|
||||
return getPasswordResetTokenLifetime(ResourceDistributor.getAppForTesting(), main);
|
||||
return getPasswordResetTokenLifetime(new TenantIdentifier(null, null, null), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -93,7 +87,7 @@ public class EmailPassword {
|
|||
throws DuplicateEmailException, StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signUp(ResourceDistributor.getAppForTesting(), storage,
|
||||
return signUp(new TenantIdentifier(null, null, null), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -160,7 +154,7 @@ public class EmailPassword {
|
|||
Storage storage = StorageLayer.getStorage(main);
|
||||
|
||||
return importUserWithPasswordHash(
|
||||
ResourceDistributor.getAppForTesting(), storage, main, email,
|
||||
new TenantIdentifier(null, null, null), storage, main, email,
|
||||
passwordHash, hashingAlgorithm);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -183,57 +177,19 @@ public class EmailPassword {
|
|||
tenantIdentifier.toAppIdentifier(), main,
|
||||
passwordHash, hashingAlgorithm);
|
||||
|
||||
EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage);
|
||||
ImportUserResponse response = null;
|
||||
|
||||
try {
|
||||
long timeJoined = System.currentTimeMillis();
|
||||
response = createUserWithPasswordHash(tenantIdentifier, storage, email, passwordHash, timeJoined);
|
||||
} catch (DuplicateEmailException e) {
|
||||
AuthRecipeUserInfo[] allUsers = epStorage.listPrimaryUsersByEmail(tenantIdentifier, email);
|
||||
AuthRecipeUserInfo userInfoToBeUpdated = null;
|
||||
LoginMethod loginMethod = null;
|
||||
for (AuthRecipeUserInfo currUser : allUsers) {
|
||||
for (LoginMethod currLM : currUser.loginMethods) {
|
||||
if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifier.getTenantId())) {
|
||||
userInfoToBeUpdated = currUser;
|
||||
loginMethod = currLM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userInfoToBeUpdated != null) {
|
||||
LoginMethod finalLoginMethod = loginMethod;
|
||||
epStorage.startTransaction(con -> {
|
||||
epStorage.updateUsersPassword_Transaction(tenantIdentifier.toAppIdentifier(), con,
|
||||
finalLoginMethod.getSupertokensUserId(), passwordHash);
|
||||
return null;
|
||||
});
|
||||
response = new ImportUserResponse(true, userInfoToBeUpdated);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public static ImportUserResponse createUserWithPasswordHash(TenantIdentifier tenantIdentifier, Storage storage,
|
||||
@Nonnull String email,
|
||||
@Nonnull String passwordHash, long timeJoined)
|
||||
throws StorageQueryException, DuplicateEmailException, TenantOrAppNotFoundException,
|
||||
StorageTransactionLogicException {
|
||||
EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage);
|
||||
while (true) {
|
||||
String userId = Utils.getUUID();
|
||||
long timeJoined = System.currentTimeMillis();
|
||||
|
||||
EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage);
|
||||
|
||||
try {
|
||||
AuthRecipeUserInfo userInfo = null;
|
||||
userInfo = epStorage.signUp(tenantIdentifier, userId, email, passwordHash, timeJoined);
|
||||
AuthRecipeUserInfo userInfo = epStorage.signUp(tenantIdentifier, userId, email, passwordHash,
|
||||
timeJoined);
|
||||
return new ImportUserResponse(false, userInfo);
|
||||
} catch (DuplicateUserIdException e) {
|
||||
// we retry with a new userId
|
||||
} catch (DuplicateEmailException e) {
|
||||
if(epStorage instanceof BulkImportStorage){
|
||||
throw e;
|
||||
}
|
||||
AuthRecipeUserInfo[] allUsers = epStorage.listPrimaryUsersByEmail(tenantIdentifier, email);
|
||||
AuthRecipeUserInfo userInfoToBeUpdated = null;
|
||||
LoginMethod loginMethod = null;
|
||||
|
|
@ -261,17 +217,6 @@ public class EmailPassword {
|
|||
}
|
||||
}
|
||||
|
||||
public static void createMultipleUsersWithPasswordHash(Storage storage,
|
||||
List<EmailPasswordImportUser> usersToImport)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException, StorageTransactionLogicException {
|
||||
|
||||
EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage);
|
||||
epStorage.startTransaction(con -> {
|
||||
epStorage.signUpMultipleViaBulkImport_Transaction(con, usersToImport);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull String email,
|
||||
@Nonnull String passwordHash)
|
||||
|
|
@ -279,7 +224,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return importUserWithPasswordHash(
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
main, email, passwordHash, null);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -292,7 +237,7 @@ public class EmailPassword {
|
|||
throws StorageQueryException, WrongCredentialsException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signIn(ResourceDistributor.getAppForTesting(), storage,
|
||||
return signIn(new TenantIdentifier(null, null, null), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -356,7 +301,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetTokenBeforeCdi4_0(
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
main, userId);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException | WebserverAPI.BadRequestException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -369,7 +314,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetToken(
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
main, userId, null);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -382,7 +327,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetToken(
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
main, userId, email);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -459,7 +404,7 @@ public class EmailPassword {
|
|||
StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return resetPassword(ResourceDistributor.getAppForTesting(), storage,
|
||||
return resetPassword(new TenantIdentifier(null, null, null), storage,
|
||||
main, token, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -533,7 +478,7 @@ public class EmailPassword {
|
|||
StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return consumeResetPasswordToken(ResourceDistributor.getAppForTesting(), storage,
|
||||
return consumeResetPasswordToken(new TenantIdentifier(null, null, null), storage,
|
||||
token);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -631,7 +576,7 @@ public class EmailPassword {
|
|||
UnknownUserIdException, DuplicateEmailException, EmailChangeNotAllowedException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
updateUsersEmailOrPassword(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
updateUsersEmailOrPassword(new AppIdentifier(null, null), storage,
|
||||
main, userId, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -728,7 +673,7 @@ public class EmailPassword {
|
|||
throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUserUsingId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
return getUserUsingId(new AppIdentifier(null, null), storage, userId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,6 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoun
|
|||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
|
|
@ -44,9 +42,6 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
final BlockingQueue<Object> firebaseSCryptBoundedQueue;
|
||||
final Main main;
|
||||
|
||||
private final Map<String, String> cachedPasswordHashForTesting = new HashMap<>();
|
||||
public static boolean bypassHashCachingInTesting = false;
|
||||
|
||||
private PasswordHashing(Main main) {
|
||||
this.argon2BoundedQueue = new LinkedBlockingQueue<>(
|
||||
Config.getBaseConfig(main).getArgon2HashingPoolSize());
|
||||
|
|
@ -80,7 +75,7 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
@TestOnly
|
||||
public String createHashWithSalt(String password) {
|
||||
try {
|
||||
return createHashWithSalt(ResourceDistributor.getAppForTesting().toAppIdentifier(), password);
|
||||
return createHashWithSalt(new AppIdentifier(null, null), password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -89,10 +84,6 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
public String createHashWithSalt(AppIdentifier appIdentifier, String password)
|
||||
throws TenantOrAppNotFoundException {
|
||||
|
||||
if (Main.isTesting && !bypassHashCachingInTesting && cachedPasswordHashForTesting.containsKey(password)) {
|
||||
return cachedPasswordHashForTesting.get(password);
|
||||
}
|
||||
|
||||
String passwordHash = "";
|
||||
|
||||
TenantIdentifier tenantIdentifier = appIdentifier.getAsPublicTenantIdentifier();
|
||||
|
|
@ -117,10 +108,6 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
} catch (UnsupportedPasswordHashingFormatException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
if (Main.isTesting) {
|
||||
cachedPasswordHashForTesting.put(password, passwordHash);
|
||||
}
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import io.supertokens.config.CoreConfig;
|
|||
import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.Cipher;
|
||||
|
|
@ -118,9 +118,9 @@ public class PasswordHashingUtils {
|
|||
|
||||
// concatenating decoded salt + separator
|
||||
byte[] byteArrTemp = response.salt.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] decodedSaltBytes = Base64.decodeBase64(byteArrTemp);
|
||||
byte[] decodedSaltBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
byteArrTemp = response.saltSeparator.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] decodedSaltSepBytes = Base64.decodeBase64(byteArrTemp);
|
||||
byte[] decodedSaltSepBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
|
||||
byte[] saltConcat = new byte[decodedSaltBytes.length + decodedSaltSepBytes.length];
|
||||
System.arraycopy(decodedSaltBytes, 0, saltConcat, 0, decodedSaltBytes.length);
|
||||
|
|
@ -136,7 +136,7 @@ public class PasswordHashingUtils {
|
|||
}
|
||||
// encrypting with aes
|
||||
byteArrTemp = base64_signer_key.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] signerBytes = Base64.decodeBase64(byteArrTemp);
|
||||
byte[] signerBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
|
||||
try {
|
||||
String CIPHER = "AES/CTR/NoPadding";
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.supertokens.emailverification;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException;
|
||||
import io.supertokens.emailverification.exception.EmailVerificationInvalidTokenException;
|
||||
|
|
@ -45,7 +44,7 @@ public class EmailVerification {
|
|||
public static long getEmailVerificationTokenLifetimeForTests(Main main) {
|
||||
try {
|
||||
return getEmailVerificationTokenLifetime(
|
||||
ResourceDistributor.getAppForTesting(), main);
|
||||
new TenantIdentifier(null, null, null), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -63,7 +62,7 @@ public class EmailVerification {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generateEmailVerificationToken(
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
main, userId, email);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -108,7 +107,7 @@ public class EmailVerification {
|
|||
EmailVerificationInvalidTokenException, NoSuchAlgorithmException, StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return verifyEmail(ResourceDistributor.getAppForTesting(), storage, token);
|
||||
return verifyEmail(new TenantIdentifier(null, null, null), storage, token);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -183,7 +182,7 @@ public class EmailVerification {
|
|||
public static boolean isEmailVerified(Main main, String userId,
|
||||
String email) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return isEmailVerified(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
return isEmailVerified(new AppIdentifier(null, null), storage,
|
||||
userId, email);
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +196,7 @@ public class EmailVerification {
|
|||
public static void revokeAllTokens(Main main, String userId,
|
||||
String email) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
revokeAllTokens(ResourceDistributor.getAppForTesting(), storage,
|
||||
revokeAllTokens(new TenantIdentifier(null, null, null), storage,
|
||||
userId, email);
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +211,7 @@ public class EmailVerification {
|
|||
String email) throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
unverifyEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId, email);
|
||||
unverifyEmail(new AppIdentifier(null, null), storage, userId, email);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -250,7 +249,7 @@ public class EmailVerification {
|
|||
|
||||
try {
|
||||
StorageUtils.getEmailVerificationStorage(StorageLayer.getStorage(main))
|
||||
.addEmailVerificationToken(ResourceDistributor.getAppForTesting(),
|
||||
.addEmailVerificationToken(new TenantIdentifier(null, null, null),
|
||||
new EmailVerificationTokenInfo(userId, hashedToken,
|
||||
System.currentTimeMillis() +
|
||||
EmailVerification.getEmailVerificationTokenLifetimeForTests(main), email));
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.supertokens.featureflag;
|
|||
|
||||
public enum EE_FEATURES {
|
||||
ACCOUNT_LINKING("account_linking"), MULTI_TENANCY("multi_tenancy"), TEST("test"),
|
||||
DASHBOARD_LOGIN("dashboard_login"), MFA("mfa"), SECURITY("security"), OAUTH("oauth"), SAML("saml");
|
||||
DASHBOARD_LOGIN("dashboard_login"), MFA("mfa"), SECURITY("security"), OAUTH("oauth");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import io.supertokens.ResourceDistributor;
|
|||
import io.supertokens.featureflag.exceptions.InvalidLicenseKeyException;
|
||||
import io.supertokens.featureflag.exceptions.NoLicenseKeyFoundException;
|
||||
import io.supertokens.httpRequest.HttpResponseException;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
|
|
@ -108,7 +107,7 @@ public class FeatureFlag extends ResourceDistributor.SingletonResource {
|
|||
public static FeatureFlag getInstance(Main main) {
|
||||
try {
|
||||
return (FeatureFlag) main.getResourceDistributor()
|
||||
.getResource(ResourceDistributor.getAppForTesting(), RESOURCE_KEY);
|
||||
.getResource(new AppIdentifier(null, null), RESOURCE_KEY);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -133,32 +132,27 @@ public class FeatureFlag extends ResourceDistributor.SingletonResource {
|
|||
.getAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
for (AppIdentifier app : apps) {
|
||||
try {
|
||||
ResourceDistributor.SingletonResource resource = existingResources.get(
|
||||
new ResourceDistributor.KeyClass(
|
||||
ResourceDistributor.SingletonResource resource = existingResources.get(
|
||||
new ResourceDistributor.KeyClass(
|
||||
app,
|
||||
RESOURCE_KEY));
|
||||
if (resource != null && !tenantsThatChanged.contains(app.getAsPublicTenantIdentifier())) {
|
||||
main.getResourceDistributor()
|
||||
.setResource(app,
|
||||
RESOURCE_KEY,
|
||||
resource);
|
||||
} else {
|
||||
main.getResourceDistributor()
|
||||
.setResource(
|
||||
app,
|
||||
RESOURCE_KEY));
|
||||
if (resource != null && !tenantsThatChanged.contains(app.getAsPublicTenantIdentifier())) {
|
||||
main.getResourceDistributor()
|
||||
.setResource(app,
|
||||
RESOURCE_KEY,
|
||||
resource);
|
||||
} else {
|
||||
main.getResourceDistributor()
|
||||
.setResource(
|
||||
app,
|
||||
RESOURCE_KEY,
|
||||
new FeatureFlag(main, app));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(), e.getMessage(), false);
|
||||
// continue loading other resources
|
||||
RESOURCE_KEY,
|
||||
new FeatureFlag(main, app));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (ResourceDistributor.FuncException e) {
|
||||
throw new IllegalStateException("should never happen", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import java.sql.Connection;
|
|||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public interface QueryExecutorTemplate {
|
||||
|
||||
|
|
@ -45,26 +44,6 @@ public interface QueryExecutorTemplate {
|
|||
}
|
||||
}
|
||||
|
||||
static void executeBatch(Connection connection, String QUERY, List<PreparedStatementValueSetter> setters)
|
||||
throws SQLException, StorageQueryException {
|
||||
if(setters == null || setters.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement pst = connection.prepareStatement(QUERY)) {
|
||||
int counter = 0;
|
||||
for(PreparedStatementValueSetter setter: setters) {
|
||||
setter.setValues(pst);
|
||||
pst.addBatch();
|
||||
counter++;
|
||||
|
||||
if(counter % 100 == 0) {
|
||||
pst.executeBatch();
|
||||
}
|
||||
}
|
||||
pst.executeBatch(); //for the possible remaining ones
|
||||
}
|
||||
}
|
||||
|
||||
public static int update(Start start, String QUERY, PreparedStatementValueSetter setter)
|
||||
throws SQLException, StorageQueryException {
|
||||
try (Connection con = ConnectionPool.getConnection(start)) {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,11 @@ import io.supertokens.pluginInterface.*;
|
|||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
|
||||
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
|
||||
import io.supertokens.pluginInterface.bulkimport.BulkImportStorage;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardUser;
|
||||
import io.supertokens.pluginInterface.dashboard.exceptions.UserIdNotFoundException;
|
||||
import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage;
|
||||
import io.supertokens.pluginInterface.emailpassword.EmailPasswordImportUser;
|
||||
import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException;
|
||||
|
|
@ -65,21 +63,14 @@ import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge;
|
|||
import io.supertokens.pluginInterface.oauth.OAuthStorage;
|
||||
import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException;
|
||||
import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException;
|
||||
import io.supertokens.pluginInterface.opentelemetry.OtelProvider;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessCode;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessDevice;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser;
|
||||
import io.supertokens.pluginInterface.passwordless.exception.*;
|
||||
import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClient;
|
||||
import io.supertokens.pluginInterface.saml.SAMLRelayStateInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
import io.supertokens.pluginInterface.session.SessionInfo;
|
||||
import io.supertokens.pluginInterface.session.SessionStorage;
|
||||
import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage;
|
||||
import io.supertokens.pluginInterface.sqlStorage.TransactionConnection;
|
||||
import io.supertokens.pluginInterface.thirdparty.ThirdPartyImportUser;
|
||||
import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException;
|
||||
import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage;
|
||||
import io.supertokens.pluginInterface.totp.TOTPDevice;
|
||||
|
|
@ -101,11 +92,6 @@ import io.supertokens.pluginInterface.userroles.UserRolesStorage;
|
|||
import io.supertokens.pluginInterface.userroles.exception.DuplicateUserRoleMappingException;
|
||||
import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException;
|
||||
import io.supertokens.pluginInterface.userroles.sqlStorage.UserRolesSQLStorage;
|
||||
import io.supertokens.pluginInterface.webauthn.AccountRecoveryTokenInfo;
|
||||
import io.supertokens.pluginInterface.webauthn.WebAuthNOptions;
|
||||
import io.supertokens.pluginInterface.webauthn.WebAuthNStoredCredential;
|
||||
import io.supertokens.pluginInterface.webauthn.exceptions.*;
|
||||
import io.supertokens.pluginInterface.webauthn.slqStorage.WebAuthNSQLStorage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.sqlite.SQLiteException;
|
||||
|
|
@ -115,14 +101,16 @@ import javax.annotation.Nullable;
|
|||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLTransactionRollbackException;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Start
|
||||
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
|
||||
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
|
||||
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage,
|
||||
ActiveUsersSQLStorage, DashboardSQLStorage, AuthRecipeSQLStorage, OAuthStorage, WebAuthNSQLStorage,
|
||||
SAMLStorage {
|
||||
ActiveUsersSQLStorage, DashboardSQLStorage, AuthRecipeSQLStorage, OAuthStorage {
|
||||
|
||||
private static final Object appenderLock = new Object();
|
||||
private static final String ACCESS_TOKEN_SIGNING_KEY_NAME = "access_token_signing_key";
|
||||
|
|
@ -154,30 +142,6 @@ public class Start
|
|||
Start.isTesting = isTesting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Storage createBulkImportProxyStorageInstance() {
|
||||
throw new UnsupportedOperationException("'createBulkImportProxyStorageInstance' is not supported for in-memory db");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnectionForBulkImportProxyStorage() throws StorageQueryException {
|
||||
throw new UnsupportedOperationException(
|
||||
"closeConnectionForBulkImportProxyStorage should only be called from BulkImportProxyStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitTransactionForBulkImportProxyStorage() throws StorageQueryException {
|
||||
throw new UnsupportedOperationException(
|
||||
"commitTransactionForBulkImportProxyStorage should only be called from BulkImportProxyStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollbackTransactionForBulkImportProxyStorage() throws StorageQueryException {
|
||||
throw new UnsupportedOperationException(
|
||||
"rollbackTransactionForBulkImportProxyStorage should only be called from BulkImportProxyStorage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public STORAGE_TYPE getType() {
|
||||
return STORAGE_TYPE.SQL;
|
||||
|
|
@ -208,7 +172,7 @@ public class Start
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initFileLogging(String infoLogPath, String errorLogPath, OtelProvider otelProvider) {
|
||||
public void initFileLogging(String infoLogPath, String errorLogPath) {
|
||||
// no op
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +197,7 @@ public class Start
|
|||
@Override
|
||||
public <T> T startTransaction(TransactionLogic<T> logic)
|
||||
throws StorageTransactionLogicException, StorageQueryException {
|
||||
return startTransaction(logic, TransactionIsolationLevel.READ_COMMITTED);
|
||||
return startTransaction(logic, TransactionIsolationLevel.SERIALIZABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -244,8 +208,7 @@ public class Start
|
|||
tries++;
|
||||
try {
|
||||
return startTransactionHelper(logic);
|
||||
} catch (SQLException | StorageQueryException | StorageTransactionLogicException |
|
||||
TenantOrAppNotFoundException e) {
|
||||
} catch (SQLException | StorageQueryException | StorageTransactionLogicException e) {
|
||||
if ((e instanceof SQLTransactionRollbackException
|
||||
|| (e.getMessage() != null && e.getMessage().toLowerCase().contains("deadlock")))
|
||||
&& tries < 3) {
|
||||
|
|
@ -264,7 +227,7 @@ public class Start
|
|||
}
|
||||
|
||||
private <T> T startTransactionHelper(TransactionLogic<T> logic)
|
||||
throws StorageQueryException, StorageTransactionLogicException, SQLException, TenantOrAppNotFoundException {
|
||||
throws StorageQueryException, StorageTransactionLogicException, SQLException {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = ConnectionPool.getConnection(this);
|
||||
|
|
@ -625,11 +588,6 @@ public class Start
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfigJsonFromEnv(JsonObject configJson) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserIdBeingUsedInNonAuthRecipe(AppIdentifier appIdentifier, String className, String userId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -670,14 +628,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> findNonAuthRecipesWhereForUserIdsUsed(AppIdentifier appIdentifier,
|
||||
List<String> userIds)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'findNonAuthRecipesWhereForUserIdsUsed' is not supported for in-memory db");
|
||||
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
@Override
|
||||
public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifier, String className, String userId)
|
||||
|
|
@ -766,12 +716,8 @@ public class Start
|
|||
}
|
||||
} else if (className.equals(JWTRecipeStorage.class.getName())) {
|
||||
/* Since JWT recipe tables do not store userId we do not add any data to them */
|
||||
} else if (className.equals(BulkImportStorage.class.getName())){
|
||||
//ignore
|
||||
} else if (className.equals(OAuthStorage.class.getName())) {
|
||||
/* Since OAuth tables store client-related data, we don't add user-specific data here */
|
||||
} else if (className.equals(SAMLStorage.class.getName())) {
|
||||
// no user specific data here
|
||||
} else if (className.equals(ActiveUsersStorage.class.getName())) {
|
||||
try {
|
||||
ActiveUsersQueries.updateUserLastActive(this, tenantIdentifier.toAppIdentifier(), userId);
|
||||
|
|
@ -948,13 +894,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signUpMultipleViaBulkImport_Transaction(TransactionConnection connection,
|
||||
List<EmailPasswordImportUser> users)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
throw new UnsupportedOperationException("'signUpMultipleViaBulkImport_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredEmailVerificationTokens() throws StorageQueryException {
|
||||
try {
|
||||
|
|
@ -1027,41 +966,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMultipleIsEmailVerified_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
Map<String, String> emailToUserId,
|
||||
boolean isEmailVerified)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
EmailVerificationQueries.updateMultipleUsersIsEmailVerified_Transaction(this, sqlCon, appIdentifier,
|
||||
emailToUserId, isEmailVerified);
|
||||
} catch (SQLException e) {
|
||||
if (e instanceof SQLiteException) {
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
String serverMessage = e.getMessage();
|
||||
|
||||
if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id"},
|
||||
new Object[]{appIdentifier.getAppId()})) {
|
||||
throw new TenantOrAppNotFoundException(appIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPSQLPrimKeyError = e instanceof SQLiteException && isPrimaryKeyError(
|
||||
e.getMessage(),
|
||||
Config.getConfig(this).getEmailVerificationTable(),
|
||||
new String[]{"app_id", "user_id", "email"});
|
||||
|
||||
if (!isEmailVerified || !isPSQLPrimKeyError) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
// we do not throw an error since the email is already verified
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteEmailVerificationUserInfo_Transaction(TransactionConnection con, AppIdentifier appIdentifier,
|
||||
String userId) throws StorageQueryException {
|
||||
|
|
@ -1172,13 +1076,6 @@ public class Start
|
|||
externalUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMultipleIsEmailVerifiedToExternalUserIds(AppIdentifier appIdentifier,
|
||||
Map<String, String> supertokensUserIdToExternalUserId)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'updateMultipleIsEmailVerifiedToExternalUserIds' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredPasswordResetTokens() throws StorageQueryException {
|
||||
try {
|
||||
|
|
@ -1213,20 +1110,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importThirdPartyUsers_Transaction(TransactionConnection con,
|
||||
List<ThirdPartyImportUser> usersToImport)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
throw new UnsupportedOperationException("'importThirdPartyUsers_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importPasswordlessUsers_Transaction(TransactionConnection con,
|
||||
List<PasswordlessImportUser> users)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'importPasswordlessUsers_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo signUp(
|
||||
TenantIdentifier tenantIdentifier, String id, String email,
|
||||
|
|
@ -1367,12 +1250,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> findExistingUserIds(AppIdentifier appIdentifier, List<String> userIds)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'findExistingUserIds' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String userId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -1413,17 +1290,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo getPrimaryUserByWebauthNCredentialId(TenantIdentifier tenantIdentifier,
|
||||
String webauthNCredentialId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return GeneralQueries.getPrimaryUserByWebauthNCredentialId(this, tenantIdentifier, webauthNCredentialId);
|
||||
} catch (SQLException | StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(TenantIdentifier tenantIdentifier, String thirdPartyId,
|
||||
String thirdPartyUserId) throws StorageQueryException {
|
||||
|
|
@ -1943,16 +1809,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, JsonObject> getMultipleUsersMetadatas_Transaction(AppIdentifier appIdentifier,
|
||||
TransactionConnection con,
|
||||
List<String> userIds)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'getMultipleUsersMetadatas_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId,
|
||||
JsonObject metadata)
|
||||
|
|
@ -1978,13 +1834,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMultipleUsersMetadatas_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
Map<String, JsonObject> metadataByUserId)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
throw new UnsupportedOperationException("'setMultipleUsersMetadatas_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteUserMetadata_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -2229,13 +2078,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> doesMultipleRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
List<String> roles) throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'doesMultipleRoleExist_Transaction' is not supported for in-memory db");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -2247,13 +2089,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRolesToUsers_Transaction(TransactionConnection connection,
|
||||
Map<TenantIdentifier, Map<String, List<String>>> rolesToUserByTenants)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'addRolesToUsers_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createUserIdMapping(AppIdentifier appIdentifier, String superTokensUserId, String externalUserId,
|
||||
@org.jetbrains.annotations.Nullable String externalUserIdInfo)
|
||||
|
|
@ -2294,13 +2129,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBulkUserIdMapping(AppIdentifier appIdentifier,
|
||||
Map<String, String> superTokensUserIdToExternalUserId)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'createBulkUserIdMapping' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -2762,7 +2590,7 @@ public class Start
|
|||
try {
|
||||
startTransaction(con -> {
|
||||
try {
|
||||
createDevice_Transaction(con, appIdentifier, device);
|
||||
createDevice_Transaction(con, new AppIdentifier(null, null), device);
|
||||
} catch (DeviceAlreadyExistsException | TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
|
|
@ -2802,13 +2630,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createDevices_Transaction(TransactionConnection con, AppIdentifier appIdentifier,
|
||||
List<TOTPDevice> devices)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
throw new UnsupportedOperationException("'createDevices_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TOTPDevice getDeviceByName_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId,
|
||||
String deviceName) throws StorageQueryException {
|
||||
|
|
@ -3017,31 +2838,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo getPrimaryUserByWebauthNCredentialId_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con,
|
||||
String credentialId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return GeneralQueries.getPrimaryUserByWebauthNCredentialId_Transaction(this, sqlCon, tenantIdentifier, credentialId);
|
||||
} catch (SQLException | StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AuthRecipeUserInfo> getPrimaryUsersByIds_Transaction(AppIdentifier appIdentifier,
|
||||
TransactionConnection con, List<String> userIds)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return GeneralQueries.getPrimaryUsersInfoForUserIds_Transaction(this, sqlCon, appIdentifier, userIds);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier,
|
||||
TransactionConnection con, String email)
|
||||
|
|
@ -3054,13 +2850,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo[] listPrimaryUsersByMultipleEmailsOrPhoneNumbersOrThirdparty_Transaction(
|
||||
AppIdentifier appIdentifier, TransactionConnection con, List<String> emails, List<String> phones,
|
||||
Map<String, String> thirdpartyIdToThirdpartyUserId) throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'listPrimaryUsersByMultipleEmailsOrPhoneNumbersOrThirdparty_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier,
|
||||
TransactionConnection con,
|
||||
|
|
@ -3116,19 +2905,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makePrimaryUsers_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
List<String> userIds) throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
// we do not bother returning if a row was updated here or not, cause it's happening
|
||||
// in a transaction anyway.
|
||||
GeneralQueries.makePrimaryUsers_Transaction(this, sqlCon, appIdentifier, userIds);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId,
|
||||
String primaryUserId) throws StorageQueryException {
|
||||
|
|
@ -3142,13 +2918,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void linkMultipleAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
Map<String, String> recipeUserIdByPrimaryUserId)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'linkMultipleAccounts_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String primaryUserId,
|
||||
String recipeUserId)
|
||||
|
|
@ -3226,14 +2995,6 @@ public class Start
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserIdMapping> getMultipleUserIdMapping_Transaction(TransactionConnection connection,
|
||||
AppIdentifier appIdentifier, List<String> userIds,
|
||||
boolean isSupertokensIds)
|
||||
throws StorageQueryException {
|
||||
throw new UnsupportedOperationException("'getMultipleUserIdMapping_Transaction' is not supported for in-memory db");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCountWithMoreThanOneLoginMethodOrTOTPEnabled(AppIdentifier appIdentifier)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -3531,444 +3292,4 @@ public class Start
|
|||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNStoredCredential saveCredentials(TenantIdentifier tenantIdentifier, WebAuthNStoredCredential credential)
|
||||
throws StorageQueryException, DuplicateCredentialException,
|
||||
io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException, TenantOrAppNotFoundException {
|
||||
try {
|
||||
return WebAuthNQueries.saveCredential(this, tenantIdentifier, credential);
|
||||
} catch (SQLException e) {
|
||||
if (e instanceof SQLiteException) {
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
String serverMessage = e.getMessage();
|
||||
|
||||
if (isPrimaryKeyError(serverMessage, config.getWebAuthNCredentialsTable(),
|
||||
new String[]{"app_id", "user_id", "id"})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.DuplicateCredentialException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getWebAuthNUsersTable(),
|
||||
new String[]{"user_id"},
|
||||
new Object[]{tenantIdentifier.getAppId()})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id", "tenant_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNOptions saveGeneratedOptions(TenantIdentifier tenantIdentifier, WebAuthNOptions optionsToSave)
|
||||
throws StorageQueryException, DuplicateOptionsIdException, TenantOrAppNotFoundException {
|
||||
try {
|
||||
return WebAuthNQueries.saveOptions(this, tenantIdentifier, optionsToSave);
|
||||
} catch (SQLException e) {
|
||||
if (e instanceof SQLiteException) {
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
String serverMessage = e.getMessage();
|
||||
|
||||
if (isPrimaryKeyError(serverMessage, config.getWebAuthNGeneratedOptionsTable(),
|
||||
new String[]{"app_id", "tenant_id", "id"})) {
|
||||
throw new DuplicateOptionsIdException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id", "tenant_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNOptions loadOptionsById(TenantIdentifier tenantIdentifier, String optionsId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return WebAuthNQueries.loadOptionsById(this, tenantIdentifier, optionsId);
|
||||
} catch (SQLException e){
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNStoredCredential loadCredentialByIdForUser(TenantIdentifier tenantIdentifier, String credentialId, String recipeUserId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return WebAuthNQueries.loadCredentialByIdForUser(this, tenantIdentifier, credentialId, recipeUserId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNOptions loadOptionsById_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
|
||||
String optionsId) throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return WebAuthNQueries.loadOptionsById_Transaction(this, sqlCon, tenantIdentifier, optionsId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNStoredCredential loadCredentialById_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con, String credentialId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return WebAuthNQueries.loadCredentialById_Transaction(this, sqlCon, tenantIdentifier, credentialId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo signUpWithCredentialsRegister_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
|
||||
String userId, String email, String relyingPartyId, WebAuthNStoredCredential credential)
|
||||
throws StorageQueryException, io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException, TenantOrAppNotFoundException,
|
||||
DuplicateUserEmailException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
return WebAuthNQueries.signUpWithCredentialRegister_Transaction(this, sqlCon, tenantIdentifier, userId, email, relyingPartyId, credential);
|
||||
} catch (StorageTransactionLogicException stle) {
|
||||
if (stle.actualException instanceof SQLiteException) {
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
String serverMessage = stle.actualException.getMessage();
|
||||
|
||||
if (isUniqueConstraintError(serverMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "email"})) {
|
||||
throw new DuplicateUserEmailException();
|
||||
} else if (isPrimaryKeyError(serverMessage, config.getWebAuthNUsersTable(),
|
||||
new String[]{"app_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getUsersTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(),
|
||||
new String[]{"app_id", "user_id"})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getAppsTable(),
|
||||
new String[]{"app_id"},
|
||||
new Object[]{tenantIdentifier.getAppId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id", "tenant_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
throw new StorageQueryException(stle.actualException);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo signUp_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
|
||||
String userId, String email, String relyingPartyId)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException, DuplicateUserEmailException,
|
||||
io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
return WebAuthNQueries.signUp_Transaction(this, sqlCon, tenantIdentifier, userId, email, relyingPartyId);
|
||||
} catch (StorageTransactionLogicException stle) {
|
||||
if (stle.actualException instanceof SQLiteException) {
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
String serverMessage = stle.actualException.getMessage();
|
||||
|
||||
if (isUniqueConstraintError(serverMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "email"})) {
|
||||
throw new DuplicateUserEmailException();
|
||||
} else if (isPrimaryKeyError(serverMessage, config.getWebAuthNUsersTable(),
|
||||
new String[]{"app_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getUsersTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"})
|
||||
|| isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(),
|
||||
new String[]{"app_id", "user_id"})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserIdException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getAppsTable(),
|
||||
new String[]{"app_id"},
|
||||
new Object[]{tenantIdentifier.getAppId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
} else if (isForeignKeyConstraintError(
|
||||
serverMessage,
|
||||
config.getTenantsTable(),
|
||||
new String[]{"app_id", "tenant_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) {
|
||||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
throw new StorageQueryException(stle.actualException);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthRecipeUserInfo getUserInfoByCredentialId_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con, String credentialId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return WebAuthNQueries.getUserInfoByCredentialId_Transaction(this, sqlCon, tenantIdentifier, credentialId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCounter_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con, String credentialId,
|
||||
long counter) throws StorageQueryException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
WebAuthNQueries.updateCounter_Transaction(this, sqlCon, tenantIdentifier, credentialId, counter);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addRecoverAccountToken(TenantIdentifier tenantIdentifier, AccountRecoveryTokenInfo accountRecoveryTokenInfo)
|
||||
throws DuplicateRecoverAccountTokenException, StorageQueryException {
|
||||
try {
|
||||
WebAuthNQueries.addRecoverAccountToken(this, tenantIdentifier, accountRecoveryTokenInfo);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCredential(TenantIdentifier tenantIdentifier, String userId, String credentialId)
|
||||
throws StorageQueryException, WebauthNCredentialNotExistsException {
|
||||
try {
|
||||
int rowsUpdated = WebAuthNQueries.removeCredential(this, tenantIdentifier, userId, credentialId);
|
||||
if(rowsUpdated < 1) {
|
||||
throw new WebauthNCredentialNotExistsException();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeOptions(TenantIdentifier tenantIdentifier, String optionsId)
|
||||
throws StorageQueryException, WebauthNOptionsNotExistsException {
|
||||
try {
|
||||
int rowsUpdated = WebAuthNQueries.removeOptions(this, tenantIdentifier, optionsId);
|
||||
if(rowsUpdated < 1) {
|
||||
throw new WebauthNOptionsNotExistsException();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebAuthNStoredCredential> listCredentialsForUser(TenantIdentifier tenantIdentifier, String userId)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return WebAuthNQueries.listCredentials(this, tenantIdentifier, userId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserEmail(TenantIdentifier tenantIdentifier, String userId, String newEmail)
|
||||
throws StorageQueryException, io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException,
|
||||
DuplicateUserEmailException {
|
||||
try {
|
||||
WebAuthNQueries.updateUserEmail(this, tenantIdentifier, userId, newEmail);
|
||||
} catch (StorageQueryException e) {
|
||||
if (e.getCause() instanceof SQLiteException){
|
||||
String errorMessage = e.getCause().getMessage();
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
|
||||
if (isUniqueConstraintError(errorMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "email"})) {
|
||||
throw new DuplicateUserEmailException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
errorMessage,
|
||||
config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), userId})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException();
|
||||
}
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUserEmail_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String userId,
|
||||
String newEmail)
|
||||
throws StorageQueryException, io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException,
|
||||
DuplicateUserEmailException {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
WebAuthNQueries.updateUserEmail_Transaction(this, sqlCon, tenantIdentifier, userId, newEmail);
|
||||
} catch (StorageQueryException e) {
|
||||
if (e.getCause() instanceof SQLiteException){
|
||||
String errorMessage = e.getCause().getMessage();
|
||||
SQLiteConfig config = Config.getConfig(this);
|
||||
|
||||
if (isUniqueConstraintError(errorMessage, config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "email"})) {
|
||||
throw new DuplicateUserEmailException();
|
||||
} else if (isForeignKeyConstraintError(
|
||||
errorMessage,
|
||||
config.getWebAuthNUserToTenantTable(),
|
||||
new String[]{"app_id", "tenant_id", "user_id"},
|
||||
new Object[]{tenantIdentifier.getAppId(), userId})) {
|
||||
throw new io.supertokens.pluginInterface.webauthn.exceptions.UserIdNotFoundException();
|
||||
}
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountRecoveryTokenInfo getAccountRecoveryTokenInfoByToken_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con,
|
||||
String token)
|
||||
throws StorageQueryException {
|
||||
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
return WebAuthNQueries.getAccountRecoveryTokenInfoByToken_Transaction(this, tenantIdentifier, sqlCon, token);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccountRecoveryTokenByEmail_Transaction(TenantIdentifier tenantIdentifier,
|
||||
TransactionConnection con, String email)
|
||||
throws StorageQueryException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
WebAuthNQueries.deleteAccountRecoveryTokenByEmail_Transaction(this, sqlCon, tenantIdentifier, email);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredAccountRecoveryTokens() throws StorageQueryException {
|
||||
try {
|
||||
WebAuthNQueries.deleteExpiredAccountRecoveryTokens(this);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteExpiredGeneratedOptions() throws StorageQueryException {
|
||||
try {
|
||||
WebAuthNQueries.deleteExpiredGeneratedOptions(this);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient createOrUpdateSAMLClient(TenantIdentifier tenantIdentifier, SAMLClient samlClient)
|
||||
throws StorageQueryException, io.supertokens.pluginInterface.saml.exception.DuplicateEntityIdException {
|
||||
try {
|
||||
return SAMLQueries.createOrUpdateSAMLClient(this, tenantIdentifier, samlClient.clientId, samlClient.clientSecret,
|
||||
samlClient.ssoLoginURL, samlClient.redirectURIs.toString(), samlClient.defaultRedirectURI,
|
||||
samlClient.idpEntityId, samlClient.idpSigningCertificate, samlClient.allowIDPInitiatedLogin,
|
||||
samlClient.enableRequestSigning);
|
||||
} catch (SQLException e) {
|
||||
String errorMessage = e.getMessage();
|
||||
String table = io.supertokens.inmemorydb.config.Config.getConfig(this).getSAMLClientsTable();
|
||||
if (isUniqueConstraintError(errorMessage, table, new String[]{"app_id", "tenant_id", "idp_entity_id"})) {
|
||||
throw new io.supertokens.pluginInterface.saml.exception.DuplicateEntityIdException();
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSAMLClient(TenantIdentifier tenantIdentifier, String clientId) throws StorageQueryException {
|
||||
return SAMLQueries.removeSAMLClient(this, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient getSAMLClient(TenantIdentifier tenantIdentifier, String clientId) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClient(this, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient getSAMLClientByIDPEntityId(TenantIdentifier tenantIdentifier, String idpEntityId) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClientByIDPEntityId(this, tenantIdentifier, idpEntityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SAMLClient> getSAMLClients(TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClients(this, tenantIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRelayStateInfo(TenantIdentifier tenantIdentifier, SAMLRelayStateInfo relayStateInfo, long relayStateValidity) throws StorageQueryException {
|
||||
SAMLQueries.saveRelayStateInfo(this, tenantIdentifier, relayStateInfo.relayState, relayStateInfo.clientId, relayStateInfo.state, relayStateInfo.redirectURI, relayStateValidity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLRelayStateInfo getRelayStateInfo(TenantIdentifier tenantIdentifier, String relayState) throws StorageQueryException {
|
||||
return SAMLQueries.getRelayStateInfo(this, tenantIdentifier, relayState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSAMLClaims(TenantIdentifier tenantIdentifier, String clientId, String code, JsonObject claims, long claimsValidity) throws StorageQueryException {
|
||||
SAMLQueries.saveSAMLClaims(this, tenantIdentifier, clientId, code, claims.toString(), claimsValidity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClaimsInfo getSAMLClaimsAndRemoveCode(TenantIdentifier tenantIdentifier, String code) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClaimsAndRemoveCode(this, tenantIdentifier, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredSAMLCodesAndRelayStates() throws StorageQueryException {
|
||||
SAMLQueries.removeExpiredSAMLCodesAndRelayStates(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countSAMLClients(TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
return SAMLQueries.countSAMLClients(this, tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,20 +184,4 @@ public class SQLiteConfig {
|
|||
public String getOAuthLogoutChallengesTable() {
|
||||
return "oauth_logout_challenges";
|
||||
}
|
||||
|
||||
public String getWebAuthNUsersTable(){ return "webauthn_users";}
|
||||
|
||||
public String getWebAuthNUserToTenantTable(){ return "webauthn_user_to_tenant"; }
|
||||
|
||||
public String getWebAuthNGeneratedOptionsTable() { return "webauthn_generated_options"; }
|
||||
|
||||
public String getWebAuthNCredentialsTable() { return "webauthn_credentials"; }
|
||||
|
||||
public String getWebAuthNAccountRecoveryTokenTable() { return "webauthn_account_recovery_tokens"; }
|
||||
|
||||
public String getSAMLClientsTable() { return "saml_clients"; }
|
||||
|
||||
public String getSAMLRelayStateTable() { return "saml_relay_state"; }
|
||||
|
||||
public String getSAMLClaimsTable() { return "saml_claims"; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,11 +58,6 @@ public class EmailPasswordQueries {
|
|||
+ ");";
|
||||
}
|
||||
|
||||
static String getQueryToCreateEmailPasswordUsersEmailIndex(Start start) {
|
||||
return "CREATE INDEX emailpassword_users_email_index ON "
|
||||
+ Config.getConfig(start).getEmailPasswordUsersTable() + "(app_id, email);";
|
||||
}
|
||||
|
||||
static String getQueryToCreateEmailPasswordUserToTenantTable(Start start) {
|
||||
String emailPasswordUserToTenantTable = Config.getConfig(start).getEmailPasswordUserToTenantTable();
|
||||
// @formatter:off
|
||||
|
|
@ -80,11 +75,6 @@ public class EmailPasswordQueries {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
static String getQueryToCreateEmailPasswordUserToTenantEmailIndex(Start start) {
|
||||
return "CREATE INDEX emailpassword_user_to_tenant_email_index ON "
|
||||
+ Config.getConfig(start).getEmailPasswordUserToTenantTable() + "(app_id, tenant_id, email);";
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordResetTokensTable(Start start) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getPasswordResetTokensTable() + " ("
|
||||
+ "app_id VARCHAR(64) DEFAULT 'public',"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.supertokens.inmemorydb.queries;
|
||||
|
||||
import io.supertokens.inmemorydb.ConnectionWithLocks;
|
||||
import io.supertokens.inmemorydb.PreparedStatementValueSetter;
|
||||
import io.supertokens.inmemorydb.Start;
|
||||
import io.supertokens.inmemorydb.Utils;
|
||||
import io.supertokens.inmemorydb.config.Config;
|
||||
|
|
@ -34,7 +33,8 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.*;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
|
||||
import static io.supertokens.inmemorydb.config.Config.getConfig;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
|
||||
|
|
@ -51,11 +51,6 @@ public class EmailVerificationQueries {
|
|||
+ ");";
|
||||
}
|
||||
|
||||
static String getQueryToCreateEmailVerificationVerifiedEmailsAppIdIndex(Start start) {
|
||||
return "CREATE INDEX emailverification_verified_emails_verified_appid_emails_index ON "
|
||||
+ Config.getConfig(start).getEmailVerificationTable() + "(app_id, email);";
|
||||
}
|
||||
|
||||
static String getQueryToCreateEmailVerificationTokensTable(Start start) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getEmailVerificationTokensTable() + " ("
|
||||
+ "app_id VARCHAR(64) DEFAULT 'public',"
|
||||
|
|
@ -108,32 +103,6 @@ public class EmailVerificationQueries {
|
|||
}
|
||||
}
|
||||
|
||||
public static void updateMultipleUsersIsEmailVerified_Transaction(Start start, Connection con, AppIdentifier appIdentifier,
|
||||
Map<String, String> emailToUserIds,
|
||||
boolean isEmailVerified)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
String QUERY;
|
||||
if (isEmailVerified) {
|
||||
QUERY = "INSERT INTO " + getConfig(start).getEmailVerificationTable()
|
||||
+ "(app_id, user_id, email) VALUES(?, ?, ?)";
|
||||
} else {
|
||||
QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTable()
|
||||
+ " WHERE app_id = ? AND user_id = ? AND email = ?";
|
||||
}
|
||||
|
||||
List<PreparedStatementValueSetter> setters = new ArrayList<>();
|
||||
for (Map.Entry<String, String> emailToUser : emailToUserIds.entrySet()) {
|
||||
setters.add(pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, emailToUser.getValue());
|
||||
pst.setString(3, emailToUser.getKey());
|
||||
});
|
||||
}
|
||||
|
||||
executeBatch(con, QUERY, setters);
|
||||
}
|
||||
|
||||
public static void deleteAllEmailVerificationTokensForUser_Transaction(Start start, Connection con,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String userId,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
package io.supertokens.inmemorydb.queries;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.inmemorydb.*;
|
||||
import io.supertokens.inmemorydb.ConnectionPool;
|
||||
import io.supertokens.inmemorydb.ConnectionWithLocks;
|
||||
import io.supertokens.inmemorydb.Start;
|
||||
import io.supertokens.inmemorydb.Utils;
|
||||
import io.supertokens.inmemorydb.config.Config;
|
||||
import io.supertokens.pluginInterface.KeyValueInfo;
|
||||
import io.supertokens.pluginInterface.RECIPE_ID;
|
||||
|
|
@ -26,7 +29,6 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
|||
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardSearchTags;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
|
@ -43,7 +45,8 @@ import java.util.stream.Collectors;
|
|||
import static io.supertokens.ProcessState.PROCESS_STATE.CREATING_NEW_TABLE;
|
||||
import static io.supertokens.ProcessState.getInstance;
|
||||
import static io.supertokens.inmemorydb.PreparedStatementValueSetter.NO_OP_SETTER;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.*;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
|
||||
import static io.supertokens.inmemorydb.config.Config.getConfig;
|
||||
import static io.supertokens.inmemorydb.queries.EmailPasswordQueries.getQueryToCreatePasswordResetTokenExpiryIndex;
|
||||
import static io.supertokens.inmemorydb.queries.EmailPasswordQueries.getQueryToCreatePasswordResetTokensTable;
|
||||
|
|
@ -259,7 +262,6 @@ public class GeneralQueries {
|
|||
|
||||
// index
|
||||
update(start, getQueryToCreateSessionExpiryIndex(start), NO_OP_SETTER);
|
||||
update(start, getQueryToCreateSessionAppIdUserIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getTenantConfigsTable())) {
|
||||
|
|
@ -293,19 +295,12 @@ public class GeneralQueries {
|
|||
if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUsersTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, EmailPasswordQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, EmailPasswordQueries.getQueryToCreateEmailPasswordUsersEmailIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUserToTenantTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, EmailPasswordQueries.getQueryToCreateEmailPasswordUserToTenantTable(start),
|
||||
NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, EmailPasswordQueries.getQueryToCreateEmailPasswordUserToTenantEmailIndex(start),
|
||||
NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getPasswordResetTokensTable())) {
|
||||
|
|
@ -318,9 +313,6 @@ public class GeneralQueries {
|
|||
if (!doesTableExists(start, Config.getConfig(start).getEmailVerificationTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, getQueryToCreateEmailVerificationTable(start), NO_OP_SETTER);
|
||||
|
||||
//index
|
||||
update(start, getQueryToCreateEmailVerificationVerifiedEmailsAppIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getEmailVerificationTokensTable())) {
|
||||
|
|
@ -333,7 +325,6 @@ public class GeneralQueries {
|
|||
if (!doesTableExists(start, Config.getConfig(start).getThirdPartyUsersTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, ThirdPartyQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, ThirdPartyQueries.getQueryToThirdPartyUserEmailIndex(start), NO_OP_SETTER);
|
||||
update(start, ThirdPartyQueries.getQueryToThirdPartyUserIdIndex(start), NO_OP_SETTER);
|
||||
|
|
@ -342,9 +333,6 @@ public class GeneralQueries {
|
|||
if (!doesTableExists(start, Config.getConfig(start).getThirdPartyUserToTenantTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, ThirdPartyQueries.getQueryToCreateThirdPartyUserToTenantTable(start), NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, ThirdPartyQueries.getQueryToCreateThirdPartyUserToTenantThirdPartyUserIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getJWTSigningKeysTable())) {
|
||||
|
|
@ -355,20 +343,12 @@ public class GeneralQueries {
|
|||
if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUsersTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, PasswordlessQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, PasswordlessQueries.getQueryToCreatePasswordlessUsersEmailIndex(start), NO_OP_SETTER);
|
||||
update(start, PasswordlessQueries.getQueryToCreatePasswordlessUsersPhoneNumberIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUserToTenantTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, PasswordlessQueries.getQueryToCreatePasswordlessUserToTenantTable(start),
|
||||
NO_OP_SETTER);
|
||||
|
||||
// index
|
||||
update(start, PasswordlessQueries.getQueryToCreatePasswordlessUserToTenantEmailIndex(start), NO_OP_SETTER);
|
||||
update(start, PasswordlessQueries.getQueryToCreatePasswordlessUserToTenantPhoneNumberIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getPasswordlessDevicesTable())) {
|
||||
|
|
@ -410,7 +390,6 @@ public class GeneralQueries {
|
|||
|
||||
// index
|
||||
update(start, UserRolesQueries.getQueryToCreateUserRolesRoleIndex(start), NO_OP_SETTER);
|
||||
update(start, UserRolesQueries.getQueryToCreateUserRolesUserIdAppIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getUserIdMappingTable())) {
|
||||
|
|
@ -478,71 +457,6 @@ public class GeneralQueries {
|
|||
// index
|
||||
update(start, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if(!doesTableExists(start, Config.getConfig(start).getWebAuthNUsersTable())){
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNUsersTable(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if(!doesTableExists(start, Config.getConfig(start).getWebAuthNUserToTenantTable())){
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNUsersToTenantTable(start), NO_OP_SETTER);
|
||||
|
||||
//index
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNUserToTenantEmailIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if(!doesTableExists(start, Config.getConfig(start).getWebAuthNGeneratedOptionsTable())){
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNGeneratedOptionsTable(start), NO_OP_SETTER);
|
||||
//index
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNChallengeExpiresIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if(!doesTableExists(start, Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable())){
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNAccountRecoveryTokenTable(start), NO_OP_SETTER);
|
||||
|
||||
//index
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNAccountRecoveryTokenTokenIndex(start), NO_OP_SETTER);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNAccountRecoveryTokenEmailIndex(start), NO_OP_SETTER);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNAccountRecoveryTokenExpiresAtIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if(!doesTableExists(start, Config.getConfig(start).getWebAuthNCredentialsTable())){
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNCredentialsTable(start), NO_OP_SETTER);
|
||||
|
||||
//index
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNCredentialsUserIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
// SAML tables
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLClientsTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClientsTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClientsAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLRelayStateTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateExpiresAtIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLClaimsTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsExpiresAtIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setKeyValue_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
|
||||
|
|
@ -749,7 +663,7 @@ public class GeneralQueries {
|
|||
// attach email tags to queries
|
||||
QUERY = QUERY +
|
||||
" WHERE (emailpasswordTable.app_id = ? AND emailpasswordTable.tenant_id = ?) AND"
|
||||
+ " ( emailpasswordTable.email LIKE ? OR emailpasswordTable.email LIKE ? ";
|
||||
+ " (emailpasswordTable.email LIKE ? OR emailpasswordTable.email LIKE ?)";
|
||||
queryList.add(tenantIdentifier.getAppId());
|
||||
queryList.add(tenantIdentifier.getTenantId());
|
||||
queryList.add(dashboardSearchTags.emails.get(0) + "%");
|
||||
|
|
@ -760,8 +674,6 @@ public class GeneralQueries {
|
|||
queryList.add("%@" + dashboardSearchTags.emails.get(i) + "%");
|
||||
}
|
||||
|
||||
QUERY += " )";
|
||||
|
||||
USER_SEARCH_TAG_CONDITION.append("SELECT * FROM ( ").append(QUERY)
|
||||
.append(" LIMIT 1000) AS emailpasswordResultTable");
|
||||
}
|
||||
|
|
@ -901,45 +813,6 @@ public class GeneralQueries {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// check if we should search through the webauthn table
|
||||
if (dashboardSearchTags.shouldWebauthnTableBeSearched()) {
|
||||
String QUERY = "SELECT allAuthUsersTable.*" + " FROM " + getConfig(start).getUsersTable()
|
||||
+ " AS allAuthUsersTable" +
|
||||
" JOIN " + getConfig(start).getWebAuthNUserToTenantTable()
|
||||
+ " AS webauthnTable ON allAuthUsersTable.app_id = webauthnTable.app_id AND "
|
||||
+ "allAuthUsersTable.tenant_id = webauthnTable.tenant_id AND "
|
||||
+ "allAuthUsersTable.user_id = webauthnTable.user_id";
|
||||
|
||||
// attach email tags to queries
|
||||
QUERY = QUERY +
|
||||
" WHERE (webauthnTable.app_id = ? AND webauthnTable.tenant_id = ?) AND"
|
||||
+ " ( webauthnTable.email LIKE ? OR webauthnTable.email LIKE ? ";
|
||||
queryList.add(tenantIdentifier.getAppId());
|
||||
queryList.add(tenantIdentifier.getTenantId());
|
||||
queryList.add(dashboardSearchTags.emails.get(0) + "%");
|
||||
queryList.add("%@" + dashboardSearchTags.emails.get(0) + "%");
|
||||
for (int i = 1; i < dashboardSearchTags.emails.size(); i++) {
|
||||
QUERY += " OR webauthnTable.email LIKE ? OR webauthnTable.email LIKE ?";
|
||||
queryList.add(dashboardSearchTags.emails.get(i) + "%");
|
||||
queryList.add("%@" + dashboardSearchTags.emails.get(i) + "%");
|
||||
}
|
||||
|
||||
QUERY += " )";
|
||||
|
||||
// check if we need to append this to an existing search query
|
||||
if (USER_SEARCH_TAG_CONDITION.length() != 0) {
|
||||
USER_SEARCH_TAG_CONDITION.append(" UNION ").append("SELECT * FROM ( ").append(QUERY)
|
||||
.append(" LIMIT 1000) AS webauthnResultTable");
|
||||
|
||||
} else {
|
||||
USER_SEARCH_TAG_CONDITION.append("SELECT * FROM ( ").append(QUERY)
|
||||
.append(" LIMIT 1000) AS webauthnResultTable");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (USER_SEARCH_TAG_CONDITION.toString().length() == 0) {
|
||||
usersFromQuery = new ArrayList<>();
|
||||
} else {
|
||||
|
|
@ -1087,32 +960,6 @@ public class GeneralQueries {
|
|||
}
|
||||
}
|
||||
|
||||
public static void makePrimaryUsers_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier,
|
||||
List<String> userIds)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
String users_update_QUERY = "UPDATE " + getConfig(start).getUsersTable() +
|
||||
" SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?";
|
||||
String appid_to_userid_update_QUERY = "UPDATE " + getConfig(start).getAppIdToUserIdTable() +
|
||||
" SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?";
|
||||
|
||||
List<PreparedStatementValueSetter> usersSetter = new ArrayList<>();
|
||||
List<PreparedStatementValueSetter> appIdToUserIdSetter = new ArrayList<>();
|
||||
|
||||
for(String userId: userIds) {
|
||||
usersSetter.add(pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
});
|
||||
appIdToUserIdSetter.add(pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
});
|
||||
}
|
||||
executeBatch(sqlCon, users_update_QUERY, usersSetter);
|
||||
executeBatch(sqlCon, appid_to_userid_update_QUERY, appIdToUserIdSetter);
|
||||
}
|
||||
|
||||
public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier,
|
||||
String recipeUserId, String primaryUserId)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
|
@ -1143,47 +990,6 @@ public class GeneralQueries {
|
|||
}
|
||||
}
|
||||
|
||||
public static void linkMultipleAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier,
|
||||
Map<String, String> recipeUserIdToPrimaryUserId)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
if(recipeUserIdToPrimaryUserId == null || recipeUserIdToPrimaryUserId.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
String update_users_QUERY = "UPDATE " + getConfig(start).getUsersTable() +
|
||||
" SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " +
|
||||
"user_id = ?";
|
||||
|
||||
String update_appid_to_userid_QUERY = "UPDATE " + getConfig(start).getAppIdToUserIdTable() +
|
||||
" SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " +
|
||||
"user_id = ?";
|
||||
|
||||
List<PreparedStatementValueSetter> updateUsersSetter = new ArrayList<>();
|
||||
List<PreparedStatementValueSetter> updateAppIdToUserIdSetter = new ArrayList<>();
|
||||
|
||||
for(Map.Entry<String, String> linkEntry : recipeUserIdToPrimaryUserId.entrySet()) {
|
||||
String primaryUserId = linkEntry.getValue();
|
||||
String recipeUserId = linkEntry.getKey();
|
||||
|
||||
updateUsersSetter.add(pst -> {
|
||||
pst.setString(1, primaryUserId);
|
||||
pst.setString(2, appIdentifier.getAppId());
|
||||
pst.setString(3, recipeUserId);
|
||||
});
|
||||
updateUsersSetter.add(pst -> {
|
||||
pst.setString(1, primaryUserId);
|
||||
pst.setString(2, appIdentifier.getAppId());
|
||||
pst.setString(3, recipeUserId);
|
||||
});
|
||||
}
|
||||
|
||||
executeBatch(sqlCon, update_users_QUERY, updateUsersSetter);
|
||||
executeBatch(sqlCon, update_appid_to_userid_QUERY, updateAppIdToUserIdSetter);
|
||||
updateTimeJoinedForPrimaryUsers_Transaction(start, sqlCon, appIdentifier,
|
||||
new ArrayList<>(recipeUserIdToPrimaryUserId.values()));
|
||||
}
|
||||
|
||||
public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier,
|
||||
String primaryUserId, String recipeUserId)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
|
@ -1302,13 +1108,6 @@ public class GeneralQueries {
|
|||
|
||||
userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail_Transaction(start, sqlCon, appIdentifier, email));
|
||||
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdForAppUsingEmail_Transaction(start, sqlCon,
|
||||
appIdentifier, email);
|
||||
if(webauthnUserId != null) {
|
||||
userIds.add(webauthnUserId);
|
||||
}
|
||||
|
||||
|
||||
// remove duplicates from userIds
|
||||
Set<String> userIdsSet = new HashSet<>(userIds);
|
||||
userIds = new ArrayList<>(userIdsSet);
|
||||
|
|
@ -1340,11 +1139,6 @@ public class GeneralQueries {
|
|||
|
||||
userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email));
|
||||
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdForTenantUsingEmail(start, tenantIdentifier, email);
|
||||
if(webauthnUserId != null) {
|
||||
userIds.add(webauthnUserId);
|
||||
}
|
||||
|
||||
// remove duplicates from userIds
|
||||
Set<String> userIdsSet = new HashSet<>(userIds);
|
||||
userIds = new ArrayList<>(userIdsSet);
|
||||
|
|
@ -1389,34 +1183,6 @@ public class GeneralQueries {
|
|||
return getPrimaryUserInfoForUserId(start, tenantIdentifier.toAppIdentifier(), userId);
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo getPrimaryUserByWebauthNCredentialId(Start start,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String credentialId)
|
||||
throws StorageQueryException, SQLException, StorageTransactionLogicException {
|
||||
AuthRecipeUserInfo webauthnUser = start.startTransaction(con -> {
|
||||
try {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
return getPrimaryUserByWebauthNCredentialId_Transaction(start, sqlCon, tenantIdentifier,
|
||||
credentialId);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
});
|
||||
return webauthnUser;
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo getPrimaryUserByWebauthNCredentialId_Transaction(Start start,
|
||||
Connection connection,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String credentialId)
|
||||
throws StorageQueryException, SQLException, StorageTransactionLogicException {
|
||||
|
||||
AuthRecipeUserInfo webauthnUser = WebAuthNQueries.getUserInfoByCredentialId_Transaction(start, connection,
|
||||
tenantIdentifier, credentialId);
|
||||
return getPrimaryUserInfoForUserId_Transaction(start, connection, tenantIdentifier.toAppIdentifier(),
|
||||
webauthnUser.getSupertokensUserId());
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdStrForUserId(Start start, AppIdentifier appIdentifier, String id)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable() +
|
||||
|
|
@ -1455,17 +1221,6 @@ public class GeneralQueries {
|
|||
return result.get(0);
|
||||
}
|
||||
|
||||
public static List<AuthRecipeUserInfo> getPrimaryUsersInfoForUserIds_Transaction(Start start, Connection con,
|
||||
AppIdentifier appIdentifier, List<String> ids)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
List<AuthRecipeUserInfo> result = getPrimaryUserInfoForUserIds_Transaction(start, con, appIdentifier, ids);
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<AuthRecipeUserInfo> getPrimaryUserInfoForUserIds(Start start,
|
||||
AppIdentifier appIdentifier,
|
||||
List<String> userIds)
|
||||
|
|
@ -1530,7 +1285,6 @@ public class GeneralQueries {
|
|||
loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier));
|
||||
loginMethods.addAll(
|
||||
PasswordlessQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier));
|
||||
loginMethods.addAll(WebAuthNQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier));
|
||||
|
||||
Map<String, LoginMethod> recipeUserIdToLoginMethodMap = new HashMap<>();
|
||||
for (LoginMethod loginMethod : loginMethods) {
|
||||
|
|
@ -1630,8 +1384,6 @@ public class GeneralQueries {
|
|||
loginMethods.addAll(
|
||||
PasswordlessQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch,
|
||||
appIdentifier));
|
||||
loginMethods.addAll(WebAuthNQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch,
|
||||
appIdentifier));
|
||||
|
||||
Map<String, LoginMethod> recipeUserIdToLoginMethodMap = new HashMap<>();
|
||||
for (LoginMethod loginMethod : loginMethods) {
|
||||
|
|
@ -1918,27 +1670,6 @@ public class GeneralQueries {
|
|||
});
|
||||
}
|
||||
|
||||
public static void updateTimeJoinedForPrimaryUsers_Transaction(Start start, Connection sqlCon,
|
||||
AppIdentifier appIdentifier, List<String> primaryUserIds)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "UPDATE " + getConfig(start).getUsersTable() +
|
||||
" SET primary_or_recipe_user_time_joined = (SELECT MIN(time_joined) FROM " +
|
||||
getConfig(start).getUsersTable() + " WHERE app_id = ? AND primary_or_recipe_user_id = ?) WHERE " +
|
||||
" app_id = ? AND primary_or_recipe_user_id = ?";
|
||||
|
||||
List<PreparedStatementValueSetter> setters = new ArrayList<>();
|
||||
for(String primaryUserId : primaryUserIds) {
|
||||
setters.add(pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, primaryUserId);
|
||||
pst.setString(3, appIdentifier.getAppId());
|
||||
pst.setString(4, primaryUserId);
|
||||
});
|
||||
}
|
||||
|
||||
executeBatch(sqlCon, QUERY, setters);
|
||||
}
|
||||
|
||||
private static class AllAuthRecipeUsersResultHolder {
|
||||
String userId;
|
||||
String tenantId;
|
||||
|
|
|
|||
|
|
@ -60,16 +60,6 @@ public class PasswordlessQueries {
|
|||
+ ");";
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordlessUsersEmailIndex(Start start) {
|
||||
return "CREATE INDEX passwordless_users_email_index ON "
|
||||
+ Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, email);";
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordlessUsersPhoneNumberIndex(Start start) {
|
||||
return "CREATE INDEX passwordless_users_phone_number_index ON "
|
||||
+ Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, phone_number);";
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordlessUserToTenantTable(Start start) {
|
||||
String passwordlessUserToTenantTable = Config.getConfig(start).getPasswordlessUserToTenantTable();
|
||||
// @formatter:off
|
||||
|
|
@ -89,16 +79,6 @@ public class PasswordlessQueries {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordlessUserToTenantEmailIndex(Start start) {
|
||||
return "CREATE INDEX passwordless_user_to_tenant_email_index ON "
|
||||
+ Config.getConfig(start).getPasswordlessUserToTenantTable() + "(app_id, tenant_id, email);";
|
||||
}
|
||||
|
||||
static String getQueryToCreatePasswordlessUserToTenantPhoneNumberIndex(Start start) {
|
||||
return "CREATE INDEX passwordless_user_to_tenant_phone_number_index ON "
|
||||
+ Config.getConfig(start).getPasswordlessUserToTenantTable() + "(app_id, tenant_id, phone_number);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateDevicesTable(Start start) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getPasswordlessDevicesTable() + " ("
|
||||
+ "app_id VARCHAR(64) DEFAULT 'public',"
|
||||
|
|
|
|||
|
|
@ -1,458 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 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.inmemorydb.queries;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
|
||||
import io.supertokens.inmemorydb.Start;
|
||||
import io.supertokens.inmemorydb.config.Config;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClient;
|
||||
import io.supertokens.pluginInterface.saml.SAMLRelayStateInfo;
|
||||
|
||||
public class SAMLQueries {
|
||||
public static String getQueryToCreateSAMLClientsTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "client_secret TEXT,"
|
||||
+ "sso_login_url TEXT NOT NULL,"
|
||||
+ "redirect_uris TEXT NOT NULL," // store JsonArray.toString()
|
||||
+ "default_redirect_uri VARCHAR(1024) NOT NULL,"
|
||||
+ "idp_entity_id VARCHAR(1024),"
|
||||
+ "idp_signing_certificate TEXT,"
|
||||
+ "allow_idp_initiated_login BOOLEAN NOT NULL DEFAULT FALSE,"
|
||||
+ "enable_request_signing BOOLEAN NOT NULL DEFAULT TRUE,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "updated_at BIGINT NOT NULL,"
|
||||
+ "UNIQUE (app_id, tenant_id, idp_entity_id),"
|
||||
+ "PRIMARY KEY (app_id, tenant_id, client_id),"
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClientsAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_clients_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "relay_state VARCHAR(255) NOT NULL,"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "state TEXT,"
|
||||
+ "redirect_uri VARCHAR(1024) NOT NULL,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "expires_at BIGINT NOT NULL,"
|
||||
+ "PRIMARY KEY (relay_state)," // relayState must be unique
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_relay_state_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateExpiresAtIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_relay_state_expires_at_index ON " + table + "(expires_at);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "code VARCHAR(255) NOT NULL,"
|
||||
+ "claims TEXT NOT NULL,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "expires_at BIGINT NOT NULL,"
|
||||
+ "PRIMARY KEY (code),"
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_claims_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsExpiresAtIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_claims_expires_at_index ON " + table + "(expires_at);";
|
||||
}
|
||||
|
||||
public static void saveRelayStateInfo(Start start, TenantIdentifier tenantIdentifier,
|
||||
String relayState, String clientId, String state, String redirectURI, long relayStateValidity)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, relay_state, client_id, state, redirect_uri, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try {
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, relayState);
|
||||
pst.setString(4, clientId);
|
||||
if (state != null) {
|
||||
pst.setString(5, state);
|
||||
} else {
|
||||
pst.setNull(5, java.sql.Types.VARCHAR);
|
||||
}
|
||||
pst.setString(6, redirectURI);
|
||||
pst.setLong(7, System.currentTimeMillis());
|
||||
pst.setLong(8, System.currentTimeMillis() + relayStateValidity);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLRelayStateInfo getRelayStateInfo(Start start, TenantIdentifier tenantIdentifier, String relayState)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String QUERY = "SELECT client_id, state, redirect_uri, expires_at FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND relay_state = ? AND expires_at >= ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, relayState);
|
||||
pst.setLong(4, System.currentTimeMillis());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String clientId = result.getString("client_id");
|
||||
String state = result.getString("state"); // may be null
|
||||
String redirectURI = result.getString("redirect_uri");
|
||||
return new SAMLRelayStateInfo(relayState, clientId, state, redirectURI);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveSAMLClaims(Start start, TenantIdentifier tenantIdentifier, String clientId, String code, String claimsJson, long claimsValidity)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, client_id, code, claims, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try {
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
pst.setString(4, code);
|
||||
pst.setString(5, claimsJson);
|
||||
pst.setLong(6, System.currentTimeMillis());
|
||||
pst.setLong(7, System.currentTimeMillis() + claimsValidity);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClaimsInfo getSAMLClaimsAndRemoveCode(Start start, TenantIdentifier tenantIdentifier, String code)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String QUERY = "SELECT client_id, claims FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND code = ? AND expires_at >= ?";
|
||||
try {
|
||||
SAMLClaimsInfo claimsInfo = execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, code);
|
||||
pst.setLong(4, System.currentTimeMillis());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String clientId = result.getString("client_id");
|
||||
JsonObject claims = com.google.gson.JsonParser.parseString(result.getString("claims")).getAsJsonObject();
|
||||
return new SAMLClaimsInfo(clientId, claims);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (claimsInfo != null) {
|
||||
String DELETE = "DELETE FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND code = ?";
|
||||
update(start, DELETE, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, code);
|
||||
});
|
||||
}
|
||||
return claimsInfo;
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClient createOrUpdateSAMLClient(
|
||||
Start start,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String clientId,
|
||||
String clientSecret,
|
||||
String ssoLoginURL,
|
||||
String redirectURIsJson,
|
||||
String defaultRedirectURI,
|
||||
String idpEntityId,
|
||||
String idpSigningCertificate,
|
||||
boolean allowIDPInitiatedLogin,
|
||||
boolean enableRequestSigning)
|
||||
throws StorageQueryException, SQLException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing, created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
||||
"ON CONFLICT (app_id, tenant_id, client_id) DO UPDATE SET " +
|
||||
"client_secret = ?, sso_login_url = ?, redirect_uris = ?, default_redirect_uri = ?, idp_entity_id = ?, idp_signing_certificate = ?, allow_idp_initiated_login = ?, enable_request_signing = ?, updated_at = ?";
|
||||
long now = System.currentTimeMillis();
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
if (clientSecret != null) {
|
||||
pst.setString(4, clientSecret);
|
||||
} else {
|
||||
pst.setNull(4, Types.VARCHAR);
|
||||
}
|
||||
pst.setString(5, ssoLoginURL);
|
||||
pst.setString(6, redirectURIsJson);
|
||||
pst.setString(7, defaultRedirectURI);
|
||||
if (idpEntityId != null) {
|
||||
pst.setString(8, idpEntityId);
|
||||
} else {
|
||||
pst.setNull(8, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (idpSigningCertificate != null) {
|
||||
pst.setString(9, idpSigningCertificate);
|
||||
} else {
|
||||
pst.setNull(9, Types.VARCHAR);
|
||||
}
|
||||
pst.setBoolean(10, allowIDPInitiatedLogin);
|
||||
pst.setBoolean(11, enableRequestSigning);
|
||||
pst.setLong(12, now);
|
||||
pst.setLong(13, now);
|
||||
|
||||
if (clientSecret != null) {
|
||||
pst.setString(14, clientSecret);
|
||||
} else {
|
||||
pst.setNull(14, Types.VARCHAR);
|
||||
}
|
||||
pst.setString(15, ssoLoginURL);
|
||||
pst.setString(16, redirectURIsJson);
|
||||
pst.setString(17, defaultRedirectURI);
|
||||
if (idpEntityId != null) {
|
||||
pst.setString(18, idpEntityId);
|
||||
} else {
|
||||
pst.setNull(18, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (idpSigningCertificate != null) {
|
||||
pst.setString(19, idpSigningCertificate);
|
||||
} else {
|
||||
pst.setNull(19, Types.VARCHAR);
|
||||
}
|
||||
pst.setBoolean(20, allowIDPInitiatedLogin);
|
||||
pst.setBoolean(21, enableRequestSigning);
|
||||
pst.setLong(22, now);
|
||||
});
|
||||
|
||||
return getSAMLClient(start, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
public static SAMLClient getSAMLClient(Start start, TenantIdentifier tenantIdentifier, String clientId)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND client_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String idpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
return new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, idpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClient getSAMLClientByIDPEntityId(Start start, TenantIdentifier tenantIdentifier, String idpEntityId) throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND idp_entity_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, idpEntityId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String fetchedIdpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
return new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, fetchedIdpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<SAMLClient> getSAMLClients(Start start, TenantIdentifier tenantIdentifier)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
List<SAMLClient> clients = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String idpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
clients.add(new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, idpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning));
|
||||
}
|
||||
return clients;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeSAMLClient(Start start, TenantIdentifier tenantIdentifier, String clientId)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "DELETE FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND client_id = ?";
|
||||
try {
|
||||
return update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
}) > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeExpiredSAMLCodesAndRelayStates(Start start) throws StorageQueryException {
|
||||
try {
|
||||
{
|
||||
String QUERY = "DELETE FROM " + Config.getConfig(start).getSAMLClaimsTable() + " WHERE expires_at <= ?";
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
{
|
||||
String QUERY = "DELETE FROM " + Config.getConfig(start).getSAMLRelayStateTable() + " WHERE expires_at <= ?";
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int countSAMLClients(Start start, TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT COUNT(*) as c FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getInt("c");
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,11 +76,6 @@ public class SessionQueries {
|
|||
+ Config.getConfig(start).getSessionInfoTable() + "(expires_at);";
|
||||
}
|
||||
|
||||
static String getQueryToCreateSessionAppIdUserIdIndex(Start start) {
|
||||
return "CREATE INDEX session_info_user_id_app_id_index ON "
|
||||
+ Config.getConfig(start).getSessionInfoTable() + "(user_id, app_id);";
|
||||
}
|
||||
|
||||
public static void createNewSession(Start start, TenantIdentifier tenantIdentifier, String sessionHandle,
|
||||
String userId, String refreshTokenHash2,
|
||||
JsonObject userDataInDatabase, long expiry, JsonObject userDataInJWT,
|
||||
|
|
@ -133,60 +128,21 @@ public class SessionQueries {
|
|||
return null;
|
||||
}
|
||||
|
||||
QUERY = "SELECT external_user_id, 0 as o " +
|
||||
"FROM " + getConfig(start).getUserIdMappingTable() + " um2 " +
|
||||
"WHERE um2.app_id = ? AND um2.supertokens_user_id IN (" +
|
||||
"SELECT primary_or_recipe_user_id " +
|
||||
"FROM " + getConfig(start).getUsersTable() + " " +
|
||||
"WHERE app_id = ? AND user_id IN (" +
|
||||
"SELECT user_id FROM (" +
|
||||
"SELECT um1.supertokens_user_id as user_id, 0 as o1 " +
|
||||
"FROM " + getConfig(start).getUserIdMappingTable() + " um1 " +
|
||||
"WHERE um1.app_id = ? AND um1.external_user_id = ? " +
|
||||
"UNION " +
|
||||
"SELECT ?, 1 as o1 " +
|
||||
"ORDER BY o1 ASC " +
|
||||
") uid1" +
|
||||
")" +
|
||||
") " +
|
||||
"UNION " +
|
||||
"SELECT primary_or_recipe_user_id, 1 as o " +
|
||||
"FROM " + getConfig(start).getUsersTable() + " " +
|
||||
"WHERE app_id = ? AND user_id IN (" +
|
||||
"SELECT user_ID FROM (" +
|
||||
"SELECT um1.supertokens_user_id as user_id, 0 as o2 " +
|
||||
"FROM " + getConfig(start).getUserIdMappingTable() + " um1 " +
|
||||
"WHERE um1.app_id = ? AND um1.external_user_id = ? " +
|
||||
"UNION " +
|
||||
"SELECT ?, 1 as o2 " +
|
||||
"ORDER BY o2 ASC " +
|
||||
") uid2 " +
|
||||
") " +
|
||||
"ORDER BY o ASC " +
|
||||
"LIMIT 1";
|
||||
QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable()
|
||||
+ " WHERE app_id = ? AND user_id = ?";
|
||||
|
||||
String finalUserId = execute(con, QUERY, pst -> {
|
||||
return execute(con, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getAppId());
|
||||
pst.setString(3, tenantIdentifier.getAppId());
|
||||
pst.setString(4, sessionInfo.recipeUserId);
|
||||
pst.setString(5, sessionInfo.recipeUserId);
|
||||
pst.setString(6, tenantIdentifier.getAppId());
|
||||
pst.setString(7, tenantIdentifier.getAppId());
|
||||
pst.setString(8, sessionInfo.recipeUserId);
|
||||
pst.setString(9, sessionInfo.recipeUserId);
|
||||
pst.setString(2, sessionInfo.recipeUserId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getString(1);
|
||||
String primaryUserId = result.getString("primary_or_recipe_user_id");
|
||||
if (primaryUserId != null) {
|
||||
sessionInfo.userId = primaryUserId;
|
||||
}
|
||||
}
|
||||
return sessionInfo.recipeUserId;
|
||||
return sessionInfo;
|
||||
});
|
||||
|
||||
if (finalUserId != null) {
|
||||
sessionInfo.userId = finalUserId;
|
||||
}
|
||||
|
||||
return sessionInfo;
|
||||
}
|
||||
|
||||
public static void updateSessionInfo_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
|
||||
|
|
|
|||
|
|
@ -85,11 +85,6 @@ public class ThirdPartyQueries {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
static String getQueryToCreateThirdPartyUserToTenantThirdPartyUserIdIndex(Start start) {
|
||||
return "CREATE INDEX thirdparty_user_to_tenant_third_party_user_id_index ON "
|
||||
+ Config.getConfig(start).getThirdPartyUserToTenantTable() + "(app_id, tenant_id, third_party_id, third_party_user_id);";
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String id, String email,
|
||||
LoginMethod.ThirdParty thirdParty, long timeJoined)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
|
|
|
|||
|
|
@ -107,16 +107,12 @@ public class UserIdMappingQueries {
|
|||
String userId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
|
||||
+ " WHERE app_id = ? AND supertokens_user_id = ?"
|
||||
+ " UNION ALL "
|
||||
+ "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
|
||||
+ " WHERE app_id = ? AND external_user_id = ?";
|
||||
+ " WHERE app_id = ? AND (supertokens_user_id = ? OR external_user_id = ?)";
|
||||
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
pst.setString(3, appIdentifier.getAppId());
|
||||
pst.setString(4, userId);
|
||||
pst.setString(3, userId);
|
||||
}, result -> {
|
||||
ArrayList<UserIdMapping> userIdMappingArray = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
|
|
@ -133,16 +129,12 @@ public class UserIdMappingQueries {
|
|||
String userId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
|
||||
+ " WHERE app_id = ? AND supertokens_user_id = ?"
|
||||
+ " UNION ALL "
|
||||
+ "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
|
||||
+ " WHERE app_id = ? AND external_user_id = ?";
|
||||
+ " WHERE app_id = ? AND (supertokens_user_id = ? OR external_user_id = ?)";
|
||||
|
||||
return execute(sqlCon, QUERY, pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
pst.setString(3, appIdentifier.getAppId());
|
||||
pst.setString(4, userId);
|
||||
pst.setString(3, userId);
|
||||
}, result -> {
|
||||
ArrayList<UserIdMapping> userIdMappingArray = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
|
|
|
|||
|
|
@ -66,11 +66,6 @@ public class UserRolesQueries {
|
|||
+ Config.getConfig(start).getUserRolesPermissionsTable() + "(app_id, permission);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateUserRolesUserIdAppIdIndex(Start start) {
|
||||
return "CREATE INDEX user_roles_app_id_user_id_index ON " + Config.getConfig(start).getUserRolesTable() +
|
||||
"(app_id, user_id)";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateUserRolesTable(Start start) {
|
||||
String tableName = Config.getConfig(start).getUserRolesTable();
|
||||
// @formatter:off
|
||||
|
|
|
|||
|
|
@ -1,777 +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.inmemorydb.queries;
|
||||
|
||||
import io.supertokens.inmemorydb.Start;
|
||||
import io.supertokens.inmemorydb.Utils;
|
||||
import io.supertokens.inmemorydb.config.Config;
|
||||
import io.supertokens.pluginInterface.RowMapper;
|
||||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.webauthn.AccountRecoveryTokenInfo;
|
||||
import io.supertokens.pluginInterface.webauthn.WebAuthNOptions;
|
||||
import io.supertokens.pluginInterface.webauthn.WebAuthNStoredCredential;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
|
||||
import static io.supertokens.inmemorydb.config.Config.getConfig;
|
||||
import static io.supertokens.pluginInterface.RECIPE_ID.WEBAUTHN;
|
||||
|
||||
public class WebAuthNQueries {
|
||||
|
||||
public static String getQueryToCreateWebAuthNUsersTable(Start start){
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getWebAuthNUsersTable() + "(" +
|
||||
" app_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" user_id CHAR(36) NOT NULL," +
|
||||
" email VARCHAR(256) NOT NULL," +
|
||||
" rp_id VARCHAR(256) NOT NULL," +
|
||||
" time_joined BIGINT UNSIGNED NOT NULL," +
|
||||
" CONSTRAINT webauthn_users_pkey PRIMARY KEY (app_id, user_id), " +
|
||||
" CONSTRAINT webauthn_users_to_app_id_fkey " +
|
||||
" FOREIGN KEY (app_id, user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() +
|
||||
" (app_id, user_id) ON DELETE CASCADE " +
|
||||
");";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNUsersToTenantTable(Start start){
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getWebAuthNUserToTenantTable() +" (" +
|
||||
" app_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" user_id CHAR(36) NOT NULL," +
|
||||
" email VARCHAR(256) NOT NULL," +
|
||||
" CONSTRAINT webauthn_user_to_tenant_email_key UNIQUE (app_id, tenant_id, email)," +
|
||||
" CONSTRAINT webauthn_user_to_tenant_pkey PRIMARY KEY (app_id, tenant_id, user_id)," +
|
||||
" CONSTRAINT webauthn_user_to_tenant_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) " +
|
||||
" REFERENCES "+ Config.getConfig(start).getUsersTable()+" (app_id, tenant_id, user_id) on delete CASCADE" +
|
||||
");";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNUserToTenantEmailIndex(Start start) {
|
||||
return "CREATE INDEX webauthn_user_to_tenant_email_index ON " +
|
||||
Config.getConfig(start).getWebAuthNUserToTenantTable() +
|
||||
" (app_id, email);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNGeneratedOptionsTable(Start start){
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable() + "(" +
|
||||
" app_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" id CHAR(36) NOT NULL," +
|
||||
" challenge VARCHAR(256) NOT NULL," +
|
||||
" email VARCHAR(256)," +
|
||||
" rp_id VARCHAR(256) NOT NULL," +
|
||||
" rp_name VARCHAR(256) NOT NULL," +
|
||||
" origin VARCHAR(256) NOT NULL," +
|
||||
" expires_at BIGINT UNSIGNED NOT NULL," +
|
||||
" created_at BIGINT UNSIGNED NOT NULL," +
|
||||
" user_presence_required BOOLEAN DEFAULT FALSE NOT NULL," +
|
||||
" user_verification VARCHAR(12) DEFAULT `preferred` NOT NULL," +
|
||||
" CONSTRAINT webauthn_user_challenges_pkey PRIMARY KEY (app_id, tenant_id, id)," +
|
||||
" CONSTRAINT webauthn_user_challenges_tenant_id_fkey FOREIGN KEY (app_id, tenant_id) " +
|
||||
" REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE" +
|
||||
");";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNChallengeExpiresIndex(Start start) {
|
||||
return "CREATE INDEX webauthn_user_challenges_expires_at_index ON " +
|
||||
Config.getConfig(start).getWebAuthNGeneratedOptionsTable() +
|
||||
" (app_id, tenant_id, expires_at);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNCredentialsTable(Start start){
|
||||
return "CREATE TABLE IF NOT EXISTS "+ Config.getConfig(start).getWebAuthNCredentialsTable() + "(" +
|
||||
" id VARCHAR(256) NOT NULL," +
|
||||
" app_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" rp_id VARCHAR(256)," +
|
||||
" user_id CHAR(36)," +
|
||||
" counter BIGINT NOT NULL," +
|
||||
" public_key BLOB NOT NULL," + //planned as bytea, which is not supported by sqlite
|
||||
" transports TEXT NOT NULL," + // planned as TEXT[], which is not supported by sqlite
|
||||
" created_at BIGINT NOT NULL," +
|
||||
" updated_at BIGINT NOT NULL," +
|
||||
" CONSTRAINT webauthn_user_credentials_pkey PRIMARY KEY (app_id, rp_id, id)," +
|
||||
" CONSTRAINT webauthn_user_credentials_webauthn_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES " +
|
||||
Config.getConfig(start).getWebAuthNUsersTable() + " (app_id, user_id) ON DELETE CASCADE" +
|
||||
");";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNCredentialsUserIdIndex(Start start) {
|
||||
return "CREATE INDEX IF NOT EXISTS webauthn_credentials_user_id_index ON " +
|
||||
Config.getConfig(start).getWebAuthNCredentialsTable() +
|
||||
" (user_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNAccountRecoveryTokenTable(Start start) {
|
||||
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() + "(" +
|
||||
" app_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" tenant_id VARCHAR(64) DEFAULT 'public' NOT NULL," +
|
||||
" user_id CHAR(36) NOT NULL," +
|
||||
" email VARCHAR(256) NOT NULL," +
|
||||
" token VARCHAR(256) NOT NULL," +
|
||||
" expires_at BIGINT UNSIGNED NOT NULL," +
|
||||
" CONSTRAINT webauthn_account_recovery_token_pkey PRIMARY KEY (app_id, tenant_id, user_id, token)," +
|
||||
" CONSTRAINT webauthn_account_recovery_token_user_id_fkey FOREIGN KEY (app_id, tenant_id, user_id) REFERENCES " +
|
||||
Config.getConfig(start).getUsersTable() + " (app_id, tenant_id, user_id) ON DELETE CASCADE" +
|
||||
");";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNAccountRecoveryTokenTokenIndex(Start start) {
|
||||
return "CREATE INDEX webauthn_account_recovery_token_token_index ON " +
|
||||
Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() +
|
||||
" (app_id, tenant_id, token);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNAccountRecoveryTokenEmailIndex(Start start) {
|
||||
return "CREATE INDEX webauthn_account_recovery_token_email_index ON " +
|
||||
Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() +
|
||||
" (app_id, tenant_id, email);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateWebAuthNAccountRecoveryTokenExpiresAtIndex(Start start) {
|
||||
return "CREATE INDEX webauthn_account_recovery_token_expires_at_index ON " +
|
||||
Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() +
|
||||
" (expires_at DESC);";
|
||||
}
|
||||
|
||||
public static WebAuthNOptions saveOptions(Start start, TenantIdentifier tenantIdentifier, WebAuthNOptions options)
|
||||
throws SQLException, StorageQueryException {
|
||||
String INSERT = "INSERT INTO " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable()
|
||||
+ " (app_id, tenant_id, id, challenge, email, rp_id, origin, expires_at, created_at, rp_name, user_verification, user_presence_required) "
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?,?,?,?);";
|
||||
|
||||
update(start, INSERT, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, options.generatedOptionsId);
|
||||
pst.setString(4, options.challenge);
|
||||
pst.setString(5, options.userEmail);
|
||||
pst.setString(6, options.relyingPartyId);
|
||||
pst.setString(7, options.origin);
|
||||
pst.setLong(8, options.expiresAt);
|
||||
pst.setLong(9, options.createdAt);
|
||||
pst.setString(10, options.relyingPartyName);
|
||||
pst.setString(11, options.userVerification);
|
||||
pst.setBoolean(12, options.userPresenceRequired);
|
||||
});
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static WebAuthNOptions loadOptionsById(Start start, TenantIdentifier tenantIdentifier, String optionsId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable()
|
||||
+ " WHERE app_id = ? AND tenant_id = ? and id = ?";
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, optionsId);
|
||||
}, result -> {
|
||||
if(result.next()){
|
||||
return WebAuthNOptionsRowMapper.getInstance().mapOrThrow(result); // we are expecting one or zero results
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static WebAuthNStoredCredential loadCredentialByIdForUser(Start start, TenantIdentifier tenantIdentifier, String credentialId, String recipeUserId)
|
||||
throws StorageQueryException, SQLException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " WHERE app_id = ? AND id = ? AND user_id = ?";
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, credentialId);
|
||||
pst.setString(3, recipeUserId);
|
||||
}, result -> {
|
||||
if(result.next()){
|
||||
return WebAuthnStoredCredentialRowMapper.getInstance().mapOrThrow(result); // we are expecting one or zero results
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static WebAuthNStoredCredential loadCredentialById_Transaction(Start start, Connection sqlConnection, TenantIdentifier tenantIdentifier, String credentialId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " WHERE app_id = ? AND id = ?";
|
||||
return execute(sqlConnection, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, credentialId);
|
||||
}, result -> {
|
||||
if(result.next()){
|
||||
return WebAuthnStoredCredentialRowMapper.getInstance().mapOrThrow(result); // we are expecting one or zero results
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static WebAuthNStoredCredential saveCredential(Start start, TenantIdentifier tenantIdentifier, WebAuthNStoredCredential credential)
|
||||
throws SQLException, StorageQueryException {
|
||||
String INSERT = "INSERT INTO " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " (id, app_id, rp_id, user_id, counter, public_key, transports, created_at, updated_at) "
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?);";
|
||||
|
||||
update(start, INSERT, pst -> {
|
||||
pst.setString(1, credential.id);
|
||||
pst.setString(2, credential.appId);
|
||||
pst.setString(3, credential.rpId);
|
||||
pst.setString(4, credential.userId);
|
||||
pst.setLong(5, credential.counter);
|
||||
pst.setBytes(6, credential.publicKey);
|
||||
pst.setString(7, credential.transports);
|
||||
pst.setLong(8, credential.createdAt);
|
||||
pst.setLong(9, credential.updatedAt);
|
||||
});
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
public static WebAuthNStoredCredential saveCredential_Transaction(Start start, Connection connection, TenantIdentifier tenantIdentifier, WebAuthNStoredCredential credential)
|
||||
throws SQLException, StorageQueryException {
|
||||
String INSERT = "INSERT INTO " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " (id, app_id, rp_id, user_id, counter, public_key, transports, created_at, updated_at) "
|
||||
+ " VALUES (?,?,?,?,?,?,?,?,?);";
|
||||
|
||||
update(connection, INSERT, pst -> {
|
||||
pst.setString(1, credential.id);
|
||||
pst.setString(2, credential.appId);
|
||||
pst.setString(3, credential.rpId);
|
||||
pst.setString(4, credential.userId);
|
||||
pst.setLong(5, credential.counter);
|
||||
pst.setBytes(6, credential.publicKey);
|
||||
pst.setString(7, credential.transports);
|
||||
pst.setLong(8, credential.createdAt);
|
||||
pst.setLong(9, credential.updatedAt);
|
||||
});
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
public static void createUser_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId, String email,
|
||||
String relyingPartyId)
|
||||
throws StorageTransactionLogicException, StorageQueryException {
|
||||
long timeJoined = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
// app_id_to_user_id
|
||||
String insertAppIdToUserId = "INSERT INTO " + getConfig(start).getAppIdToUserIdTable()
|
||||
+ "(app_id, user_id, primary_or_recipe_user_id, recipe_id)" + " VALUES(?, ?, ?, ?)";
|
||||
update(sqlCon, insertAppIdToUserId, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
pst.setString(3, userId);
|
||||
pst.setString(4, WEBAUTHN.toString());
|
||||
});
|
||||
|
||||
// all_auth_recipe_users
|
||||
String insertAllAuthRecipeUsers = "INSERT INTO " + getConfig(start).getUsersTable()
|
||||
+
|
||||
"(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, " +
|
||||
"primary_or_recipe_user_time_joined)" +
|
||||
" VALUES(?, ?, ?, ?, ?, ?, ?)";
|
||||
update(sqlCon, insertAllAuthRecipeUsers, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, userId);
|
||||
pst.setString(4, userId);
|
||||
pst.setString(5, WEBAUTHN.toString());
|
||||
pst.setLong(6, timeJoined);
|
||||
pst.setLong(7, timeJoined);
|
||||
});
|
||||
|
||||
// webauthn_user_to_tenant
|
||||
String insertWebauthNUsersToTenant =
|
||||
"INSERT INTO " + Config.getConfig(start).getWebAuthNUserToTenantTable()
|
||||
+ " (app_id, tenant_id, user_id, email) "
|
||||
+ " VALUES (?,?,?,?);";
|
||||
|
||||
update(sqlCon, insertWebauthNUsersToTenant, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, userId);
|
||||
pst.setString(4, email);
|
||||
});
|
||||
|
||||
// webauthn_users
|
||||
String insertWebauthNUsers = "INSERT INTO " + Config.getConfig(start).getWebAuthNUsersTable()
|
||||
+ " (app_id, user_id, email, rp_id, time_joined) "
|
||||
+ " VALUES (?,?,?,?,?);";
|
||||
|
||||
update(sqlCon, insertWebauthNUsers, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, userId);
|
||||
pst.setString(3, email);
|
||||
pst.setString(4, relyingPartyId);
|
||||
pst.setLong(5, timeJoined);
|
||||
});
|
||||
|
||||
} catch (SQLException throwables) {
|
||||
throw new StorageTransactionLogicException(throwables);
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo signUp_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId, String email,
|
||||
String relyingPartyId)
|
||||
throws StorageTransactionLogicException, StorageQueryException, SQLException {
|
||||
|
||||
createUser_Transaction(start, sqlCon, tenantIdentifier, userId, email, relyingPartyId);
|
||||
|
||||
return getAuthRecipeUserInfo(start, sqlCon,
|
||||
tenantIdentifier, userId);
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo signUpWithCredentialRegister_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId, String email,
|
||||
String relyingPartyId, WebAuthNStoredCredential credential)
|
||||
throws StorageQueryException, StorageTransactionLogicException, SQLException {
|
||||
|
||||
createUser_Transaction(start, sqlCon, tenantIdentifier, userId, email, relyingPartyId);
|
||||
saveCredential_Transaction(start, sqlCon, tenantIdentifier, credential);
|
||||
|
||||
return getAuthRecipeUserInfo(start, sqlCon,
|
||||
tenantIdentifier, userId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static AuthRecipeUserInfo getAuthRecipeUserInfo(Start start, Connection sqlCon,
|
||||
TenantIdentifier tenantIdentifier, String userId)
|
||||
throws SQLException, StorageQueryException {
|
||||
Collection<? extends LoginMethod> loginMethods = getUsersInfoUsingIdList_Transaction(start, sqlCon,
|
||||
Collections.singleton(userId), tenantIdentifier.toAppIdentifier());
|
||||
AuthRecipeUserInfo userInfo = null;
|
||||
if (!loginMethods.isEmpty()) {
|
||||
for (LoginMethod loginMethod : loginMethods) {
|
||||
if(userInfo == null) {
|
||||
userInfo = AuthRecipeUserInfo.create(userId, false, loginMethod);
|
||||
} else {
|
||||
userInfo.addLoginMethod(loginMethod);
|
||||
if(!loginMethod.getSupertokensUserId().equals(loginMethod.getSupertokensOrExternalUserId())){
|
||||
userInfo.setExternalUserId(loginMethod.getSupertokensOrExternalUserId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdForTenantUsingEmail(Start start, TenantIdentifier tenantIdentifier,
|
||||
String email)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return start.startTransaction(con -> {
|
||||
try {
|
||||
Connection sqlConnection = (Connection) con.getConnection();
|
||||
return getPrimaryUserIdForTenantUsingEmail_Transaction(start, sqlConnection, tenantIdentifier,
|
||||
email);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdForTenantUsingEmail_Transaction(Start start, Connection sqlConnection,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String email)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id "
|
||||
+ "FROM " + getConfig(start).getWebAuthNUserToTenantTable() + " AS ep" +
|
||||
" JOIN " + getConfig(start).getUsersTable() + " AS all_users" +
|
||||
" ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" +
|
||||
" WHERE ep.app_id = ? AND ep.email = ? AND ep.tenant_id = ?";
|
||||
|
||||
return execute(sqlConnection, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, email);
|
||||
pst.setString(3, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getString("user_id");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdForAppUsingEmail_Transaction(Start start, Connection sqlConnection,
|
||||
AppIdentifier appIdentifier, String email)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id "
|
||||
+ "FROM " + getConfig(start).getWebAuthNUserToTenantTable() + " AS ep" +
|
||||
" JOIN " + getConfig(start).getUsersTable() + " AS all_users" +
|
||||
" ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" +
|
||||
" WHERE ep.app_id = ? AND ep.email = ?";
|
||||
|
||||
return execute(sqlConnection, QUERY, pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
pst.setString(2, email);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getString("user_id");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static Collection<? extends LoginMethod> getUsersInfoUsingIdList(Start start, Set<String> ids, AppIdentifier appIdentifier)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return start.startTransaction(con -> {
|
||||
Connection sqlConnection = (Connection) con.getConnection();
|
||||
try {
|
||||
return getUsersInfoUsingIdList_Transaction(start, sqlConnection, ids, appIdentifier);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<? extends LoginMethod> getUsersInfoUsingIdList_Transaction(Start start, Connection connection, Set<String> ids, AppIdentifier appIdentifier)
|
||||
throws SQLException, StorageQueryException {
|
||||
if (!ids.isEmpty()) {
|
||||
|
||||
String webauthnUsersTable = getConfig(start).getWebAuthNUsersTable();
|
||||
String credentialTable = getConfig(start).getWebAuthNCredentialsTable();
|
||||
String usersTable = getConfig(start).getUsersTable();
|
||||
String userIdMappingTable = getConfig(start).getUserIdMappingTable();
|
||||
String emailVerificationTable = getConfig(start).getEmailVerificationTable();
|
||||
|
||||
String queryAll = "SELECT webauthn.user_id as user_id, webauthn.email as email, webauthn.time_joined as time_joined, " +
|
||||
"credentials.id as credential_id, email_verification.email as email_verified, user_id_mapping.external_user_id as external_user_id," +
|
||||
"all_users.tenant_id as tenant_id " +
|
||||
"FROM " + webauthnUsersTable + " as webauthn " +
|
||||
"JOIN " + usersTable + " as all_users ON webauthn.app_id = all_users.app_id AND webauthn.user_id = all_users.user_id " +
|
||||
"LEFT JOIN " + credentialTable + " as credentials ON webauthn.user_id = credentials.user_id " +
|
||||
"LEFT JOIN " + userIdMappingTable + " as user_id_mapping ON webauthn.user_id = user_id_mapping.supertokens_user_id " +
|
||||
"LEFT JOIN " + emailVerificationTable + " as email_verification ON webauthn.app_id = email_verification.app_id AND (user_id_mapping.external_user_id = email_verification.user_id OR user_id_mapping.supertokens_user_id = email_verification.user_id OR webauthn.user_id = email_verification.user_id) " +
|
||||
" AND email_verification.email = webauthn.email " +
|
||||
"WHERE webauthn.app_id = ? AND webauthn.user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ")";
|
||||
|
||||
return execute(connection, queryAll, pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
int index = 2;
|
||||
for (String id : ids) {
|
||||
pst.setString(index++, id);
|
||||
}
|
||||
}, result -> {
|
||||
Map<String, LoginMethod> users = new HashMap<>();
|
||||
while (result.next()) {
|
||||
String userId = result.getString("user_id");
|
||||
String email = result.getString("email");
|
||||
long timeJoined = result.getLong("time_joined");
|
||||
String credentialId = result.getString("credential_id");
|
||||
boolean emailVerified = result.getString("email_verified") != null;
|
||||
String externalUserId = result.getString("external_user_id");
|
||||
String tenantId = result.getString("tenant_id");
|
||||
if(users.containsKey(userId)) {
|
||||
users.get(userId).webauthN.addCredentialId(credentialId);
|
||||
users.get(userId).tenantIds.add(tenantId);
|
||||
} else {
|
||||
List<String> credentialIds = new ArrayList<>();
|
||||
credentialIds.add(credentialId);
|
||||
LoginMethod loginMethod = new LoginMethod(userId, timeJoined, emailVerified, email, new LoginMethod.WebAuthN(credentialIds), new String[]{tenantId});
|
||||
loginMethod.setExternalUserId(externalUserId);
|
||||
users.put(userId, loginMethod);
|
||||
}
|
||||
}
|
||||
return users.values();
|
||||
});
|
||||
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo getUserInfoByCredentialId_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String credentialId)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
String QUERY = "SELECT webauthn.user_id as user_id, webauthn.email as email, webauthn.time_joined as time_joined, " +
|
||||
"credentials.id as credential_id, email_verification.email as email_verified, user_id_mapping.external_user_id as external_user_id," +
|
||||
"all_users.tenant_id as tenant_id " +
|
||||
"FROM " + getConfig(start).getWebAuthNUsersTable() + " as webauthn " +
|
||||
"JOIN " + getConfig(start).getUsersTable() + " as all_users ON webauthn.app_id = all_users.app_id AND webauthn.user_id = all_users.user_id " +
|
||||
"LEFT JOIN " + getConfig(start).getWebAuthNCredentialsTable() + " as credentials ON webauthn.user_id = credentials.user_id " +
|
||||
"LEFT JOIN " + getConfig(start).getUserIdMappingTable() + " as user_id_mapping ON webauthn.user_id = user_id_mapping.supertokens_user_id " +
|
||||
"LEFT JOIN " + getConfig(start).getEmailVerificationTable() + " as email_verification ON webauthn.app_id = email_verification.app_id AND (user_id_mapping.external_user_id = email_verification.user_id OR user_id_mapping.supertokens_user_id = email_verification.user_id OR webauthn.user_id = email_verification.user_id)" +
|
||||
" AND email_verification.email = webauthn.email " +
|
||||
"WHERE webauthn.app_id = ? AND credentials.id = ?";
|
||||
|
||||
return execute(sqlCon, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, credentialId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String userId = result.getString("user_id");
|
||||
String email = result.getString("email");
|
||||
long timeJoined = result.getLong("time_joined");
|
||||
boolean emailVerified = result.getString("email_verified") != null;
|
||||
String externalUserId = result.getString("external_user_id");
|
||||
String tenantId = result.getString("tenant_id");
|
||||
List<String> credentialIds = new ArrayList<>();
|
||||
credentialIds.add(credentialId);
|
||||
LoginMethod.WebAuthN webAuthNLM = new LoginMethod.WebAuthN(credentialIds);
|
||||
LoginMethod loginMethod = new LoginMethod(userId, timeJoined, emailVerified, email, webAuthNLM, new String[]{tenantId});
|
||||
if(externalUserId != null) {
|
||||
loginMethod.setExternalUserId(externalUserId);
|
||||
}
|
||||
AuthRecipeUserInfo resultUserInfo = AuthRecipeUserInfo.create(userId, false, loginMethod);
|
||||
resultUserInfo.setExternalUserId(externalUserId);
|
||||
return resultUserInfo;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static WebAuthNOptions loadOptionsById_Transaction(Start start, Connection sqlCon,
|
||||
TenantIdentifier tenantIdentifier, String optionsId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable()
|
||||
+ " WHERE app_id = ? AND id = ?";
|
||||
return execute(sqlCon, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, optionsId);
|
||||
}, result -> {
|
||||
if(result.next()){
|
||||
return WebAuthNOptionsRowMapper.getInstance().mapOrThrow(result); // we are expecting one or zero results
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static void updateCounter_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String credentialId, long counter)
|
||||
throws SQLException, StorageQueryException {
|
||||
String UPDATE = "UPDATE " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " SET counter = ?, updated_at = ? WHERE app_id = ? AND id = ?";
|
||||
|
||||
update(sqlCon, UPDATE, pst -> {
|
||||
pst.setLong(1, counter);
|
||||
pst.setLong(2, System.currentTimeMillis());
|
||||
pst.setString(3, tenantIdentifier.getAppId());
|
||||
pst.setString(4, credentialId);
|
||||
});
|
||||
}
|
||||
|
||||
public static int removeCredential(Start start, TenantIdentifier tenantIdentifier, String userId, String credentialId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String UPDATE = "DELETE FROM " + Config.getConfig(start).getWebAuthNCredentialsTable()
|
||||
+ " WHERE app_id = ? AND id = ? AND user_id = ?";
|
||||
|
||||
return update(start, UPDATE, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, credentialId);
|
||||
pst.setString(3, userId);
|
||||
});
|
||||
}
|
||||
|
||||
public static int removeOptions(Start start, TenantIdentifier tenantIdentifier, String optionsId)
|
||||
throws SQLException, StorageQueryException {
|
||||
String UPDATE = "DELETE FROM " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable()
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND id = ?";
|
||||
|
||||
return update(start, UPDATE, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, optionsId);
|
||||
});
|
||||
}
|
||||
|
||||
public static List<WebAuthNStoredCredential> listCredentials(Start start, TenantIdentifier tenantIdentifier,
|
||||
String recipeUserId) throws SQLException, StorageQueryException {
|
||||
String LIST_QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNCredentialsTable() +
|
||||
" WHERE app_id = ? AND user_id = ?";
|
||||
return execute(start, LIST_QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, recipeUserId);
|
||||
}, result -> {
|
||||
List<WebAuthNStoredCredential> credentials = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
credentials.add(WebAuthnStoredCredentialRowMapper.getInstance().mapOrThrow(result));
|
||||
}
|
||||
return credentials;
|
||||
});
|
||||
}
|
||||
|
||||
public static void updateUserEmail(Start start, TenantIdentifier tenantIdentifier, String userId, String newEmail)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
start.startTransaction(con -> {
|
||||
updateUserEmail_Transaction(start, (Connection) con.getConnection(), tenantIdentifier, userId, newEmail);
|
||||
return null;
|
||||
});
|
||||
} catch (StorageTransactionLogicException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateUserEmail_Transaction(Start start, Connection sqlConnection, TenantIdentifier tenantIdentifier,
|
||||
String userId, String newEmail) throws StorageQueryException {
|
||||
try {
|
||||
String UPDATE_USER_TO_TENANT_QUERY =
|
||||
"UPDATE " + Config.getConfig(start).getWebAuthNUserToTenantTable() +
|
||||
" SET email = ? WHERE app_id = ? AND tenant_id = ? AND user_id = ?";
|
||||
String UPDATE_USER_QUERY = "UPDATE " + Config.getConfig(start).getWebAuthNUsersTable() +
|
||||
" SET email = ? WHERE app_id = ? AND user_id = ?";
|
||||
|
||||
update(sqlConnection, UPDATE_USER_TO_TENANT_QUERY, pst -> {
|
||||
pst.setString(1, newEmail);
|
||||
pst.setString(2, tenantIdentifier.getAppId());
|
||||
pst.setString(3, tenantIdentifier.getTenantId());
|
||||
pst.setString(4, userId);
|
||||
});
|
||||
|
||||
update(sqlConnection, UPDATE_USER_QUERY, pst -> {
|
||||
pst.setString(1, newEmail);
|
||||
pst.setString(2, tenantIdentifier.getAppId());
|
||||
pst.setString(3, userId);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WebAuthnStoredCredentialRowMapper implements RowMapper<WebAuthNStoredCredential, ResultSet> {
|
||||
private static final WebAuthnStoredCredentialRowMapper INSTANCE = new WebAuthnStoredCredentialRowMapper();
|
||||
|
||||
public static WebAuthnStoredCredentialRowMapper getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNStoredCredential map(ResultSet rs) throws Exception {
|
||||
WebAuthNStoredCredential result = new WebAuthNStoredCredential();
|
||||
result.id = rs.getString("id");
|
||||
result.appId = rs.getString("app_id");
|
||||
result.rpId = rs.getString("rp_id");
|
||||
result.userId = rs.getString("user_id");
|
||||
result.counter = rs.getLong("counter");
|
||||
result.publicKey = rs.getBytes("public_key");
|
||||
result.transports = rs.getString("transports");
|
||||
result.createdAt = rs.getLong("created_at");
|
||||
result.updatedAt = rs.getLong("updated_at");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class WebAuthNOptionsRowMapper implements RowMapper<WebAuthNOptions, ResultSet> {
|
||||
private static final WebAuthNOptionsRowMapper INSTANCE = new WebAuthNOptionsRowMapper();
|
||||
|
||||
public static WebAuthNOptionsRowMapper getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthNOptions map(ResultSet rs) throws Exception {
|
||||
WebAuthNOptions result = new WebAuthNOptions();
|
||||
result.timeout = rs.getLong("expires_at") - rs.getLong("created_at");
|
||||
result.expiresAt = rs.getLong("expires_at");
|
||||
result.createdAt = rs.getLong("created_at");
|
||||
result.relyingPartyId = rs.getString("rp_id");
|
||||
result.origin = rs.getString("origin");
|
||||
result.challenge = rs.getString("challenge");
|
||||
result.userEmail = rs.getString("email");
|
||||
result.generatedOptionsId = rs.getString("id");
|
||||
result.relyingPartyName = rs.getString("rp_name");
|
||||
result.userPresenceRequired = rs.getBoolean("user_presence_required");
|
||||
result.userVerification = rs.getString("user_verification");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addRecoverAccountToken(Start start, TenantIdentifier tenantIdentifier, AccountRecoveryTokenInfo accountRecoveryTokenInfo)
|
||||
throws SQLException, StorageQueryException {
|
||||
String INSERT = "INSERT INTO " + Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() + " (app_id, tenant_id, user_id, email, token, expires_at) VALUES (?, ?, ?, ?, ?, ?)";
|
||||
update(start, INSERT, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, accountRecoveryTokenInfo.userId);
|
||||
pst.setString(4, accountRecoveryTokenInfo.email);
|
||||
pst.setString(5, accountRecoveryTokenInfo.token);
|
||||
pst.setLong(6, accountRecoveryTokenInfo.expiresAt);
|
||||
});
|
||||
}
|
||||
|
||||
public static AccountRecoveryTokenInfo getAccountRecoveryTokenInfoByToken_Transaction(Start start, TenantIdentifier tenantIdentifier, Connection con, String token)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT * FROM " + Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() + " WHERE app_id = ? AND tenant_id = ? AND token = ?";
|
||||
return execute(con, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, token);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return AccountRecoveryTokenInfoRowMapper.getInstance().mapOrThrow(result);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static class AccountRecoveryTokenInfoRowMapper implements RowMapper<AccountRecoveryTokenInfo, ResultSet> {
|
||||
private static final AccountRecoveryTokenInfoRowMapper INSTANCE = new AccountRecoveryTokenInfoRowMapper();
|
||||
|
||||
public static AccountRecoveryTokenInfoRowMapper getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountRecoveryTokenInfo map(ResultSet rs) throws Exception {
|
||||
AccountRecoveryTokenInfo result = new AccountRecoveryTokenInfo(
|
||||
rs.getString("user_id"),
|
||||
rs.getString("email"),
|
||||
rs.getString("token"),
|
||||
rs.getLong("expires_at")
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteAccountRecoveryTokenByEmail_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String email)
|
||||
throws SQLException, StorageQueryException {
|
||||
String DELETE = "DELETE FROM " + Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() + " WHERE app_id = ? AND tenant_id = ? AND email = ?";
|
||||
update(con, DELETE, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, email);
|
||||
});
|
||||
}
|
||||
|
||||
public static void deleteExpiredAccountRecoveryTokens(Start start)
|
||||
throws SQLException, StorageQueryException {
|
||||
String DELETE = "DELETE FROM " + Config.getConfig(start).getWebAuthNAccountRecoveryTokenTable() + " WHERE expires_at < ?";
|
||||
update(start, DELETE, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
|
||||
public static void deleteExpiredGeneratedOptions(Start start)
|
||||
throws SQLException, StorageQueryException {
|
||||
String DELETE = "DELETE FROM " + Config.getConfig(start).getWebAuthNGeneratedOptionsTable() + " WHERE expires_at < ?";
|
||||
update(start, DELETE, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
|
|
@ -55,7 +54,7 @@ public class JWTSigningFunctions {
|
|||
throws StorageQueryException, StorageTransactionLogicException, NoSuchAlgorithmException,
|
||||
InvalidKeySpecException, JWTCreationException, UnsupportedJWTSigningAlgorithmException {
|
||||
try {
|
||||
return createJWTToken(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, algorithm, payload, jwksDomain,
|
||||
return createJWTToken(new AppIdentifier(null, null), main, algorithm, payload, jwksDomain,
|
||||
jwtValidityInSeconds, useDynamicKey);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
|||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.*;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.saml.SAMLCertificate;
|
||||
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
|
||||
import io.supertokens.session.refreshToken.RefreshTokenKey;
|
||||
import io.supertokens.signingkeys.AccessTokenSigningKey;
|
||||
import io.supertokens.signingkeys.JWTSigningKey;
|
||||
|
|
@ -118,7 +116,6 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
return StorageLayer.getMultitenancyStorage(main).getAllTenants();
|
||||
}
|
||||
|
||||
@WithinOtelSpan
|
||||
public List<TenantIdentifier> refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(
|
||||
boolean reloadAllResources) {
|
||||
try {
|
||||
|
|
@ -236,7 +233,6 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
AccessTokenSigningKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
RefreshTokenKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
SAMLCertificate.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
JWTSigningKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
SigningKeys.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ public class HttpRequestForOAuthProvider {
|
|||
// case of errors, etc.
|
||||
// Left the original HttpRequest as is to avoid any issues with existing code.
|
||||
|
||||
private static final int CONNECTION_TIMEOUT = 15000;
|
||||
private static final int READ_TIMEOUT = 15000;
|
||||
private static final int CONNECTION_TIMEOUT = 5000;
|
||||
private static final int READ_TIMEOUT = 5000;
|
||||
|
||||
public static Response doGet(String url, Map<String, String> headers, Map<String, String> queryParams) throws IOException {
|
||||
if (queryParams == null) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue