2025-05-18 20:46:59 +02:00
|
|
|
import {betterAuth} from "better-auth";
|
|
|
|
|
import {drizzleAdapter} from "better-auth/adapters/drizzle";
|
2025-07-26 14:43:59 +02:00
|
|
|
import * as drizzleDb from "@/db";
|
2025-05-18 20:46:59 +02:00
|
|
|
import {db} from "@/db";
|
|
|
|
|
import {env} from "@/env.mjs";
|
|
|
|
|
import {nextCookies} from "better-auth/next-js";
|
2025-07-29 08:51:50 +02:00
|
|
|
import {admin as adminPlugin, openAPI, Organization, organization} from "better-auth/plugins";
|
2025-05-18 20:46:59 +02:00
|
|
|
import {ac, admin, orgAdmin, orgMember, orgOwner, pending, superadmin, user} from "@/lib/auth/permissions";
|
|
|
|
|
import {headers} from "next/headers";
|
|
|
|
|
import {count, eq} from "drizzle-orm";
|
2025-11-01 14:50:08 +01:00
|
|
|
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
|
2025-05-16 15:54:17 +02:00
|
|
|
|
|
|
|
|
export const auth = betterAuth({
|
|
|
|
|
database: drizzleAdapter(db, {
|
|
|
|
|
provider: "pg",
|
2025-07-16 12:36:11 +02:00
|
|
|
schema: drizzleDb.schemas,
|
2025-05-16 15:54:17 +02:00
|
|
|
}),
|
2025-07-17 12:43:00 +02:00
|
|
|
secret: env.PROJECT_SECRET,
|
2025-05-16 15:54:17 +02:00
|
|
|
emailAndPassword: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
requireEmailVerification: false,
|
|
|
|
|
/*async sendResetPassword(data, request) {
|
|
|
|
|
// Send an email to the user with a link to reset their password
|
|
|
|
|
},
|
|
|
|
|
async sendVerificationEmail(data, request) {
|
|
|
|
|
// Send an email to the user with a link to verify their email
|
|
|
|
|
},
|
|
|
|
|
async verifyEmail(data, request) {
|
|
|
|
|
// Verify the email address
|
|
|
|
|
},*/
|
|
|
|
|
},
|
|
|
|
|
socialProviders: {
|
|
|
|
|
google: {
|
|
|
|
|
clientId: env.AUTH_GOOGLE_ID!,
|
|
|
|
|
clientSecret: env.AUTH_GOOGLE_SECRET!,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
plugins: [
|
|
|
|
|
openAPI(),
|
|
|
|
|
nextCookies(),
|
|
|
|
|
organization({
|
|
|
|
|
ac,
|
|
|
|
|
roles: {
|
2025-07-26 14:43:59 +02:00
|
|
|
owner: orgOwner,
|
|
|
|
|
admin: orgAdmin,
|
|
|
|
|
member: orgMember,
|
2025-05-16 15:54:17 +02:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
adminPlugin({
|
|
|
|
|
adminRoles: ["admin", "superadmin"],
|
2025-07-16 12:36:11 +02:00
|
|
|
defaultRole: "pending",
|
2025-05-16 15:54:17 +02:00
|
|
|
ac,
|
|
|
|
|
roles: {
|
|
|
|
|
admin,
|
|
|
|
|
user,
|
|
|
|
|
pending,
|
|
|
|
|
superadmin,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
advanced: {
|
|
|
|
|
database: {
|
|
|
|
|
generateId: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
user: {
|
2025-07-28 16:53:21 +02:00
|
|
|
deleteUser: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2025-05-16 15:54:17 +02:00
|
|
|
additionalFields: {
|
|
|
|
|
deletedAt: {
|
|
|
|
|
type: "number", //pg timestamp
|
|
|
|
|
nullable: true,
|
|
|
|
|
required: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-07-16 12:36:11 +02:00
|
|
|
databaseHooks: {
|
|
|
|
|
user: {
|
|
|
|
|
create: {
|
|
|
|
|
async before(user, context) {
|
|
|
|
|
const userCount = (await db.select({count: count()}).from(drizzleDb.schemas.user))[0].count;
|
|
|
|
|
const role = userCount === 0 ? "superadmin" : "pending";
|
|
|
|
|
return {
|
|
|
|
|
data: {
|
|
|
|
|
...user,
|
|
|
|
|
role,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
async after(user, context) {
|
2025-07-28 16:53:21 +02:00
|
|
|
const userCount = (await db.select({count: count()}).from(drizzleDb.schemas.user))[0].count;
|
|
|
|
|
const role = userCount === 0 ? "owner" : "admin";
|
|
|
|
|
|
2025-07-16 12:36:11 +02:00
|
|
|
|
2025-07-27 21:26:31 +02:00
|
|
|
const defaultOrgSlug = "default"; // change this if your default org has a different slug
|
|
|
|
|
const defaultOrg = await db.query.organization.findFirst({
|
|
|
|
|
where: eq(drizzleDb.schemas.organization.slug, defaultOrgSlug),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (defaultOrg) {
|
2025-07-28 16:53:21 +02:00
|
|
|
console.log(user)
|
2025-07-27 21:26:31 +02:00
|
|
|
await db.insert(drizzleDb.schemas.member).values({
|
|
|
|
|
userId: user.id,
|
|
|
|
|
organizationId: defaultOrg.id,
|
2025-07-28 16:53:21 +02:00
|
|
|
role: role,
|
2025-07-16 12:36:11 +02:00
|
|
|
});
|
2025-07-27 21:26:31 +02:00
|
|
|
} else {
|
|
|
|
|
console.warn("Default organization not found. Cannot assign member.");
|
2025-07-16 12:36:11 +02:00
|
|
|
}
|
2025-07-27 21:26:31 +02:00
|
|
|
|
2025-07-16 12:36:11 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
session: {
|
|
|
|
|
create: {
|
|
|
|
|
before: async (session, context) => {
|
|
|
|
|
const userId = session.userId;
|
|
|
|
|
|
2025-07-26 16:34:06 +02:00
|
|
|
|
|
|
|
|
let memberships = await db.query.member.findMany({
|
2025-07-16 12:36:11 +02:00
|
|
|
where: eq(drizzleDb.schemas.member.userId, userId),
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-27 21:26:31 +02:00
|
|
|
// if (!memberships.length) {
|
|
|
|
|
// const defaultOrgSlug = "default";
|
|
|
|
|
// const defaultOrg = await db.query.organization.findFirst({
|
|
|
|
|
// where: eq(drizzleDb.schemas.organization.slug, defaultOrgSlug),
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// if (!defaultOrg) {
|
|
|
|
|
// throw new Error("No organization found. Cannot assign member.");
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// await db.insert(drizzleDb.schemas.member).values({
|
|
|
|
|
// userId,
|
|
|
|
|
// organizationId: defaultOrg.id,
|
|
|
|
|
// role: "member",
|
|
|
|
|
// });
|
|
|
|
|
//
|
|
|
|
|
// memberships = await db.query.member.findMany({
|
|
|
|
|
// where: eq(drizzleDb.schemas.member.userId, userId),
|
|
|
|
|
// });
|
|
|
|
|
// }
|
2025-07-16 12:36:11 +02:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
data: {
|
2025-07-26 16:34:06 +02:00
|
|
|
activeOrganizationId: memberships[0].organizationId,
|
2025-07-16 12:36:11 +02:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-07-26 16:34:06 +02:00
|
|
|
|
2025-07-16 12:36:11 +02:00
|
|
|
},
|
2025-05-16 15:54:17 +02:00
|
|
|
session: {
|
|
|
|
|
additionalFields: {
|
|
|
|
|
activeOrganizationId: {
|
|
|
|
|
type: "string",
|
|
|
|
|
required: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
/* databaseHooks: {
|
|
|
|
|
session: {
|
|
|
|
|
create: {
|
|
|
|
|
before: async (session) => {
|
|
|
|
|
const organizationId = await getLastOrganizationOrFirst(session.userId);
|
|
|
|
|
|
|
|
|
|
if (!organizationId) {
|
|
|
|
|
return {
|
|
|
|
|
...session,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("sessionId", session.id);
|
|
|
|
|
|
|
|
|
|
const [aa] = await db
|
|
|
|
|
.update(drizzleUser.session)
|
|
|
|
|
.set({ activeOrganizationId: organizationId })
|
|
|
|
|
.where(eq(drizzleUser.session.id, session.id))
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
console.log("aaaaa", aa);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...session,
|
|
|
|
|
activeOrganizationId: organizationId,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},*/
|
2025-11-04 08:48:46 +01:00
|
|
|
trustedOrigins: [env.PROJECT_URL!, "http://app"],
|
2025-05-16 15:54:17 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*export const signUpUser = async (email: string, password: string, name: string) => {
|
|
|
|
|
const user = await auth.api.signUpEmail({
|
|
|
|
|
body: {
|
|
|
|
|
email,
|
|
|
|
|
password,
|
|
|
|
|
name,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return user;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const signInUser = async (email: string, password: string) => {
|
|
|
|
|
const user = await auth.api.signInEmail({
|
|
|
|
|
body: {
|
|
|
|
|
email,
|
|
|
|
|
password,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return user;
|
|
|
|
|
};*/
|
|
|
|
|
|
|
|
|
|
export const createUser = async (name: string, email: string, password: string, role: "user" | "pending" | "admin" | "superadmin" = "pending") => {
|
2025-05-18 20:46:59 +02:00
|
|
|
return await auth.api.createUser({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
body: {
|
|
|
|
|
name,
|
|
|
|
|
email,
|
|
|
|
|
password,
|
|
|
|
|
role,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getSessions = async () => {
|
2025-05-18 20:46:59 +02:00
|
|
|
return await auth.api.listSessions({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getSession = async () => {
|
2025-05-18 20:46:59 +02:00
|
|
|
return await auth.api.getSession({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const revokeSession = async (e: string) => {
|
|
|
|
|
try {
|
2025-07-16 12:36:11 +02:00
|
|
|
const {status} = await auth.api.revokeSession({
|
2025-05-16 15:54:17 +02:00
|
|
|
body: {
|
|
|
|
|
token: e,
|
|
|
|
|
},
|
|
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
return status;
|
2025-07-16 12:36:11 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
}
|
2025-05-16 15:54:17 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getAccounts = async () => {
|
2025-05-18 20:46:59 +02:00
|
|
|
return await auth.api.listUserAccounts({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const unlinkAccount = async (provider: string, account: string) => {
|
|
|
|
|
try {
|
2025-07-16 12:36:11 +02:00
|
|
|
const {status} = await auth.api.unlinkAccount({
|
2025-05-16 15:54:17 +02:00
|
|
|
body: {
|
|
|
|
|
providerId: provider,
|
|
|
|
|
accountId: account,
|
|
|
|
|
},
|
|
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return status;
|
2025-07-16 12:36:11 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
}
|
2025-05-16 15:54:17 +02:00
|
|
|
};
|
2025-11-03 11:37:14 +01:00
|
|
|
|
2025-05-18 20:46:59 +02:00
|
|
|
export const getOrganization = async ({
|
|
|
|
|
organizationId,
|
|
|
|
|
organizationSlug,
|
|
|
|
|
}: {
|
|
|
|
|
organizationId?: string;
|
|
|
|
|
organizationSlug?: string;
|
2025-07-26 14:43:59 +02:00
|
|
|
} = {}): Promise<OrganizationWithMembersAndUsers | null> => {
|
2025-07-16 12:36:11 +02:00
|
|
|
const query =
|
|
|
|
|
organizationId != null
|
2025-09-23 20:35:48 +02:00
|
|
|
? {organizationId}
|
2025-07-16 12:36:11 +02:00
|
|
|
: organizationSlug != null
|
2025-09-23 20:35:48 +02:00
|
|
|
? {organizationSlug}
|
2025-07-16 12:36:11 +02:00
|
|
|
: undefined;
|
2025-05-16 15:54:17 +02:00
|
|
|
|
2025-05-18 20:46:59 +02:00
|
|
|
try {
|
2025-07-26 14:43:59 +02:00
|
|
|
const response = await auth.api.getFullOrganization({
|
2025-05-18 20:46:59 +02:00
|
|
|
headers: await headers(),
|
2025-09-23 20:35:48 +02:00
|
|
|
...(query ? {query} : {}),
|
2025-05-18 20:46:59 +02:00
|
|
|
});
|
2025-07-26 14:43:59 +02:00
|
|
|
|
|
|
|
|
return response as OrganizationWithMembersAndUsers;
|
2025-05-18 20:46:59 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-05-16 15:54:17 +02:00
|
|
|
};
|
|
|
|
|
|
2025-07-29 08:51:50 +02:00
|
|
|
export const listOrganizations = async (): Promise<Organization[] | null> => {
|
2025-05-16 15:54:17 +02:00
|
|
|
try {
|
2025-05-18 20:46:59 +02:00
|
|
|
return await auth.api.listOrganizations({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
2025-07-29 08:51:50 +02:00
|
|
|
}) as Organization[];
|
2025-07-16 12:36:11 +02:00
|
|
|
} catch (e) {
|
2025-07-29 08:51:50 +02:00
|
|
|
return null;
|
2025-07-16 12:36:11 +02:00
|
|
|
}
|
2025-05-16 15:54:17 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getLastOrganizationOrFirst = async (userId: string) => {
|
|
|
|
|
try {
|
2025-07-16 12:36:11 +02:00
|
|
|
const organizations = await db.query.organization.findMany({
|
|
|
|
|
where: eq(drizzleDb.schemas.member.userId, userId),
|
2025-05-16 15:54:17 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (organizations.length > 0) {
|
|
|
|
|
return organizations[0].id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-07-26 14:43:59 +02:00
|
|
|
|
2025-05-16 15:54:17 +02:00
|
|
|
export const createOrganization = async (name: string, slug: string) => {
|
|
|
|
|
try {
|
2025-07-26 14:43:59 +02:00
|
|
|
return await auth.api.createOrganization({
|
2025-09-23 20:35:48 +02:00
|
|
|
headers: await headers(),
|
2025-05-16 15:54:17 +02:00
|
|
|
body: {
|
|
|
|
|
name,
|
|
|
|
|
slug,
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-07-26 14:43:59 +02:00
|
|
|
} catch (e: any) {
|
|
|
|
|
const errorMessage = e?.response?.data?.message || e?.message || "Unknown auth error";
|
|
|
|
|
const status = e?.response?.status || 500;
|
2025-05-16 15:54:17 +02:00
|
|
|
|
2025-07-26 14:43:59 +02:00
|
|
|
console.error("Auth API createOrganization error:", {
|
|
|
|
|
message: errorMessage,
|
|
|
|
|
status,
|
|
|
|
|
raw: e,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
throw {
|
|
|
|
|
name: "AuthCreateOrganizationError",
|
|
|
|
|
message: errorMessage,
|
|
|
|
|
status,
|
|
|
|
|
cause: e,
|
|
|
|
|
};
|
2025-05-16 15:54:17 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-26 14:43:59 +02:00
|
|
|
export const deleteOrganization = async (organizationId: string) => {
|
|
|
|
|
try {
|
|
|
|
|
return await auth.api.deleteOrganization({
|
|
|
|
|
body: {
|
|
|
|
|
organizationId,
|
|
|
|
|
},
|
|
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
const errorMessage = e?.response?.data?.message || e?.message || "Unknown auth error";
|
|
|
|
|
const status = e?.response?.status || 500;
|
|
|
|
|
|
|
|
|
|
console.error("Auth API deleteOrganization error:", {
|
|
|
|
|
message: errorMessage,
|
|
|
|
|
status,
|
|
|
|
|
raw: e,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
throw {
|
|
|
|
|
name: "AuthDeleteOrganizationError",
|
|
|
|
|
message: errorMessage,
|
|
|
|
|
status,
|
|
|
|
|
cause: e,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2025-05-16 15:54:17 +02:00
|
|
|
export const checkSlugOrganization = async (slug: string) => {
|
|
|
|
|
try {
|
2025-07-16 12:36:11 +02:00
|
|
|
const {status} = await auth.api.checkOrganizationSlug({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
body: {
|
|
|
|
|
slug,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("err", e);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getActiveMember = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const member = await auth.api.getActiveMember({
|
|
|
|
|
headers: await headers(),
|
|
|
|
|
});
|
2025-07-16 12:36:11 +02:00
|
|
|
console.log(member);
|
2025-05-16 15:54:17 +02:00
|
|
|
|
2025-11-01 14:50:08 +01:00
|
|
|
return member as MemberWithUser;
|
2025-05-16 15:54:17 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
console.log("err", e);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const setActiveOrganization = async (slug: string) => {
|
|
|
|
|
try {
|
2025-09-23 20:35:48 +02:00
|
|
|
return await auth.api.setActiveOrganization({
|
2025-05-16 15:54:17 +02:00
|
|
|
headers: await headers(),
|
|
|
|
|
body: {
|
|
|
|
|
organizationSlug: slug,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("error", e);
|
|
|
|
|
}
|
|
|
|
|
};
|