Compare commits
24 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
fe894ef8a0 | |
|
|
c0fb714688 | |
|
|
42d4ac0027 | |
|
|
68325eb117 | |
|
|
7bcd27cd25 | |
|
|
2447f87ca9 | |
|
|
f13f2ba4e4 | |
|
|
e774df6cf6 | |
|
|
5069bf35ed | |
|
|
08079bc5d5 | |
|
|
e1b37b0be3 | |
|
|
93df2080fc | |
|
|
1d18e368cb | |
|
|
48754e2b7c | |
|
|
01cef02d41 | |
|
|
b3585da402 | |
|
|
21ea25a722 | |
|
|
497fe8623e | |
|
|
a97b7e88d6 | |
|
|
3d8564bf5c | |
|
|
b4b266f950 | |
|
|
50d860218d | |
|
|
6a6a1d4879 | |
|
|
c270d27337 |
|
|
@ -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 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,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,30 +0,0 @@
|
|||
version: 2.1
|
||||
orbs:
|
||||
slack: circleci/slack@3.4.2
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: rishabhpoddar/supertokens_core_testing
|
||||
- image: mongo
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: root
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout
|
||||
- run: echo $'\n[mysqld]\ncharacter_set_server=utf8mb4\nmax_connections=10000' >> /etc/mysql/mysql.cnf
|
||||
- run: (cd .circleci/ && ./doTests.sh)
|
||||
- slack/status
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
tagged-build:
|
||||
jobs:
|
||||
- test:
|
||||
context:
|
||||
- slack-notification
|
||||
filters:
|
||||
tags:
|
||||
only: /dev-v[0-9]+(\.[0-9]+)*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
function cleanup {
|
||||
if test -f "pluginInterfaceExactVersionsOutput"; then
|
||||
rm pluginInterfaceExactVersionsOutput
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
cleanup
|
||||
|
||||
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
|
||||
|
||||
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 == "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
|
||||
|
||||
update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 2
|
||||
update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/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
|
||||
|
||||
if [[ $? -ne 0 ]]
|
||||
then
|
||||
cat logs/*
|
||||
cd ../project/
|
||||
echo "test failed... exiting!"
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
done
|
||||
done 10<pluginInterfaceExactVersionsOutput
|
||||
|
||||
if [[ $someTestsRan = "true" ]]
|
||||
then
|
||||
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
|
||||
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 +0,0 @@
|
|||
chown -R mysql:mysql /var/lib/mysql /var/run/mysqld && service mysql start
|
||||
|
|
@ -1,57 +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;"
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
case $1 in
|
||||
mysql)
|
||||
service mysql stop
|
||||
;;
|
||||
esac
|
||||
|
|
@ -1,72 +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_user:/mysql_user: root/g' /usr/lib/supertokens/config.yaml
|
||||
sed -i 's/# mysql_password:/mysql_password: root/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
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
import http.client
|
||||
|
||||
def register_core_version(supertokens_api_key, core_version, plugin_interface_array, core_driver_array):
|
||||
print("Core Version: ", core_version)
|
||||
print("Plugin Interface Array: ", plugin_interface_array)
|
||||
print("Core Driver Array: ", core_driver_array)
|
||||
|
||||
conn = http.client.HTTPSConnection("api.supertokens.io")
|
||||
|
||||
payload = {
|
||||
"password": supertokens_api_key,
|
||||
"planType": "FREE",
|
||||
"version": core_version,
|
||||
"pluginInterfaces": plugin_interface_array,
|
||||
"coreDriverInterfaces": core_driver_array
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'api-version': '0'
|
||||
}
|
||||
|
||||
conn.request("PUT", "/0/core", json.dumps(payload), headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status != 200:
|
||||
print(f"failed core PUT API status code: {response.status}. Exiting!")
|
||||
exit(1)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
def read_core_version():
|
||||
with open('build.gradle', 'r') as file:
|
||||
for line in file:
|
||||
if 'version =' in line:
|
||||
return line.split('=')[1].strip().strip("'\"")
|
||||
raise Exception("Could not find version in build.gradle")
|
||||
|
||||
core_version = read_core_version()
|
||||
|
||||
with open('pluginInterfaceSupported.json', 'r') as fd:
|
||||
plugin_interface_array = json.load(fd)['versions']
|
||||
|
||||
with open('coreDriverInterfaceSupported.json', 'r') as fd:
|
||||
core_driver_array = json.load(fd)['versions']
|
||||
|
||||
register_core_version(
|
||||
supertokens_api_key=os.environ.get("SUPERTOKENS_API_KEY"),
|
||||
core_version=core_version,
|
||||
plugin_interface_array=plugin_interface_array,
|
||||
core_driver_array=core_driver_array
|
||||
)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import http.client
|
||||
|
||||
|
||||
def register_plugin_version(supertokens_api_key, plugin_version, plugin_interface_array, plugin_name):
|
||||
print("Plugin Version: ", plugin_version)
|
||||
print("Plugin Interface Array: ", plugin_interface_array)
|
||||
print("Plugin Name: ", plugin_name)
|
||||
|
||||
conn = http.client.HTTPSConnection("api.supertokens.io")
|
||||
|
||||
payload = {
|
||||
"password": supertokens_api_key,
|
||||
"planType": "FREE",
|
||||
"version": plugin_version,
|
||||
"pluginInterfaces": plugin_interface_array,
|
||||
"name": plugin_name
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'api-version': '0'
|
||||
}
|
||||
|
||||
conn.request("PUT", "/0/plugin", json.dumps(payload), headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status != 200:
|
||||
print(f"failed plugin PUT API status code: {response.status}. Exiting!")
|
||||
print(f"response: {str(response.read())}")
|
||||
exit(1)
|
||||
|
||||
conn.close()
|
||||
|
||||
def read_plugin_version():
|
||||
with open('build.gradle', 'r') as file:
|
||||
for line in file:
|
||||
if 'version =' in line:
|
||||
return line.split('=')[1].strip().strip("'\"")
|
||||
raise Exception("Could not find version in build.gradle")
|
||||
|
||||
plugin_version = read_plugin_version()
|
||||
|
||||
with open('pluginInterfaceSupported.json', 'r') as fd:
|
||||
plugin_interface_array = json.load(fd)['versions']
|
||||
|
||||
def check_if_tag_exists(tag):
|
||||
try:
|
||||
result = subprocess.run(['git', 'tag', '-l', tag], capture_output=True, text=True)
|
||||
return tag in result.stdout
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Error checking for tag {tag}")
|
||||
return False
|
||||
|
||||
dev_tag = f"dev-v{plugin_version}"
|
||||
if not check_if_tag_exists(dev_tag):
|
||||
print(f"Tag {dev_tag} does not exist. Exiting!")
|
||||
exit(0)
|
||||
|
||||
register_plugin_version(
|
||||
supertokens_api_key=os.environ.get("SUPERTOKENS_API_KEY"),
|
||||
plugin_version=plugin_version,
|
||||
plugin_interface_array=plugin_interface_array,
|
||||
plugin_name=os.environ.get("PLUGIN_NAME")
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Check for required arguments
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <source-image:tag> <target-image:tag>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_IMAGE="$1"
|
||||
TARGET_IMAGE="$2"
|
||||
|
||||
# Platforms to support
|
||||
PLATFORMS=("linux/amd64" "linux/arm64")
|
||||
TEMP_IMAGES=()
|
||||
|
||||
# Pull, retag, and push platform-specific images
|
||||
for PLATFORM in "${PLATFORMS[@]}"; do
|
||||
ARCH=$(echo $PLATFORM | cut -d'/' -f2)
|
||||
TEMP_TAG="${TARGET_IMAGE}-${ARCH}"
|
||||
TEMP_IMAGES+=("$TEMP_TAG")
|
||||
|
||||
echo "Pulling $SOURCE_IMAGE for $PLATFORM..."
|
||||
docker pull --platform $PLATFORM "$SOURCE_IMAGE"
|
||||
|
||||
echo "Tagging as $TEMP_TAG..."
|
||||
docker tag "$SOURCE_IMAGE" "$TEMP_TAG"
|
||||
|
||||
echo "Pushing $TEMP_TAG..."
|
||||
docker push "$TEMP_TAG"
|
||||
done
|
||||
|
||||
# Create and push manifest for multi-arch image
|
||||
echo "Creating and pushing multi-arch manifest for $TARGET_IMAGE..."
|
||||
docker manifest create "$TARGET_IMAGE" "${TEMP_IMAGES[@]}"
|
||||
docker manifest push "$TARGET_IMAGE"
|
||||
|
||||
echo "✅ Multi-arch image pushed as $TARGET_IMAGE"
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import http.client
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
REPO = "supertokens/supertokens-core"
|
||||
SHA = os.environ.get("GITHUB_SHA")
|
||||
NAME = os.environ.get("WORKFLOW_NAME", "Publish Dev Docker Image")
|
||||
|
||||
st = time.time()
|
||||
|
||||
def get_latest_actions():
|
||||
conn = http.client.HTTPSConnection("api.github.com")
|
||||
url = f"/repos/{REPO}/actions/runs"
|
||||
headers = {"User-Agent": "Python-http.client"}
|
||||
conn.request("GET", url, headers=headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status == 200:
|
||||
data = response.read()
|
||||
runs = json.loads(data)['workflow_runs']
|
||||
found = False
|
||||
for run in runs:
|
||||
if run['head_sha'] == SHA and run['name'] == NAME:
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
print("No matching workflow run found.")
|
||||
sys.exit(1)
|
||||
|
||||
if run["status"] == "completed":
|
||||
if run["conclusion"] == "success":
|
||||
print("Workflow completed successfully.")
|
||||
return True
|
||||
else:
|
||||
print(f"Workflow failed with conclusion: {run['conclusion']}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Failed to fetch workflow runs: {response.status} {response.reason}")
|
||||
sys.exit(1)
|
||||
|
||||
return False
|
||||
|
||||
time.sleep(30) # Wait for 30 seconds before checking
|
||||
|
||||
|
||||
while not get_latest_actions():
|
||||
print("Waiting for the latest actions to complete...")
|
||||
time.sleep(10)
|
||||
if time.time() - st > 600:
|
||||
print("Timed out waiting for the latest actions.")
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
name: Add dev tags for release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
core-version:
|
||||
description: 'Core version'
|
||||
required: true
|
||||
type: string
|
||||
plugin-interface-version:
|
||||
description: 'Plugin interface version'
|
||||
required: true
|
||||
type: string
|
||||
new-release-for-plugin-interface:
|
||||
description: 'New release for plugin interface'
|
||||
required: true
|
||||
type: boolean
|
||||
postgresql-plugin-version:
|
||||
description: 'Postgres plugin version'
|
||||
required: true
|
||||
new-release-for-postgresql-plugin:
|
||||
description: 'New release for postgres plugin'
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: add-dev-tag
|
||||
core-version: ${{ github.event.inputs.core-version }}
|
||||
plugin-interface-version: ${{ github.event.inputs.plugin-interface-version }}
|
||||
postgresql-plugin-version: ${{ github.event.inputs.postgresql-plugin-version }}
|
||||
add-dev-tag:
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
steps:
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: for_jdk_15_releases
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-core.git
|
||||
cd supertokens-core
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['core'] }}
|
||||
- name: Checkout supertokens-plugin-interface
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-plugin-interface.git
|
||||
cd supertokens-plugin-interface
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- name: Checkout supertokens-postgresql-plugin
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-postgresql-plugin.git
|
||||
cd supertokens-postgresql-plugin
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['postgresql'] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
postgresql-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name "Supertokens Bot"
|
||||
git config --global user.email "<>"
|
||||
- name: Add dev tag to plugin interface
|
||||
if: ${{ github.event.inputs.new-release-for-plugin-interface == 'true' }}
|
||||
run: |
|
||||
echo "Adding dev tag to plugin interface"
|
||||
cd supertokens-root/supertokens-plugin-interface
|
||||
./addDevTag
|
||||
- name: Add dev tag to postgres plugin
|
||||
if: ${{ github.event.inputs.new-release-for-postgresql-plugin == 'true' }}
|
||||
run: |
|
||||
echo "Adding dev tag to postgres plugin"
|
||||
cd supertokens-root/supertokens-postgresql-plugin
|
||||
./addDevTag
|
||||
- name: Add dev tag to core
|
||||
run: |
|
||||
echo "Adding dev tag to core"
|
||||
cd supertokens-root/supertokens-core
|
||||
./addDevTag
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
name: Checks for release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '[0-9]+.[0-9]+'
|
||||
tags:
|
||||
- 'dev-*'
|
||||
|
||||
jobs:
|
||||
dependency-versions:
|
||||
name: Dependency Versions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
versions: ${{ steps.result.outputs.versions }}
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
with:
|
||||
run-for: PR
|
||||
id: result
|
||||
new-core-version:
|
||||
environment: publish
|
||||
name: New core version
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-versions]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Run script
|
||||
env:
|
||||
SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }}
|
||||
run: |
|
||||
python .github/helpers/register-new-core-version.py
|
||||
new-plugin-versions:
|
||||
environment: publish
|
||||
name: New plugin versions
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-versions]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ./supertokens-plugin
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-versions.outputs.branches)[matrix.plugin] }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Run script
|
||||
env:
|
||||
SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }}
|
||||
PLUGIN_NAME: ${{ matrix.plugin }}
|
||||
run: |
|
||||
cd supertokens-plugin
|
||||
python ../.github/helpers/register-new-plugin-version.py
|
||||
unit-tests:
|
||||
name: Run unit tests
|
||||
needs: [new-core-version, new-plugin-versions]
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
wait-for-docker:
|
||||
name: Wait for Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: [new-core-version, new-plugin-versions]
|
||||
outputs:
|
||||
tag: ${{ steps.set_tag.outputs.TAG }}
|
||||
steps:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Wait for Docker build
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
python .github/helpers/wait-for-docker.py
|
||||
- name: set tag
|
||||
id: set_tag
|
||||
run: |
|
||||
echo "TAG=${GITHUB_REF}" | sed 's/refs\/heads\///g' | sed 's/\//_/g' >> $GITHUB_OUTPUT
|
||||
stress-tests:
|
||||
needs: [wait-for-docker]
|
||||
uses: ./.github/workflows/stress-tests.yml
|
||||
with:
|
||||
tag: ${{ needs.wait-for-docker.outputs.tag }}
|
||||
mark-as-passed:
|
||||
environment: publish
|
||||
needs: [dependency-versions, unit-tests, stress-tests]
|
||||
name: Mark as passed
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- sqlite
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Mark plugin as passed
|
||||
if: matrix.plugin != 'sqlite' && fromJson(needs.dependency-versions.outputs.versions)[matrix.plugin] != ''
|
||||
uses: muhfaris/request-action@main
|
||||
with:
|
||||
url: https://api.supertokens.io/0/plugin
|
||||
method: PATCH
|
||||
headers: |
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"api-version": "0"
|
||||
}
|
||||
body: |
|
||||
{
|
||||
"password": "${{ secrets.SUPERTOKENS_API_KEY }}",
|
||||
"version": "${{ fromJson(needs.dependency-versions.outputs.versions)[matrix.plugin] }}",
|
||||
"planType": "FREE",
|
||||
"name": "${{ matrix.plugin }}",
|
||||
"testPassed": true
|
||||
}
|
||||
- name: Mark core as passed
|
||||
if: matrix.plugin == 'sqlite' && fromJson(needs.dependency-versions.outputs.versions)['core'] != ''
|
||||
uses: muhfaris/request-action@main
|
||||
with:
|
||||
url: https://api.supertokens.io/0/core
|
||||
method: PATCH
|
||||
headers: |
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"api-version": "0"
|
||||
}
|
||||
body: |
|
||||
{
|
||||
"password": "${{ secrets.SUPERTOKENS_API_KEY }}",
|
||||
"version": "${{ fromJson(needs.dependency-versions.outputs.versions)['core'] }}",
|
||||
"planType": "FREE",
|
||||
"testPassed": true
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
name: Do Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
core-version:
|
||||
description: 'Core version'
|
||||
required: true
|
||||
type: string
|
||||
plugin-interface-version:
|
||||
description: 'Plugin interface version'
|
||||
required: true
|
||||
type: string
|
||||
new-release-for-plugin-interface:
|
||||
description: 'New release for plugin interface'
|
||||
required: true
|
||||
type: boolean
|
||||
postgresql-plugin-version:
|
||||
description: 'Postgres plugin version'
|
||||
required: true
|
||||
new-release-for-postgresql-plugin:
|
||||
description: 'New release for postgres plugin'
|
||||
required: true
|
||||
type: boolean
|
||||
is-latest-release:
|
||||
description: 'Is this the latest release?'
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
versions: ${{ steps.result.outputs.versions }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: add-dev-tag
|
||||
core-version: ${{ github.event.inputs.core-version }}
|
||||
plugin-interface-version: ${{ github.event.inputs.plugin-interface-version }}
|
||||
postgresql-plugin-version: ${{ github.event.inputs.postgresql-plugin-version }}
|
||||
release-docker:
|
||||
environment: publish
|
||||
name: Release Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Tag and Push Docker Image
|
||||
run: |
|
||||
tag=${{ github.event.inputs.core-version }}
|
||||
major=$(echo $tag | cut -d. -f1)
|
||||
minor=$(echo $tag | cut -d. -f1,2)
|
||||
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$major
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$minor
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$tag
|
||||
|
||||
if [ "${{ github.event.inputs.is-latest-release }}" == "true" ]; then
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:latest
|
||||
fi
|
||||
add-release-tag:
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-branches, release-docker]
|
||||
steps:
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: for_jdk_15_releases
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-core.git
|
||||
cd supertokens-core
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['core'] }}
|
||||
- name: Checkout supertokens-plugin-interface
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-plugin-interface.git
|
||||
cd supertokens-plugin-interface
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- name: Checkout supertokens-postgresql-plugin
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-postgresql-plugin.git
|
||||
cd supertokens-postgresql-plugin
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['postgresql'] }}
|
||||
- name: Add release password
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "${{ secrets.SUPERTOKENS_API_KEY }}" > releasePassword
|
||||
echo "${{ secrets.SUPERTOKENS_API_KEY }}" > apiPassword
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
postgresql-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name "Supertokens Bot"
|
||||
git config --global user.email "<>"
|
||||
- name: Add release tag to plugin interface
|
||||
if: ${{ github.event.inputs.new-release-for-plugin-interface == 'true' }}
|
||||
run: |
|
||||
echo "Adding release tag to plugin interface"
|
||||
cd supertokens-root/supertokens-plugin-interface
|
||||
./addReleaseTag
|
||||
- name: Add release tag to postgres plugin
|
||||
if: ${{ github.event.inputs.new-release-for-postgresql-plugin == 'true' }}
|
||||
run: |
|
||||
echo "Adding release tag to postgres plugin"
|
||||
cd supertokens-root/supertokens-postgresql-plugin
|
||||
./addReleaseTag
|
||||
- name: Add release tag to core
|
||||
run: |
|
||||
echo "Adding release tag to core"
|
||||
cd supertokens-root/supertokens-core
|
||||
./addReleaseTag
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
name: "Enforcing changelog in PRs Workflow"
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
# Enforces the update of a changelog file on every pull request
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dangoslen/changelog-enforcer@v2
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'Skip-Changelog'
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
name: "Lint PR Title"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
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
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: PR Checks
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
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
|
||||
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'
|
||||
unit-tests:
|
||||
name: Run unit tests
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
name: Publish Dev Docker Image
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags:
|
||||
- 'dev-*'
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
outputs:
|
||||
tag: ${{ steps.set_tag.outputs.TAG }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: for_jdk_15_releases
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-plugin-interface
|
||||
path: ./supertokens-root/supertokens-plugin-interface
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- uses: actions/checkout@v2
|
||||
if: matrix.plugin != 'sqlite'
|
||||
with:
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
path: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)[matrix.plugin] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
${{ matrix.plugin }}-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Generate config file
|
||||
run: |
|
||||
cd supertokens-root
|
||||
touch config_temp.yaml
|
||||
cat supertokens-core/config.yaml >> config_temp.yaml
|
||||
cat supertokens-${{ matrix.plugin }}-plugin/config.yaml >> config_temp.yaml
|
||||
mv config_temp.yaml config.yaml
|
||||
|
||||
- name: set tag
|
||||
id: set_tag
|
||||
run: |
|
||||
echo "TAG=${GITHUB_REF}" | sed 's/refs\/heads\///g' | sed 's/\//_/g' >> $GITHUB_OUTPUT
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
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
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
name: Stress Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Docker image tag to use'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
stress-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm install
|
||||
- name: Update Docker image in compose
|
||||
run: |
|
||||
cd stress-tests
|
||||
sed -i 's|supertokens/supertokens-postgresql|supertokens/supertokens-dev-postgresql:${{ inputs.tag }}|' docker-compose.yml
|
||||
cat docker-compose.yml
|
||||
- name: Bring up the services
|
||||
run: |
|
||||
cd stress-tests
|
||||
docker compose up -d
|
||||
- name: Generate user jsons
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm run generate-users
|
||||
- name: Run one million users test
|
||||
id: one-million-users
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm run one-million-users | tee stress-tests.log
|
||||
- name: Display Test Statistics
|
||||
run: |
|
||||
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
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
name: "Check if \"Run tests\" action succeeded"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
pr-run-test-action:
|
||||
name: Check if "Run tests" action succeeded
|
||||
timeout-minutes: 60
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: node install
|
||||
run: cd ./.github/helpers && npm i
|
||||
- name: Calling github API
|
||||
run: cd ./.github/helpers && GITHUB_TOKEN=${{ github.token }} REPO=${{ github.repository }} RUN_ID=${{ github.run_id }} BRANCH=${{ github.head_ref }} JOB_ID=${{ github.job }} SOURCE_OWNER=${{ github.event.pull_request.head.repo.owner.login }} CURRENT_SHA=${{ github.event.pull_request.head.sha }} node node_modules/github-workflow-helpers/test-pass-check-pr.js
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
name: "Run tests"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pluginRepoOwnerName:
|
||||
description: 'supertokens-plugin-interface repo owner name'
|
||||
default: supertokens
|
||||
required: true
|
||||
pluginInterfaceBranch:
|
||||
description: 'supertokens-plugin-interface repos branch name'
|
||||
default: master
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
test_job:
|
||||
name: Run tests
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
container: rishabhpoddar/supertokens_core_testing
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cloning supertokens-root
|
||||
run: cd ../ && git clone https://github.com/supertokens/supertokens-root.git
|
||||
- name: Update Java 1
|
||||
run: update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 2
|
||||
- name: Update Java 2
|
||||
run: update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 2
|
||||
- name: Modifying modules.txt in supertokens-root
|
||||
run: cd ../supertokens-root && echo "core,master\nplugin-interface,${{ github.event.inputs.pluginInterfaceBranch }},${{ github.event.inputs.pluginRepoOwnerName }}" > modules.txt
|
||||
- name: Contents of modules.txt
|
||||
run: cat ../supertokens-root/modules.txt
|
||||
- name: Running loadModules in supertokens-root
|
||||
run: cd ../supertokens-root && ./loadModules
|
||||
- name: Copying current supertokens-core branch into supertokens-root
|
||||
run: cd ../supertokens-root && rm -rf ./supertokens-core && cp -r ../supertokens-core ./
|
||||
- name: Building and running tests
|
||||
run: cd ../supertokens-root && ./startTestingEnv
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
name: Unit Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
total-runners: 12
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
|
||||
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
|
||||
steps:
|
||||
- name: Set up JDK 15.0.1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 15.0.1
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: for_jdk_15_releases
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-plugin-interface
|
||||
path: ./supertokens-root/supertokens-plugin-interface
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- uses: actions/checkout@v2
|
||||
if: matrix.plugin != 'sqlite'
|
||||
with:
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
path: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)[matrix.plugin] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
${{ matrix.plugin }}-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Start ${{ matrix.plugin }} server
|
||||
if: matrix.plugin != 'sqlite'
|
||||
run: cd supertokens-root/supertokens-${{ matrix.plugin }}-plugin && ./startDb.sh
|
||||
- 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
|
||||
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()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
detailed_summary: true
|
||||
include_passed: false
|
||||
annotate_notice: true
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
|
|
@ -5,7 +5,46 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
|
||||
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [unreleased]
|
||||
## [Unreleased]
|
||||
|
||||
## [6.0.21]
|
||||
|
||||
- Placeholder backport for releasing the opentelemetry connection uri env variable
|
||||
|
||||
## [6.0.20]
|
||||
|
||||
- Adds internal opentelemetry support for logging
|
||||
|
||||
## [6.0.19] - 2024-03-29
|
||||
|
||||
- Fixes userIdMapping queries
|
||||
- Adds a new required `useDynamicSigningKey` into the request body of `RefreshSessionAPI`
|
||||
- This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to
|
||||
change the signing key type of a session
|
||||
|
||||
## [6.0.18] - 2024-02-20
|
||||
|
||||
- Fixes vulnerabilities in dependencies
|
||||
- Updates telemetry payload
|
||||
- Fixes Active User tracking to use the right storage
|
||||
|
||||
## [6.0.17] - 2024-02-06
|
||||
|
||||
- Adds new config `supertokens_saas_load_only_cud` that makes the core instance load a particular CUD only, irrespective of the CUDs present in the db.
|
||||
- Fixes connection pool handling when connection pool size changes for a tenant.
|
||||
|
||||
## [6.0.16] - 2023-11-03
|
||||
|
||||
- Collects requests stats per app
|
||||
- Adds `/requests/stats` API to return requests stats for the last day
|
||||
|
||||
## [6.0.15] - 2023-10-18
|
||||
|
||||
- Fixes issue with cron tasks that run per app and tenant
|
||||
|
||||
## [6.0.14] - 2023-10-12
|
||||
|
||||
- Fixes issue with duplicate cron task
|
||||
|
||||
## [6.0.13] - 2023-09-15
|
||||
|
||||
|
|
|
|||
310
CONTRIBUTING.md
310
CONTRIBUTING.md
|
|
@ -1,155 +1,155 @@
|
|||
# Contributing
|
||||
|
||||
We're so excited you're interested in helping with SuperTokens! We are happy to help you get started, even if you don't
|
||||
have any previous open-source experience :blush:
|
||||
|
||||
## New to Open Source?
|
||||
|
||||
1. Take a look
|
||||
at [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
2. Go through
|
||||
the [SuperTokens Code of Conduct](https://github.com/supertokens/supertokens-core/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
## Where to ask Questions?
|
||||
|
||||
1. Check our [Github Issues](https://github.com/supertokens/supertokens-core/issues) to see if someone has already
|
||||
answered your question.
|
||||
2. Join our community on [Discord](https://supertokens.io/discord) and feel free to ask us your questions
|
||||
|
||||
As you gain experience with SuperTokens, please help answer other people's questions! :pray:
|
||||
|
||||
## What to Work On?
|
||||
|
||||
You can get started by taking a look at our [Github issues](https://github.com/supertokens/supertokens-core/issues)
|
||||
If you find one that looks interesting and no one else is already working on it, comment in the issue that you are going
|
||||
to work on it.
|
||||
|
||||
Please ask as many questions as you need, either directly in the issue or on [Discord](https://supertokens.io/discord).
|
||||
We're happy to help!:raised_hands:
|
||||
|
||||
### Contributions that are ALWAYS welcome
|
||||
|
||||
1. More tests
|
||||
2. Contributing to discussions that can be
|
||||
found [here](https://github.com/supertokens/supertokens-core/issues?q=is%3Aissue+is%3Aopen+label%3Adiscussions)
|
||||
3. Improved error messages
|
||||
4. Educational content like blogs, videos, courses
|
||||
|
||||
## Development Setup
|
||||
|
||||
### With Gitpod
|
||||
|
||||
1. Navigate to the [supertokens-root](https://github.com/supertokens/supertokens-root) repository
|
||||
2. Click on the `Open in Gitpod` button
|
||||
|
||||
### 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 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
|
||||
|
||||
### Familiarize yourself with SuperTokens
|
||||
|
||||
1. [Architecture of SuperTokens](https://github.com/supertokens/supertokens-core/wiki/SuperTokens-Architecture)
|
||||
2. [SuperTokens code and file structure overview](https://github.com/supertokens/supertokens-core/wiki/Code-and-file-structure-overview)
|
||||
3. [Versioning methodology](https://github.com/supertokens/supertokens-core/wiki/Versioning,-git-and-releases)
|
||||
|
||||
### Project Setup
|
||||
|
||||
1. Fork the [supertokens-core](https://github.com/supertokens/supertokens-core) repository (**Skip this step if you are
|
||||
NOT modifying supertokens-core**)
|
||||
2. `git clone https://github.com/supertokens/supertokens-root.git`
|
||||
3. `cd supertokens-root`
|
||||
4. Open the `modules.txt` file in an editor (**Skip this step if you are NOT modifying supertokens-core**):
|
||||
- The `modules.txt` file contains the core, plugin-interface, the type of plugin and their branches(versions)
|
||||
- By default the `master` branch is used but you can change the branch depending on which version you want to modify
|
||||
- The `sqlite-plugin` is used as the default plugin as it is an in-memory database and requires no setup
|
||||
- [core](https://github.com/supertokens/supertokens-core)
|
||||
- [plugin-interface](https://github.com/supertokens/supertokens-plugin-interface)
|
||||
- Check the repository branches by clicking on the links listed above, click the branch tab and check for all
|
||||
the available versions
|
||||
- Add your github `username` separated by a ',' after `core,master` in `modules.txt`
|
||||
- If, for example, your github `username` is `helloworld` then modules.txt should look like...
|
||||
```
|
||||
// put module name like module name,branch name,github username(if contributing with a forked repository) and then call ./loadModules script
|
||||
core,master,helloworld
|
||||
plugin-interface,master
|
||||
sqlite-plugin,master
|
||||
```
|
||||
|
||||
5. Run loadModules to clone the required repositories
|
||||
`./loadModules`
|
||||
|
||||
## Modifying code
|
||||
|
||||
1. Open `supetokens-root` in your IDE
|
||||
2. After gradle has imported all the dependencies you can start modifying the code
|
||||
|
||||
## Testing
|
||||
|
||||
### On your local machine
|
||||
|
||||
1. Navigate to the `supertokens-root` repository
|
||||
2. Run all tests
|
||||
`./startTestEnv`
|
||||
3. If all tests pass the terminal should display
|
||||
|
||||
- core tests:
|
||||

|
||||
- plugin tests:
|
||||

|
||||
|
||||
### Using github actions
|
||||
|
||||
1. Go to the supertokens-core repo on github (or your forked version of it).
|
||||
2. Navigate to the Actions tab.
|
||||
3. Find the action named "Run tests" and navigate to it.
|
||||
4. Click on the "Run workflow" button.
|
||||
5. Set the config variables in the drop down:
|
||||
- **supertokens-plugin-interface repo owner name**: If you have forked the supertokens-plugin-interface repo, then
|
||||
set the value of this to your github username.
|
||||
- **supertokens-plugin-interface repos branch name**: If the core version you are working on is compatible with a
|
||||
plugin-interface version that is not in the master branch, then set the correct branch name in this value.
|
||||
6. Click on "Run workflow".
|
||||
|
||||
## Running the core manually
|
||||
|
||||
1. Run `startTestEnv --wait` in a terminal, and keep it running
|
||||
2. Then open `supertokens-root` in another terminal and run `cp ./temp/config.yaml .`
|
||||
3. Then run `java -classpath "./core/*:./plugin-interface/*:./ee/*" io.supertokens.Main ./ DEV`. This will start the
|
||||
core to listen on `http://localhost:3567`
|
||||
|
||||
## Pull Request
|
||||
|
||||
1. Before submitting a pull request make sure all tests have passed
|
||||
2. Reference the relevant issue or pull request and give a clear description of changes/features added when submitting a
|
||||
pull request
|
||||
3. Make sure the PR title follows [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification
|
||||
|
||||
## Install the supertokens CLI manually
|
||||
|
||||
1. Setup test env and keep it running
|
||||
2. In `supertokens-root`, run `cp temp/config.yaml .`
|
||||
3. On a different terminal, go to `supertokens-root` folder and
|
||||
run `java -classpath "./cli/*" io.supertokens.cli.Main true install`
|
||||
|
||||
## SuperTokens Community
|
||||
|
||||
SuperTokens is made possible by a passionate team and a strong community of developers. If you have any questions or
|
||||
would like to get more involved in the SuperTokens community you can check out:
|
||||
|
||||
- [Github Issues](https://github.com/supertokens/supertokens-core/issues)
|
||||
- [Discord](https://supertokens.io/discord)
|
||||
- [Twitter](https://twitter.com/supertokensio)
|
||||
- or [email us](mailto:team@supertokens.io)
|
||||
|
||||
Additional resources you might find useful:
|
||||
|
||||
- [SuperTokens Docs](https://supertokens.io/docs/community/getting-started/installation)
|
||||
- [Blog Posts](https://supertokens.io/blog/)
|
||||
- [Development guideline for the backend and frontend recipes](https://github.com/supertokens/supertokens-core/wiki/Development-guideline-for-the-backend-and-frontend-recipes)
|
||||
|
||||
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
We're so excited you're interested in helping with SuperTokens! We are happy to help you get started, even if you don't
|
||||
have any previous open-source experience :blush:
|
||||
|
||||
## New to Open Source?
|
||||
|
||||
1. Take a look
|
||||
at [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
|
||||
2. Go through
|
||||
the [SuperTokens Code of Conduct](https://github.com/supertokens/supertokens-core/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
## Where to ask Questions?
|
||||
|
||||
1. Check our [Github Issues](https://github.com/supertokens/supertokens-core/issues) to see if someone has already
|
||||
answered your question.
|
||||
2. Join our community on [Discord](https://supertokens.io/discord) and feel free to ask us your questions
|
||||
|
||||
As you gain experience with SuperTokens, please help answer other people's questions! :pray:
|
||||
|
||||
## What to Work On?
|
||||
|
||||
You can get started by taking a look at our [Github issues](https://github.com/supertokens/supertokens-core/issues)
|
||||
If you find one that looks interesting and no one else is already working on it, comment in the issue that you are going
|
||||
to work on it.
|
||||
|
||||
Please ask as many questions as you need, either directly in the issue or on [Discord](https://supertokens.io/discord).
|
||||
We're happy to help!:raised_hands:
|
||||
|
||||
### Contributions that are ALWAYS welcome
|
||||
|
||||
1. More tests
|
||||
2. Contributing to discussions that can be
|
||||
found [here](https://github.com/supertokens/supertokens-core/issues?q=is%3Aissue+is%3Aopen+label%3Adiscussions)
|
||||
3. Improved error messages
|
||||
4. Educational content like blogs, videos, courses
|
||||
|
||||
## Development Setup
|
||||
|
||||
### With Gitpod
|
||||
|
||||
1. Navigate to the [supertokens-root](https://github.com/supertokens/supertokens-root) repository
|
||||
2. Click on the `Open in Gitpod` button
|
||||
|
||||
### 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 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
|
||||
|
||||
### Familiarize yourself with SuperTokens
|
||||
|
||||
1. [Architecture of SuperTokens](https://github.com/supertokens/supertokens-core/wiki/SuperTokens-Architecture)
|
||||
2. [SuperTokens code and file structure overview](https://github.com/supertokens/supertokens-core/wiki/Code-and-file-structure-overview)
|
||||
3. [Versioning methodology](https://github.com/supertokens/supertokens-core/wiki/Versioning,-git-and-releases)
|
||||
|
||||
### Project Setup
|
||||
|
||||
1. Fork the [supertokens-core](https://github.com/supertokens/supertokens-core) repository (**Skip this step if you are
|
||||
NOT modifying supertokens-core**)
|
||||
2. `git clone https://github.com/supertokens/supertokens-root.git`
|
||||
3. `cd supertokens-root`
|
||||
4. Open the `modules.txt` file in an editor (**Skip this step if you are NOT modifying supertokens-core**):
|
||||
- The `modules.txt` file contains the core, plugin-interface, the type of plugin and their branches(versions)
|
||||
- By default the `master` branch is used but you can change the branch depending on which version you want to modify
|
||||
- The `sqlite-plugin` is used as the default plugin as it is an in-memory database and requires no setup
|
||||
- [core](https://github.com/supertokens/supertokens-core)
|
||||
- [plugin-interface](https://github.com/supertokens/supertokens-plugin-interface)
|
||||
- Check the repository branches by clicking on the links listed above, click the branch tab and check for all
|
||||
the available versions
|
||||
- Add your github `username` separated by a ',' after `core,master` in `modules.txt`
|
||||
- If, for example, your github `username` is `helloworld` then modules.txt should look like...
|
||||
```
|
||||
// put module name like module name,branch name,github username(if contributing with a forked repository) and then call ./loadModules script
|
||||
core,master,helloworld
|
||||
plugin-interface,master
|
||||
sqlite-plugin,master
|
||||
```
|
||||
|
||||
5. Run loadModules to clone the required repositories
|
||||
`./loadModules`
|
||||
|
||||
## Modifying code
|
||||
|
||||
1. Open `supetokens-root` in your IDE
|
||||
2. After gradle has imported all the dependencies you can start modifying the code
|
||||
|
||||
## Testing
|
||||
|
||||
### On your local machine
|
||||
|
||||
1. Navigate to the `supertokens-root` repository
|
||||
2. Run all tests
|
||||
`./startTestEnv`
|
||||
3. If all tests pass the terminal should display
|
||||
|
||||
- core tests:
|
||||

|
||||
- plugin tests:
|
||||

|
||||
|
||||
### Using github actions
|
||||
|
||||
1. Go to the supertokens-core repo on github (or your forked version of it).
|
||||
2. Navigate to the Actions tab.
|
||||
3. Find the action named "Run tests" and navigate to it.
|
||||
4. Click on the "Run workflow" button.
|
||||
5. Set the config variables in the drop down:
|
||||
- **supertokens-plugin-interface repo owner name**: If you have forked the supertokens-plugin-interface repo, then
|
||||
set the value of this to your github username.
|
||||
- **supertokens-plugin-interface repos branch name**: If the core version you are working on is compatible with a
|
||||
plugin-interface version that is not in the master branch, then set the correct branch name in this value.
|
||||
6. Click on "Run workflow".
|
||||
|
||||
## Running the core manually
|
||||
|
||||
1. Run `startTestEnv --wait` in a terminal, and keep it running
|
||||
2. Then open `supertokens-root` in another terminal and run `cp ./temp/config.yaml .`
|
||||
3. Then run `java -classpath "./core/*:./plugin-interface/*:./ee/*" io.supertokens.Main ./ DEV`. This will start the
|
||||
core to listen on `http://localhost:3567`
|
||||
|
||||
## Pull Request
|
||||
|
||||
1. Before submitting a pull request make sure all tests have passed
|
||||
2. Reference the relevant issue or pull request and give a clear description of changes/features added when submitting a
|
||||
pull request
|
||||
3. Make sure the PR title follows [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification
|
||||
|
||||
## Install the supertokens CLI manually
|
||||
|
||||
1. Setup test env and keep it running
|
||||
2. In `supertokens-root`, run `cp temp/config.yaml .`
|
||||
3. On a different terminal, go to `supertokens-root` folder and
|
||||
run `java -classpath "./cli/*" io.supertokens.cli.Main true install`
|
||||
|
||||
## SuperTokens Community
|
||||
|
||||
SuperTokens is made possible by a passionate team and a strong community of developers. If you have any questions or
|
||||
would like to get more involved in the SuperTokens community you can check out:
|
||||
|
||||
- [Github Issues](https://github.com/supertokens/supertokens-core/issues)
|
||||
- [Discord](https://supertokens.io/discord)
|
||||
- [Twitter](https://twitter.com/supertokensio)
|
||||
- or [email us](mailto:team@supertokens.io)
|
||||
|
||||
Additional resources you might find useful:
|
||||
|
||||
- [SuperTokens Docs](https://supertokens.io/docs/community/getting-started/installation)
|
||||
- [Blog Posts](https://supertokens.io/blog/)
|
||||
- [Development guideline for the backend and frontend recipes](https://github.com/supertokens/supertokens-core/wiki/Development-guideline-for-the-backend-and-frontend-recipes)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
31
build.gradle
31
build.gradle
|
|
@ -19,8 +19,7 @@ compileTestJava { options.encoding = "UTF-8" }
|
|||
// }
|
||||
//}
|
||||
|
||||
version = "6.0.13"
|
||||
|
||||
version = "6.0.21"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
|
@ -33,22 +32,19 @@ dependencies {
|
|||
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.14.0'
|
||||
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.14.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
|
||||
implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.1'
|
||||
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'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc
|
||||
implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.30.1'
|
||||
implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.45.1.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.mindrot/jbcrypt
|
||||
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4'
|
||||
|
|
@ -71,6 +67,23 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/commons-codec/commons-codec
|
||||
implementation group: 'commons-codec', name: 'commons-codec', version: '1.15'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.googlecode.libphonenumber/libphonenumber/
|
||||
implementation group: 'com.googlecode.libphonenumber', name: 'libphonenumber', version: '8.13.25'
|
||||
|
||||
|
||||
implementation platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.17.0-alpha")
|
||||
|
||||
implementation("ch.qos.logback:logback-core:1.5.18")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.18")
|
||||
|
||||
// OpenTelemetry core
|
||||
implementation("io.opentelemetry:opentelemetry-sdk")
|
||||
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
|
||||
implementation("io.opentelemetry:opentelemetry-exporter-logging")
|
||||
implementation("io.opentelemetry:opentelemetry-api")
|
||||
|
||||
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
|
||||
|
||||
compileOnly project(":supertokens-plugin-interface")
|
||||
testImplementation project(":supertokens-plugin-interface")
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ dependencies {
|
|||
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.10.0'
|
||||
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.10.0'
|
||||
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'
|
||||
|
|
|
|||
|
|
@ -7,29 +7,29 @@
|
|||
"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.10.0/jackson-dataformat-yaml-2.10.0.jar",
|
||||
"name": "Jackson Dataformat 2.10.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.10.0/jackson-dataformat-yaml-2.10.0-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/1.24/snakeyaml-1.24.jar",
|
||||
"name": "SnakeYAML 1.24",
|
||||
"src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.24/snakeyaml-1.24-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.10.0/jackson-core-2.10.0.jar",
|
||||
"name": "Jackson core 2.10.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.10.0/jackson-core-2.10.0-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.10.0/jackson-databind-2.10.0.jar",
|
||||
"name": "Jackson databind 2.10.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.10.0/jackson-databind-2.10.0-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.10.0/jackson-annotations-2.10.0.jar",
|
||||
"name": "Jackson annotation 2.10.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.10.0/jackson-annotations-2.10.0-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",
|
||||
|
|
|
|||
BIN
cli/jar/cli.jar
BIN
cli/jar/cli.jar
Binary file not shown.
|
|
@ -146,3 +146,11 @@ core_config_version: 0
|
|||
# when CDI version is not specified in the request. When set to null, the core will assume the latest version of the
|
||||
# CDI.
|
||||
# supertokens_max_cdi_version:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even
|
||||
# if there are more CUDs in the database and block all other CUDs from being used from this instance.
|
||||
# supertokens_saas_load_only_cud:
|
||||
|
||||
# (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:
|
||||
|
|
|
|||
|
|
@ -147,3 +147,11 @@ disable_telemetry: true
|
|||
# when CDI version is not specified in the request. When set to null, the core will assume the latest version of the
|
||||
# CDI.
|
||||
# supertokens_max_cdi_version:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even
|
||||
# if there are more CUDs in the database and block all other CUDs from being used from this instance.
|
||||
# supertokens_saas_load_only_cud:
|
||||
|
||||
# (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:
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -35,10 +35,10 @@ 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: '10.1.1'
|
||||
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.2.3'
|
||||
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.3.1'
|
||||
|
|
@ -46,10 +46,10 @@ dependencies {
|
|||
testImplementation 'com.tngtech.archunit:archunit-junit4:0.22.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
|
||||
testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.14.0'
|
||||
testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
|
||||
testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.0'
|
||||
testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
testImplementation group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
}
|
||||
|
|
|
|||
BIN
ee/jar/ee.jar
BIN
ee/jar/ee.jar
Binary file not shown.
|
|
@ -193,20 +193,19 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
|
||||
// TODO Active users are present only on public tenant and TOTP users may be present on different storages
|
||||
Storage publicTenantStorage = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main);
|
||||
final long now = System.currentTimeMillis();
|
||||
for (int i = 0; i < 30; i++) {
|
||||
long today = now - (now % (24 * 60 * 60 * 1000L));
|
||||
long timestamp = today - (i * 24 * 60 * 60 * 1000L);
|
||||
final long now = System.currentTimeMillis();
|
||||
for (int i = 1; i <= 31; i++) {
|
||||
long timestamp = now - (i * 24 * 60 * 60 * 1000L);
|
||||
|
||||
int totpMau = 0;
|
||||
// TODO Need to figure out a way to combine the data from different storages to get the final stats
|
||||
// for (Storage storage : storages) {
|
||||
totpMau += ((ActiveUsersStorage) publicTenantStorage).countUsersEnabledTotpAndActiveSince(this.appIdentifier, timestamp);
|
||||
// }
|
||||
totpMauArr.add(new JsonPrimitive(totpMau));
|
||||
}
|
||||
int totpMau = 0;
|
||||
// TODO Need to figure out a way to combine the data from different storages to get the final stats
|
||||
// for (Storage storage : storages) {
|
||||
totpMau += ((ActiveUsersStorage) publicTenantStorage).countUsersEnabledTotpAndActiveSince(this.appIdentifier, timestamp);
|
||||
// }
|
||||
totpMauArr.add(new JsonPrimitive(totpMau));
|
||||
}
|
||||
|
||||
totpStats.add("maus", totpMauArr);
|
||||
totpStats.add("maus", totpMauArr);
|
||||
|
||||
int totpTotalUsers = 0;
|
||||
for (Storage storage : storages) {
|
||||
|
|
@ -274,10 +273,10 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
|
||||
private JsonArray getMAUs() throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonArray mauArr = new JsonArray();
|
||||
for (int i = 0; i < 30; i++) {
|
||||
long now = System.currentTimeMillis();
|
||||
long today = now - (now % (24 * 60 * 60 * 1000L));
|
||||
long timestamp = today - (i * 24 * 60 * 60 * 1000L);
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (int i = 1; i <= 31; i++) {
|
||||
long timestamp = now - (i * 24 * 60 * 60 * 1000L);
|
||||
ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(
|
||||
this.appIdentifier.getAsPublicTenantIdentifier(), main);
|
||||
int mau = activeUsersStorage.countUsersActiveSince(this.appIdentifier, timestamp);
|
||||
|
|
|
|||
|
|
@ -1326,7 +1326,7 @@ public class EETest extends Mockito {
|
|||
JsonObject paidFeatureUsageStats = j.getAsJsonObject("paidFeatureUsageStats");
|
||||
JsonArray mauArr = paidFeatureUsageStats.get("maus").getAsJsonArray();
|
||||
assertEquals(paidFeatureUsageStats.entrySet().size(), 1);
|
||||
assertEquals(mauArr.size(), 30);
|
||||
assertEquals(mauArr.size(), 31);
|
||||
assertEquals(mauArr.get(0).getAsInt(), 0);
|
||||
assertEquals(mauArr.get(29).getAsInt(), 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class GetFeatureFlagAPITest {
|
|||
if (StorageLayer.getStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
|
||||
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
|
||||
assertEquals(1, usageStats.entrySet().size());
|
||||
assertEquals(30, mauArr.size());
|
||||
assertEquals(31, mauArr.size());
|
||||
assertEquals(0, mauArr.get(0).getAsInt());
|
||||
assertEquals(0, mauArr.get(29).getAsInt());
|
||||
} else {
|
||||
|
|
@ -92,7 +92,7 @@ public class GetFeatureFlagAPITest {
|
|||
if (StorageLayer.getStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
|
||||
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
|
||||
assertEquals(1, usageStats.entrySet().size());
|
||||
assertEquals(30, mauArr.size());
|
||||
assertEquals(31, mauArr.size());
|
||||
assertEquals(0, mauArr.get(0).getAsInt());
|
||||
assertEquals(0, mauArr.get(29).getAsInt());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,115 +1,125 @@
|
|||
{
|
||||
"_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.14.2/jackson-dataformat-yaml-2.14.2.jar",
|
||||
"name": "Jackson Dataformat 2.14.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.14.2/jackson-dataformat-yaml-2.14.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar",
|
||||
"name": "SnakeYAML 1.33",
|
||||
"src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.33/snakeyaml-1.33-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2.jar",
|
||||
"name": "Jackson core 2.14.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2.jar",
|
||||
"name": "Jackson databind 2.14.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2.jar",
|
||||
"name": "Jackson annotation 2.14.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar",
|
||||
"name": "Logback classic 1.2.3",
|
||||
"src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar",
|
||||
"name": "Logback core 1.2.3",
|
||||
"src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar",
|
||||
"name": "SLF4j API 1.7.25",
|
||||
"src": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.1/tomcat-annotations-api-10.1.1.jar",
|
||||
"name": "Tomcat annotations API 10.1.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.1/tomcat-annotations-api-10.1.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.1/tomcat-embed-core-10.1.1.jar",
|
||||
"name": "Tomcat embed core API 10.1.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.1/tomcat-embed-core-10.1.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
|
||||
"name": "JSR305 3.0.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar",
|
||||
"name": "JSR305 3.0.2",
|
||||
"src": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.30.1/sqlite-jdbc-3.30.1.jar",
|
||||
"name": "SQLite JDBC Driver 3.30.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.30.1/sqlite-jdbc-3.30.1-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/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0.jar",
|
||||
"name": "Auth0 Java JWT",
|
||||
"src": "https://repo1.maven.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0-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/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"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0.jar",
|
||||
"name": "Scrypt 1.4.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0.jar",
|
||||
"name": "Java OTP 0.4.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar",
|
||||
"name": "Commons Codec 1.15",
|
||||
"src": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar"
|
||||
}
|
||||
]
|
||||
"_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.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/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/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/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/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/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",
|
||||
"name":"jsr305 3.0.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar",
|
||||
"name":"sqlite-jdbc 3.45.1.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar",
|
||||
"name":"slf4j-api 2.0.17",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17-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"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar",
|
||||
"name":"annotations 13.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0-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/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0.jar",
|
||||
"name":"java-jwt 4.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0.jar",
|
||||
"name":"scrypt 1.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0.jar",
|
||||
"name":"java-otp 0.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar",
|
||||
"name":"commons-codec 1.15",
|
||||
"src":"https://repo.maven.apache.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25.jar",
|
||||
"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/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar",
|
||||
"name":"logback-core 1.5.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18.jar",
|
||||
"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/io/opentelemetry/opentelemetry-api/1.51.0/opentelemetry-api-1.51.0.jar",
|
||||
"name":"opentelemetry-api 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-api/1.51.0/opentelemetry-api-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-logging/1.51.0/opentelemetry-exporter-logging-1.51.0.jar",
|
||||
"name":"opentelemetry-exporter-logging 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-logging/1.51.0/opentelemetry-exporter-logging-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-sdk/1.51.0/opentelemetry-sdk-1.51.0.jar",
|
||||
"name":"opentelemetry-sdk 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-sdk/1.51.0/opentelemetry-sdk-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-otlp/1.51.0/opentelemetry-exporter-otlp-1.51.0.jar",
|
||||
"name":"opentelemetry-exporter-otlp 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-otlp/1.51.0/opentelemetry-exporter-otlp-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/semconv/opentelemetry-semconv/1.34.0/opentelemetry-semconv-1.34.0.jar",
|
||||
"name":"opentelemetry-semconv 1.34.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/semconv/opentelemetry-semconv/1.34.0/opentelemetry-semconv-1.34.0-sources.jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
|
@ -44,6 +44,7 @@ import io.supertokens.signingkeys.AccessTokenSigningKey;
|
|||
import io.supertokens.signingkeys.JWTSigningKey;
|
||||
import io.supertokens.signingkeys.SigningKeys;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.version.Version;
|
||||
import io.supertokens.webserver.Webserver;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -159,6 +160,8 @@ public class Main {
|
|||
|
||||
Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);
|
||||
|
||||
TelemetryProvider.initialize(this);
|
||||
|
||||
// loading storage layer
|
||||
try {
|
||||
StorageLayer.initPrimary(this, CLIOptions.get(this).getInstallationPath() + "plugin/",
|
||||
|
|
@ -254,6 +257,9 @@ public class Main {
|
|||
// starts DeleteExpiredAccessTokenSigningKeys cronjob if the access token signing keys can change
|
||||
Cronjobs.addCronjob(this, DeleteExpiredAccessTokenSigningKeys.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// this is to ensure tenantInfos are in sync for the new cron job as well
|
||||
MultitenancyHelper.getInstance(this).refreshCronjobs();
|
||||
|
||||
// creates password hashing pool
|
||||
PasswordHashing.init(this);
|
||||
|
||||
|
|
@ -417,6 +423,7 @@ public class Main {
|
|||
StorageLayer.close(this);
|
||||
removeDotStartedFileForThisProcess();
|
||||
Logging.stopLogging(this);
|
||||
TelemetryProvider.closeTelemetry(this);
|
||||
// uncomment this when you want to confirm that processes are actually shut.
|
||||
// printRunningThreadNames();
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ import io.supertokens.config.annotations.NotConflictingInApp;
|
|||
import io.supertokens.pluginInterface.LOG_LEVEL;
|
||||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
import io.supertokens.utils.SemVer;
|
||||
import io.supertokens.webserver.Utils;
|
||||
import io.supertokens.webserver.WebserverAPI;
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.apache.catalina.filters.RemoteAddrFilter;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
|
|
@ -197,12 +199,20 @@ public class CoreConfig {
|
|||
@JsonProperty
|
||||
private String supertokens_max_cdi_version = null;
|
||||
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
private String supertokens_saas_load_only_cud = null;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private Set<LOG_LEVEL> allowedLogLevels = null;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private boolean isNormalizedAndValid = false;
|
||||
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
private String otel_collector_connection_uri = "http://localhost:4317";
|
||||
|
||||
public static Set<String> getValidFields() {
|
||||
CoreConfig coreConfig = new CoreConfig();
|
||||
JsonObject coreConfigObj = new GsonBuilder().serializeNulls().create().toJsonTree(coreConfig).getAsJsonObject();
|
||||
|
|
@ -254,6 +264,10 @@ public class CoreConfig {
|
|||
return base_path;
|
||||
}
|
||||
|
||||
public String getSuperTokensLoadOnlyCUD() {
|
||||
return supertokens_saas_load_only_cud;
|
||||
}
|
||||
|
||||
public enum PASSWORD_HASHING_ALG {
|
||||
ARGON2, BCRYPT, FIREBASE_SCRYPT
|
||||
}
|
||||
|
|
@ -388,6 +402,10 @@ public class CoreConfig {
|
|||
return webserver_https_enabled;
|
||||
}
|
||||
|
||||
public String getOtelCollectorConnectionURI() {
|
||||
return otel_collector_connection_uri;
|
||||
}
|
||||
|
||||
private String getConfigFileLocation(Main main) {
|
||||
return new File(CLIOptions.get(main).getConfigFilePath() == null
|
||||
? CLIOptions.get(main).getInstallationPath() + "config.yaml"
|
||||
|
|
@ -663,6 +681,15 @@ public class CoreConfig {
|
|||
host = cliHost;
|
||||
}
|
||||
|
||||
if (supertokens_saas_load_only_cud != null) {
|
||||
try {
|
||||
supertokens_saas_load_only_cud =
|
||||
Utils.normalizeAndValidateConnectionUriDomain(supertokens_saas_load_only_cud, true);
|
||||
} catch (ServletException e) {
|
||||
throw new InvalidConfigException("supertokens_saas_load_only_cud is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
access_token_validity = access_token_validity * 1000;
|
||||
access_token_dynamic_signing_key_update_interval = access_token_dynamic_signing_key_update_interval * 3600 * 1000;
|
||||
refresh_token_validity = refresh_token_validity * 60 * 1000;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import io.supertokens.pluginInterface.Storage;
|
|||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -187,6 +188,11 @@ public abstract class CronTask extends ResourceDistributor.SingletonResource imp
|
|||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public List<List<TenantIdentifier>> getTenantsInfo() {
|
||||
return this.tenantsInfo;
|
||||
}
|
||||
|
||||
// the list belongs to tenants that are a part of the same user pool ID
|
||||
protected void doTaskPerStorage(Storage storage) throws Exception {
|
||||
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ public class Cronjobs extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
Cronjobs instance = getInstance(main);
|
||||
synchronized (instance.lock) {
|
||||
instance.executor.scheduleWithFixedDelay(task, task.getInitialWaitTimeSeconds(),
|
||||
task.getIntervalTimeSeconds(), TimeUnit.SECONDS);
|
||||
if (!instance.tasks.contains(task)) {
|
||||
instance.executor.scheduleWithFixedDelay(task, task.getInitialWaitTimeSeconds(),
|
||||
task.getIntervalTimeSeconds(), TimeUnit.SECONDS);
|
||||
instance.tasks.add(task);
|
||||
}
|
||||
}
|
||||
|
|
@ -103,4 +103,13 @@ public class Cronjobs extends ResourceDistributor.SingletonResource {
|
|||
public List<CronTask> getTasks() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public List<List<List<TenantIdentifier>>> getTenantInfos() {
|
||||
List<List<List<TenantIdentifier>>> tenantsInfos = new ArrayList<>();
|
||||
for (CronTask task : this.tasks) {
|
||||
tenantsInfos.add(task.getTenantsInfo());
|
||||
}
|
||||
return tenantsInfos;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,20 +16,26 @@
|
|||
|
||||
package io.supertokens.cronjobs.telemetry;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.dashboard.Dashboard;
|
||||
import io.supertokens.httpRequest.HttpRequest;
|
||||
import io.supertokens.httpRequest.HttpRequestMocking;
|
||||
import io.supertokens.pluginInterface.ActiveUsersStorage;
|
||||
import io.supertokens.pluginInterface.KeyValueInfo;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.dashboard.DashboardUser;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
|
|
@ -90,22 +96,63 @@ public class Telemetry extends CronTask {
|
|||
json.addProperty("telemetryId", telemetryId.value);
|
||||
json.addProperty("superTokensVersion", coreVersion);
|
||||
|
||||
if (StorageLayer.getBaseStorage(main).getType() == STORAGE_TYPE.SQL) {
|
||||
ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main);
|
||||
json.addProperty("mau", activeUsersStorage.countUsersActiveSince(app, System.currentTimeMillis() - 30 * 24 * 3600 * 1000L));
|
||||
} else {
|
||||
json.addProperty("mau", -1);
|
||||
}
|
||||
json.addProperty("appId", app.getAppId());
|
||||
json.addProperty("connectionUriDomain", app.getConnectionUriDomain());
|
||||
|
||||
if (StorageLayer.getBaseStorage(main).getType() == STORAGE_TYPE.SQL) {
|
||||
{ // Users count across all tenants
|
||||
Storage[] storages = StorageLayer.getStoragesForApp(main, app);
|
||||
AppIdentifierWithStorage appIdentifierWithAllTenantStorages = new AppIdentifierWithStorage(
|
||||
app.getConnectionUriDomain(), app.getAppId(),
|
||||
StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main), storages
|
||||
);
|
||||
|
||||
json.addProperty("usersCount",
|
||||
AuthRecipe.getUsersCountAcrossAllTenants(appIdentifierWithAllTenantStorages, null));
|
||||
}
|
||||
|
||||
{ // Dashboard user emails
|
||||
// Dashboard APIs are app specific and are always stored on the public tenant
|
||||
DashboardUser[] dashboardUsers = Dashboard.getAllDashboardUsers(
|
||||
app.withStorage(StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main)), main);
|
||||
JsonArray dashboardUserEmails = new JsonArray();
|
||||
for (DashboardUser user : dashboardUsers) {
|
||||
dashboardUserEmails.add(new JsonPrimitive(user.email));
|
||||
}
|
||||
|
||||
json.add("dashboardUserEmails", dashboardUserEmails);
|
||||
}
|
||||
|
||||
{ // MAUs
|
||||
// Active users are always tracked on the public tenant, so we use the public tenant's storage
|
||||
ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(
|
||||
app.getAsPublicTenantIdentifier(), main);
|
||||
|
||||
JsonArray mauArr = new JsonArray();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (int i = 1; i <= 31; i++) {
|
||||
long timestamp = now - (i * 24 * 60 * 60 * 1000L);
|
||||
int mau = activeUsersStorage.countUsersActiveSince(app, timestamp);
|
||||
mauArr.add(new JsonPrimitive(mau));
|
||||
}
|
||||
|
||||
json.add("maus", mauArr);
|
||||
}
|
||||
} else {
|
||||
json.addProperty("usersCount", -1);
|
||||
json.add("dashboardUserEmails", new JsonArray());
|
||||
json.add("maus", new JsonArray());
|
||||
}
|
||||
|
||||
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
|
||||
// to use this)
|
||||
if (!Main.isTesting || HttpRequestMocking.getInstance(main).getMockURL(REQUEST_ID, url) != null) {
|
||||
HttpRequest.sendJsonPOSTRequest(main, REQUEST_ID, url, json, 10000, 10000, 4);
|
||||
HttpRequest.sendJsonPOSTRequest(main, REQUEST_ID, url, json, 10000, 10000, 5);
|
||||
ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.SENT_TELEMETRY, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -511,10 +511,11 @@ public class Start
|
|||
@Override
|
||||
public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
|
||||
String sessionHandle, String refreshTokenHash2,
|
||||
long expiry) throws StorageQueryException {
|
||||
long expiry, boolean useStaticKey) throws StorageQueryException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle, refreshTokenHash2, expiry);
|
||||
SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle,
|
||||
refreshTokenHash2, expiry, useStaticKey);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
|
|
@ -2154,10 +2155,11 @@ public class Start
|
|||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, String> getUserIdMappingForSuperTokensIds(ArrayList<String> userIds)
|
||||
public HashMap<String, String> getUserIdMappingForSuperTokensIds(AppIdentifier appIdentifier,
|
||||
ArrayList<String> userIds)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return UserIdMappingQueries.getUserIdMappingWithUserIds(this, userIds);
|
||||
return UserIdMappingQueries.getUserIdMappingWithUserIds(this, appIdentifier, userIds);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,18 +124,19 @@ public class SessionQueries {
|
|||
|
||||
public static void updateSessionInfo_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
|
||||
String sessionHandle,
|
||||
String refreshTokenHash2, long expiry)
|
||||
String refreshTokenHash2, long expiry, boolean useStaticKey)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "UPDATE " + getConfig(start).getSessionInfoTable()
|
||||
+ " SET refresh_token_hash_2 = ?, expires_at = ?"
|
||||
+ " SET refresh_token_hash_2 = ?, expires_at = ?, use_static_key = ?"
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?";
|
||||
|
||||
update(con, QUERY, pst -> {
|
||||
pst.setString(1, refreshTokenHash2);
|
||||
pst.setLong(2, expiry);
|
||||
pst.setString(3, tenantIdentifier.getAppId());
|
||||
pst.setString(4, tenantIdentifier.getTenantId());
|
||||
pst.setString(5, sessionHandle);
|
||||
pst.setBoolean(3, useStaticKey);
|
||||
pst.setString(4, tenantIdentifier.getAppId());
|
||||
pst.setString(5, tenantIdentifier.getTenantId());
|
||||
pst.setString(6, sessionHandle);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ public class UserIdMappingQueries {
|
|||
|
||||
}
|
||||
|
||||
public static HashMap<String, String> getUserIdMappingWithUserIds(Start start, ArrayList<String> userIds)
|
||||
public static HashMap<String, String> getUserIdMappingWithUserIds(Start start,
|
||||
AppIdentifier appIdentifier,
|
||||
ArrayList<String> userIds)
|
||||
throws SQLException, StorageQueryException {
|
||||
|
||||
if (userIds.size() == 0) {
|
||||
|
|
@ -124,7 +126,8 @@ public class UserIdMappingQueries {
|
|||
|
||||
// No need to filter based on tenantId because the id list is already filtered for a tenant
|
||||
StringBuilder QUERY = new StringBuilder(
|
||||
"SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE supertokens_user_id IN (");
|
||||
"SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE app_id = ? AND " +
|
||||
"supertokens_user_id IN (");
|
||||
for (int i = 0; i < userIds.size(); i++) {
|
||||
QUERY.append("?");
|
||||
if (i != userIds.size() - 1) {
|
||||
|
|
@ -134,9 +137,10 @@ public class UserIdMappingQueries {
|
|||
}
|
||||
QUERY.append(")");
|
||||
return execute(start, QUERY.toString(), pst -> {
|
||||
pst.setString(1, appIdentifier.getAppId());
|
||||
for (int i = 0; i < userIds.size(); i++) {
|
||||
// i+1 cause this starts with 1 and not 0
|
||||
pst.setString(i + 1, userIds.get(i));
|
||||
// i+2 cause this starts with 1 and not 0, and 1 is the app_id
|
||||
pst.setString(i + 2, userIds.get(i));
|
||||
}
|
||||
}, result -> {
|
||||
HashMap<String, String> userIdMappings = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -51,9 +51,20 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
private Main main;
|
||||
private TenantConfig[] tenantConfigs;
|
||||
|
||||
// when the core has `supertokens_saas_load_only_cud` set, the tenantConfigs array will be filtered
|
||||
// based on the config value. However, we need to keep all the list of CUDs from the db to be able
|
||||
// to check if the CUD is present in the DB or not, while processing the requests.
|
||||
private final Set<String> dangerous_allCUDsFromDb = new HashSet<>();
|
||||
|
||||
private MultitenancyHelper(Main main) throws StorageQueryException {
|
||||
this.main = main;
|
||||
this.tenantConfigs = getAllTenantsFromDb();
|
||||
TenantConfig[] allTenantsFromDb = getAllTenantsFromDb();
|
||||
this.tenantConfigs = this.getFilteredTenantConfigs(allTenantsFromDb);
|
||||
this.dangerous_allCUDsFromDb.clear();
|
||||
|
||||
for (TenantConfig config : allTenantsFromDb) {
|
||||
this.dangerous_allCUDsFromDb.add(config.tenantIdentifier.getConnectionUriDomain());
|
||||
}
|
||||
}
|
||||
|
||||
public static MultitenancyHelper getInstance(Main main) {
|
||||
|
|
@ -81,7 +92,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
// instance of eeFeatureFlag. This is applicable only when the core is starting on
|
||||
// an empty database as no tenants are loaded from the db yet.
|
||||
} catch (CannotModifyBaseConfigException | BadPermissionException | FeatureNotEnabledException |
|
||||
InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) {
|
||||
InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -108,10 +119,11 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
return main.getResourceDistributor().withResourceDistributorLock(() -> {
|
||||
try {
|
||||
TenantConfig[] tenantsFromDb = getAllTenantsFromDb();
|
||||
TenantConfig[] filteredTenantsFromDb = this.getFilteredTenantConfigs(tenantsFromDb);
|
||||
|
||||
Map<ResourceDistributor.KeyClass, JsonObject> normalizedTenantsFromDb =
|
||||
Config.getNormalisedConfigsForAllTenants(
|
||||
tenantsFromDb, Config.getBaseConfigAsJsonObject(main));
|
||||
filteredTenantsFromDb, Config.getBaseConfigAsJsonObject(main));
|
||||
|
||||
Map<ResourceDistributor.KeyClass, JsonObject> normalizedTenantsFromMemory =
|
||||
Config.getNormalisedConfigsForAllTenants(
|
||||
|
|
@ -129,9 +141,14 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
}
|
||||
|
||||
boolean sameNumberOfTenants = tenantsFromDb.length == this.tenantConfigs.length;
|
||||
boolean sameNumberOfTenants =
|
||||
filteredTenantsFromDb.length == this.tenantConfigs.length;
|
||||
|
||||
this.tenantConfigs = tenantsFromDb;
|
||||
this.dangerous_allCUDsFromDb.clear();
|
||||
for (TenantConfig tenant : tenantsFromDb) {
|
||||
this.dangerous_allCUDsFromDb.add(tenant.tenantIdentifier.getConnectionUriDomain());
|
||||
}
|
||||
this.tenantConfigs = filteredTenantsFromDb;
|
||||
if (tenantsThatChanged.size() == 0 && sameNumberOfTenants) {
|
||||
return tenantsThatChanged;
|
||||
}
|
||||
|
|
@ -190,7 +207,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
public void loadFeatureFlag(List<TenantIdentifier> tenantsThatChanged) {
|
||||
List<AppIdentifier> apps = new ArrayList<>();
|
||||
Set<AppIdentifier> appsSet = new HashSet<>();
|
||||
for (TenantConfig t : tenantConfigs) {
|
||||
for (TenantConfig t : this.tenantConfigs) {
|
||||
if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -204,7 +221,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
throws UnsupportedJWTSigningAlgorithmException {
|
||||
List<AppIdentifier> apps = new ArrayList<>();
|
||||
Set<AppIdentifier> appsSet = new HashSet<>();
|
||||
for (TenantConfig t : tenantConfigs) {
|
||||
for (TenantConfig t : this.tenantConfigs) {
|
||||
if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -217,7 +234,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
SigningKeys.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
}
|
||||
|
||||
private void refreshCronjobs() {
|
||||
public void refreshCronjobs() {
|
||||
List<List<TenantIdentifier>> list = StorageLayer.getTenantsWithUniqueUserPoolId(main);
|
||||
Cronjobs.getInstance(main).setTenantsInfo(list);
|
||||
}
|
||||
|
|
@ -238,4 +255,21 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TenantConfig[] getFilteredTenantConfigs(TenantConfig[] inputTenantConfigs) {
|
||||
String loadOnlyCUD = Config.getBaseConfig(main).getSuperTokensLoadOnlyCUD();
|
||||
|
||||
if (loadOnlyCUD == null) {
|
||||
return inputTenantConfigs;
|
||||
}
|
||||
|
||||
return Arrays.stream(inputTenantConfigs)
|
||||
.filter(tenantConfig -> tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(loadOnlyCUD)
|
||||
|| tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI))
|
||||
.toArray(TenantConfig[]::new);
|
||||
}
|
||||
|
||||
public boolean isConnectionUriDomainPresentInDb(String cud) {
|
||||
return this.dangerous_allCUDsFromDb.contains(cud);
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import io.supertokens.pluginInterface.Storage;
|
|||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.utils.Utils;
|
||||
import io.supertokens.webserver.Webserver;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -109,6 +110,7 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
msg = prependTenantIdentifierToMessage(tenantIdentifier, msg);
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).infoLogger.debug(msg);
|
||||
TelemetryProvider.createLogEvent(main, tenantIdentifier, msg, "debug");
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
// sometimes logger.debug throws a null pointer exception...
|
||||
|
|
@ -132,6 +134,8 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
if (getInstance(main) != null) {
|
||||
getInstance(main).infoLogger.info(msg);
|
||||
}
|
||||
|
||||
TelemetryProvider.createLogEvent(main, tenantIdentifier, msg, "info");
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
}
|
||||
|
|
@ -145,6 +149,8 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
msg = prependTenantIdentifierToMessage(tenantIdentifier, msg);
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.warn(msg);
|
||||
TelemetryProvider.createLogEvent(main, tenantIdentifier, msg, "warn");
|
||||
|
||||
}
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
|
|
@ -166,6 +172,7 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
err = prependTenantIdentifierToMessage(tenantIdentifier, err);
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.error(err);
|
||||
TelemetryProvider.createLogEvent(main, tenantIdentifier, err, "error");
|
||||
}
|
||||
if (toConsoleAsWell || getInstance(main) == null) {
|
||||
systemErr(err);
|
||||
|
|
@ -199,6 +206,9 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
message = prependTenantIdentifierToMessage(tenantIdentifier, message);
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.error(message);
|
||||
TelemetryProvider
|
||||
.createLogEvent(main, tenantIdentifier, message,
|
||||
"error");
|
||||
}
|
||||
if (toConsoleAsWell || getInstance(main) == null) {
|
||||
systemErr(message);
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ public class Session {
|
|||
accessToken.sessionHandle,
|
||||
Utils.hashSHA256(accessToken.refreshTokenHash1),
|
||||
System.currentTimeMillis() +
|
||||
config.getRefreshTokenValidity());
|
||||
config.getRefreshTokenValidity(), sessionInfo.useStaticKey);
|
||||
}
|
||||
storage.commitTransaction(con);
|
||||
|
||||
|
|
@ -423,7 +423,7 @@ public class Session {
|
|||
Utils.hashSHA256(accessToken.refreshTokenHash1),
|
||||
System.currentTimeMillis() + Config.getConfig(tenantIdentifierWithStorage, main)
|
||||
.getRefreshTokenValidity(),
|
||||
sessionInfo.lastUpdatedSign);
|
||||
sessionInfo.lastUpdatedSign, sessionInfo.useStaticKey);
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -473,7 +473,7 @@ public class Session {
|
|||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
|
||||
try {
|
||||
return refreshSession(new AppIdentifier(null, null), main, refreshToken, antiCsrfToken,
|
||||
enableAntiCsrf, accessTokenVersion);
|
||||
enableAntiCsrf, accessTokenVersion, null);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -482,7 +482,8 @@ public class Session {
|
|||
public static SessionInformationHolder refreshSession(AppIdentifier appIdentifier, Main main,
|
||||
@Nonnull String refreshToken,
|
||||
@Nullable String antiCsrfToken, boolean enableAntiCsrf,
|
||||
AccessToken.VERSION accessTokenVersion)
|
||||
AccessToken.VERSION accessTokenVersion,
|
||||
Boolean shouldUseStaticKey)
|
||||
throws StorageTransactionLogicException,
|
||||
UnauthorisedException, StorageQueryException, TokenTheftDetectedException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError, TenantOrAppNotFoundException {
|
||||
|
|
@ -498,14 +499,15 @@ public class Session {
|
|||
|
||||
return refreshSessionHelper(refreshTokenInfo.tenantIdentifier.withStorage(
|
||||
StorageLayer.getStorage(refreshTokenInfo.tenantIdentifier, main)),
|
||||
main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion);
|
||||
main, refreshToken, refreshTokenInfo, enableAntiCsrf, accessTokenVersion, shouldUseStaticKey);
|
||||
}
|
||||
|
||||
private static SessionInformationHolder refreshSessionHelper(
|
||||
TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String refreshToken,
|
||||
RefreshToken.RefreshTokenInfo refreshTokenInfo,
|
||||
boolean enableAntiCsrf,
|
||||
AccessToken.VERSION accessTokenVersion)
|
||||
AccessToken.VERSION accessTokenVersion,
|
||||
Boolean shouldUseStaticKey)
|
||||
throws StorageTransactionLogicException, UnauthorisedException, StorageQueryException,
|
||||
TokenTheftDetectedException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError,
|
||||
TenantOrAppNotFoundException {
|
||||
|
|
@ -530,7 +532,16 @@ public class Session {
|
|||
throw new UnauthorisedException("Session missing in db or has expired");
|
||||
}
|
||||
|
||||
boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey;
|
||||
|
||||
if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) {
|
||||
if (useStaticKey != sessionInfo.useStaticKey) {
|
||||
// We do not update anything except the static key status
|
||||
storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle,
|
||||
sessionInfo.refreshTokenHash2, sessionInfo.expiry,
|
||||
useStaticKey);
|
||||
}
|
||||
|
||||
// at this point, the input refresh token is the parent one.
|
||||
storage.commitTransaction(con);
|
||||
String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null;
|
||||
|
|
@ -542,7 +553,7 @@ public class Session {
|
|||
main, sessionHandle,
|
||||
sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token),
|
||||
Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken,
|
||||
null, accessTokenVersion, sessionInfo.useStaticKey);
|
||||
null, accessTokenVersion, useStaticKey);
|
||||
|
||||
TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(),
|
||||
newRefreshToken.expiry, newRefreshToken.createdTime);
|
||||
|
|
@ -560,13 +571,13 @@ public class Session {
|
|||
.equals(sessionInfo.refreshTokenHash2))) {
|
||||
storage.updateSessionInfo_Transaction(tenantIdentifierWithStorage, con, sessionHandle,
|
||||
Utils.hashSHA256(Utils.hashSHA256(refreshToken)),
|
||||
System.currentTimeMillis() + config.getRefreshTokenValidity());
|
||||
System.currentTimeMillis() + config.getRefreshTokenValidity(), useStaticKey);
|
||||
|
||||
storage.commitTransaction(con);
|
||||
|
||||
return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken,
|
||||
refreshTokenInfo, enableAntiCsrf,
|
||||
accessTokenVersion);
|
||||
accessTokenVersion, shouldUseStaticKey);
|
||||
}
|
||||
|
||||
storage.commitTransaction(con);
|
||||
|
|
@ -613,7 +624,19 @@ public class Session {
|
|||
throw new UnauthorisedException("Session missing in db or has expired");
|
||||
}
|
||||
|
||||
boolean useStaticKey = shouldUseStaticKey != null ? shouldUseStaticKey : sessionInfo.useStaticKey;
|
||||
|
||||
if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) {
|
||||
if (sessionInfo.useStaticKey != useStaticKey) {
|
||||
// We do not update anything except the static key status
|
||||
boolean success = storage.updateSessionInfo_Transaction(sessionHandle,
|
||||
sessionInfo.refreshTokenHash2, sessionInfo.expiry,
|
||||
sessionInfo.lastUpdatedSign, useStaticKey);
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point, the input refresh token is the parent one.
|
||||
String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null;
|
||||
|
||||
|
|
@ -624,7 +647,7 @@ public class Session {
|
|||
sessionHandle,
|
||||
sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token),
|
||||
Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken,
|
||||
null, accessTokenVersion, sessionInfo.useStaticKey);
|
||||
null, accessTokenVersion, useStaticKey);
|
||||
|
||||
TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(), newRefreshToken.expiry,
|
||||
newRefreshToken.createdTime);
|
||||
|
|
@ -644,13 +667,13 @@ public class Session {
|
|||
Utils.hashSHA256(Utils.hashSHA256(refreshToken)),
|
||||
System.currentTimeMillis() +
|
||||
Config.getConfig(tenantIdentifierWithStorage, main).getRefreshTokenValidity(),
|
||||
sessionInfo.lastUpdatedSign);
|
||||
sessionInfo.lastUpdatedSign, useStaticKey);
|
||||
if (!success) {
|
||||
continue;
|
||||
}
|
||||
return refreshSessionHelper(tenantIdentifierWithStorage, main, refreshToken, refreshTokenInfo,
|
||||
enableAntiCsrf,
|
||||
accessTokenVersion);
|
||||
accessTokenVersion, shouldUseStaticKey);
|
||||
}
|
||||
|
||||
throw new TokenTheftDetectedException(sessionHandle, sessionInfo.userId);
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
|
||||
Set<String> userPoolsInUse = new HashSet<>();
|
||||
Set<String> uniquePoolsInUse = new HashSet<>();
|
||||
|
||||
for (ResourceDistributor.KeyClass key : resourceKeyToStorageMap.keySet()) {
|
||||
Storage currStorage = resourceKeyToStorageMap.get(key);
|
||||
|
|
@ -259,11 +259,16 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
main.getResourceDistributor().setResource(key.getTenantIdentifier(), RESOURCE_KEY,
|
||||
new StorageLayer(resourceKeyToStorageMap.get(key)));
|
||||
|
||||
userPoolsInUse.add(userPoolId);
|
||||
uniquePoolsInUse.add(uniqueId);
|
||||
}
|
||||
|
||||
for (ResourceDistributor.KeyClass key : existingStorageMap.keySet()) {
|
||||
if (!userPoolsInUse.contains(((StorageLayer) existingStorageMap.get(key)).storage.getUserPoolId())) {
|
||||
Storage existingStorage = ((StorageLayer) existingStorageMap.get(key)).storage;
|
||||
String userPoolId = existingStorage.getUserPoolId();
|
||||
String connectionPoolId = existingStorage.getConnectionPoolId();
|
||||
String uniqueId = userPoolId + "~" + connectionPoolId;
|
||||
|
||||
if (!uniquePoolsInUse.contains(uniqueId)) {
|
||||
((StorageLayer) existingStorageMap.get(key)).storage.close();
|
||||
((StorageLayer) existingStorageMap.get(key)).storage.stopLogging();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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.telemetry;
|
||||
|
||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
|
||||
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
|
||||
|
||||
public class TelemetryProvider extends ResourceDistributor.SingletonResource {
|
||||
|
||||
private static final String RESOURCE_ID = "io.supertokens.telemetry.TelemetryProvider";
|
||||
|
||||
private final OpenTelemetry openTelemetry;
|
||||
|
||||
private static synchronized TelemetryProvider getInstance(Main main) {
|
||||
TelemetryProvider instance = null;
|
||||
try {
|
||||
instance = (TelemetryProvider) main.getResourceDistributor()
|
||||
.getResource(TenantIdentifier.BASE_TENANT, RESOURCE_ID);
|
||||
} catch (TenantOrAppNotFoundException ignored) {
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void initialize(Main main) {
|
||||
main.getResourceDistributor()
|
||||
.setResource(TenantIdentifier.BASE_TENANT, RESOURCE_ID, new TelemetryProvider(main));
|
||||
}
|
||||
|
||||
public static void createLogEvent(Main main, TenantIdentifier tenantIdentifier, String logMessage,
|
||||
String logLevel) {
|
||||
getInstance(main).openTelemetry.getTracer("core-tracer")
|
||||
.spanBuilder(logLevel)
|
||||
.setParent(Context.current())
|
||||
.setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain())
|
||||
.setAttribute("tenant.appId", tenantIdentifier.getAppId())
|
||||
.setAttribute("tenant.tenantId", tenantIdentifier.getTenantId())
|
||||
.startSpan()
|
||||
.addEvent("log",
|
||||
Attributes.builder()
|
||||
.put("message", logMessage)
|
||||
.build(),
|
||||
System.currentTimeMillis(), TimeUnit.MILLISECONDS)
|
||||
.end();
|
||||
}
|
||||
|
||||
public static Span startSpan(Main main, TenantIdentifier tenantIdentifier, String spanName) {
|
||||
Span span = getInstance(main).openTelemetry.getTracer("core-tracer")
|
||||
.spanBuilder(spanName)
|
||||
.setParent(Context.current())
|
||||
.setAttribute("tenant.connectionUriDomain", tenantIdentifier.getConnectionUriDomain())
|
||||
.setAttribute("tenant.appId", tenantIdentifier.getAppId())
|
||||
.setAttribute("tenant.tenantId", tenantIdentifier.getTenantId())
|
||||
.startSpan();
|
||||
|
||||
span.makeCurrent(); // Set the span as the current context
|
||||
return span;
|
||||
}
|
||||
|
||||
public static Span endSpan(Span span) {
|
||||
if (span != null) {
|
||||
span.end();
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
public static Span addEventToSpan(Span span, String eventName, Attributes attributes) {
|
||||
if (span != null) {
|
||||
span.addEvent(eventName, attributes, System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
|
||||
private static OpenTelemetry initializeOpenTelemetry(Main main) {
|
||||
if (getInstance(main) != null && getInstance(main).openTelemetry != null) {
|
||||
return getInstance(main).openTelemetry; // already initialized
|
||||
}
|
||||
|
||||
Resource resource = Resource.getDefault().toBuilder()
|
||||
.put(SERVICE_NAME, "supertokens-core")
|
||||
.build();
|
||||
|
||||
String collectorUri = Config.getBaseConfig(main).getOtelCollectorConnectionURI();
|
||||
|
||||
SdkTracerProvider sdkTracerProvider =
|
||||
SdkTracerProvider.builder()
|
||||
.setResource(resource)
|
||||
.addSpanProcessor(SimpleSpanProcessor.create(OtlpGrpcSpanExporter.builder()
|
||||
.setEndpoint(collectorUri) // otel collector
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
OpenTelemetrySdk sdk =
|
||||
OpenTelemetrySdk.builder()
|
||||
.setTracerProvider(sdkTracerProvider)
|
||||
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
|
||||
.setLoggerProvider(
|
||||
SdkLoggerProvider.builder()
|
||||
.setResource(resource)
|
||||
.addLogRecordProcessor(
|
||||
BatchLogRecordProcessor.builder(
|
||||
OtlpGrpcLogRecordExporter.builder()
|
||||
.setEndpoint(collectorUri)
|
||||
.build())
|
||||
|
||||
.build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// Add hook to close SDK, which flushes logs
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(sdk::close));
|
||||
return sdk;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void resetForTest() {
|
||||
GlobalOpenTelemetry.resetForTest();
|
||||
}
|
||||
|
||||
public static void closeTelemetry(Main main) {
|
||||
OpenTelemetry telemetry = getInstance(main).openTelemetry;
|
||||
if (telemetry instanceof OpenTelemetrySdk) {
|
||||
((OpenTelemetrySdk) telemetry).close();
|
||||
}
|
||||
}
|
||||
|
||||
private TelemetryProvider(Main main) {
|
||||
openTelemetry = initializeOpenTelemetry(main);
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +258,8 @@ public class UserIdMapping {
|
|||
ArrayList<String> userIds)
|
||||
throws StorageQueryException {
|
||||
// userIds are already filtered for a tenant, so this becomes a tenant specific operation.
|
||||
return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds);
|
||||
return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(
|
||||
tenantIdentifierWithStorage.toAppIdentifier(), userIds);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 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.webserver;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
|
||||
public class RequestStats extends ResourceDistributor.SingletonResource {
|
||||
public static final String RESOURCE_KEY = "io.supertokens.webserver.RequestStats";
|
||||
|
||||
private final int MAX_MINUTES = 24 * 60;
|
||||
|
||||
private long currentMinute; // current minute since epoch
|
||||
private final int[] currentMinuteRequestCounts; // array of 60 items representing number of requests at each second in the current minute
|
||||
|
||||
// The 2 arrays below contains stats for a day for every minute
|
||||
// the array is stored in such a way that array[currentMinute % MAX_MINUTES] contains the stats for a day ago
|
||||
// until array[(currentMinute - 1) % MAX_MINUTES] which contains the stats for the last minute, circling around
|
||||
// from end of array to the beginning
|
||||
// for e.g. if currentMinute % MAX_MINUTES = 250,
|
||||
// then array[250] contains stats for now - 1440 minutes
|
||||
// array[251] contains stats for now - 1439 minutes
|
||||
// ...
|
||||
// array[1439] contains stats for now - 1191 minutes
|
||||
// array[0] contains stats for now - 1190 minutes
|
||||
// array[1] contains stats for now - 1189 minutes
|
||||
// ...
|
||||
// array[249] contains stats for now - 1 minute
|
||||
private final double[] averageRequestsPerSecond;
|
||||
private final int[] peakRequestsPerSecond;
|
||||
|
||||
private RequestStats() {
|
||||
currentMinute = System.currentTimeMillis() / 60000;
|
||||
currentMinuteRequestCounts = new int[60];
|
||||
|
||||
averageRequestsPerSecond = new double[MAX_MINUTES];
|
||||
peakRequestsPerSecond = new int[MAX_MINUTES];
|
||||
for (int i = 0; i < MAX_MINUTES; i++) {
|
||||
averageRequestsPerSecond[i] = -1;
|
||||
peakRequestsPerSecond[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndUpdateMinute(long currentSecond) {
|
||||
if (currentSecond / 60 == currentMinute) {
|
||||
return; // stats update not required
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
int max = 0;
|
||||
for (int i = 0; i < 60; i++) {
|
||||
sum += currentMinuteRequestCounts[i];
|
||||
max = Math.max(max, currentMinuteRequestCounts[i]);
|
||||
}
|
||||
|
||||
averageRequestsPerSecond[(int) (currentMinute % MAX_MINUTES)] = sum / 60.0;
|
||||
peakRequestsPerSecond[(int) (currentMinute % MAX_MINUTES)] = max;
|
||||
|
||||
// fill zeros for passed minutes
|
||||
for (long i = currentMinute + 1; i < currentSecond / 60; i++) {
|
||||
averageRequestsPerSecond[(int) (i % MAX_MINUTES)] = 0;
|
||||
peakRequestsPerSecond[(int) (i % MAX_MINUTES)] = 0;
|
||||
}
|
||||
|
||||
currentMinute = currentSecond / 60;
|
||||
for (int i = 0; i < 60; i++) {
|
||||
currentMinuteRequestCounts[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCounts(long currentSecond) {
|
||||
currentMinuteRequestCounts[(int) (currentSecond % 60)]++;
|
||||
}
|
||||
|
||||
public static RequestStats getInstance(Main main, AppIdentifier appIdentifier) throws TenantOrAppNotFoundException {
|
||||
try {
|
||||
return (RequestStats) main.getResourceDistributor()
|
||||
.getResource(appIdentifier.getAsPublicTenantIdentifier(), RESOURCE_KEY);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
// appIdentifier parameter is coming from the API request and hence we need to check if the app exists
|
||||
// before creating a resource for it, otherwise someone could fill up memory by making requests for apps
|
||||
// that don't exist.
|
||||
// The other resources are created during init or while refreshing tenants from the db, so we don't need
|
||||
// this kind of pattern for those resources.
|
||||
if (Multitenancy.getTenantInfo(main, appIdentifier.getAsPublicTenantIdentifier()) == null) {
|
||||
throw e;
|
||||
}
|
||||
return (RequestStats) main.getResourceDistributor()
|
||||
.setResource(appIdentifier.getAsPublicTenantIdentifier(), RESOURCE_KEY, new RequestStats());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRequestStats() {
|
||||
this.updateRequestStats(true);
|
||||
}
|
||||
|
||||
synchronized private void updateRequestStats(boolean updateCounts) {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
this.checkAndUpdateMinute(now);
|
||||
if (updateCounts) { this.updateCounts(now); }
|
||||
}
|
||||
|
||||
public JsonObject getStats() {
|
||||
this.updateRequestStats(false);
|
||||
|
||||
JsonArray avgRps = new JsonArray();
|
||||
JsonArray peakRps = new JsonArray();
|
||||
|
||||
long atMinute = System.currentTimeMillis() / 60000;
|
||||
|
||||
int offset = (int) (atMinute % MAX_MINUTES);
|
||||
for (int i = 0; i < MAX_MINUTES; i++) {
|
||||
avgRps.add(new JsonPrimitive(this.averageRequestsPerSecond[(i + offset) % MAX_MINUTES]));
|
||||
peakRps.add(new JsonPrimitive(this.peakRequestsPerSecond[(i + offset) % MAX_MINUTES]));
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("atMinute", atMinute);
|
||||
result.add("averageRequestsPerSecond", avgRps);
|
||||
result.add("peakRequestsPerSecond", peakRps);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -250,6 +250,8 @@ public class Webserver extends ResourceDistributor.SingletonResource {
|
|||
addAPI(new AssociateUserToTenantAPI(main));
|
||||
addAPI(new DisassociateUserFromTenant(main));
|
||||
|
||||
addAPI(new RequestStatsAPI(main));
|
||||
|
||||
StandardContext context = tomcatReference.getContext();
|
||||
Tomcat tomcat = tomcatReference.getTomcat();
|
||||
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import io.supertokens.config.Config;
|
|||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.exceptions.QuitProgramException;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
import io.supertokens.multitenancy.MultitenancyHelper;
|
||||
import io.supertokens.multitenancy.exception.BadPermissionException;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage;
|
||||
|
|
@ -286,15 +288,18 @@ public abstract class WebserverAPI extends HttpServlet {
|
|||
String connectionUriDomain = req.getServerName();
|
||||
connectionUriDomain = Utils.normalizeAndValidateConnectionUriDomain(connectionUriDomain, false);
|
||||
|
||||
try {
|
||||
if (Config.getConfig(new TenantIdentifier(connectionUriDomain, null, null), main) ==
|
||||
Config.getConfig(new TenantIdentifier(null, null, null), main)) {
|
||||
return null;
|
||||
if (MultitenancyHelper.getInstance(main).isConnectionUriDomainPresentInDb(connectionUriDomain)) {
|
||||
CoreConfig baseConfig = Config.getBaseConfig(main);
|
||||
if (baseConfig.getSuperTokensLoadOnlyCUD() != null) {
|
||||
if (!connectionUriDomain.equals(baseConfig.getSuperTokensLoadOnlyCUD())) {
|
||||
throw new ServletException(new BadRequestException("Connection URI domain is disallowed"));
|
||||
}
|
||||
}
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
||||
return connectionUriDomain;
|
||||
}
|
||||
return connectionUriDomain;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
|
@ -338,6 +343,15 @@ public abstract class WebserverAPI extends HttpServlet {
|
|||
storage, storages);
|
||||
}
|
||||
|
||||
protected AppIdentifierWithStorage getPublicTenantStorage(HttpServletRequest req)
|
||||
throws ServletException, TenantOrAppNotFoundException {
|
||||
AppIdentifier appIdentifier = new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req));
|
||||
|
||||
Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main);
|
||||
|
||||
return appIdentifier.withStorage(storage);
|
||||
}
|
||||
|
||||
protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest(
|
||||
HttpServletRequest req, String userId, UserIdType userIdType)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException {
|
||||
|
|
@ -480,6 +494,13 @@ public abstract class WebserverAPI extends HttpServlet {
|
|||
}
|
||||
Logging.info(main, tenantIdentifier, "API ended: " + req.getRequestURI() + ". Method: " + req.getMethod(),
|
||||
false);
|
||||
if (tenantIdentifier != null) {
|
||||
try {
|
||||
RequestStats.getInstance(main, tenantIdentifier.toAppIdentifier()).updateRequestStats();
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
// Ignore the error as we would have already sent the response for tenantNotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String getRIDFromRequest(HttpServletRequest req) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 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.webserver.api.core;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.cliOptions.CLIOptions;
|
||||
import io.supertokens.multitenancy.exception.BadPermissionException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.webserver.InputParser;
|
||||
import io.supertokens.webserver.RequestStats;
|
||||
import io.supertokens.webserver.WebserverAPI;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RequestStatsAPI extends WebserverAPI {
|
||||
private static final long serialVersionUID = -4641988458637882374L;
|
||||
|
||||
public RequestStatsAPI(Main main) {
|
||||
super(main, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return "/requests/stats";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
|
||||
// API is app specific
|
||||
try {
|
||||
AppIdentifier appIdentifier = getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(req);
|
||||
JsonObject stats = RequestStats.getInstance(main, appIdentifier).getStats();
|
||||
stats.addProperty("status", "OK");
|
||||
super.sendJsonResponse(200, stats, resp);
|
||||
|
||||
} catch (BadPermissionException | TenantOrAppNotFoundException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ public class SignInAPI extends WebserverAPI {
|
|||
try {
|
||||
UserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, password);
|
||||
|
||||
ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, user.id); // use the internal user id
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.id); // use the internal user id
|
||||
|
||||
// if a userIdMapping exists, pass the externalUserId to the response
|
||||
UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ public class SignUpAPI extends WebserverAPI {
|
|||
try {
|
||||
UserInfo user = EmailPassword.signUp(this.getTenantIdentifierWithStorageFromRequest(req), super.main, normalisedEmail, password);
|
||||
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.id);
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.id);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("status", "OK");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package io.supertokens.webserver.api.multitenancy;
|
|||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.multitenancy.exception.BadPermissionException;
|
||||
|
|
@ -49,6 +51,14 @@ public abstract class BaseCreateOrUpdate extends WebserverAPI {
|
|||
HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
|
||||
CoreConfig baseConfig = Config.getBaseConfig(main);
|
||||
if (baseConfig.getSuperTokensLoadOnlyCUD() != null) {
|
||||
if (!(targetTenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI) || targetTenantIdentifier.getConnectionUriDomain().equals(baseConfig.getSuperTokensLoadOnlyCUD()))) {
|
||||
throw new ServletException(new BadRequestException("Creation of connection uri domain or app or " +
|
||||
"tenant is disallowed"));
|
||||
}
|
||||
}
|
||||
|
||||
TenantConfig tenantConfig = Multitenancy.getTenantInfo(main,
|
||||
new TenantIdentifier(targetTenantIdentifier.getConnectionUriDomain(), targetTenantIdentifier.getAppId(),
|
||||
targetTenantIdentifier.getTenantId()));
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ public class ConsumeCodeAPI extends WebserverAPI {
|
|||
deviceId, deviceIdHash,
|
||||
userInputCode, linkCode);
|
||||
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.id);
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.id);
|
||||
|
||||
UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(
|
||||
this.getAppIdentifierWithStorage(req),
|
||||
|
|
|
|||
|
|
@ -61,10 +61,13 @@ public class RefreshSessionAPI extends WebserverAPI {
|
|||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
|
||||
// API is app specific, but session is updated based on tenantId obtained from the refreshToken
|
||||
SemVer version = super.getVersionFromRequest(req);
|
||||
JsonObject input = InputParser.parseJsonObjectOrThrowError(req);
|
||||
String refreshToken = InputParser.parseStringOrThrowError(input, "refreshToken", false);
|
||||
String antiCsrfToken = InputParser.parseStringOrThrowError(input, "antiCsrfToken", true);
|
||||
Boolean enableAntiCsrf = InputParser.parseBooleanOrThrowError(input, "enableAntiCsrf", false);
|
||||
Boolean useDynamicSigningKey = version.greaterThanOrEqualTo(SemVer.v3_0) ?
|
||||
InputParser.parseBooleanOrThrowError(input, "useDynamicSigningKey", true) : null;
|
||||
assert enableAntiCsrf != null;
|
||||
assert refreshToken != null;
|
||||
|
||||
|
|
@ -75,13 +78,13 @@ public class RefreshSessionAPI extends WebserverAPI {
|
|||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
SemVer version = super.getVersionFromRequest(req);
|
||||
try {
|
||||
AccessToken.VERSION accessTokenVersion = AccessToken.getAccessTokenVersionForCDI(version);
|
||||
|
||||
SessionInformationHolder sessionInfo = Session.refreshSession(appIdentifierWithStorage, main,
|
||||
refreshToken, antiCsrfToken,
|
||||
enableAntiCsrf, accessTokenVersion);
|
||||
enableAntiCsrf, accessTokenVersion,
|
||||
useDynamicSigningKey == null ? null : Boolean.FALSE.equals(useDynamicSigningKey));
|
||||
|
||||
if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() ==
|
||||
STORAGE_TYPE.SQL) {
|
||||
|
|
@ -90,10 +93,10 @@ public class RefreshSessionAPI extends WebserverAPI {
|
|||
this.getAppIdentifierWithStorage(req),
|
||||
sessionInfo.session.userId, UserIdType.ANY);
|
||||
if (userIdMapping != null) {
|
||||
ActiveUsers.updateLastActive(appIdentifierWithStorage, main,
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main,
|
||||
userIdMapping.superTokensUserId);
|
||||
} else {
|
||||
ActiveUsers.updateLastActive(appIdentifierWithStorage, main,
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main,
|
||||
sessionInfo.session.userId);
|
||||
}
|
||||
} catch (StorageQueryException ignored) {
|
||||
|
|
|
|||
|
|
@ -111,10 +111,10 @@ public class SessionAPI extends WebserverAPI {
|
|||
this.getAppIdentifierWithStorage(req),
|
||||
sessionInfo.session.userId, UserIdType.ANY);
|
||||
if (userIdMapping != null) {
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main,
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main,
|
||||
userIdMapping.superTokensUserId);
|
||||
} else {
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main,
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main,
|
||||
sessionInfo.session.userId);
|
||||
}
|
||||
} catch (StorageQueryException ignored) {
|
||||
|
|
|
|||
|
|
@ -105,10 +105,10 @@ public class SessionRemoveAPI extends WebserverAPI {
|
|||
this.getAppIdentifierWithStorage(req),
|
||||
userId, UserIdType.ANY);
|
||||
if (userIdMapping != null) {
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main,
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main,
|
||||
userIdMapping.superTokensUserId);
|
||||
} else {
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, userId);
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userId);
|
||||
}
|
||||
} catch (StorageQueryException ignored) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class SignInUpAPI extends WebserverAPI {
|
|||
thirdPartyId,
|
||||
thirdPartyUserId, email, isEmailVerified);
|
||||
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.id);
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.id);
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("status", "OK");
|
||||
|
|
@ -118,7 +118,7 @@ public class SignInUpAPI extends WebserverAPI {
|
|||
this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId,
|
||||
email);
|
||||
|
||||
ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.id);
|
||||
ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.id);
|
||||
|
||||
//
|
||||
io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import com.google.gson.JsonObject;
|
|||
import io.supertokens.ActiveUsers;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlagTestContent;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.test.httpRequest.HttpRequestForTesting;
|
||||
import io.supertokens.test.httpRequest.HttpResponseException;
|
||||
import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper;
|
||||
import io.supertokens.utils.SemVer;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
|
@ -212,4 +217,80 @@ public class ActiveUsersTest {
|
|||
assert res.get("count").getAsInt() == 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatActiveUserDataIsSavedInPublicTenantStorage() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
{ // Create a tenant
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
|
||||
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
|
||||
.modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
|
||||
TestMultitenancyAPIHelper.createTenant(
|
||||
process.getProcess(),
|
||||
new TenantIdentifier(null, null, null),
|
||||
"t1", true, true, true,
|
||||
coreConfig);
|
||||
}
|
||||
|
||||
{ // no active users yet
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("since", "0");
|
||||
JsonObject res = HttpRequestForTesting.sendGETRequest(
|
||||
process.getProcess(),
|
||||
"",
|
||||
"http://localhost:3567/users/count/active",
|
||||
params,
|
||||
1000,
|
||||
1000,
|
||||
null,
|
||||
Utils.getCdiVersionStringLatestForTests(),
|
||||
"");
|
||||
|
||||
assert res.get("status").getAsString().equals("OK");
|
||||
assert res.get("count").getAsInt() == 0;
|
||||
}
|
||||
|
||||
{ // Sign up, which updates active users
|
||||
JsonObject responseBody = new JsonObject();
|
||||
responseBody.addProperty("email", "random@gmail.com");
|
||||
responseBody.addProperty("password", "validPass123");
|
||||
|
||||
JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/t1/recipe/signup", responseBody, 1000, 1000, null, SemVer.v3_0.get(),
|
||||
"emailpassword");
|
||||
}
|
||||
|
||||
{ // 1 active user in the public tenant
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
params.put("since", "0");
|
||||
JsonObject res = HttpRequestForTesting.sendGETRequest(
|
||||
process.getProcess(),
|
||||
"",
|
||||
"http://localhost:3567/users/count/active",
|
||||
params,
|
||||
1000,
|
||||
1000,
|
||||
null,
|
||||
Utils.getCdiVersionStringLatestForTests(),
|
||||
"");
|
||||
|
||||
assert res.get("status").getAsString().equals("OK");
|
||||
assert res.get("count").getAsInt() == 1;
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import io.supertokens.session.Session;
|
|||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.thirdparty.ThirdParty;
|
||||
import io.supertokens.usermetadata.UserMetadata;
|
||||
import io.supertokens.version.Version;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
|
|
@ -488,6 +489,8 @@ public class AuthRecipeTest {
|
|||
fail();
|
||||
}
|
||||
|
||||
boolean isMySQL = Version.getVersion(process.getProcess()).getPluginName().equals("mysql");
|
||||
|
||||
for (int limit : limits) {
|
||||
|
||||
// now we paginate in asc order
|
||||
|
|
@ -497,6 +500,9 @@ public class AuthRecipeTest {
|
|||
if (o1.timeJoined != o2.timeJoined) {
|
||||
return (int) (o1.timeJoined - o2.timeJoined);
|
||||
}
|
||||
if (isMySQL) {
|
||||
return o1.id.compareTo(o2.id);
|
||||
}
|
||||
return o2.id.compareTo(o1.id);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import org.junit.rules.TestRule;
|
|||
import org.reflections.Reflections;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -308,6 +309,49 @@ public class CronjobTest {
|
|||
}
|
||||
}
|
||||
|
||||
static class CounterCronJob extends CronTask {
|
||||
private static final String RESOURCE_ID = "io.supertokens.test.CronjobTest.CounterCronJob";
|
||||
private static AtomicInteger count = new AtomicInteger();
|
||||
|
||||
private CounterCronJob(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("CounterCronJob", main, tenantsInfo, false);
|
||||
}
|
||||
|
||||
public static CounterCronJob getInstance(Main main) {
|
||||
try {
|
||||
return (CounterCronJob) main.getResourceDistributor()
|
||||
.getResource(new TenantIdentifier(null, null, null), RESOURCE_ID);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
List<TenantIdentifier> tenants = new ArrayList<>();
|
||||
tenants.add(new TenantIdentifier(null, null, null));
|
||||
List<List<TenantIdentifier>> finalList = new ArrayList<>();
|
||||
finalList.add(tenants);
|
||||
return (CounterCronJob) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_ID,
|
||||
new CounterCronJob(main, finalList));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerStorage(Storage storage) throws Exception {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
|
|
@ -780,6 +824,122 @@ public class CronjobTest {
|
|||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatCronJobsHaveTenantsInfoAfterRestart() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
|
||||
.modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
|
||||
// create CUD and apps
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
|
||||
new TenantIdentifier("127.0.0.1", null, null),
|
||||
new EmailPasswordConfig(true),
|
||||
new ThirdPartyConfig(true, null),
|
||||
new PasswordlessConfig(true),
|
||||
coreConfig
|
||||
), false, false, true);
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
|
||||
new TenantIdentifier("127.0.0.1", "a1", null),
|
||||
new EmailPasswordConfig(true),
|
||||
new ThirdPartyConfig(true, null),
|
||||
new PasswordlessConfig(true),
|
||||
coreConfig
|
||||
), false, false, true);
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
|
||||
new TenantIdentifier("127.0.0.1", "a2", null),
|
||||
new EmailPasswordConfig(true),
|
||||
new ThirdPartyConfig(true, null),
|
||||
new PasswordlessConfig(true),
|
||||
coreConfig
|
||||
), false, false, true);
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
|
||||
new TenantIdentifier("127.0.0.1", "a3", null),
|
||||
new EmailPasswordConfig(true),
|
||||
new ThirdPartyConfig(true, null),
|
||||
new PasswordlessConfig(true),
|
||||
coreConfig
|
||||
), false, false, true);
|
||||
|
||||
{
|
||||
List<List<List<TenantIdentifier>>> tenantsInfos = Cronjobs.getInstance(process.getProcess()).getTenantInfos();
|
||||
assertEquals(10, tenantsInfos.size());
|
||||
int count = 0;
|
||||
for (List<List<TenantIdentifier>> tenantsInfo : tenantsInfos) {
|
||||
if (tenantsInfo != null) {
|
||||
assertEquals(2, tenantsInfo.size());
|
||||
assertEquals(1, tenantsInfo.get(0).size());
|
||||
assertEquals(4, tenantsInfo.get(1).size());
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assertEquals(9, count);
|
||||
}
|
||||
|
||||
process.kill(false);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
|
||||
|
||||
process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
// we expect the state of the tenantsInfo to be same after core restart
|
||||
{
|
||||
List<List<List<TenantIdentifier>>> tenantsInfos = Cronjobs.getInstance(process.getProcess()).getTenantInfos();
|
||||
assertEquals(10, tenantsInfos.size());
|
||||
int count = 0;
|
||||
for (List<List<TenantIdentifier>> tenantsInfo : tenantsInfos) {
|
||||
if (tenantsInfo != null) {
|
||||
assertEquals(2, tenantsInfo.size());
|
||||
assertEquals(1, tenantsInfo.get(0).size());
|
||||
assertEquals(4, tenantsInfo.get(1).size());
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assertEquals(9, count);
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatReAddingSameCronTaskDoesNotScheduleMoreExecutors() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
Cronjobs.addCronjob(process.getProcess(), CounterCronJob.getInstance(process.getProcess()));
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
Thread.sleep(5000);
|
||||
assertTrue(CounterCronJob.getInstance(process.getProcess()).getCount() > 3 && CounterCronJob.getInstance(process.getProcess()).getCount() < 8);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatNoCronJobIntervalIsMoreThanADay() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public class FeatureFlagTest {
|
|||
|
||||
JsonObject stats = FeatureFlag.getInstance(process.getProcess()).getPaidFeatureStats();
|
||||
Assert.assertEquals(stats.entrySet().size(), 1);
|
||||
Assert.assertEquals(stats.get("maus").getAsJsonArray().size(), 30);
|
||||
Assert.assertEquals(stats.get("maus").getAsJsonArray().size(), 31);
|
||||
Assert.assertEquals(stats.get("maus").getAsJsonArray().get(0).getAsInt(), 0);
|
||||
Assert.assertEquals(stats.get("maus").getAsJsonArray().get(29).getAsInt(), 0);
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ public class FeatureFlagTest {
|
|||
assert features.size() == 1;
|
||||
}
|
||||
assert features.contains(new JsonPrimitive("totp"));
|
||||
assert maus.size() == 30;
|
||||
assert maus.size() == 31;
|
||||
assert maus.get(0).getAsInt() == 0;
|
||||
assert maus.get(29).getAsInt() == 0;
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ public class FeatureFlagTest {
|
|||
JsonArray totpMaus = totpStats.get("maus").getAsJsonArray();
|
||||
int totalTotpUsers = totpStats.get("total_users").getAsInt();
|
||||
|
||||
assert totpMaus.size() == 30;
|
||||
assert totpMaus.size() == 31;
|
||||
assert totpMaus.get(0).getAsInt() == 0;
|
||||
assert totpMaus.get(29).getAsInt() == 0;
|
||||
|
||||
|
|
@ -247,7 +247,7 @@ public class FeatureFlagTest {
|
|||
}
|
||||
|
||||
assert features.contains(new JsonPrimitive("totp"));
|
||||
assert maus.size() == 30;
|
||||
assert maus.size() == 31;
|
||||
assert maus.get(0).getAsInt() == 2; // 2 users have signed up
|
||||
assert maus.get(29).getAsInt() == 2;
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ public class FeatureFlagTest {
|
|||
JsonArray totpMaus = totpStats.get("maus").getAsJsonArray();
|
||||
int totalTotpUsers = totpStats.get("total_users").getAsInt();
|
||||
|
||||
assert totpMaus.size() == 30;
|
||||
assert totpMaus.size() == 31;
|
||||
assert totpMaus.get(0).getAsInt() == 1; // only 1 user has TOTP enabled
|
||||
assert totpMaus.get(29).getAsInt() == 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -1533,7 +1533,9 @@ public class PathRouterTest extends Mockito {
|
|||
public void tenantNotFoundTest3()
|
||||
throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException,
|
||||
InvalidConfigException,
|
||||
io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException {
|
||||
io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException,
|
||||
InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException,
|
||||
CannotModifyBaseConfigException, BadPermissionException {
|
||||
String[] args = {"../"};
|
||||
|
||||
Utils.setValueInConfig("host", "\"0.0.0.0\"");
|
||||
|
|
@ -1556,15 +1558,26 @@ public class PathRouterTest extends Mockito {
|
|||
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
|
||||
.modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2);
|
||||
|
||||
Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{
|
||||
new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false),
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(
|
||||
process.getProcess(),
|
||||
new TenantConfig(
|
||||
new TenantIdentifier("localhost", null, null),
|
||||
new EmailPasswordConfig(false),
|
||||
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
|
||||
new PasswordlessConfig(false),
|
||||
tenantConfig),
|
||||
new TenantConfig(new TenantIdentifier("localhost", null, "t1"), new EmailPasswordConfig(false),
|
||||
false
|
||||
);
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(
|
||||
process.getProcess(),
|
||||
new TenantConfig(
|
||||
new TenantIdentifier("localhost", null, "t1"),
|
||||
new EmailPasswordConfig(false),
|
||||
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
|
||||
new PasswordlessConfig(false),
|
||||
tenantConfig)}, new ArrayList<>());
|
||||
tenantConfig),
|
||||
false
|
||||
);
|
||||
|
||||
Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") {
|
||||
|
||||
|
|
@ -2788,7 +2801,9 @@ public class PathRouterTest extends Mockito {
|
|||
public void tenantNotFoundWithAppIdTest3()
|
||||
throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException,
|
||||
InvalidConfigException,
|
||||
io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException {
|
||||
io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException,
|
||||
InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException,
|
||||
CannotModifyBaseConfigException, BadPermissionException {
|
||||
String[] args = {"../"};
|
||||
|
||||
Utils.setValueInConfig("host", "\"0.0.0.0\"");
|
||||
|
|
@ -2811,15 +2826,26 @@ public class PathRouterTest extends Mockito {
|
|||
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
|
||||
.modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2);
|
||||
|
||||
Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{
|
||||
new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false),
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(
|
||||
process.getProcess(),
|
||||
new TenantConfig(
|
||||
new TenantIdentifier("localhost", null, null),
|
||||
new EmailPasswordConfig(false),
|
||||
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
|
||||
new PasswordlessConfig(false),
|
||||
tenantConfig),
|
||||
new TenantConfig(new TenantIdentifier("localhost", "app1", "t1"), new EmailPasswordConfig(false),
|
||||
false
|
||||
);
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(
|
||||
process.getProcess(),
|
||||
new TenantConfig(
|
||||
new TenantIdentifier("localhost", "app1", "t1"),
|
||||
new EmailPasswordConfig(false),
|
||||
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
|
||||
new PasswordlessConfig(false),
|
||||
tenantConfig)}, new ArrayList<>());
|
||||
tenantConfig),
|
||||
false
|
||||
);
|
||||
|
||||
Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") {
|
||||
|
||||
|
|
@ -2875,4 +2901,4 @@ public class PathRouterTest extends Mockito {
|
|||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 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.test;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlagTestContent;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.multitenancy.*;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.test.httpRequest.HttpRequestForTesting;
|
||||
import io.supertokens.test.httpRequest.HttpResponseException;
|
||||
import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper;
|
||||
import io.supertokens.webserver.RequestStats;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class RequestStatsTest {
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
@AfterClass
|
||||
public static void afterTesting() {
|
||||
Utils.afterTesting();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
Utils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastMinuteStats() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for a minute to pass
|
||||
Thread.sleep(60000 - (System.currentTimeMillis() % 60000) + 100);
|
||||
|
||||
ExecutorService ex = Executors.newFixedThreadPool(100);
|
||||
int numRequests = 1000;
|
||||
for (int i = 0; i < numRequests; i++) {
|
||||
int finalI = i;
|
||||
ex.execute(() -> {
|
||||
try {
|
||||
TestMultitenancyAPIHelper.epSignUp(TenantIdentifier.BASE_TENANT, "test" + finalI + "@example.com", "password", process.getProcess());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(45, TimeUnit.SECONDS); // should finish in 45 seconds
|
||||
|
||||
// Wait for a minute to pass
|
||||
Thread.sleep(60000 - (System.currentTimeMillis() % 60000) + 100);
|
||||
|
||||
JsonObject stats = HttpRequestForTesting
|
||||
.sendGETRequest(process.getProcess(), "", "http://localhost:3567/requests/stats", null, 5000,
|
||||
5000, null, Utils.getCdiVersionStringLatestForTests(), null);
|
||||
|
||||
JsonArray avgRps = stats.get("averageRequestsPerSecond").getAsJsonArray();
|
||||
JsonArray peakRps = stats.get("peakRequestsPerSecond").getAsJsonArray();
|
||||
|
||||
double avg = 10000;
|
||||
|
||||
int count = 0;
|
||||
for (JsonElement e : avgRps) {
|
||||
if (e.getAsDouble() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertEquals(numRequests, Math.round(e.getAsDouble() * 60));
|
||||
avg = e.getAsDouble();
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
count = 0;
|
||||
for (JsonElement e : peakRps) {
|
||||
if (e.getAsInt() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertTrue(e.getAsInt() > avg);
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
assertEquals(System.currentTimeMillis() / 60000, stats.get("atMinute").getAsLong());
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLastMinuteStatsPerApp() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
|
||||
EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig(
|
||||
new TenantIdentifier(null, "a1", null),
|
||||
new EmailPasswordConfig(true),
|
||||
new ThirdPartyConfig(true, null),
|
||||
new PasswordlessConfig(true),
|
||||
new JsonObject()
|
||||
), false);
|
||||
|
||||
// Wait for a minute to pass
|
||||
Thread.sleep(60000 - (System.currentTimeMillis() % 60000) + 100);
|
||||
|
||||
ExecutorService ex = Executors.newFixedThreadPool(100);
|
||||
int numRequests = 500;
|
||||
for (int i = 0; i < numRequests; i++) {
|
||||
int finalI = i;
|
||||
ex.execute(() -> {
|
||||
try {
|
||||
TestMultitenancyAPIHelper.epSignUp(TenantIdentifier.BASE_TENANT, "test" + finalI + "@example.com", "password", process.getProcess());
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
if (finalI < 400) {
|
||||
try {
|
||||
TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", null), "test" + finalI + "@example.com", "password", process.getProcess());
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(45, TimeUnit.SECONDS); // should finish in 45 seconds
|
||||
|
||||
// Wait for a minute to pass
|
||||
Thread.sleep(60000 - (System.currentTimeMillis() % 60000) + 100);
|
||||
|
||||
{
|
||||
JsonObject stats = HttpRequestForTesting
|
||||
.sendGETRequest(process.getProcess(), "", "http://localhost:3567/requests/stats", null, 5000,
|
||||
5000, null, Utils.getCdiVersionStringLatestForTests(), null);
|
||||
|
||||
JsonArray avgRps = stats.get("averageRequestsPerSecond").getAsJsonArray();
|
||||
JsonArray peakRps = stats.get("peakRequestsPerSecond").getAsJsonArray();
|
||||
|
||||
double avg = 10000;
|
||||
|
||||
int count = 0;
|
||||
for (JsonElement e : avgRps) {
|
||||
if (e.getAsDouble() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertEquals(numRequests, Math.round(e.getAsDouble() * 60));
|
||||
avg = e.getAsDouble();
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
count = 0;
|
||||
for (JsonElement e : peakRps) {
|
||||
if (e.getAsInt() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertTrue(e.getAsInt() > avg);
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
assertEquals(System.currentTimeMillis() / 60000, stats.get("atMinute").getAsLong());
|
||||
}
|
||||
|
||||
{
|
||||
JsonObject stats = HttpRequestForTesting
|
||||
.sendGETRequest(process.getProcess(), "", "http://localhost:3567/appid-a1/requests/stats", null, 1000,
|
||||
1000, null, Utils.getCdiVersionStringLatestForTests(), null);
|
||||
|
||||
JsonArray avgRps = stats.get("averageRequestsPerSecond").getAsJsonArray();
|
||||
JsonArray peakRps = stats.get("peakRequestsPerSecond").getAsJsonArray();
|
||||
|
||||
double avg = 10000;
|
||||
|
||||
int count = 0;
|
||||
for (JsonElement e : avgRps) {
|
||||
if (e.getAsDouble() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertEquals(400, Math.round(e.getAsDouble() * 60));
|
||||
avg = e.getAsDouble();
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
count = 0;
|
||||
for (JsonElement e : peakRps) {
|
||||
if (e.getAsInt() == -1) {
|
||||
count++;
|
||||
} else {
|
||||
assertTrue(e.getAsInt() > avg);
|
||||
}
|
||||
}
|
||||
assertEquals(1439, count);
|
||||
|
||||
assertEquals(System.currentTimeMillis() / 60000, stats.get("atMinute").getAsLong());
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNonExistantApp() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
|
||||
EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
RequestStats.getInstance(process.getProcess(), new AppIdentifier(null, "a1"));
|
||||
fail();
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
// ok
|
||||
}
|
||||
|
||||
try {
|
||||
JsonObject stats = HttpRequestForTesting
|
||||
.sendGETRequest(process.getProcess(), "", "http://localhost:3567/appid-a1/requests/stats", null, 1000,
|
||||
1000, null, Utils.getCdiVersionStringLatestForTests(), null);
|
||||
fail();
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(400, e.statusCode);
|
||||
assertEquals("Http error. Status Code: 400. Message: AppId or tenantId not found => Tenant with the following connectionURIDomain, appId and tenantId combination not found: (, a1, public)", e.getMessage());
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ public class StorageLayerTest {
|
|||
// This error will be different in Postgres and MySQL
|
||||
// We added (CHECK (LENGTH(code) <= 8)) to the table definition in SQLite
|
||||
String totpUsedCodeTable = Config.getConfig(start).getTotpUsedCodesTable();
|
||||
assert e.getMessage().contains("CHECK constraint failed: " + totpUsedCodeTable);
|
||||
assert e.getMessage().contains("CHECK constraint failed: ");
|
||||
}
|
||||
|
||||
// Try code with length < 8
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ import com.google.gson.JsonParser;
|
|||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.ProcessState.PROCESS_STATE;
|
||||
import io.supertokens.cronjobs.telemetry.Telemetry;
|
||||
import io.supertokens.dashboard.Dashboard;
|
||||
import io.supertokens.httpRequest.HttpRequestMocking;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.test.TestingProcessManager.TestingProcess;
|
||||
import io.supertokens.version.Version;
|
||||
import org.junit.AfterClass;
|
||||
|
|
@ -111,6 +114,15 @@ public class TelemetryTest extends Mockito {
|
|||
String[] args = { "../" };
|
||||
|
||||
TestingProcess process = TestingProcessManager.start(args, false);
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getBaseStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
|
||||
Dashboard.signUpDashboardUser(process.getProcess(), "test@example.com", "password123");
|
||||
}
|
||||
// Restarting the process to send telemetry again
|
||||
process.kill(false);
|
||||
process = TestingProcessManager.start(args, false);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
final HttpURLConnection mockCon = mock(HttpURLConnection.class);
|
||||
|
|
@ -149,13 +161,26 @@ public class TelemetryTest extends Mockito {
|
|||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.SENT_TELEMETRY));
|
||||
|
||||
JsonObject telemetryData = new JsonParser().parse(output.toString()).getAsJsonObject();
|
||||
assertEquals(7, telemetryData.entrySet().size());
|
||||
|
||||
assertTrue(telemetryData.has("telemetryId"));
|
||||
assertEquals(telemetryData.get("superTokensVersion").getAsString(),
|
||||
Version.getVersion(process.getProcess()).getCoreVersion());
|
||||
assertEquals(telemetryData.get("appId").getAsString(), "public");
|
||||
assertEquals(telemetryData.get("connectionUriDomain").getAsString(), "");
|
||||
assertTrue(telemetryData.has("mau"));
|
||||
assertTrue(telemetryData.has("maus"));
|
||||
assertTrue(telemetryData.has("dashboardUserEmails"));
|
||||
|
||||
if (StorageLayer.getBaseStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
|
||||
assertEquals(1, telemetryData.get("dashboardUserEmails").getAsJsonArray().size());
|
||||
assertEquals("test@example.com", telemetryData.get("dashboardUserEmails").getAsJsonArray().get(0).getAsString());
|
||||
assertEquals(31, telemetryData.get("maus").getAsJsonArray().size());
|
||||
assertEquals(0, telemetryData.get("usersCount").getAsInt());
|
||||
} else {
|
||||
assertEquals(0, telemetryData.get("dashboardUserEmails").getAsJsonArray().size());
|
||||
assertEquals(0, telemetryData.get("maus").getAsJsonArray().size());
|
||||
assertEquals(-1, telemetryData.get("usersCount").getAsInt());
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 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.test;
|
||||
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlagTestContent;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.thirdparty.ThirdParty;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestGetUserSpeed {
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
@AfterClass
|
||||
public static void afterTesting() {
|
||||
Utils.afterTesting();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
Utils.reset();
|
||||
}
|
||||
|
||||
public void testUserCreationLinkingAndGetByIdSpeedsCommon(TestingProcessManager.TestingProcess process,
|
||||
long createTime, long getTime) throws Exception {
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numberOfUsers = 10000;
|
||||
|
||||
List<String> userIds = new ArrayList<>();
|
||||
List<String> userIds2 = new ArrayList<>();
|
||||
Lock lock = new ReentrantLock();
|
||||
{
|
||||
ExecutorService es = Executors.newFixedThreadPool(32);
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < numberOfUsers; i++) {
|
||||
int finalI = i;
|
||||
es.execute(() -> {
|
||||
try {
|
||||
String email = "user" + finalI + "@example.com";
|
||||
AuthRecipeUserInfo user = ThirdParty.signInUp(
|
||||
process.getProcess(), "google", "googleid" + finalI, email).user;
|
||||
lock.lock();
|
||||
userIds.add(user.id);
|
||||
userIds2.add(user.id);
|
||||
lock.unlock();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
es.shutdown();
|
||||
es.awaitTermination(5, TimeUnit.MINUTES);
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Created users " + numberOfUsers + " in " + (end - start) + "ms");
|
||||
assert end - start < createTime; // 25 sec
|
||||
}
|
||||
|
||||
Thread.sleep(10000); // wait for index
|
||||
|
||||
Thread.sleep(10000); // wait for index
|
||||
|
||||
{
|
||||
ExecutorService es = Executors.newFixedThreadPool(32);
|
||||
long start = System.currentTimeMillis();
|
||||
for (String userId : userIds2) {
|
||||
es.execute(() -> {
|
||||
try {
|
||||
ThirdParty.getUser(process.getProcess(), userId);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
es.shutdown();
|
||||
es.awaitTermination(5, TimeUnit.MINUTES);
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.println("Time taken for " + numberOfUsers + " users: " + (end - start) + "ms");
|
||||
assert end - start < getTime; // 20 sec
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserCreationLinkingAndGetByIdSpeedsWithoutMinIdle() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
Utils.setValueInConfig("postgresql_connection_pool_size", "100");
|
||||
Utils.setValueInConfig("mysql_connection_pool_size", "100");
|
||||
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
|
||||
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
testUserCreationLinkingAndGetByIdSpeedsCommon(process, 25000, 5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserCreationLinkingAndGetByIdSpeedsWithMinIdle() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
Utils.setValueInConfig("postgresql_connection_pool_size", "100");
|
||||
Utils.setValueInConfig("mysql_connection_pool_size", "100");
|
||||
Utils.setValueInConfig("postgresql_minimum_idle_connections", "1");
|
||||
Utils.setValueInConfig("mysql_minimum_idle_connections", "1");
|
||||
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{
|
||||
EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
testUserCreationLinkingAndGetByIdSpeedsCommon(process, 60000, 5000);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import io.supertokens.Main;
|
|||
import io.supertokens.pluginInterface.PluginInterfaceTesting;
|
||||
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.test.httpRequest.HttpRequestForTesting;
|
||||
import io.supertokens.test.httpRequest.HttpResponseException;
|
||||
import io.supertokens.useridmapping.UserIdType;
|
||||
|
|
@ -69,6 +70,8 @@ public abstract class Utils extends Mockito {
|
|||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
TelemetryProvider.resetForTest();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import com.google.gson.JsonArray;
|
|||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.authRecipe.UserPaginationContainer;
|
||||
import io.supertokens.emailpassword.EmailPassword;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlagTestContent;
|
||||
|
|
@ -30,6 +32,7 @@ import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException;
|
|||
import io.supertokens.passwordless.Passwordless;
|
||||
import io.supertokens.passwordless.exceptions.*;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
|
||||
import io.supertokens.pluginInterface.emailpassword.UserInfo;
|
||||
import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException;
|
||||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
|
|
@ -38,6 +41,7 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicExceptio
|
|||
import io.supertokens.pluginInterface.multitenancy.*;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException;
|
||||
import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.test.TestingProcessManager;
|
||||
import io.supertokens.test.Utils;
|
||||
|
|
@ -325,4 +329,66 @@ public class UserPaginationTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserPaginationWithSameTimeJoined() throws Exception {
|
||||
if (StorageLayer.getBaseStorage(process.main).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdPartySQLStorage storage = (ThirdPartySQLStorage) StorageLayer.getBaseStorage(process.getProcess());
|
||||
|
||||
Set<String> userIds = new HashSet<>();
|
||||
|
||||
long timeJoined = System.currentTimeMillis();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
String userId = io.supertokens.utils.Utils.getUUID();
|
||||
storage.signUp(TenantIdentifier.BASE_TENANT, userId, "test"+i+"@example.com", new io.supertokens.pluginInterface.thirdparty.UserInfo.ThirdParty("google", userId), timeJoined);
|
||||
userIds.add(userId);
|
||||
}
|
||||
|
||||
// Test ascending
|
||||
{
|
||||
Set<String> paginationUserIds = new HashSet<>();
|
||||
UserPaginationContainer usersRes = AuthRecipe.getUsers(process.getProcess(), 10,
|
||||
"ASC", null, null, null);
|
||||
|
||||
while (true) {
|
||||
for (UserPaginationContainer.UsersContainer user : usersRes.users) {
|
||||
paginationUserIds.add(user.user.id);
|
||||
}
|
||||
|
||||
if (usersRes.nextPaginationToken == null) {
|
||||
break;
|
||||
}
|
||||
usersRes = AuthRecipe.getUsers(process.getProcess(), 10,
|
||||
"ASC", usersRes.nextPaginationToken, null, null);
|
||||
}
|
||||
|
||||
assertEquals(userIds.size(), paginationUserIds.size());
|
||||
assertEquals(userIds, paginationUserIds);
|
||||
}
|
||||
|
||||
// Test descending
|
||||
{
|
||||
Set<String> paginationUserIds = new HashSet<>();
|
||||
UserPaginationContainer usersRes = AuthRecipe.getUsers(process.getProcess(), 10,
|
||||
"DESC", null, null, null);
|
||||
|
||||
while (true) {
|
||||
for (UserPaginationContainer.UsersContainer user : usersRes.users) {
|
||||
paginationUserIds.add(user.user.id);
|
||||
}
|
||||
|
||||
if (usersRes.nextPaginationToken == null) {
|
||||
break;
|
||||
}
|
||||
usersRes = AuthRecipe.getUsers(process.getProcess(), 10,
|
||||
"DESC", usersRes.nextPaginationToken, null, null);
|
||||
}
|
||||
|
||||
assertEquals(userIds.size(), paginationUserIds.size());
|
||||
assertEquals(userIds, paginationUserIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ public class DashboardTest {
|
|||
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
|
||||
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
|
||||
assertEquals(1, usageStats.entrySet().size());
|
||||
assertEquals(30, mauArr.size());
|
||||
assertEquals(31, mauArr.size());
|
||||
assertEquals(0, mauArr.get(0).getAsInt());
|
||||
assertEquals(0, mauArr.get(29).getAsInt());
|
||||
}
|
||||
|
|
@ -312,7 +312,7 @@ public class DashboardTest {
|
|||
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
|
||||
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
|
||||
assertEquals(1, usageStats.entrySet().size());
|
||||
assertEquals(30, mauArr.size());
|
||||
assertEquals(31, mauArr.size());
|
||||
assertEquals(0, mauArr.get(0).getAsInt());
|
||||
assertEquals(0, mauArr.get(29).getAsInt());
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ public class DashboardTest {
|
|||
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
|
||||
JsonObject dashboardLoginObject = usageStats.get("dashboard_login").getAsJsonObject();
|
||||
assertEquals(2, usageStats.entrySet().size());
|
||||
assertEquals(30, usageStats.get("maus").getAsJsonArray().size());
|
||||
assertEquals(31, usageStats.get("maus").getAsJsonArray().size());
|
||||
assertEquals(1, dashboardLoginObject.entrySet().size());
|
||||
assertEquals(1, dashboardLoginObject.get("user_count").getAsInt());
|
||||
}
|
||||
|
|
@ -366,7 +366,7 @@ public class DashboardTest {
|
|||
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
|
||||
JsonObject dashboardLoginObject = usageStats.get("dashboard_login").getAsJsonObject();
|
||||
assertEquals(2, usageStats.entrySet().size());
|
||||
assertEquals(30, usageStats.get("maus").getAsJsonArray().size());
|
||||
assertEquals(31, usageStats.get("maus").getAsJsonArray().size());
|
||||
assertEquals(1, dashboardLoginObject.entrySet().size());
|
||||
assertEquals(4, dashboardLoginObject.get("user_count").getAsInt());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1914,6 +1914,7 @@ public class ConfigTest {
|
|||
"argon2_memory_kb",
|
||||
"argon2_parallelism",
|
||||
"bcrypt_log_rounds",
|
||||
"supertokens_saas_load_only_cud"
|
||||
};
|
||||
Object[] disallowedValues = new Object[]{
|
||||
3567, // port
|
||||
|
|
@ -1930,6 +1931,7 @@ public class ConfigTest {
|
|||
87795, // argon2_memory_kb
|
||||
2, // argon2_parallelism
|
||||
11, // bcrypt_log_rounds
|
||||
"mydomain.com", // supertokens_saas_load_only_cud
|
||||
};
|
||||
|
||||
process.kill();
|
||||
|
|
@ -1995,7 +1997,7 @@ public class ConfigTest {
|
|||
new Object[]{true, false}, // disable_telemetry
|
||||
new Object[]{"BCRYPT", "ARGON2"}, // password_hashing_alg
|
||||
new Object[]{"abcd1234abcd1234abcd1234abcd1234", "qwer1234qwer1234qwer1234qwer1234"}, // firebase_password_hashing_signer_key
|
||||
new Object[]{"2.21", "3.0"} // supertokens_max_cdi_version
|
||||
new Object[]{"2.21", "3.0"}, // supertokens_max_cdi_version
|
||||
};
|
||||
|
||||
for (int i=0; i<conflictingInSameUserPool.length; i++) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* 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.test.multitenant;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.Cronjobs;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlagTestContent;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
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 io.supertokens.test.TestingProcessManager;
|
||||
import io.supertokens.test.Utils;
|
||||
import io.supertokens.test.httpRequest.HttpResponseException;
|
||||
import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class LoadOnlyCUDTest {
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
@AfterClass
|
||||
public static void afterTesting() {
|
||||
Utils.afterTesting();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
Utils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAPIChecksForLoadOnlyCUD() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"127.0.0.1", true, true, true, coreConfig);
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 2);
|
||||
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"localhost", true, true, true, coreConfig);
|
||||
|
||||
process.kill(false);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
|
||||
Utils.setValueInConfig("supertokens_saas_load_only_cud", "127.0.0.1:3567");
|
||||
process = TestingProcessManager.start(args, false);
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
try {
|
||||
TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier("localhost", null, null), "test@example.com",
|
||||
"password123", process.getProcess());
|
||||
fail();
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(400, e.statusCode);
|
||||
}
|
||||
|
||||
// check that it's allowed with 127.0.0.1
|
||||
TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier("127.0.0.1", null, null), "test@example.com",
|
||||
"password123", process.getProcess());
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationOfCUDWithLoadOnlyCUD() throws Exception {
|
||||
String[] args = {"../"};
|
||||
|
||||
Utils.setValueInConfig("supertokens_saas_load_only_cud", "127.0.0.1:3567");
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"localhost:3567", true, true, true, new JsonObject());
|
||||
fail();
|
||||
} catch (HttpResponseException e) {
|
||||
assertEquals(400, e.statusCode);
|
||||
}
|
||||
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
|
||||
// This should pass
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"127.0.0.1:3567", true, true, true, coreConfig);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThatResourcesAreNotLoadedWithLoadOnlyCUD() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"127.0.0.1", true, true, true, coreConfig);
|
||||
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 2);
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"localhost.org", true, true, true, coreConfig);
|
||||
|
||||
process.kill(false);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
|
||||
Utils.setValueInConfig("supertokens_saas_load_only_cud", "127.0.0.1:3567");
|
||||
process = TestingProcessManager.start(args, false);
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
JsonObject result = TestMultitenancyAPIHelper.listConnectionUriDomains(TenantIdentifier.BASE_TENANT,
|
||||
process.getProcess());
|
||||
assertEquals("OK", result.get("status").getAsString());
|
||||
assertEquals(2, result.get("connectionUriDomains").getAsJsonArray().size());
|
||||
|
||||
for (JsonElement elem : result.get("connectionUriDomains").getAsJsonArray()) {
|
||||
assertNotEquals("localhost.org", elem.getAsJsonObject().get("connectionUriDomain").getAsString());
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCronDoesNotRunForOtherCUDsWithLoadOnlyCUD() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
|
||||
FeatureFlagTestContent.getInstance(process.getProcess())
|
||||
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY});
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject coreConfig = new JsonObject();
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1);
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"127.0.0.1", true, true, true, coreConfig);
|
||||
|
||||
StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 2);
|
||||
TestMultitenancyAPIHelper.createConnectionUriDomain(process.getProcess(), TenantIdentifier.BASE_TENANT,
|
||||
"localhost.org", true, true, true, coreConfig);
|
||||
|
||||
process.kill(false);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
|
||||
Utils.setValueInConfig("supertokens_saas_load_only_cud", "127.0.0.1:3567");
|
||||
process = TestingProcessManager.start(args, false);
|
||||
process.startProcess();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
List<List<TenantIdentifier>> uniqueUserPoolIdsTenants = StorageLayer.getTenantsWithUniqueUserPoolId(process.getProcess());
|
||||
Cronjobs.addCronjob(process.getProcess(), LoadOnlyCUDTest.PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants));
|
||||
|
||||
Thread.sleep(3000);
|
||||
Set<AppIdentifier> appIdentifiersFromCron = PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants).appIdentifiers;
|
||||
assertEquals(2, appIdentifiersFromCron.size());
|
||||
for (AppIdentifier app : appIdentifiersFromCron) {
|
||||
assertNotEquals("localhost.org", app.getConnectionUriDomain());
|
||||
}
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
static class PerAppCronjob extends CronTask {
|
||||
private static final String RESOURCE_ID = "io.supertokens.test.CronjobTest.NormalCronjob";
|
||||
|
||||
private PerAppCronjob(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("PerTenantCronjob", main, tenantsInfo, true);
|
||||
}
|
||||
|
||||
Set<AppIdentifier> appIdentifiers = new HashSet<>();
|
||||
|
||||
public static LoadOnlyCUDTest.PerAppCronjob getInstance(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
try {
|
||||
return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor().getResource(new TenantIdentifier(null, null, null), RESOURCE_ID);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_ID, new LoadOnlyCUDTest.PerAppCronjob(main, tenantsInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerApp(AppIdentifier app) throws Exception {
|
||||
appIdentifiers.add(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,7 @@ public class RefreshTokenTest {
|
|||
TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
long createdTime = System.currentTimeMillis();
|
||||
TokenInfo tokenInfo = RefreshToken.createNewRefreshToken(process.getProcess(), "sessionHandle", "userId",
|
||||
"parentRefreshTokenHash1", "antiCsrfToken");
|
||||
|
||||
|
|
@ -129,9 +130,8 @@ public class RefreshTokenTest {
|
|||
assertEquals("antiCsrfToken", infoFromToken.antiCsrfToken);
|
||||
assertNull(infoFromToken.parentRefreshTokenHash2);
|
||||
assertSame(infoFromToken.type, TYPE.FREE_OPTIMISED);
|
||||
// -5000 for some grace period for creation and checking above
|
||||
assertTrue(tokenInfo.expiry > System.currentTimeMillis()
|
||||
+ Config.getConfig(process.getProcess()).getRefreshTokenValidity() - 5000);
|
||||
assertTrue(tokenInfo.expiry >= createdTime
|
||||
+ Config.getConfig(process.getProcess()).getRefreshTokenValidity());
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* 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.test.session;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.exceptions.TryRefreshTokenException;
|
||||
import io.supertokens.exceptions.UnauthorisedException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.session.SessionStorage;
|
||||
import io.supertokens.session.Session;
|
||||
import io.supertokens.session.accessToken.AccessToken;
|
||||
import io.supertokens.session.info.SessionInformationHolder;
|
||||
import io.supertokens.session.jwt.JWT;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.test.TestingProcessManager;
|
||||
import io.supertokens.test.Utils;
|
||||
import org.junit.*;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import static junit.framework.TestCase.*;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class SessionTest6 {
|
||||
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
@AfterClass
|
||||
public static void afterTesting() {
|
||||
Utils.afterTesting();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
Utils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRefreshSwitchVerify() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT,
|
||||
userDataInDatabase, false, AccessToken.getLatestVersion(), false);
|
||||
checkIfUsingStaticKey(sessionInfo, false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true);
|
||||
assert sessionInfo.refreshToken != null;
|
||||
assert sessionInfo.accessToken != null;
|
||||
|
||||
checkIfUsingStaticKey(sessionInfo, true);
|
||||
|
||||
SessionInformationHolder verifiedSession = Session.getSession(process.getProcess(), sessionInfo.accessToken.token,
|
||||
sessionInfo.antiCsrfToken, false, true, false);
|
||||
|
||||
checkIfUsingStaticKey(verifiedSession, true);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
@Test
|
||||
public void createRefreshSwitchRegen() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT,
|
||||
userDataInDatabase, false, AccessToken.getLatestVersion(), false);
|
||||
checkIfUsingStaticKey(sessionInfo, false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true);
|
||||
assert sessionInfo.refreshToken != null;
|
||||
assert sessionInfo.accessToken != null;
|
||||
checkIfUsingStaticKey(sessionInfo, true);
|
||||
|
||||
SessionInformationHolder newSessionInfo = Session.regenerateToken(process.getProcess(),
|
||||
sessionInfo.accessToken.token, userDataInJWT);
|
||||
checkIfUsingStaticKey(newSessionInfo, true);
|
||||
|
||||
SessionInformationHolder getSessionResponse = Session.getSession(process.getProcess(),
|
||||
newSessionInfo.accessToken.token, sessionInfo.antiCsrfToken, false, true, false);
|
||||
checkIfUsingStaticKey(getSessionResponse, true);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRefreshRefreshSwitchVerify() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT,
|
||||
userDataInDatabase, false, AccessToken.getLatestVersion(), false);
|
||||
checkIfUsingStaticKey(sessionInfo, false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true);
|
||||
assert sessionInfo.refreshToken != null;
|
||||
assert sessionInfo.accessToken != null;
|
||||
|
||||
checkIfUsingStaticKey(sessionInfo, true);
|
||||
|
||||
SessionInformationHolder verifiedSession = Session.getSession(process.getProcess(), sessionInfo.accessToken.token,
|
||||
sessionInfo.antiCsrfToken, false, true, false);
|
||||
|
||||
checkIfUsingStaticKey(verifiedSession, true);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
@Test
|
||||
public void createRefreshRefreshSwitchRegen() throws Exception {
|
||||
String[] args = {"../"};
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
SessionInformationHolder sessionInfo = Session.createNewSession(process.getProcess(), userId, userDataInJWT,
|
||||
userDataInDatabase, false, AccessToken.getLatestVersion(), false);
|
||||
checkIfUsingStaticKey(sessionInfo, false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), false);
|
||||
|
||||
sessionInfo = Session.refreshSession(new AppIdentifier(null, null), process.getProcess(), sessionInfo.refreshToken.token,
|
||||
sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion(), true);
|
||||
assert sessionInfo.refreshToken != null;
|
||||
assert sessionInfo.accessToken != null;
|
||||
checkIfUsingStaticKey(sessionInfo, true);
|
||||
|
||||
SessionInformationHolder newSessionInfo = Session.regenerateToken(process.getProcess(),
|
||||
sessionInfo.accessToken.token, userDataInJWT);
|
||||
checkIfUsingStaticKey(newSessionInfo, true);
|
||||
|
||||
SessionInformationHolder getSessionResponse = Session.getSession(process.getProcess(),
|
||||
newSessionInfo.accessToken.token, sessionInfo.antiCsrfToken, false, true, false);
|
||||
checkIfUsingStaticKey(getSessionResponse, true);
|
||||
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
private static void checkIfUsingStaticKey(SessionInformationHolder info, boolean shouldBeStatic) throws JWT.JWTException {
|
||||
assert info.accessToken != null;
|
||||
JWT.JWTPreParseInfo tokenInfo = JWT.preParseJWTInfo(info.accessToken.token);
|
||||
assert tokenInfo.kid != null;
|
||||
if (shouldBeStatic) {
|
||||
assert tokenInfo.kid.startsWith("s-");
|
||||
} else {
|
||||
assert tokenInfo.kid.startsWith("d-");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ public class RefreshSessionAPITest2_21 {
|
|||
|
||||
JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
|
||||
Utils.getCdiVersionStringLatestForTests(), "session");
|
||||
SemVer.v2_21.get(), "session");
|
||||
|
||||
assertEquals(response.entrySet().size(), 2);
|
||||
assertEquals(response.get("status").getAsString(), "UNAUTHORISED");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* 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.test.session.api;
|
||||
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.session.jwt.JWT;
|
||||
import io.supertokens.test.TestingProcessManager;
|
||||
import io.supertokens.test.Utils;
|
||||
import io.supertokens.test.httpRequest.HttpRequestForTesting;
|
||||
import io.supertokens.utils.SemVer;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class RefreshSessionAPITest3_0 {
|
||||
@Rule
|
||||
public TestRule watchman = Utils.getOnFailure();
|
||||
|
||||
@AfterClass
|
||||
public static void afterTesting() {
|
||||
Utils.afterTesting();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
Utils.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successOutputWithValidRefreshTokenTest() throws Exception {
|
||||
String[] args = { "../" };
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
JsonObject request = new JsonObject();
|
||||
request.addProperty("userId", userId);
|
||||
request.add("userDataInJWT", userDataInJWT);
|
||||
request.add("userDataInDatabase", userDataInDatabase);
|
||||
request.addProperty("enableAntiCsrf", false);
|
||||
|
||||
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
|
||||
"session");
|
||||
assertEquals(sessionInfo.get("status").getAsString(), "OK");
|
||||
|
||||
JsonObject sessionRefreshBody = new JsonObject();
|
||||
|
||||
sessionRefreshBody.addProperty("refreshToken",
|
||||
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
|
||||
sessionRefreshBody.addProperty("enableAntiCsrf", false);
|
||||
|
||||
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
|
||||
SemVer.v3_0.get(), "session");
|
||||
|
||||
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false);
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successOutputUpgradeWithNonStaticKeySessionTest() throws Exception {
|
||||
String[] args = { "../" };
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
JsonObject request = new JsonObject();
|
||||
request.addProperty("userId", userId);
|
||||
request.add("userDataInJWT", userDataInJWT);
|
||||
request.add("userDataInDatabase", userDataInDatabase);
|
||||
request.addProperty("enableAntiCsrf", false);
|
||||
|
||||
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
|
||||
"session");
|
||||
assertEquals(sessionInfo.get("status").getAsString(), "OK");
|
||||
|
||||
JsonObject sessionRefreshBody = new JsonObject();
|
||||
|
||||
sessionRefreshBody.addProperty("refreshToken",
|
||||
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
|
||||
sessionRefreshBody.addProperty("enableAntiCsrf", false);
|
||||
sessionRefreshBody.addProperty("useDynamicSigningKey", true);
|
||||
|
||||
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
|
||||
SemVer.v3_0.get(), "session");
|
||||
|
||||
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, false);
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successOutputUpgradeWithStaticKeySessionTest() throws Exception {
|
||||
String[] args = { "../" };
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
String userId = "userId";
|
||||
JsonObject userDataInJWT = new JsonObject();
|
||||
userDataInJWT.add("nullProp", JsonNull.INSTANCE);
|
||||
userDataInJWT.addProperty("key", "value");
|
||||
JsonObject userDataInDatabase = new JsonObject();
|
||||
userDataInDatabase.addProperty("key", "value");
|
||||
|
||||
JsonObject request = new JsonObject();
|
||||
request.addProperty("userId", userId);
|
||||
request.add("userDataInJWT", userDataInJWT);
|
||||
request.add("userDataInDatabase", userDataInDatabase);
|
||||
request.addProperty("enableAntiCsrf", false);
|
||||
|
||||
JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session", request, 1000, 1000, null, SemVer.v2_7.get(),
|
||||
"session");
|
||||
assertEquals(sessionInfo.get("status").getAsString(), "OK");
|
||||
|
||||
JsonObject sessionRefreshBody = new JsonObject();
|
||||
|
||||
sessionRefreshBody.addProperty("refreshToken",
|
||||
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
|
||||
sessionRefreshBody.addProperty("enableAntiCsrf", false);
|
||||
sessionRefreshBody.addProperty("useDynamicSigningKey", false);
|
||||
|
||||
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
|
||||
SemVer.v3_0.get(), "session");
|
||||
|
||||
checkRefreshSessionResponse(sessionRefreshResponse, process, userId, userDataInJWT, false, true);
|
||||
process.kill();
|
||||
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
|
||||
}
|
||||
|
||||
private static void checkRefreshSessionResponse(JsonObject response, TestingProcessManager.TestingProcess process,
|
||||
String userId, JsonObject userDataInJWT, boolean hasAntiCsrf, boolean useStaticKey) throws
|
||||
JWT.JWTException {
|
||||
|
||||
assertNotNull(response.get("session").getAsJsonObject().get("handle").getAsString());
|
||||
assertEquals(response.get("session").getAsJsonObject().get("userId").getAsString(), userId);
|
||||
assertEquals(response.get("session").getAsJsonObject().get("tenantId").getAsString(), "public");
|
||||
assertEquals(response.get("session").getAsJsonObject().get("userDataInJWT").getAsJsonObject().toString(),
|
||||
userDataInJWT.toString());
|
||||
assertEquals(response.get("session").getAsJsonObject().entrySet().size(), 4);
|
||||
|
||||
assertTrue(response.get("accessToken").getAsJsonObject().has("token"));
|
||||
assertTrue(response.get("accessToken").getAsJsonObject().has("expiry"));
|
||||
assertTrue(response.get("accessToken").getAsJsonObject().has("createdTime"));
|
||||
assertEquals(response.get("accessToken").getAsJsonObject().entrySet().size(), 3);
|
||||
|
||||
JWT.JWTPreParseInfo tokenInfo = JWT.preParseJWTInfo(response.get("accessToken").getAsJsonObject().get("token").getAsString());
|
||||
|
||||
if (useStaticKey) {
|
||||
assert(tokenInfo.kid.startsWith("s-"));
|
||||
} else {
|
||||
assert(tokenInfo.kid.startsWith("d-"));
|
||||
}
|
||||
|
||||
assertTrue(response.get("refreshToken").getAsJsonObject().has("token"));
|
||||
assertTrue(response.get("refreshToken").getAsJsonObject().has("expiry"));
|
||||
assertTrue(response.get("refreshToken").getAsJsonObject().has("createdTime"));
|
||||
assertEquals(response.get("refreshToken").getAsJsonObject().entrySet().size(), 3);
|
||||
|
||||
assertEquals(response.has("antiCsrfToken"), hasAntiCsrf);
|
||||
|
||||
assertEquals(response.entrySet().size(), hasAntiCsrf ? 5 : 4);
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +107,7 @@ public class SessionRegenerateAPITest2_21 {
|
|||
sessionRefreshBody.addProperty("refreshToken",
|
||||
sessionInfo.get("refreshToken").getAsJsonObject().get("token").getAsString());
|
||||
sessionRefreshBody.addProperty("enableAntiCsrf", false);
|
||||
sessionRefreshBody.addProperty("useDynamicSigningKey", true);
|
||||
|
||||
JsonObject sessionRefreshResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "",
|
||||
"http://localhost:3567/recipe/session/refresh", sessionRefreshBody, 1000, 1000, null,
|
||||
|
|
|
|||
|
|
@ -583,7 +583,8 @@ public class UserIdMappingStorageTest {
|
|||
storage.createUserIdMapping(new AppIdentifier(null, null), superTokensUserId, externalUserId,
|
||||
null);
|
||||
}
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList);
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(
|
||||
new AppIdentifier(null, null), superTokensUserIdList);
|
||||
assertEquals(AuthRecipe.USER_PAGINATION_LIMIT, response.size());
|
||||
for (int i = 0; i < response.size(); i++) {
|
||||
assertEquals(externalUserIdList.get(i), response.get(superTokensUserIdList.get(i)));
|
||||
|
|
@ -606,7 +607,8 @@ public class UserIdMappingStorageTest {
|
|||
UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main);
|
||||
ArrayList<String> emptyList = new ArrayList<>();
|
||||
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(emptyList);
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(
|
||||
new AppIdentifier(null, null), emptyList);
|
||||
assertEquals(0, response.size());
|
||||
|
||||
process.kill();
|
||||
|
|
@ -631,7 +633,8 @@ public class UserIdMappingStorageTest {
|
|||
superTokensUserIdList.add(userInfo.id);
|
||||
}
|
||||
|
||||
HashMap<String, String> userIdMapping = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList);
|
||||
HashMap<String, String> userIdMapping = storage.getUserIdMappingForSuperTokensIds(
|
||||
new AppIdentifier(null, null), superTokensUserIdList);
|
||||
assertEquals(0, userIdMapping.size());
|
||||
|
||||
process.kill();
|
||||
|
|
@ -668,7 +671,8 @@ public class UserIdMappingStorageTest {
|
|||
}
|
||||
|
||||
// retrieve UserIDMapping
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList);
|
||||
HashMap<String, String> response = storage.getUserIdMappingForSuperTokensIds(
|
||||
new AppIdentifier(null, null), superTokensUserIdList);
|
||||
assertEquals(5, response.size());
|
||||
|
||||
// check that the last 5 users have their ids mapped
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Build output
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# IDE and editor files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
users/
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"semi": true,
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
# Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores
|
||||
db:
|
||||
image: 'postgres:latest'
|
||||
environment:
|
||||
POSTGRES_USER: supertokens
|
||||
POSTGRES_PASSWORD: supertokens
|
||||
POSTGRES_DB: supertokens
|
||||
command: postgres -c shared_preload_libraries='pg_stat_statements' -c pg_stat_statements.track=all -c max_connections=1000 -c shared_buffers=1GB -c synchronous_commit=off -c wal_buffers=16MB -c checkpoint_timeout=30min -c max_wal_size=4GB
|
||||
ports:
|
||||
- 5432:5432
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'pg_isready', '-U', 'supertokens', '-d', 'supertokens']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
supertokens:
|
||||
image: supertokens/supertokens-dev-postgresql:for-backport-release-6-0
|
||||
# platform: linux/amd64
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- 3567:3567
|
||||
environment:
|
||||
POSTGRESQL_CONNECTION_URI: "postgresql://supertokens:supertokens@db:5432/supertokens"
|
||||
PASSWORD_HASHING_ALG: "ARGON2"
|
||||
ARGON2_ITERATIONS: 1
|
||||
ARGON2_MEMORY_KB: 8
|
||||
ARGON2_PARALLELISM: 1
|
||||
ARGON2_HASHING_POOL_SIZE: 8
|
||||
API_KEYS: "qwertyuiopasdfghjklzxcvbnm"
|
||||
BULK_MIGRATION_PARALLELISM: "4"
|
||||
BULK_MIGRATION_BATCH_SIZE: "500"
|
||||
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: >
|
||||
bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"'
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
pghero:
|
||||
image: ankane/pghero
|
||||
environment:
|
||||
DATABASE_URL: "postgres://supertokens:supertokens@db:5432/supertokens"
|
||||
ports:
|
||||
- 8080:8080
|
||||
networks:
|
||||
- app_network
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
app_network:
|
||||
driver: bridge
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "stress-tests",
|
||||
"version": "1.0.0",
|
||||
"description": "Stress tests for SuperTokens",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"generate-users": "rm -rf users && mkdir -p users && ts-node src/oneMillionUsers/generateUsers.ts",
|
||||
"one-million-users": "ts-node src/oneMillionUsers/index.ts",
|
||||
"format": "prettier --write \"**/*.{ts,js,json}\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/uuid": "^10.0.0",
|
||||
"supertokens-node": "15.2.3",
|
||||
"uuid": "^11.1.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
import * as fs from 'fs';
|
||||
|
||||
export const LICENSE_FOR_TEST =
|
||||
'E1yITHflaFS4BPm7n0bnfFCjP4sJoTERmP0J=kXQ5YONtALeGnfOOe2rf2QZ0mfOh0aO3pBqfF-S0jb0ABpat6pySluTpJO6jieD6tzUOR1HrGjJO=50Ob3mHi21tQH1';
|
||||
|
||||
export const createStInstanceForTest = async () => {
|
||||
return {
|
||||
deployment_id: '1234567890',
|
||||
core_url: 'http://localhost:3567',
|
||||
api_key: 'qwertyuiopasdfghjklzxcvbnm',
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteStInstance = async (deploymentId: string) => {
|
||||
// noop
|
||||
};
|
||||
|
||||
export const formatTime = (ms: number): string => {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
if (seconds < 60) {
|
||||
return `${seconds}s`;
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
};
|
||||
|
||||
export const workInBatches = async <T>(
|
||||
count: number,
|
||||
numberOfBatches: number,
|
||||
work: (idx: number) => Promise<T>
|
||||
): Promise<T[]> => {
|
||||
const batchSize = Math.ceil(count / numberOfBatches);
|
||||
const batches = [];
|
||||
let workCount = 0;
|
||||
|
||||
const st = Date.now();
|
||||
let done = numberOfBatches;
|
||||
|
||||
for (let b = 0; b < numberOfBatches; b++) {
|
||||
batches.push(
|
||||
(async () => {
|
||||
const startIndex = b * batchSize;
|
||||
const endIndex = Math.min(startIndex + batchSize, count);
|
||||
const batchResults: T[] = [];
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
batchResults.push(await work(i));
|
||||
workCount++;
|
||||
}
|
||||
done--;
|
||||
return batchResults;
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
batches.push(
|
||||
(async () => {
|
||||
while (done > 0) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const en = Date.now();
|
||||
console.log(
|
||||
` Progress: Time=${formatTime(en - st)}, Completed=${workCount}, Throughput=${Math.round((workCount / (en - st)) * 10000) / 10}/s`
|
||||
);
|
||||
}
|
||||
return [];
|
||||
})()
|
||||
);
|
||||
|
||||
const results = await Promise.all(batches);
|
||||
return results.flat();
|
||||
};
|
||||
|
||||
export const setupLicense = async (coreUrl: string, apiKey: string) => {
|
||||
try {
|
||||
const response = await fetch(`${coreUrl}/ee/license`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'api-key': apiKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
licenseKey: LICENSE_FOR_TEST,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed with status: ${response.status}`);
|
||||
}
|
||||
const responseText = await response.text();
|
||||
console.log('License response:', responseText);
|
||||
|
||||
console.log('License key set successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to set license key:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export class StatsCollector {
|
||||
private static instance: StatsCollector;
|
||||
private measurements: { title: string; timeMs: number }[] = [];
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): StatsCollector {
|
||||
if (!StatsCollector.instance) {
|
||||
StatsCollector.instance = new StatsCollector();
|
||||
}
|
||||
return StatsCollector.instance;
|
||||
}
|
||||
|
||||
public addMeasurement(title: string, timeMs: number) {
|
||||
this.measurements.push({ title, timeMs });
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
return this.measurements;
|
||||
}
|
||||
|
||||
public writeToFile() {
|
||||
const formattedMeasurements = this.measurements.map((measurement) => ({
|
||||
title: measurement.title,
|
||||
ms: measurement.timeMs,
|
||||
formatted: formatTime(measurement.timeMs),
|
||||
}));
|
||||
|
||||
const stats = {
|
||||
measurements: formattedMeasurements,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
fs.writeFileSync('stats.json', JSON.stringify(stats, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
export const measureTime = async <T>(title: string, fn: () => Promise<T>): Promise<T> => {
|
||||
const st = Date.now();
|
||||
const result = await fn();
|
||||
const et = Date.now();
|
||||
const timeMs = et - st;
|
||||
console.log(` ${title} took ${formatTime(timeMs)}`);
|
||||
StatsCollector.getInstance().addMeasurement(title, timeMs);
|
||||
return result;
|
||||
};
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import SuperTokens from 'supertokens-node';
|
||||
import UserRoles from 'supertokens-node/recipe/userroles';
|
||||
import { measureTime, workInBatches } from '../common/utils';
|
||||
|
||||
export const addRoles = async (
|
||||
users: { recipeUserId: string; email?: string; phoneNumber?: string }[]
|
||||
) => {
|
||||
console.log('\n\n4. Adding roles');
|
||||
|
||||
await measureTime('Adding roles', async () => {
|
||||
await UserRoles.createNewRoleOrAddPermissions('admin', ['p1', 'p2']);
|
||||
|
||||
await workInBatches(users.length, 8, async (idx) => {
|
||||
const user = users[idx]!;
|
||||
await UserRoles.addRoleToUser('public', user.recipeUserId, 'admin');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import SuperTokens from 'supertokens-node';
|
||||
import Session from 'supertokens-node/recipe/session';
|
||||
import { measureTime, workInBatches } from '../common/utils';
|
||||
|
||||
export const createSessions = async (
|
||||
users: { recipeUserId: string; email?: string; phoneNumber?: string }[]
|
||||
) => {
|
||||
console.log('\n\n5. Creating sessions');
|
||||
|
||||
await measureTime('Creating sessions', async () => {
|
||||
await workInBatches(users.length, 8, async (idx) => {
|
||||
const user = users[idx]!;
|
||||
await Session.createNewSessionWithoutRequestResponse(
|
||||
'public',
|
||||
user.recipeUserId
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { measureTime, workInBatches } from '../common/utils';
|
||||
import SuperTokens from 'supertokens-node';
|
||||
|
||||
export const createUserIdMappings = async (
|
||||
users: { recipeUserId: string; email?: string; phoneNumber?: string }[]
|
||||
) => {
|
||||
console.log('\n\n3. Create user id mappings');
|
||||
|
||||
await measureTime('Create user id mappings', async () => {
|
||||
await workInBatches(users.length, 8, async (idx) => {
|
||||
const user = users[idx]!;
|
||||
if (Math.random() < 0.5) {
|
||||
const newUserId = Array(64)
|
||||
.fill(0)
|
||||
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
|
||||
.join('');
|
||||
await SuperTokens.createUserIdMapping({
|
||||
superTokensUserId: user.recipeUserId,
|
||||
externalUserId: newUserId,
|
||||
});
|
||||
user.recipeUserId = newUserId;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
import EmailPassword from 'supertokens-node/recipe/emailpassword';
|
||||
import Passwordless from 'supertokens-node/recipe/passwordless';
|
||||
import ThirdParty from 'supertokens-node/recipe/thirdparty';
|
||||
|
||||
import { workInBatches, measureTime } from '../common/utils';
|
||||
|
||||
const TOTAL_USERS = 10000;
|
||||
|
||||
const createEmailPasswordUsers = async () => {
|
||||
console.log(` Creating EmailPassword users...`);
|
||||
|
||||
return await workInBatches(Math.floor(TOTAL_USERS / 5), 4, async (idx) => {
|
||||
const email =
|
||||
Array(64)
|
||||
.fill(0)
|
||||
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
|
||||
.join('') + '@example.com';
|
||||
const createdUser = await EmailPassword.signUp('public', email, 'password');
|
||||
// expect(createdUser.status).toBe("OK");
|
||||
if (createdUser.status === 'OK') {
|
||||
return {
|
||||
recipeUserId: createdUser.user.id,
|
||||
email: email,
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createPasswordlessUsersWithEmail = async () => {
|
||||
console.log(` Creating Passwordless users (with email)...`);
|
||||
return await workInBatches(Math.floor(TOTAL_USERS / 5), 4, async (idx) => {
|
||||
const email =
|
||||
Array(64)
|
||||
.fill(0)
|
||||
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
|
||||
.join('') + '@example.com';
|
||||
const createdUser = await Passwordless.signInUp({
|
||||
tenantId: 'public',
|
||||
email,
|
||||
});
|
||||
// expect(createdUser.status).toBe("OK");
|
||||
if (createdUser.status === 'OK') {
|
||||
return {
|
||||
recipeUserId: createdUser.user.id,
|
||||
email,
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createPasswordlessUsersWithPhone = async () => {
|
||||
console.log(` Creating Passwordless users (with phone)...`);
|
||||
return await workInBatches(Math.floor(TOTAL_USERS / 5), 4, async (idx) => {
|
||||
const phoneNumber = `+1${Math.floor(Math.random() * 10000000000)}`;
|
||||
const createdUser = await Passwordless.signInUp({
|
||||
tenantId: 'public',
|
||||
phoneNumber,
|
||||
});
|
||||
// expect(createdUser.status).toBe("OK");
|
||||
if (createdUser.status === 'OK') {
|
||||
return {
|
||||
recipeUserId: createdUser.user.id,
|
||||
phoneNumber,
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const createThirdPartyUsers = async (thirdPartyId: string) => {
|
||||
console.log(` Creating ThirdParty (${thirdPartyId}) users...`);
|
||||
return await workInBatches(Math.floor(TOTAL_USERS / 5), 4, async (idx) => {
|
||||
const email =
|
||||
Array(64)
|
||||
.fill(0)
|
||||
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
|
||||
.join('') + '@example.com';
|
||||
const tpUserId = Array(64)
|
||||
.fill(0)
|
||||
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
|
||||
.join('');
|
||||
const createdUser = await ThirdParty.manuallyCreateOrUpdateUser(
|
||||
'public',
|
||||
thirdPartyId,
|
||||
tpUserId,
|
||||
email,
|
||||
true
|
||||
);
|
||||
// expect(createdUser.status).toBe("OK");
|
||||
if (createdUser.status === 'OK') {
|
||||
return {
|
||||
recipeUserId: createdUser.user.id,
|
||||
email,
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const createUsers = async () => {
|
||||
console.log('\n\n1. Create one million users');
|
||||
|
||||
const epUsers = await measureTime('Emailpassword users creation', createEmailPasswordUsers);
|
||||
|
||||
const plessEmailUsers = await measureTime(
|
||||
'Passwordless users (with email) creation',
|
||||
createPasswordlessUsersWithEmail
|
||||
);
|
||||
|
||||
const plessPhoneUsers = await measureTime(
|
||||
'Passwordless users (with phone) creation',
|
||||
createPasswordlessUsersWithPhone
|
||||
);
|
||||
|
||||
const tpUsers1 = await measureTime('ThirdParty users (google) creation', () =>
|
||||
createThirdPartyUsers('google')
|
||||
);
|
||||
|
||||
const tpUsers2 = await measureTime('ThirdParty users (facebook) creation', () =>
|
||||
createThirdPartyUsers('facebook')
|
||||
);
|
||||
|
||||
return {
|
||||
epUsers,
|
||||
plessEmailUsers,
|
||||
plessPhoneUsers,
|
||||
tpUsers1,
|
||||
tpUsers2,
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import * as fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const USERS_TO_GENERATE = 1000000;
|
||||
const USERS_PER_JSON = 10000;
|
||||
|
||||
const n = Math.floor(USERS_TO_GENERATE / USERS_PER_JSON);
|
||||
|
||||
const generatedEmails = new Set<string>();
|
||||
const generatedPhoneNumbers = new Set<string>();
|
||||
const generatedUserIds = new Set<string>();
|
||||
|
||||
interface LoginMethod {
|
||||
tenantIds: string[];
|
||||
email: string;
|
||||
recipeId: string;
|
||||
passwordHash?: string;
|
||||
hashingAlgorithm?: string;
|
||||
thirdPartyId?: string;
|
||||
thirdPartyUserId?: string;
|
||||
phoneNumber?: string;
|
||||
isVerified: boolean;
|
||||
isPrimary: boolean;
|
||||
timeJoinedInMSSinceEpoch: number;
|
||||
}
|
||||
|
||||
interface User {
|
||||
externalUserId: string;
|
||||
userRoles: Array<{
|
||||
role: string;
|
||||
tenantIds: string[];
|
||||
}>;
|
||||
loginMethods: LoginMethod[];
|
||||
}
|
||||
|
||||
function createEmailLoginMethod(email: string, tenantIds: string[]): LoginMethod {
|
||||
return {
|
||||
tenantIds,
|
||||
email,
|
||||
recipeId: 'emailpassword',
|
||||
passwordHash: '$argon2d$v=19$m=12,t=3,p=1$aGI4enNvMmd0Zm0wMDAwMA$r6p7qbr6HD+8CD7sBi4HVw',
|
||||
hashingAlgorithm: 'argon2',
|
||||
isVerified: true,
|
||||
isPrimary: false,
|
||||
timeJoinedInMSSinceEpoch:
|
||||
Math.floor(Math.random() * (Date.now() - 3 * 365 * 24 * 60 * 60 * 1000)) +
|
||||
3 * 365 * 24 * 60 * 60 * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
function createThirdPartyLoginMethod(email: string, tenantIds: string[]): LoginMethod {
|
||||
return {
|
||||
tenantIds,
|
||||
recipeId: 'thirdparty',
|
||||
email,
|
||||
thirdPartyId: 'google',
|
||||
thirdPartyUserId: String(hashCode(email)),
|
||||
isVerified: true,
|
||||
isPrimary: false,
|
||||
timeJoinedInMSSinceEpoch:
|
||||
Math.floor(Math.random() * (Date.now() - 3 * 365 * 24 * 60 * 60 * 1000)) +
|
||||
3 * 365 * 24 * 60 * 60 * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
function createPasswordlessLoginMethod(
|
||||
email: string,
|
||||
tenantIds: string[],
|
||||
phoneNumber: string
|
||||
): LoginMethod {
|
||||
return {
|
||||
tenantIds,
|
||||
email,
|
||||
recipeId: 'passwordless',
|
||||
phoneNumber,
|
||||
isVerified: true,
|
||||
isPrimary: false,
|
||||
timeJoinedInMSSinceEpoch:
|
||||
Math.floor(Math.random() * (Date.now() - 3 * 365 * 24 * 60 * 60 * 1000)) +
|
||||
3 * 365 * 24 * 60 * 60 * 1000,
|
||||
};
|
||||
}
|
||||
|
||||
function hashCode(str: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function generateRandomString(length: number, chars: string): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function generateRandomEmail(): string {
|
||||
return `${generateRandomString(24, 'abcdefghijklmnopqrstuvwxyz')}@example.com`;
|
||||
}
|
||||
|
||||
function generateRandomPhoneNumber(): string {
|
||||
return `+91${generateRandomString(10, '0123456789')}`;
|
||||
}
|
||||
|
||||
function genUser(): User {
|
||||
const user: User = {
|
||||
externalUserId: '',
|
||||
userRoles: [
|
||||
{ role: 'role1', tenantIds: ['public'] },
|
||||
{ role: 'role2', tenantIds: ['public'] },
|
||||
],
|
||||
loginMethods: [],
|
||||
};
|
||||
|
||||
let userId = `e-${uuidv4()}`;
|
||||
while (generatedUserIds.has(userId)) {
|
||||
userId = `e-${uuidv4()}`;
|
||||
}
|
||||
generatedUserIds.add(userId);
|
||||
user.externalUserId = userId;
|
||||
|
||||
const tenantIds = ['public'];
|
||||
|
||||
let email = generateRandomEmail();
|
||||
while (generatedEmails.has(email)) {
|
||||
email = generateRandomEmail();
|
||||
}
|
||||
generatedEmails.add(email);
|
||||
|
||||
const loginMethods: LoginMethod[] = [];
|
||||
|
||||
// Always add email login method
|
||||
loginMethods.push(createEmailLoginMethod(email, tenantIds));
|
||||
|
||||
// 50% chance to add third party login
|
||||
if (Math.random() < 0.5) {
|
||||
loginMethods.push(createThirdPartyLoginMethod(email, tenantIds));
|
||||
}
|
||||
|
||||
// 50% chance to add passwordless login
|
||||
if (Math.random() < 0.5) {
|
||||
let phoneNumber = generateRandomPhoneNumber();
|
||||
while (generatedPhoneNumbers.has(phoneNumber)) {
|
||||
phoneNumber = generateRandomPhoneNumber();
|
||||
}
|
||||
generatedPhoneNumbers.add(phoneNumber);
|
||||
loginMethods.push(createPasswordlessLoginMethod(email, tenantIds, phoneNumber));
|
||||
}
|
||||
|
||||
// If no methods were added, randomly add one
|
||||
if (loginMethods.length === 0) {
|
||||
const methodNumber = Math.floor(Math.random() * 3);
|
||||
if (methodNumber === 0) {
|
||||
loginMethods.push(createEmailLoginMethod(email, tenantIds));
|
||||
} else if (methodNumber === 1) {
|
||||
loginMethods.push(createThirdPartyLoginMethod(email, tenantIds));
|
||||
} else {
|
||||
let phoneNumber = generateRandomPhoneNumber();
|
||||
while (generatedPhoneNumbers.has(phoneNumber)) {
|
||||
phoneNumber = generateRandomPhoneNumber();
|
||||
}
|
||||
generatedPhoneNumbers.add(phoneNumber);
|
||||
loginMethods.push(createPasswordlessLoginMethod(email, tenantIds, phoneNumber));
|
||||
}
|
||||
}
|
||||
|
||||
loginMethods[Math.floor(Math.random() * loginMethods.length)].isPrimary = true;
|
||||
|
||||
user.loginMethods = loginMethods;
|
||||
return user;
|
||||
}
|
||||
|
||||
// Create users directory if it doesn't exist
|
||||
if (!fs.existsSync('users')) {
|
||||
fs.mkdirSync('users');
|
||||
}
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
console.log(`Generating ${USERS_PER_JSON} users for ${i}`);
|
||||
const users: User[] = [];
|
||||
for (let j = 0; j < USERS_PER_JSON; j++) {
|
||||
users.push(genUser());
|
||||
}
|
||||
fs.writeFileSync(
|
||||
`users/users-${i.toString().padStart(4, '0')}.json`,
|
||||
JSON.stringify({ users }, null, 2)
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
import {
|
||||
createStInstanceForTest,
|
||||
deleteStInstance,
|
||||
setupLicense,
|
||||
StatsCollector,
|
||||
} from '../common/utils';
|
||||
|
||||
import SuperTokens from 'supertokens-node';
|
||||
import EmailPassword from 'supertokens-node/recipe/emailpassword';
|
||||
import Passwordless from 'supertokens-node/recipe/passwordless';
|
||||
import ThirdParty from 'supertokens-node/recipe/thirdparty';
|
||||
import UserRoles from 'supertokens-node/recipe/userroles';
|
||||
import Session from 'supertokens-node/recipe/session';
|
||||
|
||||
import { createUsers } from './createUsers';
|
||||
import { createUserIdMappings } from './createUserIdMappings';
|
||||
import { addRoles } from './addRoles';
|
||||
import { createSessions } from './createSessions';
|
||||
|
||||
function stInit(connectionURI: string, apiKey: string) {
|
||||
SuperTokens.init({
|
||||
appInfo: {
|
||||
appName: 'SuperTokens',
|
||||
apiDomain: 'http://localhost:3001',
|
||||
websiteDomain: 'http://localhost:3000',
|
||||
apiBasePath: '/auth',
|
||||
websiteBasePath: '/auth',
|
||||
},
|
||||
supertokens: {
|
||||
connectionURI: connectionURI,
|
||||
apiKey: apiKey,
|
||||
},
|
||||
recipeList: [
|
||||
EmailPassword.init(),
|
||||
Passwordless.init({
|
||||
contactMethod: 'EMAIL_OR_PHONE',
|
||||
flowType: 'USER_INPUT_CODE',
|
||||
}),
|
||||
ThirdParty.init({
|
||||
signInAndUpFeature: {
|
||||
providers: [
|
||||
{
|
||||
config: { thirdPartyId: 'google' },
|
||||
},
|
||||
{
|
||||
config: { thirdPartyId: 'facebook' },
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
UserRoles.init(),
|
||||
Session.init(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const deployment = await createStInstanceForTest();
|
||||
console.log(`Deployment created: ${deployment.core_url}`);
|
||||
try {
|
||||
stInit(deployment.core_url, deployment.api_key);
|
||||
await setupLicense(deployment.core_url, deployment.api_key);
|
||||
|
||||
// 1. Create one million users
|
||||
const users = await createUsers();
|
||||
|
||||
// Randomly create groups of users for linking
|
||||
const allUsers: ({ recipeUserId: string; email?: string; phoneNumber?: string } | undefined)[] =
|
||||
[
|
||||
...users.epUsers,
|
||||
...users.plessEmailUsers,
|
||||
...users.plessPhoneUsers,
|
||||
...users.tpUsers1,
|
||||
...users.tpUsers2,
|
||||
];
|
||||
const usersToLink: { recipeUserId: string; email?: string; phoneNumber?: string }[][] = [];
|
||||
|
||||
while (allUsers.length > 0) {
|
||||
const userSet: { recipeUserId: string; email?: string; phoneNumber?: string }[] = [];
|
||||
const numAccounts = Math.min(Math.floor(Math.random() * 5 + 1), allUsers.length);
|
||||
for (let i = 0; i < numAccounts; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * allUsers.length);
|
||||
userSet.push(allUsers[randomIndex]!);
|
||||
allUsers.splice(randomIndex, 1);
|
||||
}
|
||||
usersToLink.push(userSet);
|
||||
}
|
||||
|
||||
// 2. Link accounts
|
||||
// await doAccountLinking(usersToLink);
|
||||
|
||||
// 3. Create user id mappings
|
||||
const allUsersForMapping = [
|
||||
...users.epUsers,
|
||||
...users.plessEmailUsers,
|
||||
...users.plessPhoneUsers,
|
||||
...users.tpUsers1,
|
||||
...users.tpUsers2,
|
||||
].filter((user) => user !== undefined) as {
|
||||
recipeUserId: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
}[];
|
||||
await createUserIdMappings(allUsersForMapping);
|
||||
|
||||
// 4. Add roles
|
||||
await addRoles(allUsersForMapping);
|
||||
|
||||
// 5. Create sessions
|
||||
await createSessions(allUsersForMapping);
|
||||
|
||||
// 6. List all users
|
||||
console.log('\n\n6. Listing all users');
|
||||
let userCount = 0;
|
||||
let paginationToken: string | undefined;
|
||||
while (true) {
|
||||
const result = await SuperTokens.getUsersNewestFirst({
|
||||
tenantId: 'public',
|
||||
paginationToken,
|
||||
});
|
||||
|
||||
for (const user of result.users) {
|
||||
userCount++;
|
||||
}
|
||||
|
||||
paginationToken = result.nextPaginationToken;
|
||||
|
||||
if (result.nextPaginationToken === undefined) break;
|
||||
}
|
||||
console.log(`Users count: ${userCount}`);
|
||||
|
||||
// 7. Count users
|
||||
console.log('\n\n7. Count users');
|
||||
const total = await SuperTokens.getUserCount();
|
||||
console.log(`Users count: ${total}`);
|
||||
|
||||
// Write stats to file
|
||||
StatsCollector.getInstance().writeToFile();
|
||||
console.log('\nStats written to stats.json');
|
||||
} catch (error) {
|
||||
console.error('An error occurred during execution:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await deleteStInstance(deployment.deployment_id);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Loading…
Reference in New Issue