mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 08:31:37 -05:00
fix(organization): improve type inference and session handling
This commit is contained in:
@@ -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>("");
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> =
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/test/**/*.ts"
|
||||
"**/test/**/*.ts",
|
||||
"**/test-utils/**/*.ts"
|
||||
],
|
||||
"references": [],
|
||||
"include": ["src/**/*"]
|
||||
|
||||
Reference in New Issue
Block a user