fix: resolve auth/tenant loop and serialization errors

- middleware: remove auth-path→/dashboard redirect; stale session cookies
  caused dashboard→onboarding→sign-in→dashboard infinite loop
- dashboard layout: check getCurrentUser first, redirect to /sign-in
  directly instead of going through /onboarding
- getActiveContext: use admin client (users.listMemberships) for tenant
  resolution instead of session-dependent getUserTeams()
- requireTenant: validate membership before trusting stored tenantId;
  clear stale cookie and re-resolve if user is not a member
- sunum page: JSON.parse/stringify property rows before passing to
  Client Component (Appwrite SDK objects have non-plain prototypes)
This commit is contained in:
egecankomur
2026-05-12 17:18:19 +03:00
parent a3bcb464ea
commit fe86bfe6b2
5 changed files with 34 additions and 13 deletions
+16 -5
View File
@@ -6,7 +6,7 @@ import { Query } from "node-appwrite";
import { createAdminClient, getCurrentUser } from "./server";
import { DATABASE_ID, TABLES, type TenantSettings } from "./schema";
import { ACTIVE_TENANT_COOKIE } from "./tenant-types";
import { getActiveTenantId, getUserTeams } from "./tenant";
import { getActiveTenantId } from "./tenant";
export type ActiveContext = {
user: { id: string; name: string; email: string };
@@ -30,7 +30,7 @@ export async function getActiveContext(): Promise<ActiveContext | null> {
const user = await getCurrentUser();
if (!user) return null;
const { teams: adminTeams, tablesDB } = createAdminClient();
const { teams: adminTeams, users: adminUsers, tablesDB } = createAdminClient();
let tenantId = await getActiveTenantId();
@@ -52,9 +52,20 @@ export async function getActiveContext(): Promise<ActiveContext | null> {
}
if (!tenantId) {
const userTeams = await getUserTeams();
tenantId = userTeams?.teams[0]?.$id ?? null;
// Persist so the next request skips this resolution path.
try {
const memberships = await adminUsers.listMemberships(user.$id);
if (memberships.total > 0) {
const teamIds = memberships.memberships.map((m) => m.teamId);
const settings = await tablesDB.listRows({
databaseId: DATABASE_ID,
tableId: TABLES.tenantSettings,
queries: [Query.equal("tenantId", teamIds), Query.limit(1)],
});
tenantId = (settings.rows[0] as unknown as { tenantId: string })?.tenantId ?? teamIds[0] ?? null;
}
} catch {
// admin client failure is a server config issue — fall through to null
}
if (tenantId) await setActiveTenantCookie(tenantId);
}
+8 -3
View File
@@ -93,12 +93,17 @@ export async function requireTenant(): Promise<TenantContext> {
const { tablesDB, teams } = createAdminClient();
// If we have a tenantId from cookie/prefs, verify the team still exists.
// Validate stored tenantId: team must exist AND user must be a member.
if (tenantId) {
try {
await teams.get(tenantId);
const memberships = await teams.listMemberships(tenantId);
const isMember = memberships.memberships.some((m) => m.userId === user.$id);
if (!isMember) {
try { (await cookies()).delete(ACTIVE_TENANT_COOKIE); } catch { /* ignore */ }
tenantId = null;
}
} catch {
// Team was deleted — clear stale pointer and fall through to resolution.
// Team deleted or inaccessible — clear stale pointer.
try { (await cookies()).delete(ACTIVE_TENANT_COOKIE); } catch { /* ignore */ }
tenantId = null;
}