diff --git a/packages/user/package.json b/packages/user/package.json index 039dc410d..9740e3fb8 100644 --- a/packages/user/package.json +++ b/packages/user/package.json @@ -55,7 +55,7 @@ "mercurius-auth": "6.0.0", "prettier": "3.8.3", "slonik": "46.8.0", - "supertokens-node": "14.1.4", + "supertokens-node": "15.0.0", "typescript": "5.9.3", "vite": "6.4.2", "vitest": "3.2.4", @@ -75,7 +75,7 @@ "mercurius": ">=16.1.0", "mercurius-auth": ">=6.0.0", "slonik": ">=46.1.0", - "supertokens-node": ">=14.1.4", + "supertokens-node": ">=15.0.0", "zod": ">=3.23.8" }, "engines": { diff --git a/packages/user/src/auth/supertokens.ts b/packages/user/src/auth/supertokens.ts index 78c7aa107..7c3cf4956 100644 --- a/packages/user/src/auth/supertokens.ts +++ b/packages/user/src/auth/supertokens.ts @@ -26,7 +26,7 @@ import type { } from "./adapter"; import type { ClaimValidationError, RefreshableClaim } from "./types"; -import { ERROR_CODES } from "../constants"; +import { ERROR_CODES, SUPERTOKENS_DEFAULT_TENANT_ID } from "../constants"; import supertokensPlugin from "../supertokens"; import createUserContextImpl from "../supertokens/utils/createUserContext"; import ProfileValidationClaim from "../supertokens/utils/profileValidationClaim"; @@ -123,8 +123,10 @@ const createUserContext = ( const supertokensEmailPasswordAdapter: EmailPasswordProvider = { async createResetPasswordToken(userId: string): Promise { - const response = - await ThirdPartyEmailPassword.createResetPasswordToken(userId); + const response = await ThirdPartyEmailPassword.createResetPasswordToken( + SUPERTOKENS_DEFAULT_TENANT_ID, + userId, + ); if (response.status === "OK") { return response.token; @@ -142,6 +144,7 @@ const supertokensEmailPasswordAdapter: EmailPasswordProvider = { userContext?: AuthUserContext, ): Promise { const response = await ThirdPartyEmailPassword.emailPasswordSignIn( + SUPERTOKENS_DEFAULT_TENANT_ID, email, password, userContext, @@ -166,6 +169,7 @@ const supertokensEmailPasswordAdapter: EmailPasswordProvider = { userContext?: AuthUserContext, ): Promise { const response = await ThirdPartyEmailPassword.emailPasswordSignUp( + SUPERTOKENS_DEFAULT_TENANT_ID, email, password, userContext, @@ -193,7 +197,10 @@ const supertokensEmailPasswordAdapter: EmailPasswordProvider = { }, async getUsersByEmail(email: string): Promise { - const users = await ThirdPartyEmailPassword.getUsersByEmail(email); + const users = await ThirdPartyEmailPassword.getUsersByEmail( + SUPERTOKENS_DEFAULT_TENANT_ID, + email, + ); return users.map((user) => user as AuthUser); }, @@ -203,6 +210,7 @@ const supertokensEmailPasswordAdapter: EmailPasswordProvider = { newPassword: string, ): Promise { const response = await ThirdPartyEmailPassword.resetPasswordUsingToken( + SUPERTOKENS_DEFAULT_TENANT_ID, token, newPassword, ); @@ -236,6 +244,7 @@ const supertokensEmailVerificationAdapter: EmailVerificationProvider = { userContext?: AuthUserContext, ): Promise { const response = await EmailVerification.createEmailVerificationToken( + SUPERTOKENS_DEFAULT_TENANT_ID, userId, email, userContext, @@ -258,6 +267,7 @@ const supertokensEmailVerificationAdapter: EmailVerificationProvider = { async sendVerificationEmail(input) { await EmailVerification.sendEmail({ emailVerifyLink: `${input.appOrigin}/auth/verify-email?token=${input.token}&rid=emailverification`, + tenantId: SUPERTOKENS_DEFAULT_TENANT_ID, type: "EMAIL_VERIFICATION", user: { email: input.email, @@ -278,6 +288,7 @@ const supertokensEmailVerificationAdapter: EmailVerificationProvider = { userContext?: AuthUserContext, ): Promise { const response = await EmailVerification.verifyEmailUsingToken( + SUPERTOKENS_DEFAULT_TENANT_ID, token, userContext, ); @@ -288,7 +299,11 @@ const supertokensEmailVerificationAdapter: EmailVerificationProvider = { const supertokensRolesAdapter: RolesProvider = { async addRoleToUser(userId: string, role: string): Promise { - const response = await UserRoles.addRoleToUser(userId, role); + const response = await UserRoles.addRoleToUser( + SUPERTOKENS_DEFAULT_TENANT_ID, + userId, + role, + ); if (response.status !== "OK") { throw new CustomError( @@ -333,13 +348,19 @@ const supertokensRolesAdapter: RolesProvider = { }, async getRolesForUser(userId: string): Promise { - const response = await UserRoles.getRolesForUser(userId); + const response = await UserRoles.getRolesForUser( + SUPERTOKENS_DEFAULT_TENANT_ID, + userId, + ); return response.roles; }, async getUsersThatHaveRole(role: string): Promise { - const response = await UserRoles.getUsersThatHaveRole(role); + const response = await UserRoles.getUsersThatHaveRole( + SUPERTOKENS_DEFAULT_TENANT_ID, + role, + ); if (response.status === "OK") { return response.users; @@ -376,6 +397,7 @@ const supertokensSessionAdapter: SessionProvider = { return Session.createNewSession( request, reply, + SUPERTOKENS_DEFAULT_TENANT_ID, userId, accessTokenPayload, sessionData, diff --git a/packages/user/src/constants.ts b/packages/user/src/constants.ts index d7af7ba39..37e76f4b3 100644 --- a/packages/user/src/constants.ts +++ b/packages/user/src/constants.ts @@ -37,6 +37,8 @@ const ROUTE_PERMISSIONS = "/permissions"; const EMAIL_VERIFICATION_MODE = "REQUIRED"; const EMAIL_VERIFICATION_PATH = "/verify-email"; +const SUPERTOKENS_DEFAULT_TENANT_ID = "public"; + const PERMISSIONS_INVITATIONS_CREATE = "invitations:create"; const PERMISSIONS_INVITATIONS_DELETE = "invitations:delete"; const PERMISSIONS_INVITATIONS_LIST = "invitations:list"; @@ -124,6 +126,7 @@ export { ROUTE_USERS_ENABLE, ROUTE_USERS_FIND_BY_ID, SUPERTOKENS_CORS_HEADERS, + SUPERTOKENS_DEFAULT_TENANT_ID, TABLE_INVITATIONS, TABLE_USERS, }; diff --git a/packages/user/src/migrations/supertokens-core-v6.sql b/packages/user/src/migrations/supertokens-core-v6.sql new file mode 100644 index 000000000..43e065730 --- /dev/null +++ b/packages/user/src/migrations/supertokens-core-v6.sql @@ -0,0 +1,858 @@ +-- General Tables + +CREATE TABLE IF NOT EXISTS st__apps ( + app_id VARCHAR(64) NOT NULL DEFAULT 'public', + created_at_time BIGINT, + CONSTRAINT st__apps_pkey PRIMARY KEY(app_id) +); + +INSERT INTO st__apps (app_id, created_at_time) + VALUES ('public', 0) ON CONFLICT DO NOTHING; + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__tenants ( + app_id VARCHAR(64) NOT NULL DEFAULT 'public', + tenant_id VARCHAR(64) NOT NULL DEFAULT 'public', + created_at_time BIGINT , + CONSTRAINT st__tenants_pkey + PRIMARY KEY (app_id, tenant_id), + CONSTRAINT st__tenants_app_id_fkey FOREIGN KEY(app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE +); + +INSERT INTO st__tenants (app_id, tenant_id, created_at_time) + VALUES ('public', 'public', 0) ON CONFLICT DO NOTHING; + +CREATE INDEX IF NOT EXISTS st__tenants_app_id_index ON st__tenants (app_id); + +------------------------------------------------------------ +ALTER TABLE st__key_value + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +BEGIN; +ALTER TABLE st__key_value + DROP CONSTRAINT IF EXISTS st__key_value_pkey; + +ALTER TABLE st__key_value + ADD CONSTRAINT st__key_value_pkey + PRIMARY KEY (app_id, tenant_id, name); +COMMIT; + +BEGIN; +ALTER TABLE st__key_value + DROP CONSTRAINT IF EXISTS st__key_value_tenant_id_fkey; + +ALTER TABLE st__key_value + ADD CONSTRAINT st__key_value_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; +COMMIT; + +CREATE INDEX IF NOT EXISTS st__key_value_tenant_id_index ON st__key_value (app_id, tenant_id); + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__app_id_to_user_id ( + app_id VARCHAR(64) NOT NULL DEFAULT 'public', + user_id CHAR(36) NOT NULL, + recipe_id VARCHAR(128) NOT NULL, + CONSTRAINT st__app_id_to_user_id_pkey + PRIMARY KEY (app_id, user_id), + CONSTRAINT st__app_id_to_user_id_app_id_fkey + FOREIGN KEY(app_id) REFERENCES st__apps (app_id) ON DELETE CASCADE +); + +INSERT INTO st__app_id_to_user_id (user_id, recipe_id) + SELECT user_id, recipe_id + FROM st__all_auth_recipe_users ON CONFLICT DO NOTHING; + +CREATE INDEX IF NOT EXISTS st__app_id_to_user_id_app_id_index ON st__app_id_to_user_id (app_id); + +------------------------------------------------------------ + +ALTER TABLE st__all_auth_recipe_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__all_auth_recipe_users + DROP CONSTRAINT st__all_auth_recipe_users_pkey CASCADE; + +ALTER TABLE st__all_auth_recipe_users + ADD CONSTRAINT st__all_auth_recipe_users_pkey + PRIMARY KEY (app_id, tenant_id, user_id); + +ALTER TABLE st__all_auth_recipe_users + DROP CONSTRAINT IF EXISTS st__all_auth_recipe_users_tenant_id_fkey; + +ALTER TABLE st__all_auth_recipe_users + ADD CONSTRAINT st__all_auth_recipe_users_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +ALTER TABLE st__all_auth_recipe_users + DROP CONSTRAINT IF EXISTS st__all_auth_recipe_users_user_id_fkey; + +ALTER TABLE st__all_auth_recipe_users + ADD CONSTRAINT st__all_auth_recipe_users_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__all_auth_recipe_users_pagination_index; + +CREATE INDEX st__all_auth_recipe_users_pagination_index ON st__all_auth_recipe_users (time_joined DESC, user_id DESC, tenant_id DESC, app_id DESC); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_user_id_index ON st__all_auth_recipe_users (app_id, user_id); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_tenant_id_index ON st__all_auth_recipe_users (app_id, tenant_id); + +-- Multitenancy + +CREATE TABLE IF NOT EXISTS st__tenant_configs ( + connection_uri_domain VARCHAR(256) DEFAULT '', + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + core_config TEXT, + email_password_enabled BOOLEAN, + passwordless_enabled BOOLEAN, + third_party_enabled BOOLEAN, + CONSTRAINT st__tenant_configs_pkey + PRIMARY KEY (connection_uri_domain, app_id, tenant_id) +); + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__tenant_thirdparty_providers ( + connection_uri_domain VARCHAR(256) DEFAULT '', + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + third_party_id VARCHAR(28) NOT NULL, + name VARCHAR(64), + authorization_endpoint TEXT, + authorization_endpoint_query_params TEXT, + token_endpoint TEXT, + token_endpoint_body_params TEXT, + user_info_endpoint TEXT, + user_info_endpoint_query_params TEXT, + user_info_endpoint_headers TEXT, + jwks_uri TEXT, + oidc_discovery_endpoint TEXT, + require_email BOOLEAN, + user_info_map_from_id_token_payload_user_id VARCHAR(64), + user_info_map_from_id_token_payload_email VARCHAR(64), + user_info_map_from_id_token_payload_email_verified VARCHAR(64), + user_info_map_from_user_info_endpoint_user_id VARCHAR(64), + user_info_map_from_user_info_endpoint_email VARCHAR(64), + user_info_map_from_user_info_endpoint_email_verified VARCHAR(64), + CONSTRAINT st__tenant_thirdparty_providers_pkey + PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id), + CONSTRAINT st__tenant_thirdparty_providers_tenant_id_fkey + FOREIGN KEY(connection_uri_domain, app_id, tenant_id) + REFERENCES st__tenant_configs (connection_uri_domain, app_id, tenant_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS st__tenant_thirdparty_providers_tenant_id_index ON st__tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id); + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__tenant_thirdparty_provider_clients ( + connection_uri_domain VARCHAR(256) DEFAULT '', + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + third_party_id VARCHAR(28) NOT NULL, + client_type VARCHAR(64) NOT NULL DEFAULT '', + client_id VARCHAR(256) NOT NULL, + client_secret TEXT, + scope VARCHAR(128)[], + force_pkce BOOLEAN, + additional_config TEXT, + CONSTRAINT st__tenant_thirdparty_provider_clients_pkey + PRIMARY KEY (connection_uri_domain, app_id, tenant_id, third_party_id, client_type), + CONSTRAINT st__tenant_thirdparty_provider_clients_third_party_id_fkey + FOREIGN KEY (connection_uri_domain, app_id, tenant_id, third_party_id) + REFERENCES st__tenant_thirdparty_providers (connection_uri_domain, app_id, tenant_id, third_party_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS st__tenant_thirdparty_provider_clients_third_party_id_index ON st__tenant_thirdparty_provider_clients (connection_uri_domain, app_id, tenant_id, third_party_id); + +-- Session + +ALTER TABLE st__session_info + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__session_info + DROP CONSTRAINT st__session_info_pkey CASCADE; + +ALTER TABLE st__session_info + ADD CONSTRAINT st__session_info_pkey + PRIMARY KEY (app_id, tenant_id, session_handle); + +ALTER TABLE st__session_info + DROP CONSTRAINT IF EXISTS st__session_info_tenant_id_fkey; + +ALTER TABLE st__session_info + ADD CONSTRAINT st__session_info_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__session_expiry_index ON st__session_info (expires_at); + +CREATE INDEX IF NOT EXISTS st__session_info_tenant_id_index ON st__session_info (app_id, tenant_id); + +------------------------------------------------------------ + +ALTER TABLE st__session_access_token_signing_keys + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__session_access_token_signing_keys + DROP CONSTRAINT st__session_access_token_signing_keys_pkey CASCADE; + +ALTER TABLE st__session_access_token_signing_keys + ADD CONSTRAINT st__session_access_token_signing_keys_pkey + PRIMARY KEY (app_id, created_at_time); + +ALTER TABLE st__session_access_token_signing_keys + DROP CONSTRAINT IF EXISTS st__session_access_token_signing_keys_app_id_fkey; + +ALTER TABLE st__session_access_token_signing_keys + ADD CONSTRAINT st__session_access_token_signing_keys_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__access_token_signing_keys_app_id_index ON st__session_access_token_signing_keys (app_id); + +-- JWT + +ALTER TABLE st__jwt_signing_keys + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__jwt_signing_keys + DROP CONSTRAINT st__jwt_signing_keys_pkey CASCADE; + +ALTER TABLE st__jwt_signing_keys + ADD CONSTRAINT st__jwt_signing_keys_pkey + PRIMARY KEY (app_id, key_id); + +ALTER TABLE st__jwt_signing_keys + DROP CONSTRAINT IF EXISTS st__jwt_signing_keys_app_id_fkey; + +ALTER TABLE st__jwt_signing_keys + ADD CONSTRAINT st__jwt_signing_keys_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__jwt_signing_keys_app_id_index ON st__jwt_signing_keys (app_id); + +-- EmailVerification + +ALTER TABLE st__emailverification_verified_emails + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__emailverification_verified_emails + DROP CONSTRAINT st__emailverification_verified_emails_pkey CASCADE; + +ALTER TABLE st__emailverification_verified_emails + ADD CONSTRAINT st__emailverification_verified_emails_pkey + PRIMARY KEY (app_id, user_id, email); + +ALTER TABLE st__emailverification_verified_emails + DROP CONSTRAINT IF EXISTS st__emailverification_verified_emails_app_id_fkey; + +ALTER TABLE st__emailverification_verified_emails + ADD CONSTRAINT st__emailverification_verified_emails_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__emailverification_verified_emails_app_id_index ON st__emailverification_verified_emails (app_id); + +------------------------------------------------------------ + +ALTER TABLE st__emailverification_tokens + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__emailverification_tokens + DROP CONSTRAINT st__emailverification_tokens_pkey CASCADE; + +ALTER TABLE st__emailverification_tokens + ADD CONSTRAINT st__emailverification_tokens_pkey + PRIMARY KEY (app_id, tenant_id, user_id, email, token); + +ALTER TABLE st__emailverification_tokens + DROP CONSTRAINT IF EXISTS st__emailverification_tokens_tenant_id_fkey; + +ALTER TABLE st__emailverification_tokens + ADD CONSTRAINT st__emailverification_tokens_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__emailverification_tokens_tenant_id_index ON st__emailverification_tokens (app_id, tenant_id); + +-- EmailPassword + +ALTER TABLE st__emailpassword_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__emailpassword_users + DROP CONSTRAINT st__emailpassword_users_pkey CASCADE; + +ALTER TABLE st__emailpassword_users + DROP CONSTRAINT IF EXISTS st__emailpassword_users_email_key CASCADE; + +ALTER TABLE st__emailpassword_users + ADD CONSTRAINT st__emailpassword_users_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__emailpassword_users + DROP CONSTRAINT IF EXISTS st__emailpassword_users_user_id_fkey; + +ALTER TABLE st__emailpassword_users + ADD CONSTRAINT st__emailpassword_users_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__emailpassword_user_to_tenant ( + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + user_id CHAR(36) NOT NULL, + email VARCHAR(256) NOT NULL, + CONSTRAINT st__emailpassword_user_to_tenant_email_key + UNIQUE (app_id, tenant_id, email), + CONSTRAINT st__emailpassword_user_to_tenant_pkey + PRIMARY KEY (app_id, tenant_id, user_id), + CONSTRAINT st__emailpassword_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE +); + +ALTER TABLE st__emailpassword_user_to_tenant + DROP CONSTRAINT IF EXISTS st__emailpassword_user_to_tenant_email_key; + +ALTER TABLE st__emailpassword_user_to_tenant + ADD CONSTRAINT st__emailpassword_user_to_tenant_email_key + UNIQUE (app_id, tenant_id, email); + +ALTER TABLE st__emailpassword_user_to_tenant + DROP CONSTRAINT IF EXISTS st__emailpassword_user_to_tenant_user_id_fkey; + +ALTER TABLE st__emailpassword_user_to_tenant + ADD CONSTRAINT st__emailpassword_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE; + +INSERT INTO st__emailpassword_user_to_tenant (user_id, email) + SELECT user_id, email FROM st__emailpassword_users ON CONFLICT DO NOTHING; + +------------------------------------------------------------ + +ALTER TABLE st__emailpassword_pswd_reset_tokens + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__emailpassword_pswd_reset_tokens + DROP CONSTRAINT st__emailpassword_pswd_reset_tokens_pkey CASCADE; + +ALTER TABLE st__emailpassword_pswd_reset_tokens + ADD CONSTRAINT st__emailpassword_pswd_reset_tokens_pkey + PRIMARY KEY (app_id, user_id, token); + +ALTER TABLE st__emailpassword_pswd_reset_tokens + DROP CONSTRAINT IF EXISTS st__emailpassword_pswd_reset_tokens_user_id_fkey; + +ALTER TABLE st__emailpassword_pswd_reset_tokens + ADD CONSTRAINT st__emailpassword_pswd_reset_tokens_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__emailpassword_users (app_id, user_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__emailpassword_pswd_reset_tokens_user_id_index ON st__emailpassword_pswd_reset_tokens (app_id, user_id); + +-- Passwordless + +ALTER TABLE st__passwordless_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__passwordless_users + DROP CONSTRAINT st__passwordless_users_pkey CASCADE; + +ALTER TABLE st__passwordless_users + ADD CONSTRAINT st__passwordless_users_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__passwordless_users + DROP CONSTRAINT IF EXISTS st__passwordless_users_email_key; + +ALTER TABLE st__passwordless_users + DROP CONSTRAINT IF EXISTS st__passwordless_users_phone_number_key; + +ALTER TABLE st__passwordless_users + DROP CONSTRAINT IF EXISTS st__passwordless_users_user_id_fkey; + +ALTER TABLE st__passwordless_users + ADD CONSTRAINT st__passwordless_users_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__passwordless_user_to_tenant ( + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + user_id CHAR(36) NOT NULL, + email VARCHAR(256), + phone_number VARCHAR(256), + CONSTRAINT st__passwordless_user_to_tenant_email_key + UNIQUE (app_id, tenant_id, email), + CONSTRAINT st__passwordless_user_to_tenant_phone_number_key + UNIQUE (app_id, tenant_id, phone_number), + CONSTRAINT st__passwordless_user_to_tenant_pkey + PRIMARY KEY (app_id, tenant_id, user_id), + CONSTRAINT st__passwordless_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE +); + +ALTER TABLE st__passwordless_user_to_tenant + DROP CONSTRAINT IF EXISTS st__passwordless_user_to_tenant_user_id_fkey; + +ALTER TABLE st__passwordless_user_to_tenant + ADD CONSTRAINT st__passwordless_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE; + +INSERT INTO st__passwordless_user_to_tenant (user_id, email, phone_number) + SELECT user_id, email, phone_number FROM st__passwordless_users ON CONFLICT DO NOTHING; + +------------------------------------------------------------ + +ALTER TABLE st__passwordless_devices + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__passwordless_devices + DROP CONSTRAINT st__passwordless_devices_pkey CASCADE; + +ALTER TABLE st__passwordless_devices + ADD CONSTRAINT st__passwordless_devices_pkey + PRIMARY KEY (app_id, tenant_id, device_id_hash); + +ALTER TABLE st__passwordless_devices + DROP CONSTRAINT IF EXISTS st__passwordless_devices_tenant_id_fkey; + +ALTER TABLE st__passwordless_devices + ADD CONSTRAINT st__passwordless_devices_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__passwordless_devices_email_index; + +CREATE INDEX IF NOT EXISTS st__passwordless_devices_email_index ON st__passwordless_devices (app_id, tenant_id, email); + +DROP INDEX IF EXISTS st__passwordless_devices_phone_number_index; + +CREATE INDEX IF NOT EXISTS st__passwordless_devices_phone_number_index ON st__passwordless_devices (app_id, tenant_id, phone_number); + +CREATE INDEX IF NOT EXISTS st__passwordless_devices_tenant_id_index ON st__passwordless_devices (app_id, tenant_id); + +------------------------------------------------------------ + +ALTER TABLE st__passwordless_codes + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__passwordless_codes + DROP CONSTRAINT st__passwordless_codes_pkey CASCADE; + +ALTER TABLE st__passwordless_codes + ADD CONSTRAINT st__passwordless_codes_pkey + PRIMARY KEY (app_id, tenant_id, code_id); + +ALTER TABLE st__passwordless_codes + DROP CONSTRAINT IF EXISTS st__passwordless_codes_device_id_hash_fkey; + +ALTER TABLE st__passwordless_codes + ADD CONSTRAINT st__passwordless_codes_device_id_hash_fkey + FOREIGN KEY (app_id, tenant_id, device_id_hash) + REFERENCES st__passwordless_devices (app_id, tenant_id, device_id_hash) ON DELETE CASCADE; + +ALTER TABLE st__passwordless_codes + DROP CONSTRAINT st__passwordless_codes_link_code_hash_key; + +ALTER TABLE st__passwordless_codes + DROP CONSTRAINT IF EXISTS st__passwordless_codes_link_code_hash_key; + +ALTER TABLE st__passwordless_codes + ADD CONSTRAINT st__passwordless_codes_link_code_hash_key + UNIQUE (app_id, tenant_id, link_code_hash); + +DROP INDEX IF EXISTS st__passwordless_codes_created_at_index; + +CREATE INDEX IF NOT EXISTS st__passwordless_codes_created_at_index ON st__passwordless_codes (app_id, tenant_id, created_at); + +DROP INDEX IF EXISTS st__passwordless_codes_device_id_hash_index; +CREATE INDEX IF NOT EXISTS st__passwordless_codes_device_id_hash_index ON st__passwordless_codes (app_id, tenant_id, device_id_hash); + +-- ThirdParty + +ALTER TABLE st__thirdparty_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__thirdparty_users + DROP CONSTRAINT st__thirdparty_users_pkey CASCADE; + +ALTER TABLE st__thirdparty_users + DROP CONSTRAINT IF EXISTS st__thirdparty_users_user_id_key CASCADE; + +ALTER TABLE st__thirdparty_users + ADD CONSTRAINT st__thirdparty_users_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__thirdparty_users + DROP CONSTRAINT IF EXISTS st__thirdparty_users_user_id_fkey; + +ALTER TABLE st__thirdparty_users + ADD CONSTRAINT st__thirdparty_users_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__thirdparty_users_thirdparty_user_id_index; + +CREATE INDEX IF NOT EXISTS st__thirdparty_users_thirdparty_user_id_index ON st__thirdparty_users (app_id, third_party_id, third_party_user_id); + +DROP INDEX IF EXISTS st__thirdparty_users_email_index; + +CREATE INDEX IF NOT EXISTS st__thirdparty_users_email_index ON st__thirdparty_users (app_id, email); + +------------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS st__thirdparty_user_to_tenant ( + app_id VARCHAR(64) DEFAULT 'public', + tenant_id VARCHAR(64) DEFAULT 'public', + user_id CHAR(36) NOT NULL, + third_party_id VARCHAR(28) NOT NULL, + third_party_user_id VARCHAR(256) NOT NULL, + CONSTRAINT st__thirdparty_user_to_tenant_third_party_user_id_key + UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id), + CONSTRAINT st__thirdparty_user_to_tenant_pkey + PRIMARY KEY (app_id, tenant_id, user_id), + CONSTRAINT st__thirdparty_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE +); + +ALTER TABLE st__thirdparty_user_to_tenant + DROP CONSTRAINT IF EXISTS st__thirdparty_user_to_tenant_third_party_user_id_key; + +ALTER TABLE st__thirdparty_user_to_tenant + ADD CONSTRAINT st__thirdparty_user_to_tenant_third_party_user_id_key + UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id); + +ALTER TABLE st__thirdparty_user_to_tenant + DROP CONSTRAINT IF EXISTS st__thirdparty_user_to_tenant_user_id_fkey; + +ALTER TABLE st__thirdparty_user_to_tenant + ADD CONSTRAINT st__thirdparty_user_to_tenant_user_id_fkey + FOREIGN KEY (app_id, tenant_id, user_id) + REFERENCES st__all_auth_recipe_users (app_id, tenant_id, user_id) ON DELETE CASCADE; + +INSERT INTO st__thirdparty_user_to_tenant (user_id, third_party_id, third_party_user_id) + SELECT user_id, third_party_id, third_party_user_id FROM st__thirdparty_users ON CONFLICT DO NOTHING; + +-- UserIdMapping + +ALTER TABLE st__userid_mapping + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__userid_mapping + DROP CONSTRAINT IF EXISTS st__userid_mapping_pkey CASCADE; + +ALTER TABLE st__userid_mapping + ADD CONSTRAINT st__userid_mapping_pkey + PRIMARY KEY (app_id, supertokens_user_id, external_user_id); + +ALTER TABLE st__userid_mapping + DROP CONSTRAINT IF EXISTS st__userid_mapping_supertokens_user_id_key; + +ALTER TABLE st__userid_mapping + ADD CONSTRAINT st__userid_mapping_supertokens_user_id_key + UNIQUE (app_id, supertokens_user_id); + +ALTER TABLE st__userid_mapping + DROP CONSTRAINT IF EXISTS st__userid_mapping_external_user_id_key; + +ALTER TABLE st__userid_mapping + ADD CONSTRAINT st__userid_mapping_external_user_id_key + UNIQUE (app_id, external_user_id); + +ALTER TABLE st__userid_mapping + DROP CONSTRAINT IF EXISTS st__userid_mapping_supertokens_user_id_fkey; + +ALTER TABLE st__userid_mapping + ADD CONSTRAINT st__userid_mapping_supertokens_user_id_fkey + FOREIGN KEY (app_id, supertokens_user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__userid_mapping_supertokens_user_id_index ON st__userid_mapping (app_id, supertokens_user_id); + +-- UserRoles + +ALTER TABLE st__roles + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__roles + DROP CONSTRAINT st__roles_pkey CASCADE; + +ALTER TABLE st__roles + ADD CONSTRAINT st__roles_pkey + PRIMARY KEY (app_id, role); + +ALTER TABLE st__roles + DROP CONSTRAINT IF EXISTS st__roles_app_id_fkey; + +ALTER TABLE st__roles + ADD CONSTRAINT st__roles_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__roles_app_id_index ON st__roles (app_id); + +------------------------------------------------------------ + +ALTER TABLE st__role_permissions + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__role_permissions + DROP CONSTRAINT st__role_permissions_pkey CASCADE; + +ALTER TABLE st__role_permissions + ADD CONSTRAINT st__role_permissions_pkey + PRIMARY KEY (app_id, role, permission); + +ALTER TABLE st__role_permissions + DROP CONSTRAINT IF EXISTS st__role_permissions_role_fkey; + +ALTER TABLE st__role_permissions + ADD CONSTRAINT st__role_permissions_role_fkey + FOREIGN KEY (app_id, role) + REFERENCES st__roles (app_id, role) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__role_permissions_permission_index; + +CREATE INDEX IF NOT EXISTS st__role_permissions_permission_index ON st__role_permissions (app_id, permission); + +CREATE INDEX IF NOT EXISTS st__role_permissions_role_index ON st__role_permissions (app_id, role); + +------------------------------------------------------------ + +ALTER TABLE st__user_roles + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__user_roles + DROP CONSTRAINT st__user_roles_pkey CASCADE; + +ALTER TABLE st__user_roles + ADD CONSTRAINT st__user_roles_pkey + PRIMARY KEY (app_id, tenant_id, user_id, role); + +ALTER TABLE st__user_roles + DROP CONSTRAINT IF EXISTS st__user_roles_tenant_id_fkey; + +ALTER TABLE st__user_roles + ADD CONSTRAINT st__user_roles_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +ALTER TABLE st__user_roles + DROP CONSTRAINT IF EXISTS st__user_roles_role_fkey; + +ALTER TABLE st__user_roles + ADD CONSTRAINT st__user_roles_role_fkey + FOREIGN KEY (app_id, role) + REFERENCES st__roles (app_id, role) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__user_roles_role_index; + +CREATE INDEX IF NOT EXISTS st__user_roles_role_index ON st__user_roles (app_id, tenant_id, role); + +CREATE INDEX IF NOT EXISTS st__user_roles_tenant_id_index ON st__user_roles (app_id, tenant_id); + +CREATE INDEX IF NOT EXISTS st__user_roles_app_id_role_index ON st__user_roles (app_id, role); + +-- UserMetadata + +ALTER TABLE st__user_metadata + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__user_metadata + DROP CONSTRAINT st__user_metadata_pkey CASCADE; + +ALTER TABLE st__user_metadata + ADD CONSTRAINT st__user_metadata_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__user_metadata + DROP CONSTRAINT IF EXISTS st__user_metadata_app_id_fkey; + +ALTER TABLE st__user_metadata + ADD CONSTRAINT st__user_metadata_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__user_metadata_app_id_index ON st__user_metadata (app_id); + +-- Dashboard + +ALTER TABLE st__dashboard_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__dashboard_users + DROP CONSTRAINT st__dashboard_users_pkey CASCADE; + +ALTER TABLE st__dashboard_users + ADD CONSTRAINT st__dashboard_users_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__dashboard_users + DROP CONSTRAINT IF EXISTS st__dashboard_users_email_key; + +ALTER TABLE st__dashboard_users + ADD CONSTRAINT st__dashboard_users_email_key + UNIQUE (app_id, email); + +ALTER TABLE st__dashboard_users + DROP CONSTRAINT IF EXISTS st__dashboard_users_app_id_fkey; + +ALTER TABLE st__dashboard_users + ADD CONSTRAINT st__dashboard_users_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__dashboard_users_app_id_index ON st__dashboard_users (app_id); + +------------------------------------------------------------ + +ALTER TABLE st__dashboard_user_sessions + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__dashboard_user_sessions + DROP CONSTRAINT st__dashboard_user_sessions_pkey CASCADE; + +ALTER TABLE st__dashboard_user_sessions + ADD CONSTRAINT st__dashboard_user_sessions_pkey + PRIMARY KEY (app_id, session_id); + +ALTER TABLE st__dashboard_user_sessions + DROP CONSTRAINT IF EXISTS st__dashboard_user_sessions_user_id_fkey; + +ALTER TABLE st__dashboard_user_sessions + ADD CONSTRAINT st__dashboard_user_sessions_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__dashboard_users (app_id, user_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__dashboard_user_sessions_user_id_index ON st__dashboard_user_sessions (app_id, user_id); + +-- TOTP + +ALTER TABLE st__totp_users + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__totp_users + DROP CONSTRAINT st__totp_users_pkey CASCADE; + +ALTER TABLE st__totp_users + ADD CONSTRAINT st__totp_users_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__totp_users + DROP CONSTRAINT IF EXISTS st__totp_users_app_id_fkey; + +ALTER TABLE st__totp_users + ADD CONSTRAINT st__totp_users_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__totp_users_app_id_index ON st__totp_users (app_id); + +------------------------------------------------------------ + +ALTER TABLE st__totp_user_devices + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__totp_user_devices + DROP CONSTRAINT st__totp_user_devices_pkey; + +ALTER TABLE st__totp_user_devices + ADD CONSTRAINT st__totp_user_devices_pkey + PRIMARY KEY (app_id, user_id, device_name); + +ALTER TABLE st__totp_user_devices + DROP CONSTRAINT IF EXISTS st__totp_user_devices_user_id_fkey; + +ALTER TABLE st__totp_user_devices + ADD CONSTRAINT st__totp_user_devices_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__totp_users (app_id, user_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__totp_user_devices_user_id_index ON st__totp_user_devices (app_id, user_id); + +------------------------------------------------------------ + +ALTER TABLE st__totp_used_codes + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public', + ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__totp_used_codes + DROP CONSTRAINT st__totp_used_codes_pkey CASCADE; + +ALTER TABLE st__totp_used_codes + ADD CONSTRAINT st__totp_used_codes_pkey + PRIMARY KEY (app_id, tenant_id, user_id, created_time_ms); + +ALTER TABLE st__totp_used_codes + DROP CONSTRAINT IF EXISTS st__totp_used_codes_user_id_fkey; + +ALTER TABLE st__totp_used_codes + ADD CONSTRAINT st__totp_used_codes_user_id_fkey + FOREIGN KEY (app_id, user_id) + REFERENCES st__totp_users (app_id, user_id) ON DELETE CASCADE; + +ALTER TABLE st__totp_used_codes + DROP CONSTRAINT IF EXISTS st__totp_used_codes_tenant_id_fkey; + +ALTER TABLE st__totp_used_codes + ADD CONSTRAINT st__totp_used_codes_tenant_id_fkey + FOREIGN KEY (app_id, tenant_id) + REFERENCES st__tenants (app_id, tenant_id) ON DELETE CASCADE; + +DROP INDEX IF EXISTS st__totp_used_codes_expiry_time_ms_index; + +CREATE INDEX IF NOT EXISTS st__totp_used_codes_expiry_time_ms_index ON st__totp_used_codes (app_id, tenant_id, expiry_time_ms); + +CREATE INDEX IF NOT EXISTS st__totp_used_codes_user_id_index ON st__totp_used_codes (app_id, user_id); + +CREATE INDEX IF NOT EXISTS st__totp_used_codes_tenant_id_index ON st__totp_used_codes (app_id, tenant_id); + +-- ActiveUsers + +ALTER TABLE st__user_last_active + ADD COLUMN IF NOT EXISTS app_id VARCHAR(64) DEFAULT 'public'; + +ALTER TABLE st__user_last_active + DROP CONSTRAINT st__user_last_active_pkey CASCADE; + +ALTER TABLE st__user_last_active + ADD CONSTRAINT st__user_last_active_pkey + PRIMARY KEY (app_id, user_id); + +ALTER TABLE st__user_last_active + DROP CONSTRAINT IF EXISTS st__user_last_active_app_id_fkey; + +ALTER TABLE st__user_last_active + ADD CONSTRAINT st__user_last_active_app_id_fkey + FOREIGN KEY (app_id) + REFERENCES st__apps (app_id) ON DELETE CASCADE; + +CREATE INDEX IF NOT EXISTS st__user_last_active_app_id_index ON st__user_last_active (app_id); diff --git a/packages/user/src/migrations/supertokens-core-v7.sql b/packages/user/src/migrations/supertokens-core-v7.sql new file mode 100644 index 000000000..d511387bb --- /dev/null +++ b/packages/user/src/migrations/supertokens-core-v7.sql @@ -0,0 +1,72 @@ +ALTER TABLE st__all_auth_recipe_users + ADD COLUMN IF NOT EXISTS primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + +ALTER TABLE st__all_auth_recipe_users + ADD COLUMN IF NOT EXISTS is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + +ALTER TABLE st__all_auth_recipe_users + ADD COLUMN IF NOT EXISTS primary_or_recipe_user_time_joined BIGINT NOT NULL DEFAULT 0; + +UPDATE st__all_auth_recipe_users + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + +UPDATE st__all_auth_recipe_users + SET primary_or_recipe_user_time_joined = time_joined + WHERE primary_or_recipe_user_time_joined = 0; + +ALTER TABLE st__all_auth_recipe_users + DROP CONSTRAINT IF EXISTS st__all_auth_recipe_users_primary_or_recipe_user_id_fkey; + +ALTER TABLE st__all_auth_recipe_users + ADD CONSTRAINT st__all_auth_recipe_users_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +ALTER TABLE st__all_auth_recipe_users + ALTER primary_or_recipe_user_id DROP DEFAULT; + +ALTER TABLE st__app_id_to_user_id + ADD COLUMN IF NOT EXISTS primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + +ALTER TABLE st__app_id_to_user_id + ADD COLUMN IF NOT EXISTS is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE st__app_id_to_user_id + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + +ALTER TABLE st__app_id_to_user_id + DROP CONSTRAINT IF EXISTS st__app_id_to_user_id_primary_or_recipe_user_id_fkey; + +ALTER TABLE st__app_id_to_user_id + ADD CONSTRAINT st__app_id_to_user_id_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +ALTER TABLE st__app_id_to_user_id + ALTER primary_or_recipe_user_id DROP DEFAULT; + +DROP INDEX IF EXISTS st__all_auth_recipe_users_pagination_index; + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_pagination_index1 ON st__all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_pagination_index2 ON st__all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_pagination_index3 ON st__all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_pagination_index4 ON st__all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_primary_user_id_index ON st__all_auth_recipe_users (primary_or_recipe_user_id, app_id); + +CREATE INDEX IF NOT EXISTS st__all_auth_recipe_users_recipe_id_index ON st__all_auth_recipe_users (app_id, recipe_id, tenant_id); + +ALTER TABLE st__emailpassword_pswd_reset_tokens DROP CONSTRAINT IF EXISTS st__emailpassword_pswd_reset_tokens_user_id_fkey; + +ALTER TABLE st__emailpassword_pswd_reset_tokens ADD CONSTRAINT st__emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES st__app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +ALTER TABLE st__emailpassword_pswd_reset_tokens ADD COLUMN IF NOT EXISTS email VARCHAR(256); diff --git a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts index a3ebbabc1..4c392396a 100644 --- a/packages/user/src/supertokens/recipes/config/session/createNewSession.ts +++ b/packages/user/src/supertokens/recipes/config/session/createNewSession.ts @@ -20,12 +20,14 @@ const createNewSession = ( | FastifyRequest | undefined; + let user: FastifyRequest["user"] | undefined; + if (request && !request.user) { const { config, dbSchema, slonik } = request; const userService = getUserService(config, slonik, dbSchema); - const user = (await userService.findById(input.userId)) || undefined; + user = (await userService.findById(input.userId)) || undefined; if (user?.deletedAt) { throw fastify.httpErrors.unauthorized("User not found"); @@ -36,14 +38,51 @@ const createNewSession = ( } request.user = user; + } else if (!request) { + // supertokens-node v15 multitenancy internal flow + // may not set request in userContext. Fetch user and inject + // a request-like object so ProfileValidationClaim.fetchValue works. + const { config, slonik } = fastify; + + const userService = getUserService(config, slonik); + + user = (await userService.findById(input.userId)) || undefined; + + if (user?.deletedAt) { + throw fastify.httpErrors.unauthorized("User not found"); + } + + if (user?.disabled) { + throw fastify.httpErrors.unauthorized("User is disabled"); + } + + input.userContext._default = input.userContext._default || {}; + input.userContext._default.request = { + original: { + config, + user, + }, + }; } const session = await originalImplementation.createNewSession(input); - if ( - request?.user && - request?.config.user.features?.profileValidation?.enabled - ) { + // re-inject in case v15 cleared _default during createNewSession + if (!request && user) { + input.userContext._default = input.userContext._default || {}; + input.userContext._default.request = { + original: { + config: fastify.config, + user, + }, + }; + } + + const profileValidationEnabled = request + ? request.config.user.features?.profileValidation?.enabled + : fastify.config.user?.features?.profileValidation?.enabled; + + if ((request?.user ?? user) && profileValidationEnabled) { await session.fetchAndSetClaim( new ProfileValidationClaim(), input.userContext, diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/appleRedirectHandlerPost.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/appleRedirectHandlerPost.ts index 6a28df908..cc717aa94 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/appleRedirectHandlerPost.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/appleRedirectHandlerPost.ts @@ -11,14 +11,14 @@ const appleRedirectHandlerPOST = ( throw new Error("Should never come here"); } - const stateInBase64 = input.state; + const { code, state: stateInBase64 } = input.formPostInfoFromProvider; const state = JSON.parse( Buffer.from(stateInBase64, "base64").toString("ascii"), ); if (state.isAndroid && state.appId) { - const queryString = `code=${input.code}&state=${input.state}`; + const queryString = `code=${code}&state=${stateInBase64}`; const redirectUrl = `intent://callback?${queryString}#Intent;package=${state.appId};scheme=signinwithapple;end`; diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts index e87dac20d..ca4a69449 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/emailPasswordSignUp.ts @@ -8,6 +8,7 @@ import UserRoles from "supertokens-node/recipe/userroles"; import type { User } from "../../../../types"; +import { SUPERTOKENS_DEFAULT_TENANT_ID } from "../../../../constants"; import getUserService from "../../../../lib/getUserService"; import sendEmail from "../../../../lib/sendEmail"; import verifyEmail from "../../../../lib/verifyEmail"; @@ -61,6 +62,7 @@ const emailPasswordSignUp = ( for (const role of roles) { const rolesResponse = await UserRoles.addRoleToUser( + SUPERTOKENS_DEFAULT_TENANT_ID, originalResponse.user.id, role, ); @@ -79,6 +81,7 @@ const emailPasswordSignUp = ( // send email verification const tokenResponse = await EmailVerification.createEmailVerificationToken( + SUPERTOKENS_DEFAULT_TENANT_ID, originalResponse.user.id, ); @@ -87,6 +90,7 @@ const emailPasswordSignUp = ( // emailVerifyLink is same as what would supertokens create. await EmailVerification.sendEmail({ emailVerifyLink: `${config.appOrigin[0]}/auth/verify-email?token=${tokenResponse.token}&rid=emailverification`, + tenantId: SUPERTOKENS_DEFAULT_TENANT_ID, type: "EMAIL_VERIFICATION", user: originalResponse.user, userContext: input.userContext, diff --git a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts index 55bd2836c..da8bcc603 100644 --- a/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts +++ b/packages/user/src/supertokens/recipes/config/third-party-email-password/thirdPartySignInUp.ts @@ -9,6 +9,7 @@ import UserRoles from "supertokens-node/recipe/userroles"; import type { User } from "../../../../types"; +import { SUPERTOKENS_DEFAULT_TENANT_ID } from "../../../../constants"; import getUserService from "../../../../lib/getUserService"; import areRolesExist from "../../../utils/areRolesExist"; @@ -22,6 +23,7 @@ const thirdPartySignInUp = ( const roles = (input.userContext.roles || []) as string[]; const thirdPartyUser = await getUserByThirdPartyInfo( + SUPERTOKENS_DEFAULT_TENANT_ID, input.thirdPartyId, input.thirdPartyUserId, input.userContext, @@ -52,6 +54,7 @@ const thirdPartySignInUp = ( for (const role of roles) { const rolesResponse = await UserRoles.addRoleToUser( + SUPERTOKENS_DEFAULT_TENANT_ID, originalResponse.user.id, role, ); diff --git a/packages/user/src/supertokens/recipes/config/thirdPartyProviders.ts b/packages/user/src/supertokens/recipes/config/thirdPartyProviders.ts index e6e53cd3a..ab64cb2c1 100644 --- a/packages/user/src/supertokens/recipes/config/thirdPartyProviders.ts +++ b/packages/user/src/supertokens/recipes/config/thirdPartyProviders.ts @@ -1,35 +1,88 @@ import type { ApiConfig } from "@prefabs.tech/fastify-config"; +import type { ProviderClientConfig } from "supertokens-node/lib/build/recipe/thirdparty/types"; import type { TypeProvider } from "supertokens-node/recipe/thirdpartyemailpassword"; -import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword"; +import { + Apple, + Facebook, + Github, + Google, +} from "supertokens-node/lib/build/recipe/thirdparty/providers"; + +interface AppleSingleProviderConfig { + clientId: string; + clientSecret: { + keyId: string; + privateKey: string; + teamId: string; + }; + isDefault?: boolean; +} + +interface NonAppleProviderConfig { + clientId: string; + clientSecret: string; +} const getThirdPartyProviders = (config: ApiConfig) => { - const { Apple, Facebook, Github, Google } = ThirdPartyEmailPassword; const providersConfig = config.user.supertokens!.providers; const providers: TypeProvider[] = []; const providerFunctions = [ - { initProvider: Google, name: "google" }, - { initProvider: Github, name: "github" }, - { initProvider: Facebook, name: "facebook" }, - { initProvider: Apple, name: "apple" }, + { initProvider: Google, name: "google" as const }, + { initProvider: Github, name: "github" as const }, + { initProvider: Facebook, name: "facebook" as const }, + { initProvider: Apple, name: "apple" as const }, ]; for (const provider of providerFunctions) { - if (providersConfig?.[provider.name as never]) { - if (provider.name === "apple") { - const appleProviderConfigs = providersConfig[provider.name]; - - if (appleProviderConfigs) { - for (const appleProviderConfig of appleProviderConfigs) { - providers.push(provider.initProvider(appleProviderConfig as never)); - } + if (provider.name === "apple") { + const appleProviderConfigs = providersConfig?.apple as + | AppleSingleProviderConfig[] + | undefined; + + if (appleProviderConfigs && appleProviderConfigs.length > 0) { + const clients: (ProviderClientConfig & { isDefault?: boolean })[] = []; + + for (const cfg of appleProviderConfigs) { + clients.push({ + additionalConfig: { ...cfg.clientSecret }, + clientId: cfg.clientId, + isDefault: cfg.isDefault, + }); } - } else { + + providers.push( + Apple({ + config: { + clients, + thirdPartyId: "apple", + }, + }), + ); + } + } else if ( + provider.name === "google" || + provider.name === "github" || + provider.name === "facebook" + ) { + const cfg = providersConfig?.[provider.name] as + | NonAppleProviderConfig + | undefined; + + if (cfg && cfg.clientId) { providers.push( - provider.initProvider( - providersConfig[provider.name as never] as never, - ), + provider.initProvider({ + config: { + clients: [ + { + clientId: cfg.clientId, + clientSecret: cfg.clientSecret, + }, + ], + thirdPartyId: provider.name, + }, + }), ); } } diff --git a/packages/user/src/supertokens/types/index.ts b/packages/user/src/supertokens/types/index.ts index a7df5c2ee..aef7a8441 100644 --- a/packages/user/src/supertokens/types/index.ts +++ b/packages/user/src/supertokens/types/index.ts @@ -10,7 +10,7 @@ import { Facebook, Github, Google, -} from "supertokens-node/recipe/thirdpartyemailpassword"; +} from "supertokens-node/lib/build/recipe/thirdparty/providers"; import type { EmailVerificationRecipe } from "./emailVerificationRecipe"; import type { SessionRecipe } from "./sessionRecipe"; diff --git a/packages/user/src/supertokens/utils/profileValidationClaim.ts b/packages/user/src/supertokens/utils/profileValidationClaim.ts index a81279203..040948121 100644 --- a/packages/user/src/supertokens/utils/profileValidationClaim.ts +++ b/packages/user/src/supertokens/utils/profileValidationClaim.ts @@ -86,13 +86,23 @@ class ProfileValidationClaim extends SessionClaim { }; } - fetchValue = async (userId: string, userContext: any): Promise => { + // supertokens-node v15 SessionClaim.build now calls + // fetchValue with 3 args: (userId, tenantId, userContext). + // The 2nd arg is tenantId (string), and the actual userContext is the 3rd arg. + fetchValue = async ( + userId: string, + _tenantId: string, + userContext: any, + ): Promise => { const request = getRequestFromUserContext(userContext)?.original as | SessionRequest | undefined; if (!request) { - throw new Error("Request not set in userContext"); + // supertokens-node v15 multitenancy internal flow + // may call fetchValue without setting request in userContext. + // Return a safe fallback instead of crashing. + return { isVerified: true }; } const profileValidation = request.config.user?.features diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72ce9811f..ff5bb683f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -594,8 +594,8 @@ importers: specifier: 46.8.0 version: 46.8.0(zod@3.25.76) supertokens-node: - specifier: 14.1.4 - version: 14.1.4 + specifier: 15.0.0 + version: 15.0.0 typescript: specifier: 5.9.3 version: 5.9.3 @@ -2795,6 +2795,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} @@ -4805,6 +4808,9 @@ packages: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true + pkce-challenge@3.1.0: + resolution: {integrity: sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==} + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -5367,6 +5373,9 @@ packages: supertokens-node@14.1.4: resolution: {integrity: sha512-Hf/z4r1W9Pk6rlRp7Pa9ibgsqnIy0tc4kzZk72gZ/fHArNKb+G0BZI3JJ5Vm8TrOZx5+DabLN40xahx2qLwalQ==} + supertokens-node@15.0.0: + resolution: {integrity: sha512-47VW4jmqXugnK+0laEzdSL2RGcHM1wI3hE/znAyHJkg2oZkv8aqCmWaoGLl6RrVB11WOdBTJq+5OJ5JtMe4cBg==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8545,6 +8554,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crypto-js@4.2.0: {} + css-select@5.2.2: dependencies: boolbase: 1.0.0 @@ -11066,6 +11077,10 @@ snapshots: sonic-boom: 3.8.1 thread-stream: 2.7.0 + pkce-challenge@3.1.0: + dependencies: + crypto-js: 4.2.0 + pluralize@8.0.0: {} possible-typed-array-names@1.1.0: {} @@ -11706,6 +11721,25 @@ snapshots: - encoding - supports-color + supertokens-node@15.0.0: + dependencies: + content-type: 1.0.5 + cookie: 0.4.0 + cross-fetch: 3.2.0 + debug: 4.4.3 + inflation: 2.1.0 + jose: 4.15.9 + libphonenumber-js: 1.12.25 + nodemailer: 6.10.1 + pkce-challenge: 3.1.0 + psl: 1.8.0 + raw-body: 2.5.2 + supertokens-js-override: 0.0.4 + twilio: 4.23.0(debug@4.4.3) + transitivePeerDependencies: + - encoding + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0