mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
- 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
305 lines
12 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|