stack/apps/e2e/tests/js/restricted-users.test.ts
Konsti Wohlwend 1618f89c46
Onboarding app & restricted users (#1069)
- restricted users
- onboarding app
- waitlist app
- fixed an exception when setting primary email
- automatically update the JWT token on the client when the user object
changes
2026-01-11 17:22:14 -08:00

305 lines
12 KiB
TypeScript

import { describe } from "vitest";
import { it } from "../helpers";
import { createApp } from "./js-helpers";
describe("restricted user SDK filtering", () => {
describe("getUser with includeRestricted option", () => {
it("should return null for restricted user by default", async ({ expect }) => {
const { clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// By default, getUser should return null for restricted users
const user = await clientApp.getUser();
expect(user).toBeNull();
});
it("should return restricted user when includeRestricted is true", async ({ expect }) => {
const { clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// With includeRestricted: true, should return the restricted user
const user = await clientApp.getUser({ includeRestricted: true });
expect(user).not.toBeNull();
expect(user!.isRestricted).toBe(true);
expect(user!.restrictedReason).toEqual({ type: "email_not_verified" });
});
it("should return non-restricted user without includeRestricted option", async ({ expect }) => {
const { clientApp, adminApp, serverApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-verified@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// User should be restricted at first
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
expect(restrictedUser!.isRestricted).toBe(true);
// Verify the email using server SDK
const serverUsers = await serverApp.listUsers({ query: "test-verified@example.com", includeRestricted: true });
expect(serverUsers.length).toBeGreaterThan(0);
const serverUser = serverUsers[0];
const serverContactChannels = await serverUser.listContactChannels();
const serverEmailChannel = serverContactChannels.find(c => c.value === "test-verified@example.com");
if (serverEmailChannel) {
await serverEmailChannel.update({ isVerified: true });
}
// Now user should be returned without includeRestricted since they're verified
const user = await clientApp.getUser();
expect(user).not.toBeNull();
expect(user!.isRestricted).toBe(false);
expect(user!.primaryEmailVerified).toBe(true);
});
it("should throw error when or: anonymous is combined with includeRestricted: false", async ({ expect }) => {
const { clientApp } = await createApp({});
await expect(
clientApp.getUser({ or: "anonymous", includeRestricted: false })
).rejects.toThrow("Cannot use { or: 'anonymous' } with { includeRestricted: false }");
});
it("should return the same restricted user when or: anonymous is used (without explicit includeRestricted)", async ({ expect }) => {
const { clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// Get the restricted user with includeRestricted to capture its ID
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
expect(restrictedUser!.isRestricted).toBe(true);
const restrictedUserId = restrictedUser!.id;
// With or: "anonymous", should return the SAME restricted user (not create a new anonymous user)
const userWithAnonymousFallback = await clientApp.getUser({ or: "anonymous" });
expect(userWithAnonymousFallback).not.toBeNull();
expect(userWithAnonymousFallback!.id).toBe(restrictedUserId);
expect(userWithAnonymousFallback!.isRestricted).toBe(true);
expect(userWithAnonymousFallback!.isAnonymous).toBe(false);
});
});
describe("server app getUser with includeRestricted option", () => {
it("should return null for restricted user by default on server app", async ({ expect }) => {
const { serverApp, clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// Get the tokens from the restricted user (must use includeRestricted to get the user first)
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
const authJson = await restrictedUser!.getAuthJson();
// By default, getUser should return null for restricted users
const user = await serverApp.getUser({ tokenStore: authJson as any });
expect(user).toBeNull();
});
it("should return restricted user when includeRestricted is true on server app", async ({ expect }) => {
const { serverApp, clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// Get the tokens from the restricted user (must use includeRestricted to get the user first)
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
const authJson = await restrictedUser!.getAuthJson();
// With includeRestricted: true, should return the restricted user
const user = await serverApp.getUser({ tokenStore: authJson as any, includeRestricted: true });
expect(user).not.toBeNull();
expect(user!.isRestricted).toBe(true);
});
it("should return the same restricted user when or: anonymous is used on server app", async ({ expect }) => {
const { serverApp, clientApp, adminApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// Get the tokens from the restricted user
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
const authJson = await restrictedUser!.getAuthJson();
const restrictedUserId = restrictedUser!.id;
// With or: "anonymous", should return the SAME restricted user (not create a new anonymous user)
const userWithAnonymousFallback = await serverApp.getUser({ tokenStore: authJson as any, or: "anonymous" });
expect(userWithAnonymousFallback).not.toBeNull();
expect(userWithAnonymousFallback!.id).toBe(restrictedUserId);
expect(userWithAnonymousFallback!.isRestricted).toBe(true);
expect(userWithAnonymousFallback!.isAnonymous).toBe(false);
});
it("should throw error when or: anonymous is combined with includeRestricted: false on server app", async ({ expect }) => {
const { serverApp } = await createApp({});
await expect(
serverApp.getUser({ or: "anonymous", includeRestricted: false })
).rejects.toThrow("Cannot use { or: 'anonymous' } with { includeRestricted: false }");
});
});
describe("transition from restricted to non-restricted", () => {
it("should return user after email verification even without includeRestricted", async ({ expect }) => {
const { clientApp, adminApp, serverApp } = await createApp({
config: {
credentialEnabled: true,
},
});
// Enable email verification requirement
const project = await adminApp.getProject();
await project.updateConfig({
"onboarding.requireEmailVerification": true,
});
// Sign up a user (email won't be verified automatically)
await clientApp.signUpWithCredential({
email: "test-restricted@example.com",
password: "password123",
verificationCallbackUrl: "http://localhost:3000",
});
// By default, getUser should return null for restricted users
let user = await clientApp.getUser();
expect(user).toBeNull();
// Get the restricted user with includeRestricted
const restrictedUser = await clientApp.getUser({ includeRestricted: true });
expect(restrictedUser).not.toBeNull();
expect(restrictedUser!.isRestricted).toBe(true);
// Verify the email (using internal API)
const contactChannels = await restrictedUser!.listContactChannels();
const emailChannel = contactChannels.find(c => c.value === "test-restricted@example.com");
expect(emailChannel).toBeDefined();
// Use server API to verify the email
const serverUsers = await serverApp.listUsers({ query: "test-restricted@example.com", includeRestricted: true });
expect(serverUsers.length).toBeGreaterThan(0);
// Update primary email to be verified (simulating verification)
const serverUser = serverUsers[0];
const serverContactChannels = await serverUser.listContactChannels();
const serverEmailChannel = serverContactChannels.find(c => c.value === "test-restricted@example.com");
if (serverEmailChannel) {
await serverEmailChannel.update({ isVerified: true });
}
// Now user should be returned without includeRestricted
user = await clientApp.getUser();
expect(user).not.toBeNull();
expect(user!.isRestricted).toBe(false);
});
});
});