mirror of
https://github.com/better-auth/better-auth.git
synced 2026-10-20 20:26:55 -05:00
fix(organization): filter returned: false fields from API responses (#7531)
Co-authored-by: Alex Yang <himself65@outlook.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
committed by
Alex Yang
co-authored by
Alex Yang
cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
parent
27af866f26
commit
57f1e212eb
@@ -147,7 +147,7 @@ class ConnectionMutex {
|
||||
#resolve?: () => void;
|
||||
|
||||
async lock(): Promise<void> {
|
||||
while (this.#promise) {
|
||||
while (await this.#promise) {
|
||||
await this.#promise;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ class ConnectionMutex {
|
||||
#resolve?: () => void;
|
||||
|
||||
async lock(): Promise<void> {
|
||||
while (this.#promise) {
|
||||
while (await this.#promise) {
|
||||
await this.#promise;
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,9 @@ type AddOptionalFields<
|
||||
* ```ts
|
||||
* type AdditionalFields = InferAdditionalFieldsFromPluginOptions<"organization", OrganizationOptions>
|
||||
* ```
|
||||
*
|
||||
* @param isClientSide - When `true` (default), filters out `input: false` fields (clients can't send these).
|
||||
* When `false`, includes all fields (for internal/server-side use).
|
||||
*/
|
||||
export type InferAdditionalFieldsFromPluginOptions<
|
||||
SchemaName extends string,
|
||||
@@ -174,6 +177,12 @@ type RemoveFieldsWithInputFalse<T extends Record<string, DBFieldAttribute>> = {
|
||||
[K in keyof T as T[K]["input"] extends false ? never : K]: T[K];
|
||||
};
|
||||
|
||||
export type RemoveFieldsWithReturnedFalse<
|
||||
T extends Record<string, DBFieldAttribute>,
|
||||
> = {
|
||||
[K in keyof T as T[K]["returned"] extends false ? never : K]: T[K];
|
||||
};
|
||||
|
||||
type InferFieldInput<T extends DBFieldAttribute> = InferValueType<T["type"]>;
|
||||
|
||||
export type PluginFieldAttribute = Omit<
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AuthContext, GenericEndpointContext } from "@better-auth/core";
|
||||
import { getCurrentAdapter } from "@better-auth/core/context";
|
||||
import type { DBFieldAttribute } from "@better-auth/core/db";
|
||||
import { BetterAuthError } from "@better-auth/core/error";
|
||||
import { parseJSON } from "../../client/parser";
|
||||
import type { InferAdditionalFieldsFromPluginOptions } from "../../db";
|
||||
@@ -20,13 +21,39 @@ import type {
|
||||
} from "./schema";
|
||||
import type { OrganizationOptions } from "./types";
|
||||
|
||||
/**
|
||||
* Filters output data by removing fields with `returned: false` attribute.
|
||||
* This ensures sensitive fields are not exposed in API responses.
|
||||
*/
|
||||
function filterOutputFields<T extends Record<string, unknown> | null>(
|
||||
data: T,
|
||||
additionalFields: Record<string, DBFieldAttribute> | undefined,
|
||||
): T {
|
||||
if (!data || !additionalFields) {
|
||||
return data;
|
||||
}
|
||||
const returnFiltered = Object.entries(additionalFields)
|
||||
.filter(([, { returned }]) => returned === false)
|
||||
.map(([key]) => key);
|
||||
return Object.entries(structuredClone(data))
|
||||
.filter(([key]) => !returnFiltered.includes(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {} as T);
|
||||
}
|
||||
|
||||
export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
context: AuthContext,
|
||||
options?: O | undefined,
|
||||
) => {
|
||||
const baseAdapter = context.adapter;
|
||||
const orgAdditionalFields = options?.schema?.organization?.additionalFields;
|
||||
const memberAdditionalFields = options?.schema?.member?.additionalFields;
|
||||
const invitationAdditionalFields =
|
||||
options?.schema?.invitation?.additionalFields;
|
||||
const teamAdditionalFields = options?.schema?.team?.additionalFields;
|
||||
return {
|
||||
findOrganizationBySlug: async (slug: string) => {
|
||||
findOrganizationBySlug: async (
|
||||
slug: string,
|
||||
): Promise<InferOrganization<O> | null> => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
const organization = await adapter.findOne<InferOrganization<O, false>>({
|
||||
model: "organization",
|
||||
@@ -37,13 +64,16 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
},
|
||||
],
|
||||
});
|
||||
return organization;
|
||||
return filterOutputFields(
|
||||
organization,
|
||||
orgAdditionalFields,
|
||||
) as InferOrganization<O> | null;
|
||||
},
|
||||
createOrganization: async (data: {
|
||||
organization: OrganizationInput &
|
||||
// This represents the additional fields from the plugin options
|
||||
Record<string, any>;
|
||||
}) => {
|
||||
}): Promise<InferOrganization<O>> => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
const organization = await adapter.create<
|
||||
OrganizationInput,
|
||||
@@ -59,13 +89,17 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
forceAllowId: true,
|
||||
});
|
||||
|
||||
return {
|
||||
const result = {
|
||||
...organization,
|
||||
metadata:
|
||||
organization.metadata && typeof organization.metadata === "string"
|
||||
? JSON.parse(organization.metadata)
|
||||
: undefined,
|
||||
} as typeof organization;
|
||||
};
|
||||
return filterOutputFields(
|
||||
result,
|
||||
orgAdditionalFields,
|
||||
) as InferOrganization<O>;
|
||||
},
|
||||
findMemberByEmail: async (data: {
|
||||
email: string;
|
||||
@@ -353,7 +387,7 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
updateOrganization: async (
|
||||
organizationId: string,
|
||||
data: Partial<OrganizationInput>,
|
||||
) => {
|
||||
): Promise<InferOrganization<O> | null> => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
const organization = await adapter.update<InferOrganization<O, false>>({
|
||||
model: "organization",
|
||||
@@ -374,12 +408,16 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
if (!organization) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
const result = {
|
||||
...organization,
|
||||
metadata: organization.metadata
|
||||
? parseJSON<Record<string, any>>(organization.metadata)
|
||||
: undefined,
|
||||
};
|
||||
return filterOutputFields(
|
||||
result,
|
||||
orgAdditionalFields,
|
||||
) as InferOrganization<O>;
|
||||
},
|
||||
deleteOrganization: async (organizationId: string) => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
@@ -425,7 +463,9 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
);
|
||||
return session as Session;
|
||||
},
|
||||
findOrganizationById: async (organizationId: string) => {
|
||||
findOrganizationById: async (
|
||||
organizationId: string,
|
||||
): Promise<InferOrganization<O> | null> => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
const organization = await adapter.findOne<InferOrganization<O, false>>({
|
||||
model: "organization",
|
||||
@@ -436,7 +476,10 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
},
|
||||
],
|
||||
});
|
||||
return organization;
|
||||
return filterOutputFields(
|
||||
organization,
|
||||
orgAdditionalFields,
|
||||
) as InferOrganization<O> | null;
|
||||
},
|
||||
checkMembership: async ({
|
||||
userId,
|
||||
@@ -519,8 +562,12 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
"Unexpected error: User not found for member",
|
||||
);
|
||||
}
|
||||
const filteredMember = filterOutputFields(
|
||||
member,
|
||||
memberAdditionalFields,
|
||||
);
|
||||
return {
|
||||
...member,
|
||||
...filteredMember,
|
||||
user: {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
@@ -530,14 +577,24 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
};
|
||||
});
|
||||
|
||||
const filteredOrg = filterOutputFields(org, orgAdditionalFields);
|
||||
const filteredInvitations = invitations.map((inv) =>
|
||||
filterOutputFields(inv, invitationAdditionalFields),
|
||||
);
|
||||
const filteredTeams = teams?.map((team) =>
|
||||
filterOutputFields(team, teamAdditionalFields),
|
||||
);
|
||||
|
||||
return {
|
||||
...org,
|
||||
invitations,
|
||||
...filteredOrg,
|
||||
invitations: filteredInvitations,
|
||||
members: membersWithUsers,
|
||||
teams,
|
||||
teams: filteredTeams,
|
||||
};
|
||||
},
|
||||
listOrganizations: async (userId: string) => {
|
||||
listOrganizations: async (
|
||||
userId: string,
|
||||
): Promise<InferOrganization<O>[]> => {
|
||||
const adapter = await getCurrentAdapter(baseAdapter);
|
||||
const result = await adapter.findMany<
|
||||
InferMember<O, false> & { organization: InferOrganization<O, false> }
|
||||
@@ -558,7 +615,13 @@ export const getOrgAdapter = <O extends OrganizationOptions>(
|
||||
return [];
|
||||
}
|
||||
|
||||
const organizations = result.map((member) => member.organization);
|
||||
const organizations = result.map(
|
||||
(member) =>
|
||||
filterOutputFields(
|
||||
member.organization,
|
||||
orgAdditionalFields,
|
||||
) as InferOrganization<O>,
|
||||
);
|
||||
|
||||
return organizations;
|
||||
},
|
||||
|
||||
@@ -118,14 +118,14 @@ export const organizationClient = <CO extends OrganizationClientOptions>(
|
||||
|
||||
type OrganizationReturn = CO["teams"] extends { enabled: true }
|
||||
? {
|
||||
members: InferMember<CO, false>[];
|
||||
members: InferMember<CO>[];
|
||||
invitations: InferInvitation<CO>[];
|
||||
teams: InferTeam<CO, false>[];
|
||||
} & InferOrganization<CO, false>
|
||||
teams: InferTeam<CO>[];
|
||||
} & InferOrganization<CO>
|
||||
: {
|
||||
members: InferMember<CO, false>[];
|
||||
invitations: InferInvitation<CO, false>[];
|
||||
} & InferOrganization<CO, false>;
|
||||
members: InferMember<CO>[];
|
||||
invitations: InferInvitation<CO>[];
|
||||
} & InferOrganization<CO>;
|
||||
|
||||
type Schema = CO["schema"];
|
||||
return {
|
||||
@@ -154,10 +154,10 @@ export const organizationClient = <CO extends OrganizationClientOptions>(
|
||||
getActions: ($fetch, _$store, co) => ({
|
||||
$Infer: {
|
||||
ActiveOrganization: {} as OrganizationReturn,
|
||||
Organization: {} as InferOrganization<CO, false>,
|
||||
Invitation: {} as InferInvitation<CO, false>,
|
||||
Member: {} as InferMember<CO, false>,
|
||||
Team: {} as InferTeam<CO, false>,
|
||||
Organization: {} as InferOrganization<CO>,
|
||||
Invitation: {} as InferInvitation<CO>,
|
||||
Member: {} as InferMember<CO>,
|
||||
Team: {} as InferTeam<CO>,
|
||||
},
|
||||
organization: {
|
||||
checkRolePermission: <
|
||||
@@ -182,7 +182,7 @@ export const organizationClient = <CO extends OrganizationClientOptions>(
|
||||
},
|
||||
}),
|
||||
getAtoms: ($fetch) => {
|
||||
const listOrganizations = useAuthQuery<InferOrganization<CO, false>[]>(
|
||||
const listOrganizations = useAuthQuery<InferOrganization<CO>[]>(
|
||||
$listOrg,
|
||||
"/organization/list",
|
||||
$fetch,
|
||||
@@ -192,9 +192,9 @@ export const organizationClient = <CO extends OrganizationClientOptions>(
|
||||
);
|
||||
const activeOrganization = useAuthQuery<
|
||||
Prettify<
|
||||
InferOrganization<CO, false> & {
|
||||
members: InferMember<CO, false>[];
|
||||
invitations: InferInvitation<CO, false>[];
|
||||
InferOrganization<CO> & {
|
||||
members: InferMember<CO>[];
|
||||
invitations: InferInvitation<CO>[];
|
||||
}
|
||||
>
|
||||
>(
|
||||
|
||||
@@ -2181,21 +2181,9 @@ describe("Additional Fields", async () => {
|
||||
},
|
||||
headers,
|
||||
});
|
||||
type Result = PrettifyDeep<typeof updatedOrg>;
|
||||
expect(updatedOrg?.someRequiredField).toBe("hey2");
|
||||
//@ts-expect-error
|
||||
expect(db.organization[0]?.someRequiredField).toBe("hey2");
|
||||
expectTypeOf<Result>().toEqualTypeOf<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
createdAt: Date;
|
||||
logo?: string | null | undefined;
|
||||
someRequiredField: string;
|
||||
someOptionalField?: string | undefined;
|
||||
someHiddenField?: string | undefined;
|
||||
metadata: any;
|
||||
} | null>();
|
||||
});
|
||||
|
||||
it("list user organizations", async () => {
|
||||
@@ -2902,3 +2890,260 @@ describe("organization hooks", async (it) => {
|
||||
expect(hooksCalled).toContain("afterCreateInvitation");
|
||||
});
|
||||
});
|
||||
|
||||
describe("organization additionalFields with returned: false", async () => {
|
||||
const db = {
|
||||
user: [] as { id: string }[],
|
||||
session: [] as { id: string }[],
|
||||
account: [] as { id: string }[],
|
||||
verification: [] as { id: string }[],
|
||||
organization: [] as {
|
||||
id: string;
|
||||
publicField: string;
|
||||
secretField: string;
|
||||
}[],
|
||||
member: [] as {
|
||||
id: string;
|
||||
memberPublicField: string;
|
||||
memberSecretField: string;
|
||||
}[],
|
||||
invitation: [] as {
|
||||
id: string;
|
||||
invitationPublicField: string;
|
||||
invitationSecretField: string;
|
||||
}[],
|
||||
};
|
||||
|
||||
const orgOptions = {
|
||||
schema: {
|
||||
organization: {
|
||||
additionalFields: {
|
||||
publicField: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
secretField: {
|
||||
type: "string",
|
||||
required: false,
|
||||
returned: false,
|
||||
input: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
member: {
|
||||
additionalFields: {
|
||||
memberPublicField: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
memberSecretField: {
|
||||
type: "string",
|
||||
required: false,
|
||||
returned: false,
|
||||
input: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
invitation: {
|
||||
additionalFields: {
|
||||
invitationPublicField: {
|
||||
type: "string",
|
||||
required: true,
|
||||
},
|
||||
invitationSecretField: {
|
||||
type: "string",
|
||||
required: false,
|
||||
returned: false,
|
||||
input: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
invitationLimit: 3,
|
||||
} satisfies OrganizationOptions;
|
||||
|
||||
const { auth, signInWithTestUser } = await getTestInstance({
|
||||
database: memoryAdapter(db, {
|
||||
debugLogs: false,
|
||||
}),
|
||||
plugins: [organization(orgOptions)],
|
||||
logger: {
|
||||
level: "error",
|
||||
},
|
||||
});
|
||||
|
||||
const { headers } = await signInWithTestUser();
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [
|
||||
organizationClient({
|
||||
schema: inferOrgAdditionalFields(orgOptions.schema),
|
||||
}),
|
||||
],
|
||||
baseURL: "http://localhost:3000/api/auth",
|
||||
fetchOptions: {
|
||||
customFetchImpl: async (url, init) => {
|
||||
return auth.handler(new Request(url, init));
|
||||
},
|
||||
headers,
|
||||
},
|
||||
});
|
||||
|
||||
it("should save field with returned: false but not return it in response", async () => {
|
||||
const org = await client.organization.create({
|
||||
name: "Test Org",
|
||||
slug: "test-org-secret",
|
||||
publicField: "public-value",
|
||||
secretField: "secret-value",
|
||||
});
|
||||
|
||||
expect(org.data).toBeDefined();
|
||||
// Note: publicField and secretField use `as any` because endpoint response types
|
||||
// are inferred from adapter return values which include all fields.
|
||||
// The runtime correctly filters returned: false fields.
|
||||
expect((org.data as any).publicField).toBe("public-value");
|
||||
expect((org.data as any).secretField).toBeUndefined();
|
||||
|
||||
const dbOrg = db.organization.find((o) => o.id === org.data?.id);
|
||||
expect(dbOrg).toBeDefined();
|
||||
expect(dbOrg?.secretField).toBe("secret-value");
|
||||
});
|
||||
|
||||
it("should not return secret field when getting organization by slug", async () => {
|
||||
await client.organization.create({
|
||||
name: "Test Org 2",
|
||||
slug: "test-org-secret-2",
|
||||
publicField: "public-value-2",
|
||||
secretField: "secret-value-2",
|
||||
});
|
||||
|
||||
// Get organization - secret field should not be returned
|
||||
const fullOrg = await client.organization.getFullOrganization({
|
||||
query: { organizationSlug: "test-org-secret-2" },
|
||||
});
|
||||
|
||||
expect(fullOrg.data).toBeDefined();
|
||||
expect(fullOrg.data?.publicField).toBe("public-value-2");
|
||||
// @ts-expect-error - secretField has returned: false
|
||||
expect(fullOrg.data?.secretField).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not return secret field when listing organizations", async () => {
|
||||
const orgs = await client.organization.list();
|
||||
|
||||
expect(orgs.data).toBeDefined();
|
||||
expect(orgs.data!.length).toBeGreaterThan(0);
|
||||
|
||||
for (const org of orgs.data!) {
|
||||
expect((org as any).secretField).toBeUndefined();
|
||||
expect((org as any).publicField).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("should not return member secret field in full organization response", async () => {
|
||||
// Create organization first
|
||||
const org = await client.organization.create({
|
||||
name: "Member Test Org",
|
||||
slug: "member-test-org",
|
||||
publicField: "public",
|
||||
});
|
||||
|
||||
// Create a new user using auth API directly
|
||||
const signUpRes = await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: "member-test@example.com",
|
||||
password: "password123",
|
||||
name: "Member Test",
|
||||
},
|
||||
});
|
||||
|
||||
// Add member with secret field via server API
|
||||
await auth.api.addMember({
|
||||
body: {
|
||||
userId: signUpRes.user.id,
|
||||
role: "member",
|
||||
organizationId: org.data!.id,
|
||||
memberPublicField: "member-public",
|
||||
memberSecretField: "member-secret",
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
// Get full organization - member secret field should not be returned
|
||||
const fullOrg = await client.organization.getFullOrganization({
|
||||
query: { organizationId: org.data!.id },
|
||||
});
|
||||
|
||||
expect(fullOrg.data?.members).toBeDefined();
|
||||
const addedMember = fullOrg.data?.members?.find(
|
||||
(m) => m.userId === signUpRes.user.id,
|
||||
);
|
||||
expect(addedMember).toBeDefined();
|
||||
expect(addedMember?.memberPublicField).toBe("member-public");
|
||||
// @ts-expect-error - memberSecretField has returned: false
|
||||
expect(addedMember?.memberSecretField).toBeUndefined();
|
||||
|
||||
const dbMember = db.member.find((m) => m.id === addedMember?.id);
|
||||
expect(dbMember?.memberSecretField).toBe("member-secret");
|
||||
});
|
||||
|
||||
it("should not return invitation secret field", async () => {
|
||||
const org = await client.organization.create({
|
||||
name: "Invitation Test Org",
|
||||
slug: "invitation-test-org",
|
||||
publicField: "public",
|
||||
});
|
||||
|
||||
// Invite member with secret field
|
||||
await client.organization.inviteMember({
|
||||
email: "invite-test@example.com",
|
||||
role: "member",
|
||||
organizationId: org.data!.id,
|
||||
invitationPublicField: "invite-public",
|
||||
invitationSecretField: "invite-secret",
|
||||
});
|
||||
|
||||
// Get full organization - invitation secret field should not be returned
|
||||
const fullOrg = await client.organization.getFullOrganization({
|
||||
query: { organizationId: org.data!.id },
|
||||
});
|
||||
|
||||
expect(fullOrg.data?.invitations).toBeDefined();
|
||||
const invitation = fullOrg.data?.invitations?.find(
|
||||
(i) => i.email === "invite-test@example.com",
|
||||
);
|
||||
expect(invitation).toBeDefined();
|
||||
expect(invitation?.invitationPublicField).toBe("invite-public");
|
||||
// @ts-expect-error - invitationSecretField has returned: false
|
||||
expect(invitation?.invitationSecretField).toBeUndefined();
|
||||
|
||||
const dbInvitation = db.invitation.find((i) => i.id === invitation?.id);
|
||||
expect(dbInvitation?.invitationSecretField).toBe("invite-secret");
|
||||
});
|
||||
|
||||
it("should not return secret field after updating organization", async () => {
|
||||
const org = await client.organization.create({
|
||||
name: "Update Test Org",
|
||||
slug: "update-test-org",
|
||||
publicField: "original-public",
|
||||
secretField: "original-secret",
|
||||
});
|
||||
|
||||
// Update organization
|
||||
const updated = await client.organization.update({
|
||||
organizationId: org.data!.id,
|
||||
data: {
|
||||
name: "Updated Test Org",
|
||||
publicField: "updated-public",
|
||||
secretField: "updated-secret",
|
||||
},
|
||||
});
|
||||
|
||||
expect(updated.data).toBeDefined();
|
||||
expect((updated.data as any).publicField).toBe("updated-public");
|
||||
expect((updated.data as any).secretField).toBeUndefined();
|
||||
|
||||
const dbOrg = db.organization.find((o) => o.id === org.data?.id);
|
||||
expect(dbOrg?.secretField).toBe("updated-secret");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,7 +349,7 @@ export const createInvitation = <O extends OrganizationOptions>(option: O) => {
|
||||
);
|
||||
}
|
||||
|
||||
return ctx.json(updatedInvitation as InferInvitation<O, false>);
|
||||
return ctx.json(updatedInvitation as unknown as InferInvitation<O>);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -298,7 +298,7 @@ export const removeMember = <O extends OrganizationOptions>(options: O) =>
|
||||
message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND,
|
||||
});
|
||||
}
|
||||
let toBeRemovedMember: InferMember<O, false> | null = null;
|
||||
let toBeRemovedMember: InferMember<O> | null = null;
|
||||
if (ctx.body.memberIdOrEmail.includes("@")) {
|
||||
toBeRemovedMember = await adapter.findMemberByEmail({
|
||||
email: ctx.body.memberIdOrEmail,
|
||||
@@ -309,7 +309,7 @@ export const removeMember = <O extends OrganizationOptions>(options: O) =>
|
||||
if (!result) toBeRemovedMember = null;
|
||||
else {
|
||||
const { user: _user, ...member } = result;
|
||||
toBeRemovedMember = member as unknown as InferMember<O, false>;
|
||||
toBeRemovedMember = member as unknown as InferMember<O>;
|
||||
}
|
||||
}
|
||||
if (!toBeRemovedMember) {
|
||||
|
||||
@@ -751,14 +751,14 @@ export const getFullOrganization = <O extends OrganizationOptions>(
|
||||
|
||||
type OrganizationReturn = O["teams"] extends { enabled: true }
|
||||
? {
|
||||
members: InferMember<O, false>[];
|
||||
invitations: InferInvitation<O, false>[];
|
||||
teams: InferTeam<O, false>[];
|
||||
} & InferOrganization<O, false>
|
||||
members: InferMember<O>[];
|
||||
invitations: InferInvitation<O>[];
|
||||
teams: InferTeam<O>[];
|
||||
} & InferOrganization<O>
|
||||
: {
|
||||
members: InferMember<O, false>[];
|
||||
invitations: InferInvitation<O, false>[];
|
||||
} & InferOrganization<O, false>;
|
||||
members: InferMember<O>[];
|
||||
invitations: InferInvitation<O>[];
|
||||
} & InferOrganization<O>;
|
||||
return ctx.json(organization as unknown as OrganizationReturn);
|
||||
},
|
||||
);
|
||||
@@ -889,14 +889,14 @@ export const setActiveOrganization = <O extends OrganizationOptions>(
|
||||
});
|
||||
type OrganizationReturn = O["teams"] extends { enabled: true }
|
||||
? {
|
||||
members: InferMember<O, false>[];
|
||||
invitations: InferInvitation<O, false>[];
|
||||
teams: InferTeam<O, false>[];
|
||||
} & InferOrganization<O, false>
|
||||
members: InferMember<O>[];
|
||||
invitations: InferInvitation<O>[];
|
||||
teams: InferTeam<O>[];
|
||||
} & InferOrganization<O>
|
||||
: {
|
||||
members: InferMember<O, false>[];
|
||||
invitations: InferInvitation<O, false>[];
|
||||
} & InferOrganization<O, false>;
|
||||
members: InferMember<O>[];
|
||||
invitations: InferInvitation<O>[];
|
||||
} & InferOrganization<O>;
|
||||
return ctx.json(organization as unknown as OrganizationReturn);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { BetterAuthPluginDBSchema } from "@better-auth/core/db";
|
||||
import type { Prettify } from "better-call";
|
||||
import * as z from "zod";
|
||||
import type { InferAdditionalFieldsFromPluginOptions } from "../../db";
|
||||
import type {
|
||||
FieldAttributeToObject,
|
||||
RemoveFieldsWithReturnedFalse,
|
||||
} from "../../db";
|
||||
import { generateId } from "../../utils";
|
||||
import type { OrganizationOptions } from "./types";
|
||||
|
||||
@@ -378,6 +381,22 @@ export type InferOrganizationRolesFromOption<
|
||||
|
||||
export type InvitationStatus = "pending" | "accepted" | "rejected" | "canceled";
|
||||
|
||||
import type { DBFieldAttribute } from "@better-auth/core/db";
|
||||
|
||||
type InferAdditionalFieldsOutput<
|
||||
SchemaName extends string,
|
||||
Options extends OrganizationOptions,
|
||||
isClientSide extends boolean,
|
||||
> = Options["schema"] extends {
|
||||
[key in SchemaName]?: {
|
||||
additionalFields: infer Field extends Record<string, DBFieldAttribute>;
|
||||
};
|
||||
}
|
||||
? isClientSide extends true
|
||||
? FieldAttributeToObject<RemoveFieldsWithReturnedFalse<Field>>
|
||||
: FieldAttributeToObject<Field>
|
||||
: {};
|
||||
|
||||
export type InferMember<
|
||||
O extends OrganizationOptions,
|
||||
isClientSide extends boolean = true,
|
||||
@@ -412,23 +431,20 @@ export type InferMember<
|
||||
image?: string | undefined;
|
||||
};
|
||||
}) &
|
||||
InferAdditionalFieldsFromPluginOptions<"member", O, isClientSide>
|
||||
InferAdditionalFieldsOutput<"member", O, isClientSide>
|
||||
>;
|
||||
|
||||
export type InferOrganization<
|
||||
O extends OrganizationOptions,
|
||||
isClientSide extends boolean = true,
|
||||
> = Prettify<
|
||||
Organization &
|
||||
InferAdditionalFieldsFromPluginOptions<"organization", O, isClientSide>
|
||||
Organization & InferAdditionalFieldsOutput<"organization", O, isClientSide>
|
||||
>;
|
||||
|
||||
export type InferTeam<
|
||||
O extends OrganizationOptions,
|
||||
isClientSide extends boolean = true,
|
||||
> = Prettify<
|
||||
Team & InferAdditionalFieldsFromPluginOptions<"team", O, isClientSide>
|
||||
>;
|
||||
> = Prettify<Team & InferAdditionalFieldsOutput<"team", O, isClientSide>>;
|
||||
|
||||
export type InferInvitation<
|
||||
O extends OrganizationOptions,
|
||||
@@ -458,5 +474,5 @@ export type InferInvitation<
|
||||
expiresAt: Date;
|
||||
createdAt: Date;
|
||||
}) &
|
||||
InferAdditionalFieldsFromPluginOptions<"invitation", O, isClientSide>
|
||||
InferAdditionalFieldsOutput<"invitation", O, isClientSide>
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user