diff --git a/dev/next-app/prisma/db.sqlite b/dev/next-app/prisma/db.sqlite
index 73da8971..13829ea9 100644
Binary files a/dev/next-app/prisma/db.sqlite and b/dev/next-app/prisma/db.sqlite differ
diff --git a/dev/next-app/src/app/(auth)/sign-in/page.tsx b/dev/next-app/src/app/(auth)/sign-in/page.tsx
index 3ce9bef9..6be4faf1 100644
--- a/dev/next-app/src/app/(auth)/sign-in/page.tsx
+++ b/dev/next-app/src/app/(auth)/sign-in/page.tsx
@@ -15,15 +15,20 @@ import { authClient } from "@/lib/auth-client";
import { useState } from "react";
import { Key } from "lucide-react";
import { PasswordInput } from "@/components/ui/password-input";
+import { Checkbox } from "@/components/ui/checkbox";
+import { toast } from "sonner";
+import { useRouter } from "next/navigation";
export default function Page() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
+ const [rememberMe, setRememberMe] = useState(false);
+ const router = useRouter()
return (
{/* Radial gradient for the container to give a faded look */}
-
+
Login
@@ -63,12 +68,22 @@ export default function Page() {
placeholder="Password"
/>
+
+ {
+ setRememberMe(!rememberMe)
+ }} />
+ Remember me
+
{
- await authClient.signIn.credential({
+ const res = await authClient.signIn.credential({
email,
password,
- callbackURL: "/"
+ callbackURL: "/",
+ dontRememberMe: !rememberMe
})
+ if (res.error) {
+ toast.error(res.error.message)
+ }
}}>
Login
@@ -85,9 +100,14 @@ export default function Page() {
Login with Github
{
- await authClient.passkey.signIn({
+ const res = await authClient.passkey.signIn({
callbackURL: "/"
})
+ if (res?.error) {
+ toast.error(res.error.message)
+ } else {
+ router.push("/")
+ }
}}>
Login with Passkey
diff --git a/dev/next-app/src/app/layout.tsx b/dev/next-app/src/app/layout.tsx
index 16682eca..87b05d5b 100644
--- a/dev/next-app/src/app/layout.tsx
+++ b/dev/next-app/src/app/layout.tsx
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeWrapper } from "@/components/theme-provider";
+import { Toaster } from "@/components/ui/sonner";
const inter = Inter({ subsets: ["latin"] });
@@ -16,10 +17,13 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
- {children}
-
+
+
+
+ {children}
+
+
+
);
}
diff --git a/dev/next-app/src/app/page.tsx b/dev/next-app/src/app/page.tsx
index 1d7cefbf..cb11870b 100644
--- a/dev/next-app/src/app/page.tsx
+++ b/dev/next-app/src/app/page.tsx
@@ -11,7 +11,7 @@ export default async function TypewriterEffectSmoothDemo() {
{/* Radial gradient for the container to give a faded look */}
{
- session ? : null
+ session ? : null
}
diff --git a/dev/next-app/src/components/signout.tsx b/dev/next-app/src/components/signout.tsx
index 9c5a576f..ddea3eb2 100644
--- a/dev/next-app/src/components/signout.tsx
+++ b/dev/next-app/src/components/signout.tsx
@@ -8,9 +8,7 @@ export const SignOut = () => {
{
await authClient.signOut({
- body: {
- callbackURL: "/"
- }
+
})
}}
>
diff --git a/dev/next-app/src/components/ui/sonner.tsx b/dev/next-app/src/components/ui/sonner.tsx
index c5ba1bfe..51b7ab15 100644
--- a/dev/next-app/src/components/ui/sonner.tsx
+++ b/dev/next-app/src/components/ui/sonner.tsx
@@ -12,6 +12,8 @@ const Toaster = ({ ...props }: ToasterProps) => {
@@ -26,23 +26,42 @@ export default function UserCard({
-
- {user.name.charAt(0)}
+
+ {session?.user.name.charAt(0)}
-
{user.name}
-
{user.email}
+
{session?.user.name}
+
{session?.user.email}
-
+
+ {
+ session?.user.twoFactorEnabled ?
{
+ const res = await authClient.twoFactor.disable()
+ if (res.error) {
+ toast.error(res.error.message)
+ }
+ }}>
+ Disable 2FA
+ :
{
+ const res = await authClient.twoFactor.enable()
+ if (res.error) {
+ toast.error(res.error.message)
+ }
+ }}>
+
+ Enable 2FA
+
+
+ }
{
- const res = await authClient.signOut()
+ await authClient.signOut()
router.refresh()
}}>
Sign Out
diff --git a/dev/next-app/src/lib/types.ts b/dev/next-app/src/lib/types.ts
new file mode 100644
index 00000000..53f7214f
--- /dev/null
+++ b/dev/next-app/src/lib/types.ts
@@ -0,0 +1,5 @@
+import { InferSession, InferUser } from "better-auth/types";
+import type { auth } from "./auth";
+
+export type User = InferUser;
+export type Session = InferSession;
diff --git a/dev/next-app/src/middleware.ts b/dev/next-app/src/middleware.ts
index fe6157b9..b6a3e721 100644
--- a/dev/next-app/src/middleware.ts
+++ b/dev/next-app/src/middleware.ts
@@ -15,7 +15,11 @@ export async function middleware(request: NextRequest) {
permission: {
invitation: ["create"],
},
+ options: {
+ headers: request.headers,
+ },
});
+ console.log({ canInvite });
return NextResponse.next();
}
diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json
index 1660ae3f..6d1ff4a8 100644
--- a/packages/better-auth/package.json
+++ b/packages/better-auth/package.json
@@ -15,6 +15,7 @@
".": "./dist/index.js",
"./provider": "./dist/provider.js",
"./client": "./dist/client.js",
+ "./types": "./dist/types.js",
"./cli": "./dist/cli.js",
"./react": "./dist/react.js",
"./preact": "./dist/preact.js",
@@ -52,7 +53,7 @@
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.1",
"arctic": "^1.9.2",
- "better-call": "^0.1.33",
+ "better-call": "^0.1.36",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"consola": "^3.2.3",
diff --git a/packages/better-auth/src/adapters/internal-adapter.ts b/packages/better-auth/src/adapters/internal-adapter.ts
index 5cece90c..979cdd46 100644
--- a/packages/better-auth/src/adapters/internal-adapter.ts
+++ b/packages/better-auth/src/adapters/internal-adapter.ts
@@ -93,16 +93,11 @@ export const createInternalAdapter = (
session.expiresAt.valueOf() - maxAge.valueOf() + updateDate <=
Date.now();
if (shouldBeUpdated) {
- const updatedSession = await adapter.update({
+ const updatedSession = await adapter.create({
model: tables.session.tableName,
- where: [
- {
- field: "id",
- value: session.id,
- },
- ],
- update: {
+ data: {
...session,
+ id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
expiresAt: new Date(Date.now() + sessionExpiration),
},
});
diff --git a/packages/better-auth/src/api/index.ts b/packages/better-auth/src/api/index.ts
index a28a58dc..7fb9f7ab 100644
--- a/packages/better-auth/src/api/index.ts
+++ b/packages/better-auth/src/api/index.ts
@@ -1,22 +1,27 @@
-import { createRouter, Endpoint } from "better-call";
+import { Context, createRouter, Endpoint } from "better-call";
import {
signInOAuth,
callbackOAuth,
- getSession,
signOut,
signInCredential,
forgetPassword,
resetPassword,
verifyEmail,
sendVerificationEmail,
+ getSession,
} from "./routes";
import { AuthContext } from "../init";
import { csrfMiddleware } from "./middlewares/csrf";
import { getCSRFToken } from "./routes/csrf";
import { signUpCredential } from "./routes/sign-up";
import { parseAccount, parseSession, parseUser } from "../adapters/schema";
+import { BetterAuthOptions, InferSession, InferUser } from "../types";
+import { Prettify } from "../types/helper";
-export const router = (ctx: C) => {
+export const router = (
+ ctx: C,
+ option: Option,
+) => {
const pluginEndpoints = ctx.options.plugins?.reduce(
(acc, plugin) => {
return {
@@ -65,11 +70,30 @@ export const router = (ctx: C) => {
.filter((plugin) => plugin !== undefined)
.flat() || [];
+ async function typedSession(
+ ctx: Context<
+ "/session",
+ {
+ method: "GET";
+ requireHeaders: true;
+ }
+ >,
+ ) {
+ const handler = await getSession(ctx);
+ return handler as {
+ session: Prettify>;
+ user: Prettify>;
+ } | null;
+ }
+ typedSession.path = getSession.path;
+ typedSession.method = getSession.method;
+ typedSession.options = getSession.options;
+ typedSession.headers = getSession.headers;
const baseEndpoints = {
signInOAuth,
callbackOAuth,
getCSRFToken,
- getSession,
+ getSession: typedSession,
signOut,
signUpCredential,
signInCredential,
@@ -85,15 +109,49 @@ export const router = (ctx: C) => {
};
let api: Record = {};
for (const [key, value] of Object.entries(endpoints)) {
- api[key] = (context: any) => {
+ api[key] = async (context: any) => {
+ for (const plugin of ctx.options.plugins || []) {
+ if (plugin.hooks?.before) {
+ for (const hook of plugin.hooks.before) {
+ const match = hook.matcher(context);
+ if (match) {
+ const hookRes = await hook.handler(context);
+ if (hookRes && "context" in hookRes) {
+ context = {
+ ...context,
+ ...hookRes.context,
+ };
+ }
+ }
+ }
+ }
+ }
//@ts-ignore
- return value({
+ const endpointRes = value({
...context,
context: {
...ctx,
...context.context,
},
});
+ let response = endpointRes;
+ for (const plugin of ctx.options.plugins || []) {
+ if (plugin.hooks?.after) {
+ for (const hook of plugin.hooks.after) {
+ const match = hook.matcher(context);
+ if (match) {
+ const hookRes = await hook.handler({
+ ...context,
+ returned: endpointRes,
+ });
+ if (hookRes && "response" in hookRes) {
+ response = hookRes.response as any;
+ }
+ }
+ }
+ }
+ }
+ return response;
};
api[key].path = value.path;
api[key].method = value.method;
@@ -134,7 +192,7 @@ export const router = (ctx: C) => {
});
},
onError(e) {
- console.log(e);
+ // console.log(e);
},
});
};
diff --git a/packages/better-auth/src/api/middlewares/csrf.ts b/packages/better-auth/src/api/middlewares/csrf.ts
index 20676f98..b5e73225 100644
--- a/packages/better-auth/src/api/middlewares/csrf.ts
+++ b/packages/better-auth/src/api/middlewares/csrf.ts
@@ -19,16 +19,14 @@ export const csrfMiddleware = createAuthMiddleware(
return;
}
const url = new URL(ctx.request.url);
- console.log({
- url: ctx.request.url,
- });
+ console.log(url.origin, ctx.context.options.baseURL);
/**
* If origin is the same as baseURL or if the
* origin is in the trustedOrigins then we
* don't need to check the CSRF token.
*/
if (
- url.origin === ctx.context.baseURL ||
+ url.origin === ctx.context.options.baseURL ||
ctx.context.options.trustedOrigins?.includes(url.origin)
) {
return;
diff --git a/packages/better-auth/src/api/routes/callback.ts b/packages/better-auth/src/api/routes/callback.ts
index 5cbfe19b..b1c0bb46 100644
--- a/packages/better-auth/src/api/routes/callback.ts
+++ b/packages/better-auth/src/api/routes/callback.ts
@@ -4,6 +4,7 @@ import { APIError } from "better-call";
import { parseState } from "../../utils/state";
import { userSchema } from "../../adapters/schema";
import { HIDE_ON_CLIENT_METADATA } from "../../client/client-utils";
+import { generateId } from "../../utils/id";
export const callbackOAuth = createAuthEndpoint(
"/callback/:id",
@@ -36,11 +37,11 @@ export const callbackOAuth = createAuthEndpoint(
c.context.logger.error("Code verification failed");
throw new APIError("UNAUTHORIZED");
}
-
const user = await provider.userInfo.getUserInfo(tokens);
+ const id = generateId();
const data = userSchema.safeParse({
...user,
- id: user?.id.toString(),
+ id,
});
if (!user || data.success === false) {
throw new APIError("BAD_REQUEST");
@@ -52,7 +53,7 @@ export const callbackOAuth = createAuthEndpoint(
}
//find user in db
const dbUser = await c.context.internalAdapter.findUserByEmail(user.email);
- let userId = dbUser?.user.id;
+ const userId = dbUser?.user.id;
if (dbUser) {
//check if user has already linked this provider
const hasBeenLinked = dbUser.accounts.find(
@@ -76,14 +77,13 @@ export const callbackOAuth = createAuthEndpoint(
}
} else {
try {
- await c.context.internalAdapter.createOAuthUser(user, {
+ await c.context.internalAdapter.createOAuthUser(data.data, {
...tokens,
id: `${provider.id}:${user.id}`,
providerId: provider.id,
accountId: user.id,
- userId: user.id,
+ userId: id,
});
- userId = user.id;
} catch (e) {
const url = new URL(currentURL || callbackURL);
url.searchParams.set("error", "unable_to_create_user");
@@ -93,10 +93,9 @@ export const callbackOAuth = createAuthEndpoint(
}
//this should never happen
if (!userId) throw new APIError("INTERNAL_SERVER_ERROR");
-
//create session
const session = await c.context.internalAdapter.createSession(
- userId,
+ userId || id,
c.request,
);
try {
diff --git a/packages/better-auth/src/api/routes/session.ts b/packages/better-auth/src/api/routes/session.ts
index 6836eb6f..d4058d25 100644
--- a/packages/better-auth/src/api/routes/session.ts
+++ b/packages/better-auth/src/api/routes/session.ts
@@ -1,6 +1,5 @@
import { Context } from "better-call";
import { createAuthEndpoint } from "../call";
-import { HIDE_ON_CLIENT_METADATA } from "../../client/client-utils";
export const getSession = createAuthEndpoint(
"/session",
@@ -31,6 +30,15 @@ export const getSession = createAuthEndpoint(
const updatedSession = await ctx.context.internalAdapter.updateSession(
session.session,
);
+ await ctx.setSignedCookie(
+ ctx.context.authCookies.sessionToken.name,
+ updatedSession.id,
+ ctx.context.secret,
+ {
+ ...ctx.context.authCookies.sessionToken.options,
+ maxAge: updatedSession.expiresAt.valueOf() - Date.now(),
+ },
+ );
return ctx.json({
session: updatedSession,
user: session.user,
diff --git a/packages/better-auth/src/api/routes/sign-in.ts b/packages/better-auth/src/api/routes/sign-in.ts
index 971c783c..802859a9 100644
--- a/packages/better-auth/src/api/routes/sign-in.ts
+++ b/packages/better-auth/src/api/routes/sign-in.ts
@@ -87,16 +87,18 @@ export const signInCredential = createAuthEndpoint(
email: z.string().email(),
password: z.string(),
callbackURL: z.string().optional(),
+ /**
+ * If this is true the session will only be valid for the current browser session
+ * @default false
+ */
+ dontRememberMe: z.boolean().default(false).optional(),
}),
},
async (ctx) => {
if (!ctx.context.options?.emailAndPassword?.enabled) {
ctx.context.logger.error("Email and password is not enabled");
- return ctx.json(null, {
- body: {
- message: "Email and password is not enabled",
- },
- status: 400,
+ throw new APIError("BAD_REQUEST", {
+ message: "Email and password is not enabled",
});
}
const currentSession = await getSessionFromCtx(ctx);
@@ -114,8 +116,8 @@ export const signInCredential = createAuthEndpoint(
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
ctx.context.logger.error("User not found", { email });
- return ctx.json(null, {
- status: 401,
+ throw new APIError("UNAUTHORIZED", {
+ message: "Invalid email or password",
});
}
const credentialAccount = user.accounts.find(
@@ -124,20 +126,17 @@ export const signInCredential = createAuthEndpoint(
const currentPassword = credentialAccount?.password;
if (!currentPassword) {
ctx.context.logger.error("Password not found", { email });
- return ctx.json(null, {
- status: 401,
- body: { message: "Unexpected error" },
+ throw new APIError("UNAUTHORIZED", {
+ message: "Unexpected error",
});
}
const validPassword = await argon2id.verify(currentPassword, password);
if (!validPassword) {
ctx.context.logger.error("Invalid password");
- return ctx.json(null, {
- status: 401,
- body: { message: "Invalid email or password" },
+ throw new APIError("UNAUTHORIZED", {
+ message: "Invalid email or password",
});
}
-
const session = await ctx.context.internalAdapter.createSession(
user.user.id,
ctx.request,
@@ -146,7 +145,12 @@ export const signInCredential = createAuthEndpoint(
ctx.context.authCookies.sessionToken.name,
session.id,
ctx.context.secret,
- ctx.context.authCookies.sessionToken.options,
+ ctx.body.dontRememberMe
+ ? {
+ ...ctx.context.authCookies.sessionToken.options,
+ maxAge: undefined,
+ }
+ : ctx.context.authCookies.sessionToken.options,
);
return ctx.json({
user: user.user,
diff --git a/packages/better-auth/src/auth.ts b/packages/better-auth/src/auth.ts
index 29f44263..b62adc43 100644
--- a/packages/better-auth/src/auth.ts
+++ b/packages/better-auth/src/auth.ts
@@ -1,7 +1,7 @@
import { router } from "./api";
import type { BetterAuthOptions } from "./types/options";
import type { UnionToIntersection } from "type-fest";
-import type { Plugin } from "./types/plugins";
+import type { BetterAuthPlugin } from "./types/plugins";
import { init } from "./init";
import type { CustomProvider } from "./providers";
@@ -9,20 +9,20 @@ export const betterAuth = (options: O) => {
const authContext = init(options);
type PluginEndpoint = UnionToIntersection<
O["plugins"] extends Array
- ? T extends Plugin
+ ? T extends BetterAuthPlugin
? T["endpoints"]
- : Record
- : Record
+ : {}
+ : {}
>;
type ProviderEndpoint = UnionToIntersection<
O["providers"] extends Array
? T extends CustomProvider
? T["endpoints"]
- : Record
- : Record
+ : {}
+ : {}
>;
- const { handler, endpoints } = router(authContext);
+ const { handler, endpoints } = router(authContext, options);
type Endpoint = typeof endpoints;
return {
handler,
@@ -31,11 +31,7 @@ export const betterAuth = (options: O) => {
};
};
-export type BetterAuth<
- Endpoints extends Record = ReturnType<
- typeof router
- >["endpoints"],
-> = {
+export type BetterAuth = {}> = {
handler: (request: Request) => Promise;
api: Endpoints;
options: BetterAuthOptions;
diff --git a/packages/better-auth/src/client/base.ts b/packages/better-auth/src/client/base.ts
index 42b2e0f9..857db643 100644
--- a/packages/better-auth/src/client/base.ts
+++ b/packages/better-auth/src/client/base.ts
@@ -19,7 +19,7 @@ export const createVanillaClient = (
: BAuth["api"];
const $fetch = createFetch({
...options,
- baseURL: getBaseURL(options?.baseURL),
+ baseURL: getBaseURL(options?.baseURL).withPath,
plugins: [redirectPlugin, addCurrentURL, csrfPlugin],
});
const { $session, $sessionSignal } = getSessionAtom($fetch);
diff --git a/packages/better-auth/src/client/proxy.ts b/packages/better-auth/src/client/proxy.ts
index a79d4960..e175cb5a 100644
--- a/packages/better-auth/src/client/proxy.ts
+++ b/packages/better-auth/src/client/proxy.ts
@@ -64,6 +64,7 @@ export function createDynamicPathProxy>(
method,
onSuccess() {
const signal = $signal?.[routePath as string];
+ console.log({ routePath, signal });
if (signal) {
signal.set(!signal.get());
}
diff --git a/packages/better-auth/src/client/react.ts b/packages/better-auth/src/client/react.ts
index 18a96f90..40ec0f26 100644
--- a/packages/better-auth/src/client/react.ts
+++ b/packages/better-auth/src/client/react.ts
@@ -2,13 +2,23 @@ import { useStore } from "@nanostores/react";
import { createVanillaClient } from "./base";
import { BetterFetchOption } from "@better-fetch/fetch";
import { BetterAuth } from "../auth";
+import { InferSession, InferUser } from "../types";
export const createAuthClient = (
options?: BetterFetchOption,
) => {
const client = createVanillaClient(options);
- function useSession() {
- return useStore(client.$atoms.$session);
+ function useSession(
+ initialValue: {
+ user: InferUser;
+ session: InferSession;
+ } | null = null,
+ ) {
+ const session = useStore(client.$atoms.$session);
+ if (session) {
+ return session;
+ }
+ return initialValue;
}
function useActiveOrganization() {
return useStore(client.$atoms.$activeOrganization);
diff --git a/packages/better-auth/src/client/session-atom.ts b/packages/better-auth/src/client/session-atom.ts
index 3a9a27e5..92fe4f2e 100644
--- a/packages/better-auth/src/client/session-atom.ts
+++ b/packages/better-auth/src/client/session-atom.ts
@@ -1,64 +1,12 @@
import { atom, computed, task } from "nanostores";
-import { Session, User } from "../adapters/schema";
-import { Prettify, UnionToIntersection } from "../types/helper";
+import { Prettify } from "../types/helper";
import { BetterAuth } from "../auth";
-import { FieldAttribute, InferFieldOutput } from "../db";
import { BetterFetch } from "@better-fetch/fetch";
+import { InferSession, InferUser } from "../types/models";
export function getSessionAtom(client: BetterFetch) {
- type AdditionalSessionFields = Auth["options"]["plugins"] extends Array<
- infer T
- >
- ? T extends {
- schema: {
- session: {
- fields: infer Field;
- };
- };
- }
- ? Field extends Record
- ? {
- [key in keyof Field]: InferFieldOutput;
- }
- : {}
- : {}
- : {};
- type AdditionalUserFields = Auth["options"]["plugins"] extends Array
- ? T extends {
- schema: {
- user: {
- fields: infer Field;
- };
- };
- }
- ? Field extends Record
- ? Prettify<
- {
- [key in Key as Field[key]["required"] extends false
- ? never
- : Field[key]["defaultValue"] extends
- | boolean
- | string
- | number
- | Date
- | Function
- ? key
- : never]: InferFieldOutput;
- } & {
- [key in Key as Field[key]["returned"] extends false
- ? never
- : key]?: InferFieldOutput;
- }
- >
- : {}
- : {}
- : {};
-
- type UserWithAdditionalFields = User &
- UnionToIntersection;
- type SessionWithAdditionalFields = Session &
- UnionToIntersection;
-
+ type UserWithAdditionalFields = InferUser;
+ type SessionWithAdditionalFields = InferSession;
const $signal = atom(false);
const $session = computed($signal, () =>
task(async () => {
diff --git a/packages/better-auth/src/init.ts b/packages/better-auth/src/init.ts
index 28f18878..d3d9eb1f 100644
--- a/packages/better-auth/src/init.ts
+++ b/packages/better-auth/src/init.ts
@@ -1,3 +1,4 @@
+import { Context, ContextTools } from "better-call";
import { createKyselyAdapter } from "./adapters/kysely";
import { getAdapter } from "./adapters/utils";
import { createInternalAdapter } from "./db";
@@ -13,13 +14,13 @@ import { createLogger } from "./utils/logger";
export const init = (options: BetterAuthOptions) => {
const adapter = getAdapter(options);
const db = createKyselyAdapter(options);
- const baseURL = getBaseURL(options.baseURL, options.basePath);
+ const { baseURL, withPath } = getBaseURL(options.baseURL, options.basePath);
return {
options: {
...options,
- baseURL,
+ baseURL: baseURL,
},
- baseURL,
+ baseURL: withPath,
secret:
options.secret ||
process.env.BETTER_AUTH_SECRET ||
diff --git a/packages/better-auth/src/plugins/organization/call.ts b/packages/better-auth/src/plugins/organization/call.ts
index ac000dde..38b64c23 100644
--- a/packages/better-auth/src/plugins/organization/call.ts
+++ b/packages/better-auth/src/plugins/organization/call.ts
@@ -3,7 +3,6 @@ import { createAuthMiddleware, optionsMiddleware } from "../../api/call";
import { OrganizationOptions } from "./organization";
import { defaultRoles, Role } from "./access";
import { Session, User } from "../../adapters/schema";
-import { getSession } from "../../api/routes";
import { sessionMiddleware } from "../../api/middlewares/session";
export const orgMiddleware = createAuthMiddleware(async (ctx) => {
diff --git a/packages/better-auth/src/plugins/organization/organization.ts b/packages/better-auth/src/plugins/organization/organization.ts
index 33554038..49774a46 100644
--- a/packages/better-auth/src/plugins/organization/organization.ts
+++ b/packages/better-auth/src/plugins/organization/organization.ts
@@ -9,7 +9,7 @@ import {
updateOrganization,
} from "./routes/crud-org";
import { AccessControl, defaultRoles, defaultStatements, Role } from "./access";
-import { getSession } from "../../api/routes";
+import { getSessionFromCtx } from "../../api/routes";
import { AuthContext } from "../../init";
import {
acceptInvitation,
@@ -18,6 +18,10 @@ import {
rejectInvitation,
} from "./routes/crud-invites";
import { deleteMember, updateMember } from "./routes/crud-members";
+import { sessionMiddleware } from "../../api/middlewares/session";
+import { orgMiddleware, orgSessionMiddleware } from "./call";
+import { getOrgAdapter } from "./adapter";
+import { APIError } from "better-call";
export interface OrganizationOptions {
/**
@@ -94,7 +98,7 @@ export const organization = (options?: O) => {
roles,
getSession: async (context: AuthContext) => {
//@ts-expect-error
- return await getSession(context);
+ return await getSessionFromCtx(context);
},
});
@@ -112,6 +116,7 @@ export const organization = (options?: O) => {
"/org/has-permission",
{
method: "POST",
+ requireHeaders: true,
body: z.object({
permission: z.record(z.string(), z.array(z.string())),
}) as unknown as ZodObject<{
@@ -122,10 +127,42 @@ export const organization = (options?: O) => {
>;
}>;
}>,
+ use: [orgSessionMiddleware],
},
- async () => {
- const hasPerm = true;
- return hasPerm;
+ async (ctx) => {
+ if (!ctx.context.session.session.activeOrganizationId) {
+ throw new APIError("BAD_REQUEST", {
+ message: "No active organization",
+ });
+ }
+ const adapter = getOrgAdapter(ctx.context.adapter);
+ const member = await adapter.findMemberByOrgId({
+ userId: ctx.context.session.user.id,
+ organizationId:
+ ctx.context.session.session.activeOrganizationId || "",
+ });
+ if (!member) {
+ throw new APIError("UNAUTHORIZED", {
+ message: "You are not a member of this organization",
+ });
+ }
+ const role = roles[member.role];
+ const result = role.authorize(ctx.body.permission as any);
+ if (result.error) {
+ return ctx.json(
+ {
+ error: result.error,
+ success: false,
+ },
+ {
+ status: 403,
+ },
+ );
+ }
+ return ctx.json({
+ error: null,
+ success: true,
+ });
},
),
},
diff --git a/packages/better-auth/src/plugins/remeber-me/index.ts b/packages/better-auth/src/plugins/remeber-me/index.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/better-auth/src/plugins/remeber-me/remeber-me.ts b/packages/better-auth/src/plugins/remeber-me/remeber-me.ts
deleted file mode 100644
index 004262db..00000000
--- a/packages/better-auth/src/plugins/remeber-me/remeber-me.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { BetterAuthPlugin } from "../../types/plugins";
-
-export const rememberMePlugin = async () => {
- return {} satisfies BetterAuthPlugin;
-};
diff --git a/packages/better-auth/src/types/context.ts b/packages/better-auth/src/types/context.ts
new file mode 100644
index 00000000..15ff2c7a
--- /dev/null
+++ b/packages/better-auth/src/types/context.ts
@@ -0,0 +1,13 @@
+import { ContextTools } from "better-call";
+import { AuthContext } from "../init";
+
+export type GenericEndpointContext = ContextTools & {
+ context: AuthContext;
+} & {
+ body: any;
+ request: Request;
+ headers: Headers;
+ params?: Record | undefined;
+ query: any;
+ method: "*";
+};
diff --git a/packages/better-auth/src/types/index.ts b/packages/better-auth/src/types/index.ts
index 9c15534f..84df9929 100644
--- a/packages/better-auth/src/types/index.ts
+++ b/packages/better-auth/src/types/index.ts
@@ -1 +1,2 @@
export * from "./options";
+export * from "./models";
diff --git a/packages/better-auth/src/types/models.ts b/packages/better-auth/src/types/models.ts
new file mode 100644
index 00000000..fb5cd7bc
--- /dev/null
+++ b/packages/better-auth/src/types/models.ts
@@ -0,0 +1,73 @@
+import { BetterAuthOptions } from ".";
+import { Session, User } from "../adapters/schema";
+import { BetterAuth } from "../auth";
+import { FieldAttribute, InferFieldOutput } from "../db";
+import { Prettify, UnionToIntersection } from "./helper";
+
+type AdditionalSessionFields =
+ Options["plugins"] extends Array
+ ? T extends {
+ schema: {
+ session: {
+ fields: infer Field;
+ };
+ };
+ }
+ ? Field extends Record
+ ? {
+ [key in keyof Field]: InferFieldOutput;
+ }
+ : {}
+ : {}
+ : {};
+type AdditionalUserFields =
+ Options["plugins"] extends Array
+ ? T extends {
+ schema: {
+ user: {
+ fields: infer Field;
+ };
+ };
+ }
+ ? Field extends Record
+ ? Prettify<
+ {
+ [key in Key as Field[key]["required"] extends false
+ ? never
+ : Field[key]["defaultValue"] extends
+ | boolean
+ | string
+ | number
+ | Date
+ | Function
+ ? key
+ : never]: InferFieldOutput;
+ } & {
+ [key in Key as Field[key]["returned"] extends false
+ ? never
+ : key]?: InferFieldOutput;
+ }
+ >
+ : {}
+ : {}
+ : {};
+
+export type InferUser =
+ UnionToIntersection<
+ User &
+ (O extends BetterAuthOptions
+ ? AdditionalUserFields
+ : O extends BetterAuth
+ ? AdditionalUserFields
+ : {})
+ >;
+
+export type InferSession =
+ UnionToIntersection<
+ Session &
+ (O extends BetterAuthOptions
+ ? AdditionalSessionFields
+ : O extends BetterAuth
+ ? AdditionalSessionFields
+ : {})
+ >;
diff --git a/packages/better-auth/src/types/options.ts b/packages/better-auth/src/types/options.ts
index 4c90bf43..b124646e 100644
--- a/packages/better-auth/src/types/options.ts
+++ b/packages/better-auth/src/types/options.ts
@@ -1,7 +1,7 @@
import { Dialect } from "kysely";
import type { FieldAttribute } from "../db/field";
import type { Provider } from "./provider";
-import type { Plugin } from "./plugins";
+import type { BetterAuthPlugin } from "./plugins";
import type { Adapter } from "./adapter";
import { User } from "../adapters/schema";
@@ -55,7 +55,7 @@ export interface BetterAuthOptions {
/**
* Plugins
*/
- plugins?: Plugin[];
+ plugins?: BetterAuthPlugin[];
/**
* Advanced options
*/
diff --git a/packages/better-auth/src/types/plugins.ts b/packages/better-auth/src/types/plugins.ts
index 8507d606..55cbc6a6 100644
--- a/packages/better-auth/src/types/plugins.ts
+++ b/packages/better-auth/src/types/plugins.ts
@@ -1,8 +1,9 @@
import { Migration } from "kysely";
-import { AuthEndpoint, AuthMiddleware } from "../api/call";
+import { AuthEndpoint } from "../api/call";
import { FieldAttribute } from "../db/field";
import { LiteralString } from "./helper";
-import { Endpoint } from "better-call";
+import { Endpoint, EndpointResponse } from "better-call";
+import { GenericEndpointContext } from "./context";
export type PluginSchema = {
[table: string]: {
@@ -22,6 +23,28 @@ export type BetterAuthPlugin = {
path: string;
middleware: Endpoint;
}[];
+ hooks?: {
+ before?: {
+ matcher: (context: GenericEndpointContext) => boolean;
+ handler: Endpoint<
+ (context: GenericEndpointContext) => Promise;
+ }>
+ >;
+ }[];
+ after?: {
+ matcher: (context: GenericEndpointContext) => boolean;
+ handler: Endpoint<
+ (
+ context: GenericEndpointContext & {
+ returned: EndpointResponse;
+ },
+ ) => Promise
+ >;
+ }[];
+ };
/**
* Schema the plugin needs
*
diff --git a/packages/better-auth/src/utils/base-url.ts b/packages/better-auth/src/utils/base-url.ts
index 7eb7b04b..db1d600b 100644
--- a/packages/better-auth/src/utils/base-url.ts
+++ b/packages/better-auth/src/utils/base-url.ts
@@ -11,10 +11,16 @@ function checkHasPath(url: string): boolean {
function withPath(url: string, path = "/api/auth") {
const hasPath = checkHasPath(url);
if (hasPath) {
- return url;
+ return {
+ baseURL: new URL(url).origin,
+ withPath: url,
+ };
}
path = path.startsWith("/") ? path : `/${path}`;
- return `${url}${path}`;
+ return {
+ baseURL: url,
+ withPath: `${url}${path}`,
+ };
}
export function getBaseURL(url?: string, path?: string) {
@@ -33,7 +39,10 @@ export function getBaseURL(url?: string, path?: string) {
!fromEnv &&
(process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test")
) {
- return "http://localhost:3000/api/auth";
+ return {
+ baseURL: "http://localhost:3000",
+ withPath: "http://localhost:3000/api/auth",
+ };
}
throw new Error(
"Could not infer baseURL from environment variables. Please pass it as an option to the createClient function.",
diff --git a/packages/better-auth/tsup.config.ts b/packages/better-auth/tsup.config.ts
index 0aaaa522..d23f7269 100644
--- a/packages/better-auth/tsup.config.ts
+++ b/packages/better-auth/tsup.config.ts
@@ -4,6 +4,7 @@ export default defineConfig({
entry: {
index: "./src/index.ts",
provider: "./src/providers/index.ts",
+ types: "./src/types/index.ts",
client: "./src/client/index.ts",
cli: "./src/cli/index.ts",
react: "./src/client/react.ts",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d9b99ec1..f878154e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -51,7 +51,7 @@ importers:
devDependencies:
'@types/bun':
specifier: latest
- version: 1.1.7
+ version: 1.1.8
vite:
specifier: ^5.3.5
version: 5.3.5(@types/node@20.14.12)
@@ -478,8 +478,8 @@ importers:
specifier: ^1.9.2
version: 1.9.2
better-call:
- specifier: ^0.1.33
- version: 0.1.33(typescript@5.5.4)
+ specifier: ^0.1.36
+ version: 0.1.36(typescript@5.5.4)
chalk:
specifier: ^5.3.0
version: 5.3.0
@@ -2659,8 +2659,8 @@ packages:
'@types/better-sqlite3@7.6.11':
resolution: {integrity: sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==}
- '@types/bun@1.1.7':
- resolution: {integrity: sha512-iIIn26SOX8qI5E8Juh+0rUgBmFHvll1akscwerhp9O/fHZGdQBWNLJkkRg/3z2Mh6a3ZgWUIkXViLZZYg47TXw==}
+ '@types/bun@1.1.8':
+ resolution: {integrity: sha512-PIwVFQKPviksiibobyvcWtMvMFMTj91T8dQEh9l1P3Ypr3ZuVn9w7HSr+5mTNrPqD1xpdDLEErzZPU8gqHBu6g==}
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@@ -3042,10 +3042,10 @@ packages:
peerDependencies:
typescript: ^5.0.0
- better-call@0.1.33:
- resolution: {integrity: sha512-gzthE/AnimwMCNBNyy9LRqqAtjXkqO+dR4n1OjCiUhiBK4X+NZMKekQUKIzpfwDRC4k3hCshb1LsPhqwiSM7Bw==}
+ better-call@0.1.36:
+ resolution: {integrity: sha512-+FsoIB8tMVRciTTN9UUXXoJEzAqIaaNPrxa9kDYoTaGCTCimNKbHNuLwsnIAibamG0Lo7CFCiSL4yyV+I32O4A==}
peerDependencies:
- typescript: ^5.0.0
+ typescript: ^5.6.0-beta
better-sqlite3@11.1.2:
resolution: {integrity: sha512-gujtFwavWU4MSPT+h9B+4pkvZdyOUkH54zgLdIrMmmmd4ZqiBIrRNBzNzYVFO417xo882uP5HBu4GjOfaSrIQw==}
@@ -3089,8 +3089,8 @@ packages:
bun-html-live-reload@0.1.3:
resolution: {integrity: sha512-PW1sp9ZmBAqiAa0aUhHpFc6sEQmC6FgRNKVAvcjSDUMqASzgq7xYpNkEt2Z6VjuiPXKtOx/49b6sLLmjyojrOw==}
- bun-types@1.1.25:
- resolution: {integrity: sha512-WpRb8/N3S5IE8UYdIn39+0Is1XzxsC78+MCe5cIdaer0lfFs6+DREtQH9TM6KJNKTxBYDvbx81RwbvxS5+CkVQ==}
+ bun-types@1.1.26:
+ resolution: {integrity: sha512-n7jDe62LsB2+WE8Q8/mT3azkPaatKlj/2MyP6hi3mKvPz9oPpB6JW/Ll6JHtNLudasFFuvfgklYSE+rreGvBjw==}
bundle-require@5.0.0:
resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==}
@@ -8820,9 +8820,9 @@ snapshots:
dependencies:
'@types/node': 20.14.12
- '@types/bun@1.1.7':
+ '@types/bun@1.1.8':
dependencies:
- bun-types: 1.1.25
+ bun-types: 1.1.26
'@types/cookie@0.6.0': {}
@@ -9275,7 +9275,7 @@ snapshots:
rou3: 0.5.1
typescript: 5.5.4
- better-call@0.1.33(typescript@5.5.4):
+ better-call@0.1.36(typescript@5.5.4):
dependencies:
'@better-fetch/fetch': 1.1.4
'@types/set-cookie-parser': 2.4.10
@@ -9343,7 +9343,7 @@ snapshots:
bun-html-live-reload@0.1.3: {}
- bun-types@1.1.25:
+ bun-types@1.1.26:
dependencies:
'@types/node': 20.12.14
'@types/ws': 8.5.11
diff --git a/todo.md b/todo.md
index 6ffb402c..08946f7d 100644
--- a/todo.md
+++ b/todo.md
@@ -1,4 +1,4 @@
## TODO
[ ] handle migration when the config removes existing schema
[ ] refresh oauth tokens
-[ ] remember me functionality
\ No newline at end of file
+[x] remember me functionality
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index 813291fb..5a72832f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,6 +5,7 @@
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
+ "disableReferencedProjectLoad": true,
"moduleDetection": "force",
"isolatedModules": true,
"strict": true,