fix(organization): improve type inference and session handling

This commit is contained in:
Bereket Engida
2025-03-01 02:36:52 +03:00
parent 17d17deafc
commit 0c7a098e95
7 changed files with 110 additions and 85 deletions

View File

@@ -67,7 +67,7 @@ export default function UserCard(props: {
}) {
const router = useRouter();
const { data, isPending } = useSession();
const session = data;
const session = data || props.session;
const [isTerminating, setIsTerminating] = useState<string>();
const [isPendingTwoFa, setIsPendingTwoFa] = useState<boolean>(false);
const [twoFaPassword, setTwoFaPassword] = useState<string>("");

View File

@@ -80,7 +80,7 @@ export const organizationClient = Object.assign(
>,
getActions: ($fetch) => ({
$Infer: {
ActiveOrganization: {} as Prettify<OrganizationReturn>,
ActiveOrganization: {} as OrganizationReturn,
Organization: {} as Organization,
Invitation: {} as InferInvitation<O>,
Member: {} as InferInvitation<O>,

View File

@@ -315,7 +315,7 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
createOrganization,
updateOrganization,
deleteOrganization,
setActiveOrganization,
setActiveOrganization: setActiveOrganization<O>(),
getFullOrganization: getFullOrganization<O>(),
listOrganizations,
createInvitation: createInvitation(options as O),

View File

@@ -497,38 +497,40 @@ export const getFullOrganization = <O extends OrganizationOptions>() =>
},
);
export const setActiveOrganization = createAuthEndpoint(
"/organization/set-active",
{
method: "POST",
body: z.object({
organizationId: z
.string({
description:
"The organization id to set as active. It can be null to unset the active organization",
})
.nullable()
.optional(),
organizationSlug: z
.string({
description:
"The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided",
})
.optional(),
}),
use: [orgSessionMiddleware, orgMiddleware],
metadata: {
openapi: {
description: "Set the active organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The organization",
$ref: "#/components/schemas/Organization",
export const setActiveOrganization = <O extends OrganizationOptions>() => {
return createAuthEndpoint(
"/organization/set-active",
{
method: "POST",
body: z.object({
organizationId: z
.string({
description:
"The organization id to set as active. It can be null to unset the active organization",
})
.nullable()
.optional(),
organizationSlug: z
.string({
description:
"The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided",
})
.optional(),
}),
use: [orgSessionMiddleware, orgMiddleware],
metadata: {
openapi: {
description: "Set the active organization",
responses: {
"200": {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
description: "The organization",
$ref: "#/components/schemas/Organization",
},
},
},
},
@@ -536,63 +538,73 @@ export const setActiveOrganization = createAuthEndpoint(
},
},
},
},
async (ctx) => {
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const session = ctx.context.session;
let organizationId = ctx.body.organizationSlug || ctx.body.organizationId;
if (organizationId === null) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
async (ctx) => {
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
const session = ctx.context.session;
let organizationId = ctx.body.organizationSlug || ctx.body.organizationId;
if (organizationId === null) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
return ctx.json(null);
}
const updatedSession = await adapter.setActiveOrganization(
session.session.token,
null,
);
await setSessionCookie(ctx, {
session: updatedSession,
user: session.user,
});
return ctx.json(null);
}
if (!organizationId) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
return ctx.json(null);
}
organizationId = sessionOrgId;
}
const organization = await adapter.findFullOrganization({
organizationId,
isSlug: !!ctx.body.organizationSlug,
});
const isMember = organization?.members.find(
(member) => member.userId === session.user.id,
);
if (!isMember) {
await adapter.setActiveOrganization(session.session.token, null);
throw new APIError("FORBIDDEN", {
message:
ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
});
}
if (!organization) {
throw new APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
});
}
const updatedSession = await adapter.setActiveOrganization(
session.session.token,
null,
organization.id,
);
await setSessionCookie(ctx, {
session: updatedSession,
user: session.user,
});
return ctx.json(null);
}
if (!organizationId) {
const sessionOrgId = session.session.activeOrganizationId;
if (!sessionOrgId) {
return ctx.json(null);
}
organizationId = sessionOrgId;
}
const organization = await adapter.findFullOrganization({
organizationId,
isSlug: !!ctx.body.organizationSlug,
});
const isMember = organization?.members.find(
(member) => member.userId === session.user.id,
);
if (!isMember) {
await adapter.setActiveOrganization(session.session.token, null);
throw new APIError("FORBIDDEN", {
message:
ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION,
});
}
if (!organization) {
throw new APIError("BAD_REQUEST", {
message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND,
});
}
const updatedSession = await adapter.setActiveOrganization(
session.session.token,
organization.id,
);
await setSessionCookie(ctx, {
session: updatedSession,
user: session.user,
});
return ctx.json(organization);
},
);
type OrganizationReturn = O["teams"] extends { enabled: true }
? {
members: InferMember<O>[];
invitations: InferInvitation<O>[];
teams: Team[];
} & Organization
: {
members: InferMember<O>[];
invitations: InferInvitation<O>[];
} & Organization;
return ctx.json(organization as unknown as OrganizationReturn);
},
);
};
export const listOrganizations = createAuthEndpoint(
"/organization/list",

View File

@@ -78,6 +78,12 @@ export type InferMember<O extends OrganizationOptions> = O["teams"] extends {
organizationId: string;
role: InferRolesFromOption<O>;
createdAt: Date;
userId: string;
user: {
email: string;
name: string;
image?: string;
};
}
: {
id: string;
@@ -85,6 +91,12 @@ export type InferMember<O extends OrganizationOptions> = O["teams"] extends {
createdAt: Date;
role: InferRolesFromOption<O>;
teamId?: string;
userId: string;
user: {
email: string;
name: string;
image?: string;
};
};
export type InferInvitation<O extends OrganizationOptions> =

View File

@@ -15,7 +15,7 @@ import { Pool } from "pg";
import { MongoClient } from "mongodb";
import { mongodbAdapter } from "../adapters/mongodb-adapter";
import { createPool } from "mysql2/promise";
import { bearer } from "../../src/plugins";
import { bearer } from "../plugins";
export async function getTestInstance<
O extends Partial<BetterAuthOptions>,

View File

@@ -23,7 +23,8 @@
"dist",
"**/*.test.ts",
"**/*.spec.ts",
"**/test/**/*.ts"
"**/test/**/*.ts",
"**/test-utils/**/*.ts"
],
"references": [],
"include": ["src/**/*"]