mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
feat: new client primitives
This commit is contained in:
@@ -31,8 +31,7 @@
|
||||
".next",
|
||||
".svelte-kit",
|
||||
"package.json",
|
||||
".contentlayer",
|
||||
"dev"
|
||||
".contentlayer"
|
||||
]
|
||||
}
|
||||
}
|
||||
23
dev/bc-fe/hono/auth.d.ts
vendored
Normal file
23
dev/bc-fe/hono/auth.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
export type Auth = {
|
||||
"baseURL": "http://localhost:3000",
|
||||
"basePath": "/auth",
|
||||
"database": {
|
||||
"provider": "sqlite",
|
||||
"url": "./db.sqlite"
|
||||
},
|
||||
"socialProvider": [
|
||||
{
|
||||
"id": "github"
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
{
|
||||
"id": "two-factor",
|
||||
"endpoints": {}
|
||||
},
|
||||
{
|
||||
"id": "organization",
|
||||
"endpoints": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
dev/bc-fe/hono/db.sqlite
Normal file
BIN
dev/bc-fe/hono/db.sqlite
Normal file
Binary file not shown.
17
dev/bc-fe/hono/package.json
Normal file
17
dev/bc-fe/hono/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "hono",
|
||||
"scripts": {
|
||||
"dev": "tsx -r dotenv/config src/index.ts",
|
||||
"auth": "pnpm better-auth"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.12.2",
|
||||
"better-auth": "^0.0.2-beta.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"hono": "^4.5.9",
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
}
|
||||
}
|
||||
27
dev/bc-fe/hono/src/auth.ts
Normal file
27
dev/bc-fe/hono/src/auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { organization, twoFactor } from "better-auth/plugins";
|
||||
import { github } from "better-auth/social-providers";
|
||||
|
||||
export const auth = betterAuth({
|
||||
baseURL: "http://localhost:3000",
|
||||
basePath: "/auth",
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
socialProvider: [
|
||||
github({
|
||||
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||
}),
|
||||
],
|
||||
plugins: [
|
||||
twoFactor({
|
||||
issuer: "BetterAuth",
|
||||
}),
|
||||
organization(),
|
||||
],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
24
dev/bc-fe/hono/src/index.ts
Normal file
24
dev/bc-fe/hono/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Hono } from "hono";
|
||||
import { auth } from "./auth";
|
||||
import { serve } from "@hono/node-server";
|
||||
import { cors } from "hono/cors";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use(
|
||||
"/api/auth/**",
|
||||
cors({
|
||||
origin: "http://localhost:5173",
|
||||
allowHeaders: ["Content-Type", "Authorization"],
|
||||
allowMethods: ["POST", "GET", "OPTIONS"],
|
||||
exposeHeaders: ["Content-Length"],
|
||||
maxAge: 600,
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.on(["POST", "GET"], "/api/auth/**", (c) => {
|
||||
return auth.handler(c.req.raw);
|
||||
});
|
||||
|
||||
serve(app);
|
||||
@@ -10,6 +10,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-auth": "workspace:^0.0.2-beta.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
117
dev/bc-fe/react/src/App.tsx
Normal file
117
dev/bc-fe/react/src/App.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { useState } from "react";
|
||||
import "./App.css";
|
||||
import { auth } from "./lib/auth";
|
||||
|
||||
function App() {
|
||||
const session = auth.useSession()
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
Better Auth
|
||||
</p>
|
||||
<div>
|
||||
{
|
||||
session ? (
|
||||
<div style={{
|
||||
borderRadius: "10px",
|
||||
border: "1px solid #4B453F",
|
||||
padding: "10px",
|
||||
gap: "10px",
|
||||
}}>
|
||||
<p>
|
||||
{session.user.name}
|
||||
</p>
|
||||
<p>
|
||||
{session.user.email}
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
|
||||
{
|
||||
session.user.twoFactorEnabled ? (
|
||||
<button onClick={async () => {
|
||||
await auth.twoFactor.disable()
|
||||
}}>
|
||||
Disable 2FA
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={async () => {
|
||||
await auth.twoFactor.enable()
|
||||
}}>
|
||||
Enable 2FA
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
<button onClick={async () => {
|
||||
await auth.signOut()
|
||||
}}>
|
||||
Signout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<button onClick={async () => {
|
||||
await auth.signIn.social({
|
||||
provider: "github",
|
||||
})
|
||||
}}>
|
||||
Continue with github
|
||||
</button>
|
||||
<SignUp />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
|
||||
function SignUp() {
|
||||
const [email, setEmail] = useState("")
|
||||
const [name, setName] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
borderRadius: "10px",
|
||||
border: "1px solid #4B453F",
|
||||
padding: "20px",
|
||||
marginTop: "10px"
|
||||
}}>
|
||||
<input type="email" id="email" placeholder="Email" style={{
|
||||
width: "100%",
|
||||
}}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<input type="name" id="name" placeholder="Name" style={{
|
||||
width: "100%"
|
||||
}}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<input type="password" id="password" placeholder="Password" style={{
|
||||
width: "100%"
|
||||
}}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button onClick={async () => {
|
||||
const res = await auth.signUp.credential({
|
||||
email,
|
||||
password,
|
||||
name
|
||||
})
|
||||
console.log(res)
|
||||
}}>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
7
dev/bc-fe/react/src/lib/auth.ts
Normal file
7
dev/bc-fe/react/src/lib/auth.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { twoFactorClient } from "better-auth/client";
|
||||
|
||||
export const auth = createAuthClient({
|
||||
baseURL: "http://localhost:3000/api/auth",
|
||||
authPlugins: [twoFactorClient],
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"name": "hono",
|
||||
"scripts": {
|
||||
"dev": "bun run --hot src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"hono": "^4.5.9",
|
||||
"better-auth": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { github } from "better-auth/social-providers";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
socialProvider: [github({
|
||||
clientId: process.env.GITHUB_CLIENT_ID as string,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||
})],
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
import { auth } from "./auth";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.get("/", (c) => {
|
||||
return c.text("Hello Hono!");
|
||||
});
|
||||
|
||||
app.all("/auth/**", async(c)=>{
|
||||
return auth.handler(c.req.raw)
|
||||
})
|
||||
|
||||
export default app;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import viteLogo from "/vite.svg";
|
||||
import reactLogo from "./assets/react.svg";
|
||||
import "./App.css";
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className="card">
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test HMR
|
||||
</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"auth": "pnpm better-auth",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
@@ -23,7 +24,7 @@
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"better-auth": "workspace:*",
|
||||
"better-auth": "^0.0.2-beta.8",
|
||||
"better-call": "^0.1.0",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
twoFactor,
|
||||
validEmail,
|
||||
} from "better-auth/plugins";
|
||||
import { github } from "better-auth/provider";
|
||||
import { github } from "better-auth/social-providers";
|
||||
|
||||
export const auth = betterAuth({
|
||||
basePath: "/api/auth",
|
||||
|
||||
@@ -9,19 +9,27 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"bin": "./dist/cli.js",
|
||||
"publishConfig": {
|
||||
"executableFiles": [
|
||||
"./dist/cli.js"
|
||||
]
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./social-providers": "./dist/social.js",
|
||||
"./client": "./dist/client.js",
|
||||
"./client/plugins": "./dist/client/plugins.js",
|
||||
"./types": "./dist/types.js",
|
||||
"./cli": "./dist/cli.js",
|
||||
"./react": "./dist/react.js",
|
||||
"./preact": "./dist/preact.js",
|
||||
"./solid": "./dist/solid.js",
|
||||
"./vue": "./dist/vue.js",
|
||||
"./plugins": "./dist/plugins.js"
|
||||
"./plugins": "./dist/plugins.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simplewebauthn/types": "^10.0.0",
|
||||
@@ -68,6 +76,7 @@
|
||||
"oslo": "^1.2.1",
|
||||
"pg": "^8.12.0",
|
||||
"prompts": "^2.4.2",
|
||||
"ts-morph": "^23.0.0",
|
||||
"zod": "^3.22.5"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -16,8 +16,9 @@ import {
|
||||
verifyEmail,
|
||||
} from "./routes";
|
||||
import { getCSRFToken } from "./routes/csrf";
|
||||
import { ok } from "./routes/ok";
|
||||
import { ok, welcome } from "./routes/ok";
|
||||
import { signUpCredential } from "./routes/sign-up";
|
||||
import { error } from "./routes/error";
|
||||
|
||||
export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
||||
ctx: C,
|
||||
@@ -93,6 +94,8 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
||||
...baseEndpoints,
|
||||
...pluginEndpoints,
|
||||
ok,
|
||||
welcome,
|
||||
error,
|
||||
};
|
||||
let api: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(endpoints)) {
|
||||
@@ -166,7 +169,9 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
||||
let body: Record<string, any> = {};
|
||||
try {
|
||||
body = await res.json();
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
return res;
|
||||
}
|
||||
if (body?.user) {
|
||||
body.user = parseUser(ctx.options, body.user);
|
||||
}
|
||||
|
||||
99
packages/better-auth/src/api/routes/error.ts
Normal file
99
packages/better-auth/src/api/routes/error.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { createAuthEndpoint } from "../call";
|
||||
|
||||
const html = (errorCode: string = "Unknown") => `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Authentication Error</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #f8f9fa;
|
||||
--text-color: #212529;
|
||||
--accent-color: #000000;
|
||||
--error-color: #dc3545;
|
||||
--border-color: #e9ecef;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.error-container {
|
||||
background-color: #ffffff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
padding: 2.5rem;
|
||||
text-align: center;
|
||||
max-width: 90%;
|
||||
width: 400px;
|
||||
}
|
||||
h1 {
|
||||
color: var(--error-color);
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
color: #495057;
|
||||
}
|
||||
.btn {
|
||||
background-color: var(--accent-color);
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
border: 2px solid var(--accent-color);
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #131721;
|
||||
}
|
||||
.error-code {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
.icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="icon">⚠️</div>
|
||||
<h1>Better Auth Error</h1>
|
||||
<p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
|
||||
<a href="#" id="returnLink" class="btn">Return to Application</a>
|
||||
<div class="error-code">Error Code: <span id="errorCode">${errorCode}</span></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
export const error = createAuthEndpoint(
|
||||
"/error",
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
async (c) => {
|
||||
const query =
|
||||
new URL(c.request?.url || "").searchParams.get("error") || "Unknown";
|
||||
return new Response(html(query), {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -11,3 +11,13 @@ export const ok = createAuthEndpoint(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export const welcome = createAuthEndpoint(
|
||||
"/welcome/ok",
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
async () => {
|
||||
return new Response("Welcome to Better Auth");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -22,8 +22,8 @@ export const betterAuth = <O extends BetterAuthOptions>(options: O) => {
|
||||
};
|
||||
};
|
||||
|
||||
export type BetterAuth<Endpoints extends Record<string, any> = {}> = {
|
||||
export type Auth = {
|
||||
handler: (request: Request) => Promise<Response>;
|
||||
api: Endpoints;
|
||||
api: ReturnType<typeof router>["endpoints"];
|
||||
options: BetterAuthOptions;
|
||||
};
|
||||
|
||||
62
packages/better-auth/src/cli/commands/export-type.ts
Normal file
62
packages/better-auth/src/cli/commands/export-type.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Command } from "commander";
|
||||
import { getConfig } from "../get-config";
|
||||
import { Project } from "ts-morph";
|
||||
import type { Provider } from "../../social-providers";
|
||||
import * as fs from "fs/promises";
|
||||
import type { BetterAuthPlugin } from "../../types/plugins";
|
||||
|
||||
export const exportTypes = new Command()
|
||||
.name("export")
|
||||
.description("Export types")
|
||||
.option("--config <config>", "the path to the configuration file")
|
||||
.option(
|
||||
"--cwd <cwd>",
|
||||
"the working directory. defaults to the current directory.",
|
||||
process.cwd(),
|
||||
)
|
||||
.action(async (opts) => {
|
||||
const config = await getConfig({ cwd: opts.cwd, configPath: opts.config });
|
||||
if (config?.socialProvider) {
|
||||
config.socialProvider = config.socialProvider.map((provider) => {
|
||||
if ("scopes" in provider) {
|
||||
provider.scopes = [];
|
||||
}
|
||||
if ("handler" in provider) {
|
||||
provider.handler = undefined;
|
||||
}
|
||||
|
||||
return provider;
|
||||
});
|
||||
}
|
||||
if (config?.database) {
|
||||
config.database = {
|
||||
provider: "sqlite",
|
||||
url: "./db.sqlite",
|
||||
};
|
||||
}
|
||||
if (config?.socialProvider) {
|
||||
config.socialProvider = config.socialProvider.map((provider) => {
|
||||
return {
|
||||
id: provider.id,
|
||||
} as Provider;
|
||||
});
|
||||
}
|
||||
if (config?.plugins) {
|
||||
config.plugins = config.plugins.map((plugin) => {
|
||||
return {
|
||||
id: plugin.id,
|
||||
endpoints: plugin.endpoints,
|
||||
};
|
||||
});
|
||||
}
|
||||
const project = new Project();
|
||||
const sourceFile = project.createSourceFile(
|
||||
"auth.d.ts",
|
||||
(writer) =>
|
||||
writer.write(`export type Auth = ${JSON.stringify(config, null, 2)}`),
|
||||
{
|
||||
overwrite: true,
|
||||
},
|
||||
);
|
||||
await sourceFile.save();
|
||||
});
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Command } from "commander";
|
||||
import "dotenv/config";
|
||||
import { migrate } from "./commands/migrate";
|
||||
import { exportTypes } from "./commands/export-type";
|
||||
|
||||
async function main() {
|
||||
const program = new Command().name("better-auth");
|
||||
program.addCommand(migrate);
|
||||
program.addCommand(migrate).addCommand(exportTypes);
|
||||
program.parse();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { createFetch } from "@better-fetch/fetch";
|
||||
import type { router } from "../api";
|
||||
import type { BetterAuth } from "../auth";
|
||||
import type { Auth } from "../auth";
|
||||
import { getBaseURL } from "../utils/base-url";
|
||||
import { addCurrentURL, csrfPlugin, redirectPlugin } from "./fetch-plugins";
|
||||
import type { InferRoutes } from "./path-to-object";
|
||||
import { getOrganizationAtoms } from "./plugins/org-client";
|
||||
import { getPasskeyActions } from "./plugins/passkey-client";
|
||||
import { createDynamicPathProxy } from "./proxy";
|
||||
import { createDynamicPathProxy, type AuthProxySignal } from "./proxy";
|
||||
import { getSessionAtom } from "./session-atom";
|
||||
import type { ClientOptions, HasPlugin } from "./type";
|
||||
import type { AuthPlugin, ClientOptions } from "./type";
|
||||
import type { UnionToIntersection } from "../types/helper";
|
||||
import type { PreinitializedWritableAtom } from "nanostores";
|
||||
import type { BetterAuthPlugin } from "../types/plugins";
|
||||
|
||||
export const createAuthClient = <Auth extends BetterAuth = never>(
|
||||
options?: ClientOptions,
|
||||
export const createAuthClient = <O extends ClientOptions = ClientOptions>(
|
||||
options?: O,
|
||||
) => {
|
||||
type BAuth = Auth extends never ? BetterAuth : Auth;
|
||||
type API = Auth extends never
|
||||
? ReturnType<typeof router>["endpoints"]
|
||||
: BAuth["api"];
|
||||
type API = O["authPlugins"] extends Array<any>
|
||||
? (O["authPlugins"] extends Array<infer Pl>
|
||||
? UnionToIntersection<
|
||||
//@ts-expect-error
|
||||
ReturnType<Pl> extends {
|
||||
plugin: infer Plug;
|
||||
}
|
||||
? Plug extends BetterAuthPlugin
|
||||
? Plug["endpoints"]
|
||||
: {}
|
||||
: {}
|
||||
>
|
||||
: {}) &
|
||||
Auth["api"]
|
||||
: Auth["api"];
|
||||
const $fetch = createFetch({
|
||||
method: "GET",
|
||||
...options,
|
||||
@@ -28,72 +39,101 @@ export const createAuthClient = <Auth extends BetterAuth = never>(
|
||||
...(options?.plugins || []),
|
||||
],
|
||||
});
|
||||
const { $session, $sessionSignal } = getSessionAtom<Auth>($fetch);
|
||||
const { signInPasskey, register } = getPasskeyActions($fetch);
|
||||
const {
|
||||
$activeOrganization,
|
||||
$listOrganizations,
|
||||
activeOrgId,
|
||||
$listOrg,
|
||||
$activeOrgSignal,
|
||||
|
||||
$activeInvitationId,
|
||||
$invitation,
|
||||
} = getOrganizationAtoms($fetch, $session);
|
||||
|
||||
const actions = {
|
||||
setActiveOrganization: (orgId: string | null) => {
|
||||
activeOrgId.set(orgId);
|
||||
},
|
||||
setInvitationId: (id: string | null) => {
|
||||
$activeInvitationId.set(id);
|
||||
},
|
||||
passkey: {
|
||||
signIn: signInPasskey,
|
||||
/**
|
||||
* Add a new passkey
|
||||
*/
|
||||
register: register,
|
||||
},
|
||||
$atoms: {
|
||||
$session,
|
||||
$activeOrganization,
|
||||
$listOrganizations,
|
||||
$activeInvitationId,
|
||||
$invitation,
|
||||
},
|
||||
$fetch,
|
||||
type Plugins = O["authPlugins"] extends Array<AuthPlugin>
|
||||
? Array<ReturnType<O["authPlugins"][number]>["plugin"]>
|
||||
: undefined;
|
||||
//@ts-expect-error
|
||||
const { $session, $sessionSignal } = getSessionAtom<{
|
||||
handler: any;
|
||||
api: any;
|
||||
options: {
|
||||
database: any;
|
||||
plugins: Plugins;
|
||||
};
|
||||
type HasPasskeyConfig = HasPlugin<"passkey", BAuth>;
|
||||
type HasOrganizationConfig = HasPlugin<"organization", BAuth>;
|
||||
type Actions = Pick<
|
||||
typeof actions,
|
||||
| (HasPasskeyConfig extends true ? "passkey" : never)
|
||||
| (HasOrganizationConfig extends true
|
||||
? "setActiveOrganization" | "setInvitationId"
|
||||
: never)
|
||||
| "$atoms"
|
||||
| "$fetch"
|
||||
}>($fetch);
|
||||
|
||||
let pluginsActions = {} as Record<string, any>;
|
||||
type PluginActions = UnionToIntersection<
|
||||
O["authPlugins"] extends Array<infer Pl>
|
||||
? //@ts-expect-error
|
||||
ReturnType<Pl> extends {
|
||||
actions?: infer R;
|
||||
}
|
||||
? R
|
||||
: {}
|
||||
: {}
|
||||
>;
|
||||
|
||||
const proxy = createDynamicPathProxy(actions, $fetch, [
|
||||
const pluginProxySignals: AuthProxySignal[] = [];
|
||||
let pluginSignals: Record<string, PreinitializedWritableAtom<boolean>> = {};
|
||||
let pluginPathMethods: Record<string, "POST" | "GET"> = {};
|
||||
|
||||
for (const plugin of options?.authPlugins || []) {
|
||||
const pl = plugin($fetch);
|
||||
if (pl.authProxySignal) {
|
||||
pluginProxySignals.push(...pl.authProxySignal);
|
||||
}
|
||||
if (pl.actions) {
|
||||
pluginsActions = {
|
||||
...pluginsActions,
|
||||
...pl.actions,
|
||||
};
|
||||
}
|
||||
if (pl.signals) {
|
||||
pluginSignals = {
|
||||
...pluginSignals,
|
||||
...pl.signals,
|
||||
};
|
||||
}
|
||||
if (pl.pathMethods) {
|
||||
pluginPathMethods = {
|
||||
...pluginPathMethods,
|
||||
...pl.pathMethods,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
$atoms: {
|
||||
$session,
|
||||
},
|
||||
$fetch,
|
||||
...(pluginsActions as object),
|
||||
};
|
||||
|
||||
type Actions = typeof actions & PluginActions;
|
||||
|
||||
const proxy = createDynamicPathProxy(
|
||||
actions,
|
||||
$fetch,
|
||||
{
|
||||
...pluginPathMethods,
|
||||
"/sign-out": "POST",
|
||||
},
|
||||
[
|
||||
{
|
||||
matcher: (path) => path === "/organization/create",
|
||||
atom: $listOrg,
|
||||
atom: "$listOrg",
|
||||
},
|
||||
{
|
||||
matcher: (path) => path.startsWith("/organization"),
|
||||
atom: $activeOrgSignal,
|
||||
atom: "$activeOrgSignal",
|
||||
},
|
||||
{
|
||||
matcher: (path) => path === "/sign-out",
|
||||
atom: $sessionSignal,
|
||||
atom: "$sessionSignal",
|
||||
},
|
||||
{
|
||||
matcher: (path) =>
|
||||
path === "/two-factor/enable" || path === "/two-factor/send-otp",
|
||||
atom: $sessionSignal,
|
||||
matcher: (path) => path.startsWith("/sign-up"),
|
||||
atom: "$sessionSignal",
|
||||
},
|
||||
]) as unknown as InferRoutes<API> & Actions;
|
||||
...pluginProxySignals,
|
||||
],
|
||||
{
|
||||
$sessionSignal,
|
||||
...pluginSignals,
|
||||
},
|
||||
) as unknown as InferRoutes<API> & Actions;
|
||||
return proxy;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { createClient } from "better-call/client";
|
||||
import { describe, it } from "vitest";
|
||||
import { passkey } from "../plugins";
|
||||
|
||||
import { getTestInstance } from "../test-utils/test-instance";
|
||||
import { createAuthClient } from "./react";
|
||||
import { createAuthClient } from "./base";
|
||||
import { createAuthClient as createReactClient } from "./react";
|
||||
import { twoFactor } from "./plugins/two-factor";
|
||||
import { organization } from "./plugins/organization";
|
||||
import { passkey } from "./plugins/passkey";
|
||||
import { twoFactorClient } from "../plugins";
|
||||
|
||||
describe("client path to object", async () => {
|
||||
const auth = await getTestInstance({
|
||||
plugins: [
|
||||
passkey({
|
||||
rpID: "test",
|
||||
rpName: "test",
|
||||
}),
|
||||
// passkey({
|
||||
// rpID: "test",
|
||||
// rpName: "test",
|
||||
// }),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -20,6 +24,13 @@ describe("client path to object", async () => {
|
||||
customFetchImpl: async (url, options) => {
|
||||
return new Response();
|
||||
},
|
||||
});
|
||||
authPlugins: [twoFactor, passkey],
|
||||
});
|
||||
client.$atoms.$session;
|
||||
|
||||
const client2 = createReactClient({
|
||||
authPlugins: [organization, twoFactorClient],
|
||||
});
|
||||
client2.$atoms.$session;
|
||||
});
|
||||
});
|
||||
|
||||
44
packages/better-auth/src/client/create-client-plugin.ts
Normal file
44
packages/better-auth/src/client/create-client-plugin.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { BetterFetch } from "@better-fetch/fetch";
|
||||
import type { Endpoint } from "better-call";
|
||||
import type { AuthProxySignal } from "./proxy";
|
||||
import type { Atom, PreinitializedWritableAtom } from "nanostores";
|
||||
import type { BetterAuthPlugin } from "../types/plugins";
|
||||
import type { AuthPlugin } from "./type";
|
||||
import type { useAuthStore as reactStore } from "./react";
|
||||
import type { useAuthStore as vueStore } from "./vue";
|
||||
import type { useAuthStore as preactStore } from "./preact";
|
||||
|
||||
export const createClientPlugin = <E extends BetterAuthPlugin = never>() => {
|
||||
return <
|
||||
Actions extends Record<string, any>,
|
||||
Integrations extends {
|
||||
react?: (useStore: typeof reactStore) => Record<string, any>;
|
||||
vue?: (useStore: typeof vueStore) => Record<string, any>;
|
||||
preact?: (useStore: typeof preactStore) => Record<string, any>;
|
||||
},
|
||||
>(
|
||||
$fn: ($fetch: BetterFetch) => {
|
||||
id: string;
|
||||
actions?: Actions;
|
||||
authProxySignal?: AuthProxySignal[];
|
||||
signals?: Record<string, PreinitializedWritableAtom<boolean>>;
|
||||
atoms?: Record<string, Atom<any>>;
|
||||
integrations?: Integrations;
|
||||
pathMethods?: Record<string, "POST" | "GET">;
|
||||
},
|
||||
) => {
|
||||
return ($fetch: BetterFetch) => {
|
||||
const data = $fn($fetch);
|
||||
return {
|
||||
...data,
|
||||
integrations: data.integrations as Integrations,
|
||||
plugin: {} as E,
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export interface AuthClientPlugin {
|
||||
id: string;
|
||||
endpoint: Record<string, Endpoint>;
|
||||
}
|
||||
@@ -38,9 +38,11 @@ export const csrfPlugin = {
|
||||
const { data, error } = await betterFetch<{
|
||||
csrfToken: string;
|
||||
}>("/csrf", {
|
||||
body: undefined,
|
||||
baseURL: options.baseURL,
|
||||
...options,
|
||||
plugins: [],
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
if (error?.status === 404) {
|
||||
throw new BetterAuthError(
|
||||
@@ -55,6 +57,7 @@ export const csrfPlugin = {
|
||||
csrfToken: data.csrfToken,
|
||||
};
|
||||
}
|
||||
options.credentials = "include";
|
||||
return { url, options };
|
||||
},
|
||||
} satisfies BetterFetchPlugin;
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./base";
|
||||
export * from "./plugins";
|
||||
|
||||
3
packages/better-auth/src/client/plugins.ts
Normal file
3
packages/better-auth/src/client/plugins.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "../plugins/two-factor/client";
|
||||
export * from "../plugins/organization/client";
|
||||
export * from "../plugins/passkey/client";
|
||||
@@ -1,92 +0,0 @@
|
||||
import { type BetterFetch, BetterFetchError } from "@better-fetch/fetch";
|
||||
import { type Atom, atom, computed, onMount, task } from "nanostores";
|
||||
import type {
|
||||
Invitation,
|
||||
Member,
|
||||
Organization,
|
||||
} from "../../plugins/organization/schema";
|
||||
import type { Prettify } from "../../types/helper";
|
||||
|
||||
export function getOrganizationAtoms($fetch: BetterFetch, $session: Atom) {
|
||||
const $listOrg = atom<boolean>(false);
|
||||
const activeOrgId = atom<string | null>(null);
|
||||
const $activeOrgSignal = atom<boolean>(false);
|
||||
const $activeOrganization = computed([activeOrgId, $activeOrgSignal], () =>
|
||||
task(async () => {
|
||||
if (!activeOrgId.get()) {
|
||||
return null;
|
||||
}
|
||||
const session = $session.get();
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
const organization = await $fetch<
|
||||
Prettify<
|
||||
Organization & {
|
||||
members: Member[];
|
||||
invitations: Invitation[];
|
||||
}
|
||||
>
|
||||
>("/organization/set-active", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: {
|
||||
orgId: activeOrgId.get(),
|
||||
},
|
||||
});
|
||||
return organization.data;
|
||||
}),
|
||||
);
|
||||
|
||||
const $listOrganizations = computed($listOrg, () =>
|
||||
task(async () => {
|
||||
const organizations = await $fetch<Organization[]>("/organization/list", {
|
||||
method: "GET",
|
||||
});
|
||||
return organizations.data;
|
||||
}),
|
||||
);
|
||||
|
||||
const $activeInvitationId = atom<string | null>(null);
|
||||
const $invitation = computed($activeInvitationId, () =>
|
||||
task(async () => {
|
||||
if (!$activeInvitationId.get()) {
|
||||
return {
|
||||
error: {
|
||||
message: "No invitation found",
|
||||
status: 400,
|
||||
data: null,
|
||||
},
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
const res = await $fetch<
|
||||
Prettify<
|
||||
Invitation & {
|
||||
organizationName: string;
|
||||
organizationSlug: string;
|
||||
inviterEmail: string;
|
||||
inviterName: string;
|
||||
}
|
||||
>
|
||||
>("/organization/get-active-invitation", {
|
||||
method: "GET",
|
||||
query: {
|
||||
id: $activeInvitationId.get(),
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
return res;
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
$listOrganizations,
|
||||
$activeOrganization,
|
||||
activeOrgId,
|
||||
$listOrg,
|
||||
$activeOrgSignal,
|
||||
$activeInvitationId,
|
||||
$invitation,
|
||||
};
|
||||
}
|
||||
140
packages/better-auth/src/client/plugins/organization.ts
Normal file
140
packages/better-auth/src/client/plugins/organization.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { atom, computed, task } from "nanostores";
|
||||
import type {
|
||||
Invitation,
|
||||
Member,
|
||||
Organization,
|
||||
} from "../../plugins/organization/schema";
|
||||
import type { Prettify } from "../../types/helper";
|
||||
import type { organization as org } from "../../plugins";
|
||||
import { createClientPlugin } from "../create-client-plugin";
|
||||
|
||||
export const organization = createClientPlugin<ReturnType<typeof org>>()(
|
||||
($fetch) => {
|
||||
const activeOrgId = atom<string | null>(null);
|
||||
const $listOrg = atom<boolean>(false);
|
||||
const $activeOrgSignal = atom<boolean>(false);
|
||||
const $activeOrganization = computed([activeOrgId, $activeOrgSignal], () =>
|
||||
task(async () => {
|
||||
if (!activeOrgId.get()) {
|
||||
return null;
|
||||
}
|
||||
const organization = await $fetch<
|
||||
Prettify<
|
||||
Organization & {
|
||||
members: Member[];
|
||||
invitations: Invitation[];
|
||||
}
|
||||
>
|
||||
>("/organization/set-active", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: {
|
||||
orgId: activeOrgId.get(),
|
||||
},
|
||||
});
|
||||
return organization.data;
|
||||
}),
|
||||
);
|
||||
|
||||
const $listOrganizations = computed($listOrg, () =>
|
||||
task(async () => {
|
||||
const organizations = await $fetch<Organization[]>(
|
||||
"/organization/list",
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
);
|
||||
return organizations.data;
|
||||
}),
|
||||
);
|
||||
|
||||
const $activeInvitationId = atom<string | null>(null);
|
||||
const $invitation = computed($activeInvitationId, () =>
|
||||
task(async () => {
|
||||
if (!$activeInvitationId.get()) {
|
||||
return {
|
||||
error: {
|
||||
message: "No invitation found",
|
||||
status: 400,
|
||||
data: null,
|
||||
},
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
const res = await $fetch<
|
||||
Prettify<
|
||||
Invitation & {
|
||||
organizationName: string;
|
||||
organizationSlug: string;
|
||||
inviterEmail: string;
|
||||
inviterName: string;
|
||||
}
|
||||
>
|
||||
>("/organization/get-active-invitation", {
|
||||
method: "GET",
|
||||
query: {
|
||||
id: $activeInvitationId.get(),
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
return res;
|
||||
}),
|
||||
);
|
||||
return {
|
||||
id: "organization",
|
||||
actions: {
|
||||
setActiveOrganization(orgId: string | null) {
|
||||
activeOrgId.set(orgId);
|
||||
},
|
||||
setInvitationId: (id: string | null) => {
|
||||
$activeInvitationId.set(id);
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
react(useStore) {
|
||||
return {
|
||||
useActiveOrganization() {
|
||||
return useStore($activeOrganization);
|
||||
},
|
||||
useListOrganization() {
|
||||
return useStore($listOrganizations);
|
||||
},
|
||||
useInvitation() {
|
||||
return useStore($invitation);
|
||||
},
|
||||
};
|
||||
},
|
||||
vue(useStore) {
|
||||
return {
|
||||
useActiveOrganization() {
|
||||
return useStore($activeOrganization);
|
||||
},
|
||||
useListOrganization() {
|
||||
return useStore($listOrganizations);
|
||||
},
|
||||
useInvitation() {
|
||||
return useStore($invitation);
|
||||
},
|
||||
};
|
||||
},
|
||||
preact(useStore) {
|
||||
return {
|
||||
useActiveOrganization() {
|
||||
return useStore($activeOrganization);
|
||||
},
|
||||
useListOrganization() {
|
||||
return useStore($listOrganizations);
|
||||
},
|
||||
useInvitation() {
|
||||
return useStore($invitation);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
signals: {
|
||||
$listOrg,
|
||||
$activeOrgSignal,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -10,7 +10,8 @@ import type {
|
||||
} from "@simplewebauthn/types";
|
||||
import type { Session } from "inspector";
|
||||
import type { User } from "../../adapters/schema";
|
||||
import type { Passkey } from "../../plugins";
|
||||
import type { passkey as passkeyPl, Passkey } from "../../plugins";
|
||||
import { createClientPlugin } from "../create-client-plugin";
|
||||
|
||||
export const getPasskeyActions = ($fetch: BetterFetch) => {
|
||||
const signInPasskey = async (opts?: {
|
||||
@@ -95,3 +96,12 @@ export const getPasskeyActions = ($fetch: BetterFetch) => {
|
||||
register,
|
||||
};
|
||||
};
|
||||
|
||||
export const passkey = createClientPlugin<ReturnType<typeof passkeyPl>>()(
|
||||
($fetch) => {
|
||||
return {
|
||||
id: "passkey",
|
||||
actions: getPasskeyActions($fetch),
|
||||
};
|
||||
},
|
||||
);
|
||||
23
packages/better-auth/src/client/plugins/two-factor.ts
Normal file
23
packages/better-auth/src/client/plugins/two-factor.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createClientPlugin } from "../create-client-plugin";
|
||||
import type { twoFactor as twoFa } from "../../plugins/two-factor";
|
||||
|
||||
export const twoFactor = createClientPlugin<ReturnType<typeof twoFa>>()(
|
||||
($fetch) => {
|
||||
return {
|
||||
id: "two-factor",
|
||||
authProxySignal: [
|
||||
{
|
||||
matcher: (path) =>
|
||||
path === "/two-factor/enable" || path === "/two-factor/send-otp",
|
||||
atom: "$sessionSignal",
|
||||
},
|
||||
],
|
||||
pathMethods: {
|
||||
"enable/totp": "POST",
|
||||
"/two-factor/disable": "POST",
|
||||
"/two-factor/enable": "POST",
|
||||
"/two-factor/send-otp": "POST",
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -6,27 +6,5 @@ import { createAuthClient as createVanillaClient } from "./base";
|
||||
|
||||
export const createAuthClient = (options?: BetterFetchOption) => {
|
||||
const client = createVanillaClient(options);
|
||||
function useSession() {
|
||||
return useStore(client.$atoms.$session);
|
||||
}
|
||||
function useActiveOrganization() {
|
||||
return useStore(client.$atoms.$activeOrganization);
|
||||
}
|
||||
function useListOrganization() {
|
||||
return useStore(client.$atoms.$listOrganizations);
|
||||
}
|
||||
const useInvitation = () => {
|
||||
return (
|
||||
useAuthStore(client.$atoms.$invitation) || {
|
||||
error: null,
|
||||
data: null,
|
||||
}
|
||||
);
|
||||
};
|
||||
return Object.assign(client, {
|
||||
useSession,
|
||||
useActiveOrganization,
|
||||
useListOrganization,
|
||||
useInvitation,
|
||||
});
|
||||
return Object.assign(client);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { BetterFetch } from "@better-fetch/fetch";
|
||||
import type { PreinitializedWritableAtom } from "nanostores";
|
||||
import type { ProxyRequest } from "./path-to-object";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
|
||||
const knownPathMethods: Record<string, "POST" | "GET"> = {
|
||||
"/sign-out": "POST",
|
||||
"enable/totp": "POST",
|
||||
"/two-factor/disable": "POST",
|
||||
"/two-factor/enable": "POST",
|
||||
"/two-factor/send-otp": "POST",
|
||||
};
|
||||
|
||||
function getMethod(path: string, args?: ProxyRequest) {
|
||||
function getMethod(
|
||||
path: string,
|
||||
knownPathMethods: Record<string, "POST" | "GET">,
|
||||
args?: ProxyRequest,
|
||||
) {
|
||||
const method = knownPathMethods[path];
|
||||
const { options, query, ...body } = args || {};
|
||||
if (method) {
|
||||
@@ -25,13 +22,17 @@ function getMethod(path: string, args?: ProxyRequest) {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
export type AuthProxySignal = {
|
||||
atom: LiteralUnion<string, "$sessionSignal">;
|
||||
matcher: (path: string) => boolean;
|
||||
};
|
||||
|
||||
export function createDynamicPathProxy<T extends Record<string, any>>(
|
||||
routes: T,
|
||||
client: BetterFetch,
|
||||
$signal?: {
|
||||
atom: PreinitializedWritableAtom<boolean>;
|
||||
matcher: (path: string) => boolean;
|
||||
}[],
|
||||
knownPathMethods: Record<string, "POST" | "GET">,
|
||||
$signal?: AuthProxySignal[],
|
||||
$signals?: Record<string, PreinitializedWritableAtom<boolean>>,
|
||||
): T {
|
||||
const handler: ProxyHandler<any> = {
|
||||
get(target, prop: string) {
|
||||
@@ -56,7 +57,7 @@ export function createDynamicPathProxy<T extends Record<string, any>>(
|
||||
.join("/");
|
||||
const routePath = `/${path}`;
|
||||
const arg = (args[0] || {}) as ProxyRequest;
|
||||
const method = getMethod(routePath, arg);
|
||||
const method = getMethod(routePath, knownPathMethods, arg);
|
||||
const { query, options, ...body } = arg;
|
||||
return await client(routePath, {
|
||||
...options,
|
||||
@@ -65,9 +66,10 @@ export function createDynamicPathProxy<T extends Record<string, any>>(
|
||||
method,
|
||||
onSuccess() {
|
||||
const signal = $signal?.find((s) => s.matcher(routePath));
|
||||
if (signal) {
|
||||
signal.atom.set(!signal.atom.get());
|
||||
}
|
||||
if (!signal) return;
|
||||
const signalAtom = $signals?.[signal.atom];
|
||||
if (!signalAtom) return;
|
||||
signalAtom.set(!signalAtom.get());
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
import type { BetterFetchOption } from "@better-fetch/fetch";
|
||||
import { useStore } from "@nanostores/react";
|
||||
import type { BetterAuth } from "../auth";
|
||||
import type { InferSession, InferUser } from "../types";
|
||||
import { createAuthClient as createVanillaClient } from "./base";
|
||||
import type { AuthPlugin, ClientOptions } from "./type";
|
||||
import type { UnionToIntersection } from "../types/helper";
|
||||
|
||||
export const createAuthClient = <Auth extends BetterAuth>(
|
||||
options?: BetterFetchOption,
|
||||
export const createAuthClient = <Option extends ClientOptions>(
|
||||
options?: Option,
|
||||
) => {
|
||||
const client = createVanillaClient<Auth>(options);
|
||||
const client = createVanillaClient(options);
|
||||
const hooks = options?.authPlugins?.reduce(
|
||||
(acc, plugin) => {
|
||||
return {
|
||||
...acc,
|
||||
...(plugin(client.$fetch).integrations?.react?.(useStore) || {}),
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
) as Option["authPlugins"] extends Array<infer Pl>
|
||||
? Pl extends AuthPlugin
|
||||
? UnionToIntersection<
|
||||
ReturnType<Pl>["integrations"] extends
|
||||
| {
|
||||
react?: (useStore: any) => infer R;
|
||||
}
|
||||
| undefined
|
||||
? R
|
||||
: {
|
||||
test1: ReturnType<Pl>["integrations"];
|
||||
}
|
||||
>
|
||||
: {}
|
||||
: {};
|
||||
|
||||
function useSession(
|
||||
initialValue: {
|
||||
user: InferUser<Auth>;
|
||||
session: InferSession<Auth>;
|
||||
} | null = null,
|
||||
initialValue: typeof client.$atoms.$session.value = null,
|
||||
) {
|
||||
const session = useStore(client.$atoms.$session);
|
||||
if (session) {
|
||||
@@ -20,26 +41,10 @@ export const createAuthClient = <Auth extends BetterAuth>(
|
||||
}
|
||||
return initialValue;
|
||||
}
|
||||
function useActiveOrganization() {
|
||||
return useStore(client.$atoms.$activeOrganization);
|
||||
}
|
||||
function useListOrganization() {
|
||||
return useStore(client.$atoms.$listOrganizations);
|
||||
}
|
||||
function useInvitation() {
|
||||
return (
|
||||
useAuthStore(client.$atoms.$invitation) || {
|
||||
error: null,
|
||||
data: null,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const obj = Object.assign(client, {
|
||||
useSession,
|
||||
useActiveOrganization,
|
||||
useListOrganization,
|
||||
useInvitation,
|
||||
...hooks,
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BetterFetch } from "@better-fetch/fetch";
|
||||
import { atom, computed, task } from "nanostores";
|
||||
import type { BetterAuth } from "../auth";
|
||||
import type { Auth as BetterAuth } from "../auth";
|
||||
import type { Prettify } from "../types/helper";
|
||||
import type { InferSession, InferUser } from "../types/models";
|
||||
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
import type { BetterFetchOption } from "@better-fetch/fetch";
|
||||
import type { BetterAuth } from "../auth";
|
||||
import type { BetterFetch, BetterFetchOption } from "@better-fetch/fetch";
|
||||
import type { Auth } from "../auth";
|
||||
import type { UnionToIntersection } from "../types/helper";
|
||||
import type { useAuthStore as reactStore } from "./react";
|
||||
import type { useAuthStore as vueStore } from "./vue";
|
||||
import type { useAuthStore as preactStore } from "./preact";
|
||||
import type { AuthProxySignal } from "./proxy";
|
||||
import type { Atom, PreinitializedWritableAtom } from "nanostores";
|
||||
import type { BetterAuthPlugin } from "../types/plugins";
|
||||
|
||||
export type AuthStore = typeof reactStore | typeof vueStore;
|
||||
|
||||
export type AuthPlugin = ($fetch: BetterFetch) => {
|
||||
id: string;
|
||||
/**
|
||||
* only used for type inference. don't pass the
|
||||
* actual plugin
|
||||
*/
|
||||
plugin?: BetterAuthPlugin;
|
||||
actions?: Record<string, any>;
|
||||
authProxySignal?: AuthProxySignal[];
|
||||
signals?: Record<string, PreinitializedWritableAtom<boolean>>;
|
||||
atoms?: Record<string, Atom<any>>;
|
||||
/**
|
||||
* Framework integrations
|
||||
*/
|
||||
integrations?: {
|
||||
react?: (useStore: typeof reactStore) => Record<string, any>;
|
||||
vue?: (useStore: typeof vueStore) => Record<string, any>;
|
||||
preact?: (useStore: typeof preactStore) => Record<string, any>;
|
||||
};
|
||||
pathMethods?: Record<string, "POST" | "GET">;
|
||||
};
|
||||
export interface ClientOptions extends BetterFetchOption {
|
||||
/**
|
||||
* csrf plugin is enabled by default
|
||||
*/
|
||||
csrfPlugin?: boolean;
|
||||
authPlugins?: AuthPlugin[];
|
||||
}
|
||||
|
||||
export type HasPlugin<
|
||||
PluginId extends string,
|
||||
Auth extends BetterAuth,
|
||||
> = Auth["options"]["plugins"] extends Array<infer T>
|
||||
A extends Auth,
|
||||
> = A["options"]["plugins"] extends Array<infer T>
|
||||
? UnionToIntersection<T extends { id: PluginId } ? true : false>
|
||||
: false;
|
||||
|
||||
@@ -4,29 +4,7 @@ import { createAuthClient as createVanillaClient } from "./base";
|
||||
|
||||
export const createAuthClient = (options?: BetterFetchOption) => {
|
||||
const client = createVanillaClient(options);
|
||||
function useSession() {
|
||||
return useStore(client.$atoms.$session);
|
||||
}
|
||||
function useActiveOrganization() {
|
||||
return useStore(client.$atoms.$activeOrganization);
|
||||
}
|
||||
function useListOrganization() {
|
||||
return useStore(client.$atoms.$listOrganizations);
|
||||
}
|
||||
const useInvitation = () => {
|
||||
return (
|
||||
useAuthStore(client.$atoms.$invitation) || {
|
||||
error: null,
|
||||
data: null,
|
||||
}
|
||||
);
|
||||
};
|
||||
return Object.assign(client, {
|
||||
useSession,
|
||||
useActiveOrganization,
|
||||
useListOrganization,
|
||||
useInvitation,
|
||||
});
|
||||
return Object.assign(client);
|
||||
};
|
||||
|
||||
export const useAuthStore = useStore;
|
||||
|
||||
66
packages/better-auth/src/crypto/encryption.ts
Normal file
66
packages/better-auth/src/crypto/encryption.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
async function importKeyFromString(keyString: string): Promise<CryptoKey> {
|
||||
const encoder = new TextEncoder();
|
||||
const keyData = encoder.encode(keyString);
|
||||
return crypto.subtle.importKey(
|
||||
"raw",
|
||||
keyData,
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
false,
|
||||
["encrypt", "decrypt"],
|
||||
);
|
||||
}
|
||||
|
||||
export async function symmetricEncrypt({
|
||||
data,
|
||||
key,
|
||||
}: {
|
||||
data: string;
|
||||
key: string;
|
||||
}): Promise<string> {
|
||||
const _key = await importKeyFromString(key);
|
||||
const encoder = new TextEncoder();
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12)); // Initialization vector
|
||||
|
||||
const encryptedContent = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: iv,
|
||||
},
|
||||
_key,
|
||||
encoder.encode(data),
|
||||
);
|
||||
|
||||
const encryptedArray = new Uint8Array(
|
||||
iv.byteLength + encryptedContent.byteLength,
|
||||
);
|
||||
encryptedArray.set(iv);
|
||||
encryptedArray.set(new Uint8Array(encryptedContent), iv.byteLength);
|
||||
|
||||
return Buffer.from(encryptedArray).toString("base64");
|
||||
}
|
||||
|
||||
export async function symmetricDecrypt({
|
||||
data,
|
||||
key,
|
||||
}: {
|
||||
data: string;
|
||||
key: string;
|
||||
}): Promise<string> {
|
||||
const _key = await importKeyFromString(key);
|
||||
const encryptedArray = new Uint8Array(Buffer.from(data, "base64"));
|
||||
const iv = encryptedArray.slice(0, 12);
|
||||
const encryptedContent = encryptedArray.slice(12);
|
||||
const decryptedContent = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: iv,
|
||||
},
|
||||
_key,
|
||||
encryptedContent,
|
||||
);
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(decryptedContent);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const init = (options: BetterAuthOptions) => {
|
||||
const adapter = getAdapter(options);
|
||||
const db = createKyselyAdapter(options);
|
||||
const { baseURL, withPath } = getBaseURL(options.baseURL, options.basePath);
|
||||
|
||||
return {
|
||||
options: {
|
||||
...options,
|
||||
|
||||
1
packages/better-auth/src/plugins/organization/client.ts
Normal file
1
packages/better-auth/src/plugins/organization/client.ts
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
} from "zod";
|
||||
import type { User } from "../../adapters/schema";
|
||||
import { createAuthEndpoint } from "../../api/call";
|
||||
import { sessionMiddleware } from "../../api/middlewares/session";
|
||||
import { getSessionFromCtx } from "../../api/routes";
|
||||
import type { AuthContext } from "../../init";
|
||||
import type { BetterAuthPlugin } from "../../types/plugins";
|
||||
|
||||
107
packages/better-auth/src/plugins/passkey/client.ts
Normal file
107
packages/better-auth/src/plugins/passkey/client.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { BetterFetch } from "@better-fetch/fetch";
|
||||
import {
|
||||
WebAuthnError,
|
||||
startAuthentication,
|
||||
startRegistration,
|
||||
} from "@simplewebauthn/browser";
|
||||
import type {
|
||||
PublicKeyCredentialCreationOptionsJSON,
|
||||
PublicKeyCredentialRequestOptionsJSON,
|
||||
} from "@simplewebauthn/types";
|
||||
import type { Session } from "inspector";
|
||||
import type { User } from "../../adapters/schema";
|
||||
import type { passkey as passkeyPl, Passkey } from "../../plugins";
|
||||
import { createClientPlugin } from "../../client/create-client-plugin";
|
||||
|
||||
export const getPasskeyActions = ($fetch: BetterFetch) => {
|
||||
const signInPasskey = async (opts?: {
|
||||
autoFill?: boolean;
|
||||
email?: string;
|
||||
callbackURL?: string;
|
||||
}) => {
|
||||
const response = await $fetch<PublicKeyCredentialRequestOptionsJSON>(
|
||||
"/passkey/generate-authenticate-options",
|
||||
{
|
||||
method: "POST",
|
||||
body: {
|
||||
email: opts?.email,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!response.data) {
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
const res = await startAuthentication(
|
||||
response.data,
|
||||
opts?.autoFill || false,
|
||||
);
|
||||
const verified = await $fetch<{
|
||||
session: Session;
|
||||
user: User;
|
||||
}>("/passkey/verify-authentication", {
|
||||
body: {
|
||||
response: res,
|
||||
type: "authenticate",
|
||||
},
|
||||
});
|
||||
if (!verified.data) {
|
||||
return verified;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const register = async () => {
|
||||
const options = await $fetch<PublicKeyCredentialCreationOptionsJSON>(
|
||||
"/passkey/generate-register-options",
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
);
|
||||
if (!options.data) {
|
||||
return options;
|
||||
}
|
||||
try {
|
||||
const res = await startRegistration(options.data);
|
||||
const verified = await $fetch<{
|
||||
passkey: Passkey;
|
||||
}>("/passkey/verify-registration", {
|
||||
body: {
|
||||
response: res,
|
||||
type: "register",
|
||||
},
|
||||
});
|
||||
if (!verified.data) {
|
||||
return verified;
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof WebAuthnError) {
|
||||
if (e.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
|
||||
return {
|
||||
data: null,
|
||||
error: {
|
||||
message: "previously registered",
|
||||
status: 400,
|
||||
statusText: "BAD_REQUEST",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
signInPasskey,
|
||||
register,
|
||||
};
|
||||
};
|
||||
|
||||
export const passkeyClient = createClientPlugin<ReturnType<typeof passkeyPl>>()(
|
||||
($fetch) => {
|
||||
return {
|
||||
id: "passkey",
|
||||
actions: getPasskeyActions($fetch),
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -459,3 +459,5 @@ export const passkey = (options: PasskeyOptions) => {
|
||||
},
|
||||
} satisfies BetterAuthPlugin;
|
||||
};
|
||||
|
||||
export * from "./client";
|
||||
|
||||
@@ -49,23 +49,23 @@ export async function generateBackupCodes(
|
||||
};
|
||||
}
|
||||
|
||||
export function verifyBackupCode(
|
||||
export async function verifyBackupCode(
|
||||
data: {
|
||||
user: UserWithTwoFactor;
|
||||
code: string;
|
||||
},
|
||||
key: string,
|
||||
) {
|
||||
const codes = getBackupCodes(data.user, key);
|
||||
const codes = await getBackupCodes(data.user, key);
|
||||
if (!codes) {
|
||||
return false;
|
||||
}
|
||||
return codes.includes(data.code);
|
||||
}
|
||||
|
||||
export function getBackupCodes(user: UserWithTwoFactor, key: string) {
|
||||
export async function getBackupCodes(user: UserWithTwoFactor, key: string) {
|
||||
const secret = Buffer.from(
|
||||
symmetricDecrypt({ key, data: user.twoFactorBackupCodes }),
|
||||
await symmetricDecrypt({ key, data: user.twoFactorBackupCodes }),
|
||||
).toString("utf-8");
|
||||
const data = JSON.parse(secret);
|
||||
const result = z.array(z.string()).safeParse(data);
|
||||
@@ -81,6 +81,7 @@ export const backupCode2fa = (options?: BackupCodeOptions) => {
|
||||
endpoints: {
|
||||
verifyBackupCode: createAuthEndpoint(
|
||||
"/two-factor/verify-backup-code",
|
||||
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
|
||||
23
packages/better-auth/src/plugins/two-factor/client.ts
Normal file
23
packages/better-auth/src/plugins/two-factor/client.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createClientPlugin } from "../../client/create-client-plugin";
|
||||
import type { twoFactor as twoFa } from "../../plugins/two-factor";
|
||||
|
||||
export const twoFactorClient = createClientPlugin<ReturnType<typeof twoFa>>()(
|
||||
($fetch) => {
|
||||
return {
|
||||
id: "two-factor",
|
||||
authProxySignal: [
|
||||
{
|
||||
matcher: (path) =>
|
||||
path === "/two-factor/enable" || path === "/two-factor/send-otp",
|
||||
atom: "$sessionSignal",
|
||||
},
|
||||
],
|
||||
pathMethods: {
|
||||
"enable/totp": "POST",
|
||||
"/two-factor/disable": "POST",
|
||||
"/two-factor/enable": "POST",
|
||||
"/two-factor/send-otp": "POST",
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -36,7 +36,7 @@ export const twoFactor = <O extends TwoFactorOptions>(options: O) => {
|
||||
async (ctx) => {
|
||||
const user = ctx.context.session.user as UserWithTwoFactor;
|
||||
const secret = generateRandomString(16, alphabet("a-z", "0-9", "-"));
|
||||
const encryptedSecret = symmetricEncrypt({
|
||||
const encryptedSecret = await symmetricEncrypt({
|
||||
key: ctx.context.secret,
|
||||
data: secret,
|
||||
});
|
||||
@@ -114,3 +114,5 @@ export const twoFactor = <O extends TwoFactorOptions>(options: O) => {
|
||||
},
|
||||
} satisfies BetterAuthPlugin;
|
||||
};
|
||||
|
||||
export * from "./client";
|
||||
|
||||
@@ -107,7 +107,7 @@ export const totp2fa = (options: TOTPOptions) => {
|
||||
}
|
||||
const totp = new TOTPController(opts);
|
||||
const secret = Buffer.from(
|
||||
symmetricDecrypt({
|
||||
await symmetricDecrypt({
|
||||
key: ctx.context.secret,
|
||||
data: ctx.context.session.user.twoFactorSecret,
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BetterAuthOptions } from ".";
|
||||
import type { Session, User } from "../adapters/schema";
|
||||
import type { BetterAuth } from "../auth";
|
||||
import type { Auth } from "../auth";
|
||||
import type { FieldAttribute, InferFieldOutput } from "../db";
|
||||
import type { Prettify, UnionToIntersection } from "./helper";
|
||||
|
||||
@@ -52,22 +52,21 @@ type AdditionalUserFields<Options extends BetterAuthOptions> =
|
||||
: {}
|
||||
: {};
|
||||
|
||||
export type InferUser<O extends BetterAuthOptions | BetterAuth> =
|
||||
UnionToIntersection<
|
||||
export type InferUser<O extends BetterAuthOptions | Auth> = UnionToIntersection<
|
||||
User &
|
||||
(O extends BetterAuthOptions
|
||||
? AdditionalUserFields<O>
|
||||
: O extends BetterAuth
|
||||
: O extends Auth
|
||||
? AdditionalUserFields<O["options"]>
|
||||
: {})
|
||||
>;
|
||||
|
||||
export type InferSession<O extends BetterAuthOptions | BetterAuth> =
|
||||
export type InferSession<O extends BetterAuthOptions | Auth> =
|
||||
UnionToIntersection<
|
||||
Session &
|
||||
(O extends BetterAuthOptions
|
||||
? AdditionalSessionFields<O>
|
||||
: O extends BetterAuth
|
||||
: O extends Auth
|
||||
? AdditionalSessionFields<O["options"]>
|
||||
: {})
|
||||
>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BetterAuthError } from "../error/better-auth-error";
|
||||
|
||||
function checkHasPath(url: string): boolean {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
@@ -35,6 +37,7 @@ export function getBaseURL(url?: string, path?: string) {
|
||||
if (fromEnv) {
|
||||
return withPath(fromEnv, path);
|
||||
}
|
||||
console.log(process.env);
|
||||
if (
|
||||
!fromEnv &&
|
||||
(process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test")
|
||||
@@ -44,7 +47,7 @@ export function getBaseURL(url?: string, path?: string) {
|
||||
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.",
|
||||
throw new BetterAuthError(
|
||||
"Could not infer baseURL from environment variables",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"resolveJsonModule": true,
|
||||
"module": "Preserve",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
preact: "./src/client/preact.ts",
|
||||
vue: "./src/client/vue.ts",
|
||||
plugins: "./src/plugins/index.ts",
|
||||
"client/plugins": "./src/client/plugins.ts",
|
||||
},
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
|
||||
144
pnpm-lock.yaml
generated
144
pnpm-lock.yaml
generated
@@ -34,21 +34,33 @@ importers:
|
||||
specifier: 5.5.0-dev.20240520
|
||||
version: 5.5.0-dev.20240520
|
||||
|
||||
dev/hono+react/hono:
|
||||
dev/bc-fe/hono:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.12.2
|
||||
version: 1.12.2(hono@4.5.9)
|
||||
better-auth:
|
||||
specifier: workspace:*
|
||||
specifier: ^0.0.2-beta.8
|
||||
version: link:../../../packages/better-auth
|
||||
dotenv:
|
||||
specifier: ^16.4.5
|
||||
version: 16.4.5
|
||||
hono:
|
||||
specifier: ^4.5.9
|
||||
version: 4.5.9
|
||||
tsx:
|
||||
specifier: ^4.19.0
|
||||
version: 4.19.0
|
||||
devDependencies:
|
||||
'@types/bun':
|
||||
specifier: latest
|
||||
version: 1.1.8
|
||||
|
||||
dev/hono+react/react:
|
||||
dev/bc-fe/react:
|
||||
dependencies:
|
||||
better-auth:
|
||||
specifier: workspace:^0.0.2-beta.8
|
||||
version: link:../../../packages/better-auth
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -135,7 +147,7 @@ importers:
|
||||
specifier: ^3.12.0
|
||||
version: 3.12.0(react@18.3.1)
|
||||
better-auth:
|
||||
specifier: workspace:*
|
||||
specifier: ^0.0.2-beta.8
|
||||
version: link:../../packages/better-auth
|
||||
better-call:
|
||||
specifier: ^0.1.0
|
||||
@@ -294,6 +306,9 @@ importers:
|
||||
prompts:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
ts-morph:
|
||||
specifier: ^23.0.0
|
||||
version: 23.0.0
|
||||
zod:
|
||||
specifier: ^3.22.5
|
||||
version: 3.23.8
|
||||
@@ -333,7 +348,7 @@ importers:
|
||||
version: 2.2.5(react@18.3.1)
|
||||
tsup:
|
||||
specifier: ^8.2.4
|
||||
version: 8.2.4(jiti@1.21.6)(postcss@8.4.41)(typescript@5.5.4)(yaml@2.5.0)
|
||||
version: 8.2.4(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)(typescript@5.5.4)(yaml@2.5.0)
|
||||
type-fest:
|
||||
specifier: ^4.24.0
|
||||
version: 4.24.0
|
||||
@@ -412,11 +427,6 @@ packages:
|
||||
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.25.3':
|
||||
resolution: {integrity: sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.25.6':
|
||||
resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -442,10 +452,6 @@ packages:
|
||||
resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.25.2':
|
||||
resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.25.6':
|
||||
resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -850,6 +856,12 @@ packages:
|
||||
'@hexagon/base64@1.1.28':
|
||||
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
|
||||
|
||||
'@hono/node-server@1.12.2':
|
||||
resolution: {integrity: sha512-xjzhqhSWUE/OhN0g3KCNVzNsQMlFUAL+/8GgPUr3TKcU7cvgZVBGswFofJ8WwGEHTqobzze1lDpGJl9ZNckDhA==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@hookform/resolvers@3.9.0':
|
||||
resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
|
||||
peerDependencies:
|
||||
@@ -1878,6 +1890,9 @@ packages:
|
||||
'@tabler/icons@3.12.0':
|
||||
resolution: {integrity: sha512-Im37ar/mQkqLb6XUXsU7nOc4/66VB9/3KLuZ+6tUsJKHHNLaDUkYfCTNG3pnGDI03laByxVf5+umSNK2yPTx8A==}
|
||||
|
||||
'@ts-morph/common@0.24.0':
|
||||
resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==}
|
||||
|
||||
'@tybys/wasm-util@0.8.3':
|
||||
resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==}
|
||||
|
||||
@@ -2262,6 +2277,9 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
code-block-writer@13.0.2:
|
||||
resolution: {integrity: sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==}
|
||||
|
||||
color-convert@1.9.3:
|
||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||
|
||||
@@ -2604,6 +2622,9 @@ packages:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
get-tsconfig@4.8.0:
|
||||
resolution: {integrity: sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==}
|
||||
|
||||
giget@1.2.3:
|
||||
resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==}
|
||||
hasBin: true
|
||||
@@ -2996,6 +3017,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mkdirp@3.0.1:
|
||||
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mlly@1.7.1:
|
||||
resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
|
||||
|
||||
@@ -3157,6 +3183,9 @@ packages:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3480,6 +3509,9 @@ packages:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
@@ -3773,6 +3805,9 @@ packages:
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
ts-morph@23.0.0:
|
||||
resolution: {integrity: sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==}
|
||||
|
||||
tslib@2.6.3:
|
||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||
|
||||
@@ -3795,6 +3830,11 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
tsx@4.19.0:
|
||||
resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
@@ -4075,10 +4115,10 @@ snapshots:
|
||||
'@babel/helper-compilation-targets': 7.25.2
|
||||
'@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2)
|
||||
'@babel/helpers': 7.25.6
|
||||
'@babel/parser': 7.25.3
|
||||
'@babel/parser': 7.25.6
|
||||
'@babel/template': 7.25.0
|
||||
'@babel/traverse': 7.25.6
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/types': 7.25.6
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.6
|
||||
gensync: 1.0.0-beta.2
|
||||
@@ -4105,7 +4145,7 @@ snapshots:
|
||||
'@babel/helper-module-imports@7.24.7':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.25.6
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/types': 7.25.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4124,7 +4164,7 @@ snapshots:
|
||||
'@babel/helper-simple-access@7.24.7':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.25.6
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/types': 7.25.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -4146,10 +4186,6 @@ snapshots:
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.0.1
|
||||
|
||||
'@babel/parser@7.25.3':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.2
|
||||
|
||||
'@babel/parser@7.25.6':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.6
|
||||
@@ -4167,8 +4203,8 @@ snapshots:
|
||||
'@babel/template@7.25.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
'@babel/parser': 7.25.3
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/parser': 7.25.6
|
||||
'@babel/types': 7.25.6
|
||||
|
||||
'@babel/traverse@7.25.6':
|
||||
dependencies:
|
||||
@@ -4182,12 +4218,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/types@7.25.2':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.24.8
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
'@babel/types@7.25.6':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.24.8
|
||||
@@ -4448,6 +4478,10 @@ snapshots:
|
||||
|
||||
'@hexagon/base64@1.1.28': {}
|
||||
|
||||
'@hono/node-server@1.12.2(hono@4.5.9)':
|
||||
dependencies:
|
||||
hono: 4.5.9
|
||||
|
||||
'@hookform/resolvers@3.9.0(react-hook-form@7.52.2(react@18.3.1))':
|
||||
dependencies:
|
||||
react-hook-form: 7.52.2(react@18.3.1)
|
||||
@@ -5317,6 +5351,13 @@ snapshots:
|
||||
|
||||
'@tabler/icons@3.12.0': {}
|
||||
|
||||
'@ts-morph/common@0.24.0':
|
||||
dependencies:
|
||||
fast-glob: 3.3.2
|
||||
minimatch: 9.0.5
|
||||
mkdirp: 3.0.1
|
||||
path-browserify: 1.0.1
|
||||
|
||||
'@tybys/wasm-util@0.8.3':
|
||||
dependencies:
|
||||
tslib: 2.6.3
|
||||
@@ -5324,24 +5365,24 @@ snapshots:
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.25.3
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/parser': 7.25.6
|
||||
'@babel/types': 7.25.6
|
||||
'@types/babel__generator': 7.6.8
|
||||
'@types/babel__template': 7.4.4
|
||||
'@types/babel__traverse': 7.20.6
|
||||
|
||||
'@types/babel__generator@7.6.8':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/types': 7.25.6
|
||||
|
||||
'@types/babel__template@7.4.4':
|
||||
dependencies:
|
||||
'@babel/parser': 7.25.3
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/parser': 7.25.6
|
||||
'@babel/types': 7.25.6
|
||||
|
||||
'@types/babel__traverse@7.20.6':
|
||||
dependencies:
|
||||
'@babel/types': 7.25.2
|
||||
'@babel/types': 7.25.6
|
||||
|
||||
'@types/better-sqlite3@7.6.11':
|
||||
dependencies:
|
||||
@@ -5812,6 +5853,8 @@ snapshots:
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
code-block-writer@13.0.2: {}
|
||||
|
||||
color-convert@1.9.3:
|
||||
dependencies:
|
||||
color-name: 1.1.3
|
||||
@@ -6164,6 +6207,10 @@ snapshots:
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
get-tsconfig@4.8.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
giget@1.2.3:
|
||||
dependencies:
|
||||
citty: 0.1.6
|
||||
@@ -6504,6 +6551,8 @@ snapshots:
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mkdirp@3.0.1: {}
|
||||
|
||||
mlly@1.7.1:
|
||||
dependencies:
|
||||
acorn: 8.12.1
|
||||
@@ -6680,6 +6729,8 @@ snapshots:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
@@ -6781,12 +6832,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
postcss: 8.4.40
|
||||
|
||||
postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.41)(yaml@2.5.0):
|
||||
postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)(yaml@2.5.0):
|
||||
dependencies:
|
||||
lilconfig: 3.1.2
|
||||
optionalDependencies:
|
||||
jiti: 1.21.6
|
||||
postcss: 8.4.41
|
||||
tsx: 4.19.0
|
||||
yaml: 2.5.0
|
||||
|
||||
postcss-nested@6.2.0(postcss@8.4.40):
|
||||
@@ -6978,6 +7030,8 @@ snapshots:
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.15.0
|
||||
@@ -7288,9 +7342,14 @@ snapshots:
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
ts-morph@23.0.0:
|
||||
dependencies:
|
||||
'@ts-morph/common': 0.24.0
|
||||
code-block-writer: 13.0.2
|
||||
|
||||
tslib@2.6.3: {}
|
||||
|
||||
tsup@8.2.4(jiti@1.21.6)(postcss@8.4.41)(typescript@5.5.4)(yaml@2.5.0):
|
||||
tsup@8.2.4(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)(typescript@5.5.4)(yaml@2.5.0):
|
||||
dependencies:
|
||||
bundle-require: 5.0.0(esbuild@0.23.0)
|
||||
cac: 6.7.14
|
||||
@@ -7302,7 +7361,7 @@ snapshots:
|
||||
globby: 11.1.0
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.0.1
|
||||
postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.41)(yaml@2.5.0)
|
||||
postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.41)(tsx@4.19.0)(yaml@2.5.0)
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.19.1
|
||||
source-map: 0.8.0-beta.0
|
||||
@@ -7317,6 +7376,13 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
tsx@4.19.0:
|
||||
dependencies:
|
||||
esbuild: 0.23.0
|
||||
get-tsconfig: 4.8.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
tunnel-agent@0.6.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
@@ -5,21 +5,15 @@
|
||||
"target": "es2022",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"disableReferencedProjectLoad": true,
|
||||
"module": "Preserve",
|
||||
"noEmit": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitAny": true,
|
||||
"checkJs": true,
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".tsbuildinfo",
|
||||
"types": ["node"]
|
||||
"noImplicitOverride": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"exclude": ["**/dist/**", "**/node_modules/**", "**/examples/**"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user