mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 12:27:43 +00:00
feat: open api docs plugin
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
twoFactor,
|
||||
oneTap,
|
||||
oAuthProxy,
|
||||
createAuthEndpoint,
|
||||
openAPI,
|
||||
} from "better-auth/plugins";
|
||||
import { reactInvitationEmail } from "./email/invitation";
|
||||
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
||||
@@ -18,7 +18,6 @@ import { MysqlDialect } from "kysely";
|
||||
import { createPool } from "mysql2/promise";
|
||||
import { nextCookies } from "better-auth/next-js";
|
||||
import { customSession } from "./auth/plugins/custom-session";
|
||||
import { openAPI } from "@better-auth/open-api";
|
||||
|
||||
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
||||
const to = process.env.TEST_EMAIL || "";
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/open-api": "workspace:1.0.0-canary.12",
|
||||
"@better-fetch/fetch": "1.1.12",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@libsql/client": "^0.12.0",
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ForkButton } from "@/components/fork-button";
|
||||
import Link from "next/link";
|
||||
import defaultMdxComponents from "fumadocs-ui/mdx";
|
||||
import { AutoTypeTable } from "fumadocs-typescript/ui";
|
||||
import { openapi } from '@/app/source';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
@@ -74,11 +73,9 @@ export default async function Page({
|
||||
Features,
|
||||
ForkButton,
|
||||
DatabaseTable,
|
||||
APIPage: openapi.APIPage,
|
||||
iframe: (props) => (
|
||||
<iframe {...props} className="w-full h-[500px]" />
|
||||
),
|
||||
APIPage: openapi.APIPage,
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
@@ -87,48 +84,48 @@ export default async function Page({
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
// const res = source.getPages().map((page) => ({
|
||||
// slug: page.slugs,
|
||||
// }));
|
||||
const res = source.getPages().map((page) => ({
|
||||
slug: page.slugs,
|
||||
}));
|
||||
return source.generateParams();
|
||||
}
|
||||
|
||||
// export async function generateMetadata({
|
||||
// params,
|
||||
// }: { params: Promise<{ slug?: string[] }> }) {
|
||||
// const { slug } = await params;
|
||||
// const page = source.getPage(slug);
|
||||
// if (page == null) notFound();
|
||||
// const baseUrl = process.env.NEXT_PUBLIC_URL || process.env.VERCEL_URL;
|
||||
// const url = new URL(`${baseUrl}/api/og`);
|
||||
// const { title, description } = page.data;
|
||||
// const pageSlug = page.file.path;
|
||||
// url.searchParams.set("type", "Documentation");
|
||||
// url.searchParams.set("mode", "dark");
|
||||
// url.searchParams.set("heading", `${title}`);
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: { params: Promise<{ slug?: string[] }> }) {
|
||||
const { slug } = await params;
|
||||
const page = source.getPage(slug);
|
||||
if (page == null) notFound();
|
||||
const baseUrl = process.env.NEXT_PUBLIC_URL || process.env.VERCEL_URL;
|
||||
const url = new URL(`${baseUrl}/api/og`);
|
||||
const { title, description } = page.data;
|
||||
const pageSlug = page.file.path;
|
||||
url.searchParams.set("type", "Documentation");
|
||||
url.searchParams.set("mode", "dark");
|
||||
url.searchParams.set("heading", `${title}`);
|
||||
|
||||
// return {
|
||||
// title,
|
||||
// description,
|
||||
// openGraph: {
|
||||
// title,
|
||||
// description,
|
||||
// type: "website",
|
||||
// url: absoluteUrl(`docs/${pageSlug}`),
|
||||
// images: [
|
||||
// {
|
||||
// url: url.toString(),
|
||||
// width: 1200,
|
||||
// height: 630,
|
||||
// alt: title,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// twitter: {
|
||||
// card: "summary_large_image",
|
||||
// title,
|
||||
// description,
|
||||
// images: [url.toString()],
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
type: "website",
|
||||
url: absoluteUrl(`docs/${pageSlug}`),
|
||||
images: [
|
||||
{
|
||||
url: url.toString(),
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: title,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: [url.toString()],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import { createOpenAPI } from "fumadocs-openapi/server";
|
||||
export const source = loader({
|
||||
baseUrl: "/docs",
|
||||
source: createMDXSource(docs, meta),
|
||||
pageTree: {
|
||||
attachFile,
|
||||
},
|
||||
});
|
||||
|
||||
export const changelog = loader({
|
||||
|
||||
@@ -877,6 +877,13 @@ export const contents: Content[] = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Open API",
|
||||
href: "/docs/plugins/open-api",
|
||||
icon: ()=>(
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1.1em" height="1.1em" viewBox="0 0 32 32"><path fill="currentColor" d="M16 0C7.177 0 0 7.177 0 16s7.177 16 16 16s16-7.177 16-16S24.823 0 16 0m0 1.527c7.995 0 14.473 6.479 14.473 14.473S23.994 30.473 16 30.473S1.527 23.994 1.527 16S8.006 1.527 16 1.527m-4.839 6.296c-.188-.005-.375 0-.568.005c-1.307.079-2.093.693-2.312 1.964c-.151.891-.125 1.796-.188 2.692a9 9 0 0 1-.156 1.38c-.177.813-.525 1.068-1.353 1.109q-.167.018-.324.057v1.948c1.5.073 1.704.605 1.823 2.172c.048.573-.015 1.147.021 1.719q.042.816.208 1.6c.344 1.432 1.745 1.911 3.433 1.624V22.38c-.272 0-.511.005-.74 0c-.579-.016-.792-.161-.844-.713c-.079-.713-.057-1.437-.099-2.156c-.089-1.339-.235-2.651-1.541-3.5c.672-.495 1.161-1.084 1.312-1.865c.109-.547.177-1.099.219-1.651s-.025-1.12.021-1.667c.077-.885.135-1.249 1.197-1.213c.161 0 .317-.021.495-.036V7.834c-.213 0-.411-.005-.604-.011m10.126.016a5.4 5.4 0 0 0-1.089.079v1.697c.329 0 .584 0 .833.005c.439.005.772.177.813.661c.041.443.041.891.083 1.339c.089.896.136 1.796.292 2.677c.136.724.636 1.265 1.255 1.713c-1.088.729-1.411 1.776-1.463 2.953c-.032.801-.052 1.615-.093 2.427c-.037.74-.297.979-1.043.995c-.208.011-.411.027-.64.041v1.74c.432 0 .833.027 1.235 0c1.239-.073 1.995-.677 2.239-1.885a15 15 0 0 0 .183-2.005c.041-.615.036-1.235.099-1.844c.093-.953.532-1.349 1.484-1.411q.133-.018.267-.057v-1.953c-.161-.021-.271-.037-.391-.041c-.713-.032-1.068-.272-1.251-.948a6.6 6.6 0 0 1-.197-1.324c-.052-.823-.047-1.656-.099-2.479c-.109-1.588-1.063-2.339-2.516-2.38zm-9.188 7.036c-1.432 0-1.536 2.109-.115 2.245h.079a1.103 1.103 0 0 0 1.167-1.037v-.061a1.13 1.13 0 0 0-1.104-1.147zm3.88 0a1.083 1.083 0 0 0-1.115 1.043c0 .036 0 .067.005.104c0 .672.459 1.099 1.147 1.099c.677 0 1.104-.443 1.104-1.136c-.005-.672-.459-1.115-1.141-1.109zm3.948 0a1.15 1.15 0 0 0-1.167 1.115c0 .625.505 1.131 1.136 1.131h.011c.567.099 1.135-.448 1.172-1.104c.031-.609-.521-1.141-1.152-1.141z"></path></svg>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "JWT",
|
||||
icon: () => (
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Link social account
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /link-social
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/link-social","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: List all accounts
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /list-accounts
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/list-accounts","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Callback
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /callback/:id
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/callback/:id","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Send verification email
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /send-verification-email
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/send-verification-email","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Verify email
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /verify-email
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/verify-email","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Forget password
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /forget-password
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/forget-password","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Reset password with token
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /reset-password/:token
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/reset-password/:token","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Reset password
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /reset-password
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/reset-password","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Get current session
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /get-session
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/get-session","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: List all sessions
|
||||
full: true
|
||||
_openapi:
|
||||
method: GET
|
||||
route: /list-sessions
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/list-sessions","method":"get"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Revoke other sessions
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /revoke-other-sessions
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/revoke-other-sessions","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Revoke a session
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /revoke-session
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/revoke-session","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Revoke all sessions
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /revoke-sessions
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/revoke-sessions","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Sign in with email
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /sign-in/email
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/sign-in/email","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Sign in with social account
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /sign-in/social
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/sign-in/social","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Sign out
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /sign-out
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/sign-out","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Sign up with email
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /sign-up/email
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/sign-up/email","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Change email
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /change-email
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/change-email","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Change password
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /change-password
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/change-password","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Delete user
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /delete-user
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/delete-user","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Set password
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /set-password
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/set-password","method":"post"}]} hasHead={false} />
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
title: Update user
|
||||
full: true
|
||||
_openapi:
|
||||
method: POST
|
||||
route: /update-user
|
||||
toc: []
|
||||
structuredData:
|
||||
headings: []
|
||||
contents: []
|
||||
---
|
||||
|
||||
<APIPage document={"./open-api.json"} operations={[{"path":"/update-user","method":"post"}]} hasHead={false} />
|
||||
38
docs/content/docs/plugins/open-api.mdx
Normal file
38
docs/content/docs/plugins/open-api.mdx
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Open API
|
||||
description: Open API reference for Better Auth.
|
||||
---
|
||||
|
||||
This is a plugin that provides an Open API reference for Better Auth. It shows all endpoints added by plugins and the core. It also provides a way to test the endpoints. It uses [Scalar](https://scalar.com/) to display the Open API reference.
|
||||
|
||||
|
||||
<Callout>
|
||||
This plugin is still in the early stages of development. We are working on adding more features to it and filling in the gaps.
|
||||
</Callout>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
### Add the plugin to your **auth** config
|
||||
```ts title="auth.ts"
|
||||
import { betterAuth } from "better-auth"
|
||||
import { openAPI } from "better-auth/plugins"
|
||||
|
||||
export const auth = betterAuth({
|
||||
plugins: [ // [!code highlight]
|
||||
openAPI(), // [!code highlight]
|
||||
] // [!code highlight]
|
||||
})
|
||||
```
|
||||
</Step>
|
||||
<Step>
|
||||
### Navigate to `/api/auth/reference` to view the Open API reference
|
||||
|
||||
Each plugin endpoints are grouped by the plugin name. The core endpoints are grouped under the `Default` group. And Model schemas are grouped under the `Models` group.
|
||||
|
||||

|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
7266
docs/openapi.json
7266
docs/openapi.json
File diff suppressed because it is too large
Load Diff
1341
docs/openapi.yml
1341
docs/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
||||
"dev": "next dev",
|
||||
"start": "next start",
|
||||
"build:docs": "node ./scripts/generate-docs.mjs",
|
||||
"build:docs": "node ./scripts/generate-docs.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"postinstall": "fumadocs-mdx"
|
||||
},
|
||||
|
||||
BIN
docs/public/open-api-reference.png
Normal file
BIN
docs/public/open-api-reference.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 415 KiB |
File diff suppressed because it is too large
Load Diff
@@ -405,7 +405,7 @@
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@simplewebauthn/browser": "^10.0.0",
|
||||
"@simplewebauthn/server": "^10.0.1",
|
||||
"better-call": "0.3.1",
|
||||
"better-call": "0.3.2",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"jose": "^5.9.4",
|
||||
|
||||
@@ -10,6 +10,34 @@ export const listUserAccounts = createAuthEndpoint(
|
||||
{
|
||||
method: "GET",
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "List all accounts linked to the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
provider: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (c) => {
|
||||
const session = c.context.session;
|
||||
@@ -45,13 +73,45 @@ export const linkSocialAccount = createAuthEndpoint(
|
||||
/**
|
||||
* Callback URL to redirect to after the user has signed in.
|
||||
*/
|
||||
callbackURL: z.string().optional(),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description: "The URL to redirect to after the user has signed in",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* OAuth2 provider to use`
|
||||
*/
|
||||
provider: z.enum(socialProviderList),
|
||||
provider: z.enum(socialProviderList, {
|
||||
description: "The OAuth2 provider to use",
|
||||
}),
|
||||
}),
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Link a social account to the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
redirect: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["url", "redirect"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (c) => {
|
||||
const session = c.context.session;
|
||||
|
||||
@@ -38,13 +38,68 @@ export const sendVerificationEmail = createAuthEndpoint(
|
||||
method: "POST",
|
||||
query: z
|
||||
.object({
|
||||
currentURL: z.string().optional(),
|
||||
currentURL: z
|
||||
.string({
|
||||
description: "The URL to use for email verification callback",
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
body: z.object({
|
||||
email: z.string().email(),
|
||||
callbackURL: z.string().optional(),
|
||||
email: z
|
||||
.string({
|
||||
description: "The email to send the verification email to",
|
||||
})
|
||||
.email(),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description: "The URL to use for email verification callback",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Send a verification email to the user",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: {
|
||||
type: "string",
|
||||
description: "The email to send the verification email to",
|
||||
},
|
||||
callbackURL: {
|
||||
type: "string",
|
||||
description:
|
||||
"The URL to use for email verification callback",
|
||||
},
|
||||
},
|
||||
required: ["email"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.emailVerification?.sendVerificationEmail) {
|
||||
@@ -85,9 +140,41 @@ export const verifyEmail = createAuthEndpoint(
|
||||
{
|
||||
method: "GET",
|
||||
query: z.object({
|
||||
token: z.string(),
|
||||
callbackURL: z.string().optional(),
|
||||
token: z.string({
|
||||
description: "The token to verify the email",
|
||||
}),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description: "The URL to redirect to after email verification",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Verify the email of the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["user", "status"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
function redirectOnError(error: string) {
|
||||
|
||||
@@ -87,7 +87,24 @@ export const error = createAuthEndpoint(
|
||||
"/error",
|
||||
{
|
||||
method: "GET",
|
||||
metadata: HIDE_METADATA,
|
||||
metadata: {
|
||||
...HIDE_METADATA,
|
||||
openapi: {
|
||||
description: "Displays an error page",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"text/html": {
|
||||
schema: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (c) => {
|
||||
const query =
|
||||
|
||||
@@ -37,15 +37,47 @@ export const forgetPassword = createAuthEndpoint(
|
||||
/**
|
||||
* The email address of the user to send a password reset email to.
|
||||
*/
|
||||
email: z.string().email(),
|
||||
email: z
|
||||
.string({
|
||||
description:
|
||||
"The email address of the user to send a password reset email to",
|
||||
})
|
||||
.email(),
|
||||
/**
|
||||
* The URL to redirect the user to reset their password.
|
||||
* If the token isn't valid or expired, it'll be redirected with a query parameter `?
|
||||
* error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?
|
||||
* token=VALID_TOKEN
|
||||
*/
|
||||
redirectTo: z.string().optional(),
|
||||
redirectTo: z
|
||||
.string({
|
||||
description:
|
||||
"The URL to redirect the user to reset their password. If the token isn't valid or expired, it'll be redirected with a query parameter `?error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?token=VALID_TOKEN",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Send a password reset email to the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.emailAndPassword?.sendResetPassword) {
|
||||
@@ -108,8 +140,32 @@ export const forgetPasswordCallback = createAuthEndpoint(
|
||||
{
|
||||
method: "GET",
|
||||
query: z.object({
|
||||
callbackURL: z.string(),
|
||||
callbackURL: z.string({
|
||||
description: "The URL to redirect the user to reset their password",
|
||||
}),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Redirects the user to the callback URL with the token",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const { token } = ctx.params;
|
||||
@@ -144,9 +200,37 @@ export const resetPassword = createAuthEndpoint(
|
||||
),
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
newPassword: z.string(),
|
||||
token: z.string().optional(),
|
||||
newPassword: z.string({
|
||||
description: "The new password to set",
|
||||
}),
|
||||
token: z
|
||||
.string({
|
||||
description: "The token to reset the password",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Reset the password for a user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const token =
|
||||
|
||||
@@ -5,7 +5,29 @@ export const ok = createAuthEndpoint(
|
||||
"/ok",
|
||||
{
|
||||
method: "GET",
|
||||
metadata: HIDE_METADATA,
|
||||
metadata: {
|
||||
...HIDE_METADATA,
|
||||
openapi: {
|
||||
description: "Check if the API is working",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
ok: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
return ctx.json({
|
||||
|
||||
@@ -26,13 +26,50 @@ export const getSession = <Option extends BetterAuthOptions>() =>
|
||||
* If cookie cache is enabled, it will disable the cache
|
||||
* and fetch the session from the database
|
||||
*/
|
||||
disableCookieCache: z.boolean().optional(),
|
||||
disableCookieCache: z
|
||||
.boolean({
|
||||
description:
|
||||
"Disable cookie cache and fetch session from database",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
requireHeaders: true,
|
||||
metadata: {
|
||||
openapi: {
|
||||
tags: ["Session"],
|
||||
description: "Get the current session",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
session: {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: {
|
||||
type: "string",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
},
|
||||
expiresAt: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
type: "object",
|
||||
$ref: "#/components/schemas/User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -243,6 +280,37 @@ export const listSessions = <Option extends BetterAuthOptions>() =>
|
||||
method: "GET",
|
||||
use: [sessionMiddleware],
|
||||
requireHeaders: true,
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "List all active sessions for the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: {
|
||||
type: "string",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
},
|
||||
expiresAt: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const sessions = await ctx.context.internalAdapter.listSessions(
|
||||
@@ -265,10 +333,32 @@ export const revokeSession = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
token: z.string(),
|
||||
token: z.string({
|
||||
description: "The token to revoke",
|
||||
}),
|
||||
}),
|
||||
use: [sessionMiddleware],
|
||||
requireHeaders: true,
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Revoke a single session",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
token: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["token"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const token = ctx.body.token;
|
||||
@@ -306,6 +396,29 @@ export const revokeSessions = createAuthEndpoint(
|
||||
method: "POST",
|
||||
use: [sessionMiddleware],
|
||||
requireHeaders: true,
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Revoke all sessions for the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["status"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
try {
|
||||
@@ -333,6 +446,29 @@ export const revokeOtherSessions = createAuthEndpoint(
|
||||
method: "POST",
|
||||
requireHeaders: true,
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description:
|
||||
"Revoke all other sessions for the user except the current one",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
|
||||
@@ -25,25 +25,41 @@ export const signInSocial = createAuthEndpoint(
|
||||
* Callback URL to redirect to after the user
|
||||
* has signed in.
|
||||
*/
|
||||
callbackURL: z.string().optional(),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description:
|
||||
"Callback URL to redirect to after the user has signed in",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Callback url to redirect to if an error happens
|
||||
*
|
||||
* If it's initiated from the client sdk this defaults to
|
||||
* the current url.
|
||||
*/
|
||||
errorCallbackURL: z.string().optional(),
|
||||
errorCallbackURL: z
|
||||
.string({
|
||||
description: "Callback URL to redirect to if an error happens",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* OAuth2 provider to use`
|
||||
*/
|
||||
provider: z.enum(socialProviderList),
|
||||
provider: z.enum(socialProviderList, {
|
||||
description: "OAuth2 provider to use",
|
||||
}),
|
||||
/**
|
||||
* Disable automatic redirection to the provider
|
||||
*
|
||||
* This is useful if you want to handle the redirection
|
||||
* yourself like in a popup or a different tab.
|
||||
*/
|
||||
disableRedirect: z.boolean().optional(),
|
||||
disableRedirect: z
|
||||
.boolean({
|
||||
description:
|
||||
"Disable automatic redirection to the provider. Useful for handling the redirection yourself",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* ID token from the provider
|
||||
*
|
||||
@@ -60,26 +76,80 @@ export const signInSocial = createAuthEndpoint(
|
||||
/**
|
||||
* ID token from the provider
|
||||
*/
|
||||
token: z.string(),
|
||||
token: z.string({
|
||||
description: "ID token from the provider",
|
||||
}),
|
||||
/**
|
||||
* The nonce used to generate the token
|
||||
*/
|
||||
nonce: z.string().optional(),
|
||||
nonce: z
|
||||
.string({
|
||||
description: "Nonce used to generate the token",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Access token from the provider
|
||||
*/
|
||||
accessToken: z.string().optional(),
|
||||
accessToken: z
|
||||
.string({
|
||||
description: "Access token from the provider",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Refresh token from the provider
|
||||
*/
|
||||
refreshToken: z.string().optional(),
|
||||
refreshToken: z
|
||||
.string({
|
||||
description: "Refresh token from the provider",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Expiry date of the token
|
||||
*/
|
||||
expiresAt: z.number().optional(),
|
||||
expiresAt: z
|
||||
.number({
|
||||
description: "Expiry date of the token",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
{
|
||||
description:
|
||||
"ID token from the provider to sign in the user with id token",
|
||||
},
|
||||
),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Sign in with a social provider",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
session: {
|
||||
type: "string",
|
||||
},
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
redirect: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["session", "user", "url", "redirect"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (c) => {
|
||||
const provider = c.context.socialProviders.find(
|
||||
@@ -192,22 +262,69 @@ export const signInEmail = createAuthEndpoint(
|
||||
/**
|
||||
* Email of the user
|
||||
*/
|
||||
email: z.string(),
|
||||
email: z.string({
|
||||
description: "Email of the user",
|
||||
}),
|
||||
/**
|
||||
* Password of the user
|
||||
*/
|
||||
password: z.string(),
|
||||
password: z.string({
|
||||
description: "Password of the user",
|
||||
}),
|
||||
/**
|
||||
* Callback URL to use as a redirect for email
|
||||
* verification and for possible redirects
|
||||
*/
|
||||
callbackURL: z.string().optional(),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description:
|
||||
"Callback URL to use as a redirect for email verification",
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* If this is false, the session will not be remembered
|
||||
* @default true
|
||||
*/
|
||||
rememberMe: z.boolean().default(true).optional(),
|
||||
rememberMe: z
|
||||
.boolean({
|
||||
description:
|
||||
"If this is false, the session will not be remembered. Default is `true`.",
|
||||
})
|
||||
.default(true)
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Sign in with email and password",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
session: {
|
||||
type: "string",
|
||||
},
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
},
|
||||
redirect: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["session", "user", "url", "redirect"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options?.emailAndPassword?.enabled) {
|
||||
|
||||
@@ -8,6 +8,28 @@ export const signOut = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
requireHeaders: true,
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Sign out the current user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
success: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const sessionCookieToken = await ctx.getSignedCookie(
|
||||
|
||||
@@ -29,6 +29,60 @@ export const signUpEmail = <O extends BetterAuthOptions>() =>
|
||||
password: ZodString;
|
||||
}> &
|
||||
toZod<AdditionalUserFieldsInput<O>>,
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Sign up a user using email and password",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "The name of the user",
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
description: "The email of the user",
|
||||
},
|
||||
password: {
|
||||
type: "string",
|
||||
description: "The password of the user",
|
||||
},
|
||||
callbackURL: {
|
||||
type: "string",
|
||||
description:
|
||||
"The URL to use for email verification callback",
|
||||
},
|
||||
},
|
||||
required: ["name", "email", "password"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
session: {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.emailAndPassword?.enabled) {
|
||||
|
||||
@@ -20,6 +20,47 @@ export const updateUser = <O extends BetterAuthOptions>() =>
|
||||
}> &
|
||||
toZod<AdditionalUserFieldsInput<O>>,
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Update the current user",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
description: "The name of the user",
|
||||
},
|
||||
image: {
|
||||
type: "string",
|
||||
description: "The image of the user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const body = ctx.body as {
|
||||
@@ -74,18 +115,49 @@ export const changePassword = createAuthEndpoint(
|
||||
/**
|
||||
* The new password to set
|
||||
*/
|
||||
newPassword: z.string(),
|
||||
newPassword: z.string({
|
||||
description: "The new password to set",
|
||||
}),
|
||||
/**
|
||||
* The current password of the user
|
||||
*/
|
||||
currentPassword: z.string(),
|
||||
currentPassword: z.string({
|
||||
description: "The current password",
|
||||
}),
|
||||
/**
|
||||
* revoke all sessions that are not the
|
||||
* current one logged in by the user
|
||||
*/
|
||||
revokeOtherSessions: z.boolean().optional(),
|
||||
revokeOtherSessions: z
|
||||
.boolean({
|
||||
description: "Revoke all other sessions",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Change the password of the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user: {
|
||||
description: "The user object",
|
||||
$ref: "#/components/schemas/User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const { newPassword, currentPassword, revokeOtherSessions } = ctx.body;
|
||||
@@ -215,9 +287,28 @@ export const deleteUser = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
password: z.string(),
|
||||
password: z.string({
|
||||
description: "The password of the user",
|
||||
}),
|
||||
}),
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Delete the user",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const { password } = ctx.body;
|
||||
@@ -259,10 +350,42 @@ export const changeEmail = createAuthEndpoint(
|
||||
})
|
||||
.optional(),
|
||||
body: z.object({
|
||||
newEmail: z.string().email(),
|
||||
callbackURL: z.string().optional(),
|
||||
newEmail: z
|
||||
.string({
|
||||
description: "The new email to set",
|
||||
})
|
||||
.email(),
|
||||
callbackURL: z
|
||||
.string({
|
||||
description: "The URL to redirect to after email verification",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
use: [sessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
user: {
|
||||
type: "object",
|
||||
},
|
||||
status: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.options.user?.changeEmail?.enabled) {
|
||||
|
||||
@@ -17,3 +17,4 @@ export * from "./email-otp";
|
||||
export * from "./one-tap";
|
||||
export * from "./oauth-proxy";
|
||||
export * from "./custom-session";
|
||||
export * from "./open-api";
|
||||
|
||||
434
packages/better-auth/src/plugins/open-api/generator.ts
Normal file
434
packages/better-auth/src/plugins/open-api/generator.ts
Normal file
@@ -0,0 +1,434 @@
|
||||
import type { Endpoint, EndpointOptions } from "better-call";
|
||||
import { ZodObject, ZodOptional, ZodSchema } from "zod";
|
||||
import type { OpenAPISchemaType, OpenAPIParameter } from "better-call";
|
||||
import type { AuthContext, BetterAuthOptions } from "better-auth";
|
||||
import { getEndpoints } from "better-auth/api";
|
||||
import { getAuthTables } from "../../db";
|
||||
|
||||
interface Path {
|
||||
get?: {
|
||||
tags?: string[];
|
||||
operationId?: string;
|
||||
description?: string;
|
||||
security?: [{ bearerAuth: string[] }];
|
||||
parameters?: OpenAPIParameter[];
|
||||
responses?: {
|
||||
[key in string]: {
|
||||
description?: string;
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type?: OpenAPISchemaType;
|
||||
properties?: Record<string, any>;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
post?: {
|
||||
tags?: string[];
|
||||
operationId?: string;
|
||||
description?: string;
|
||||
security?: [{ bearerAuth: string[] }];
|
||||
parameters?: OpenAPIParameter[];
|
||||
requestBody?: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type?: OpenAPISchemaType;
|
||||
properties?: Record<string, any>;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
responses?: {
|
||||
[key in string]: {
|
||||
description?: string;
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type?: OpenAPISchemaType;
|
||||
properties?: Record<string, any>;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
const paths: Record<string, Path> = {};
|
||||
|
||||
function getTypeFromZodType(zodType: ZodSchema) {
|
||||
switch (zodType.constructor.name) {
|
||||
case "ZodString":
|
||||
return "string";
|
||||
case "ZodNumber":
|
||||
return "number";
|
||||
case "ZodBoolean":
|
||||
return "boolean";
|
||||
case "ZodObject":
|
||||
return "object";
|
||||
case "ZodArray":
|
||||
return "array";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
function getParameters(options: EndpointOptions) {
|
||||
const parameters: OpenAPIParameter[] = [];
|
||||
if (options.metadata?.openapi?.parameters) {
|
||||
parameters.push(...options.metadata.openapi.parameters);
|
||||
return parameters;
|
||||
}
|
||||
if (options.query instanceof ZodObject) {
|
||||
Object.entries(options.query.shape).forEach(([key, value]) => {
|
||||
if (value instanceof ZodSchema) {
|
||||
parameters.push({
|
||||
name: key,
|
||||
in: "query",
|
||||
schema: {
|
||||
type: getTypeFromZodType(value),
|
||||
...("minLength" in value && value.minLength
|
||||
? {
|
||||
minLength: value.minLength as number,
|
||||
}
|
||||
: {}),
|
||||
description: value.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
function getRequestBody(options: EndpointOptions): any {
|
||||
if (options.metadata?.openapi?.requestBody) {
|
||||
return options.metadata.openapi.requestBody;
|
||||
}
|
||||
if (!options.body) return undefined;
|
||||
if (
|
||||
options.body instanceof ZodObject ||
|
||||
options.body instanceof ZodOptional
|
||||
) {
|
||||
// @ts-ignore
|
||||
const shape = options.body.shape;
|
||||
if (!shape) return undefined;
|
||||
const properties: Record<string, any> = {};
|
||||
const required: string[] = [];
|
||||
Object.entries(shape).forEach(([key, value]) => {
|
||||
if (value instanceof ZodSchema) {
|
||||
properties[key] = {
|
||||
type: getTypeFromZodType(value),
|
||||
description: value.description,
|
||||
};
|
||||
if (!(value instanceof ZodOptional)) {
|
||||
required.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
required:
|
||||
options.body instanceof ZodOptional
|
||||
? false
|
||||
: options.body
|
||||
? true
|
||||
: false,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties,
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getResponse(responses?: Record<string, any>) {
|
||||
return {
|
||||
"400": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Bad Request. Usually due to missing parameters, or invalid parameters.",
|
||||
},
|
||||
"401": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
description: "Unauthorized. Due to missing or invalid authentication.",
|
||||
},
|
||||
"403": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Forbidden. You do not have permission to access this resource or to perform this action.",
|
||||
},
|
||||
"404": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description: "Not Found. The requested resource was not found.",
|
||||
},
|
||||
"429": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Too Many Requests. You have exceeded the rate limit. Try again later.",
|
||||
},
|
||||
"500": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Internal Server Error. This is a problem with the server that you cannot fix.",
|
||||
},
|
||||
...responses,
|
||||
} as any;
|
||||
}
|
||||
|
||||
export async function generator(ctx: AuthContext, options: BetterAuthOptions) {
|
||||
const baseEndpoints = getEndpoints(ctx, {
|
||||
...options,
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
const tables = getAuthTables(options);
|
||||
const models = Object.entries(tables).reduce((acc, [key, value]) => {
|
||||
const modelName = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
// @ts-ignore
|
||||
acc[modelName] = {
|
||||
type: "object",
|
||||
properties: Object.entries(value.fields).reduce(
|
||||
(acc, [key, value]) => {
|
||||
acc[key] = {
|
||||
type: value.type,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
),
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const components = {
|
||||
schemas: {
|
||||
...models,
|
||||
},
|
||||
};
|
||||
|
||||
Object.entries(baseEndpoints.api).forEach(([_, value]) => {
|
||||
const options = value.options as EndpointOptions;
|
||||
if (options.metadata?.SERVER_ONLY) return;
|
||||
if (options.method === "GET") {
|
||||
paths[value.path] = {
|
||||
get: {
|
||||
tags: ["Default", ...(options.metadata?.openapi?.tags || [])],
|
||||
description: options.metadata?.openapi?.description,
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
responses: getResponse(options.metadata?.openapi?.responses),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (options.method === "POST") {
|
||||
const body = getRequestBody(options);
|
||||
paths[value.path] = {
|
||||
post: {
|
||||
tags: ["Default", ...(options.metadata?.openapi?.tags || [])],
|
||||
description: options.metadata?.openapi?.description,
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
...(body
|
||||
? { requestBody: body }
|
||||
: {
|
||||
requestBody: {
|
||||
//set body none
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
responses: getResponse(options.metadata?.openapi?.responses),
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
for (const plugin of options.plugins || []) {
|
||||
if (plugin.id === "open-api") {
|
||||
continue;
|
||||
}
|
||||
const pluginEndpoints = getEndpoints(ctx, {
|
||||
...options,
|
||||
plugins: [plugin],
|
||||
});
|
||||
const api = Object.keys(pluginEndpoints.api)
|
||||
.map((key) => {
|
||||
if (
|
||||
baseEndpoints.api[key as keyof typeof baseEndpoints.api] === undefined
|
||||
) {
|
||||
return pluginEndpoints.api[key as keyof typeof pluginEndpoints.api];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((x) => x !== null) as Endpoint[];
|
||||
Object.entries(api).forEach(([key, value]) => {
|
||||
const options = value.options as EndpointOptions;
|
||||
if (options.metadata?.SERVER_ONLY) return;
|
||||
if (options.method === "GET") {
|
||||
paths[value.path] = {
|
||||
get: {
|
||||
tags: options.metadata?.openapi?.tags || [
|
||||
plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1),
|
||||
],
|
||||
description: options.metadata?.openapi?.description,
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
responses: getResponse(options.metadata?.openapi?.responses),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (options.method === "POST") {
|
||||
paths[value.path] = {
|
||||
post: {
|
||||
tags: options.metadata?.openapi?.tags || [
|
||||
plugin.id.charAt(0).toUpperCase() + plugin.id.slice(1),
|
||||
],
|
||||
description: options.metadata?.openapi?.description,
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
requestBody: getRequestBody(options),
|
||||
responses: getResponse(options.metadata?.openapi?.responses),
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const res = {
|
||||
openapi: "3.1.1",
|
||||
info: {
|
||||
title: "Better Auth",
|
||||
description: "API Reference for your Better Auth Instance",
|
||||
},
|
||||
components,
|
||||
security: [
|
||||
{
|
||||
apiKeyCookie: [],
|
||||
},
|
||||
],
|
||||
servers: [
|
||||
{
|
||||
url: ctx.baseURL,
|
||||
},
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: "Default",
|
||||
description:
|
||||
"Default endpoints that are included with Better Auth by default. These endpoints are not part of any plugin.",
|
||||
},
|
||||
],
|
||||
paths,
|
||||
};
|
||||
return res;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BetterAuthPlugin } from "better-auth";
|
||||
import { createAuthEndpoint } from "better-auth/plugins";
|
||||
import { getEndpoints } from "better-auth/api";
|
||||
import { generator } from "./generator";
|
||||
import { logo } from "./logo";
|
||||
|
||||
const getHTML = (apiReference: Record<string, any>) => `<!doctype html>
|
||||
<html>
|
||||
@@ -18,7 +18,20 @@ const getHTML = (apiReference: Record<string, any>) => `<!doctype html>
|
||||
type="application/json">
|
||||
${JSON.stringify(apiReference)}
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
<script>
|
||||
var configuration = {
|
||||
favicon: "data:image/svg+xml;utf8,${encodeURIComponent(logo)}",
|
||||
theme: "saturn",
|
||||
metaData: {
|
||||
title: "Better Auth API",
|
||||
description: "API Reference for your Better Auth Instance",
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('api-reference').dataset.configuration =
|
||||
JSON.stringify(configuration)
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -27,7 +40,7 @@ export const openAPI = () => {
|
||||
id: "open-api",
|
||||
endpoints: {
|
||||
openAPI: createAuthEndpoint(
|
||||
"/api-reference",
|
||||
"/reference",
|
||||
{
|
||||
method: "GET",
|
||||
},
|
||||
10
packages/better-auth/src/plugins/open-api/logo.ts
Normal file
10
packages/better-auth/src/plugins/open-api/logo.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -260,6 +260,48 @@ export const organization = <O extends OrganizationOptions>(options?: O) => {
|
||||
}>;
|
||||
}>,
|
||||
use: [orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Check if the user has permission",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
permission: {
|
||||
type: "object",
|
||||
description: "The permission to check",
|
||||
},
|
||||
},
|
||||
required: ["permission"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
error: {
|
||||
type: "string",
|
||||
},
|
||||
success: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
required: ["success"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.session.session.activeOrganizationId) {
|
||||
|
||||
@@ -16,11 +16,73 @@ export const createInvitation = <O extends OrganizationOptions | undefined>(
|
||||
method: "POST",
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
body: z.object({
|
||||
email: z.string(),
|
||||
role: z.string() as unknown as InferRolesFromOption<O>,
|
||||
organizationId: z.string().optional(),
|
||||
resend: z.boolean().optional(),
|
||||
email: z.string({
|
||||
description: "The email address of the user to invite",
|
||||
}),
|
||||
role: z.string({
|
||||
description: "The role to assign to the user",
|
||||
}) as unknown as InferRolesFromOption<O>,
|
||||
organizationId: z
|
||||
.string({
|
||||
description: "The organization ID to invite the user to",
|
||||
})
|
||||
.optional(),
|
||||
resend: z
|
||||
.boolean({
|
||||
description:
|
||||
"Resend the invitation email, if the user is already invited",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Invite a user to an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
},
|
||||
role: {
|
||||
type: "string",
|
||||
},
|
||||
organizationId: {
|
||||
type: "string",
|
||||
},
|
||||
inviterId: {
|
||||
type: "string",
|
||||
},
|
||||
status: {
|
||||
type: "string",
|
||||
},
|
||||
expiresAt: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
"id",
|
||||
"email",
|
||||
"role",
|
||||
"organizationId",
|
||||
"inviterId",
|
||||
"status",
|
||||
"expiresAt",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.context.orgOptions.sendInvitationEmail) {
|
||||
@@ -121,9 +183,36 @@ export const acceptInvitation = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
invitationId: z.string(),
|
||||
invitationId: z.string({
|
||||
description: "The ID of the invitation to accept",
|
||||
}),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Accept an invitation to an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
invitation: {
|
||||
type: "object",
|
||||
},
|
||||
member: {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -176,9 +265,36 @@ export const rejectInvitation = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
invitationId: z.string(),
|
||||
invitationId: z.string({
|
||||
description: "The ID of the invitation to reject",
|
||||
}),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Reject an invitation to an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
invitation: {
|
||||
type: "object",
|
||||
},
|
||||
member: {
|
||||
type: "null",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -214,9 +330,31 @@ export const cancelInvitation = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
invitationId: z.string(),
|
||||
invitationId: z.string({
|
||||
description: "The ID of the invitation to cancel",
|
||||
}),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
openapi: {
|
||||
description: "Cancel an invitation to an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
invitation: {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -259,8 +397,71 @@ export const getInvitation = createAuthEndpoint(
|
||||
use: [orgMiddleware],
|
||||
requireHeaders: true,
|
||||
query: z.object({
|
||||
id: z.string(),
|
||||
id: z.string({
|
||||
description: "The ID of the invitation to get",
|
||||
}),
|
||||
}),
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Get an invitation by ID",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
email: {
|
||||
type: "string",
|
||||
},
|
||||
role: {
|
||||
type: "string",
|
||||
},
|
||||
organizationId: {
|
||||
type: "string",
|
||||
},
|
||||
inviterId: {
|
||||
type: "string",
|
||||
},
|
||||
status: {
|
||||
type: "string",
|
||||
},
|
||||
expiresAt: {
|
||||
type: "string",
|
||||
},
|
||||
organizationName: {
|
||||
type: "string",
|
||||
},
|
||||
organizationSlug: {
|
||||
type: "string",
|
||||
},
|
||||
inviterEmail: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
"id",
|
||||
"email",
|
||||
"role",
|
||||
"organizationId",
|
||||
"inviterId",
|
||||
"status",
|
||||
"expiresAt",
|
||||
"organizationName",
|
||||
"organizationSlug",
|
||||
"inviterEmail",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = await getSessionFromCtx(ctx);
|
||||
|
||||
@@ -11,13 +11,58 @@ export const removeMember = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
memberIdOrEmail: z.string(),
|
||||
memberIdOrEmail: z.string({
|
||||
description: "The ID or email of the member to remove",
|
||||
}),
|
||||
/**
|
||||
* If not provided, the active organization will be used
|
||||
*/
|
||||
organizationId: z.string().optional(),
|
||||
organizationId: z
|
||||
.string({
|
||||
description:
|
||||
"The ID of the organization to remove the member from. If not provided, the active organization will be used",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Remove a member from an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
member: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
},
|
||||
organizationId: {
|
||||
type: "string",
|
||||
},
|
||||
role: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["id", "userId", "organizationId", "role"],
|
||||
},
|
||||
},
|
||||
required: ["member"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -110,6 +155,44 @@ export const updateMemberRole = <O extends OrganizationOptions>(option: O) =>
|
||||
organizationId: z.string().optional(),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Update the role of a member in an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
member: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
},
|
||||
organizationId: {
|
||||
type: "string",
|
||||
},
|
||||
role: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["id", "userId", "organizationId", "role"],
|
||||
},
|
||||
},
|
||||
required: ["member"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -184,6 +267,38 @@ export const getActiveMember = createAuthEndpoint(
|
||||
{
|
||||
method: "GET",
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Get the active member in the organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
},
|
||||
userId: {
|
||||
type: "string",
|
||||
},
|
||||
organizationId: {
|
||||
type: "string",
|
||||
},
|
||||
role: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["id", "userId", "organizationId", "role"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
|
||||
@@ -11,13 +11,49 @@ export const createOrganization = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
userId: z.string().optional(),
|
||||
logo: z.string().optional(),
|
||||
metadata: z.record(z.string(), z.any()).optional(),
|
||||
name: z.string({
|
||||
description: "The name of the organization",
|
||||
}),
|
||||
slug: z.string({
|
||||
description: "The slug of the organization",
|
||||
}),
|
||||
userId: z
|
||||
.string({
|
||||
description:
|
||||
"The user id of the organization creator. If not provided, the current user will be used. Should only be used by admins or when called by the server.",
|
||||
})
|
||||
.optional(),
|
||||
logo: z
|
||||
.string({
|
||||
description: "The logo of the organization",
|
||||
})
|
||||
.optional(),
|
||||
metadata: z
|
||||
.record(z.string(), z.any(), {
|
||||
description: "The metadata of the organization",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Create an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
description: "The organization that was created",
|
||||
$ref: "#/components/schemas/Organization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const user = ctx.context.session.user;
|
||||
@@ -89,15 +125,46 @@ export const updateOrganization = createAuthEndpoint(
|
||||
body: z.object({
|
||||
data: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
slug: z.string().optional(),
|
||||
logo: z.string().optional(),
|
||||
name: z
|
||||
.string({
|
||||
description: "The name of the organization",
|
||||
})
|
||||
.optional(),
|
||||
slug: z
|
||||
.string({
|
||||
description: "The slug of the organization",
|
||||
})
|
||||
.optional(),
|
||||
logo: z
|
||||
.string({
|
||||
description: "The logo of the organization",
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.partial(),
|
||||
organizationId: z.string().optional(),
|
||||
}),
|
||||
requireHeaders: true,
|
||||
use: [orgMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Update an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
description: "The updated organization",
|
||||
$ref: "#/components/schemas/Organization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = await ctx.context.getSession(ctx);
|
||||
@@ -162,10 +229,30 @@ export const deleteOrganization = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
organizationId: z.string(),
|
||||
organizationId: z.string({
|
||||
description: "The organization id to delete",
|
||||
}),
|
||||
}),
|
||||
requireHeaders: true,
|
||||
use: [orgMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Delete an organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "string",
|
||||
description: "The organization id that was deleted",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = await ctx.context.getSession(ctx);
|
||||
@@ -230,11 +317,34 @@ export const getFullOrganization = createAuthEndpoint(
|
||||
method: "GET",
|
||||
query: z.optional(
|
||||
z.object({
|
||||
organizationId: z.string().optional(),
|
||||
organizationId: z
|
||||
.string({
|
||||
description: "The organization id to get",
|
||||
})
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
requireHeaders: true,
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Get the full organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
description: "The organization",
|
||||
$ref: "#/components/schemas/Organization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const session = ctx.context.session;
|
||||
@@ -261,9 +371,34 @@ export const setActiveOrganization = createAuthEndpoint(
|
||||
{
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
organizationId: z.string().nullable().optional(),
|
||||
organizationId: z
|
||||
.string({
|
||||
description:
|
||||
"The organization id to set as active. Can be null to unset the active organization",
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
}),
|
||||
use: [orgSessionMiddleware, orgMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "Set the active organization",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
description: "The organization",
|
||||
$ref: "#/components/schemas/Organization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
||||
@@ -319,6 +454,26 @@ export const listOrganizations = createAuthEndpoint(
|
||||
{
|
||||
method: "GET",
|
||||
use: [orgMiddleware, orgSessionMiddleware],
|
||||
metadata: {
|
||||
openapi: {
|
||||
description: "List all organizations",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "array",
|
||||
items: {
|
||||
$ref: "#/components/schemas/Organization",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx) => {
|
||||
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "@better-auth/open-api",
|
||||
"version": "1.0.0-canary.12",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"build": "tsup --dts --minify --clean",
|
||||
"dev": "tsup --watch --sourcemap --dts"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"better-auth": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-call": "^0.3.0"
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
import type { Endpoint, EndpointOptions } from "better-call";
|
||||
import { ZodObject, ZodSchema } from "zod";
|
||||
import type { OpenAPISchemaType, OpenAPIParameter } from "better-call";
|
||||
import type { AuthContext, BetterAuthOptions } from "better-auth";
|
||||
import { getEndpoints } from "better-auth/api";
|
||||
|
||||
interface Path {
|
||||
get?: {
|
||||
tags?: string[];
|
||||
operationId?: string;
|
||||
security?: [{ bearerAuth: string[] }];
|
||||
parameters?: OpenAPIParameter[];
|
||||
responses?: {
|
||||
[key in string]: {
|
||||
description?: string;
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type?: OpenAPISchemaType;
|
||||
properties?: Record<string, any>;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
const paths: Record<string, Path> = {};
|
||||
|
||||
function getTypeFromZodType(zodType: ZodSchema) {
|
||||
switch (zodType.constructor.name) {
|
||||
case "ZodString":
|
||||
return "string";
|
||||
case "ZodNumber":
|
||||
return "number";
|
||||
case "ZodBoolean":
|
||||
return "boolean";
|
||||
case "ZodObject":
|
||||
return "object";
|
||||
case "ZodArray":
|
||||
return "array";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
function getParameters(options: EndpointOptions) {
|
||||
const parameters: OpenAPIParameter[] = [];
|
||||
if (options.metadata?.openapi?.parameters) {
|
||||
parameters.push(...options.metadata.openapi.parameters);
|
||||
return parameters;
|
||||
}
|
||||
if (options.query instanceof ZodObject) {
|
||||
Object.entries(options.query.shape).forEach(([key, value]) => {
|
||||
if (value instanceof ZodSchema) {
|
||||
parameters.push({
|
||||
name: key,
|
||||
in: "query",
|
||||
schema: {
|
||||
type: getTypeFromZodType(value),
|
||||
...("minLength" in value && value.minLength
|
||||
? {
|
||||
minLength: value.minLength as number,
|
||||
}
|
||||
: {}),
|
||||
description: value.description,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
export async function generator(ctx: AuthContext, options: BetterAuthOptions) {
|
||||
const baseEndpoints = getEndpoints(ctx, {
|
||||
...options,
|
||||
plugins: [],
|
||||
});
|
||||
|
||||
Object.entries(baseEndpoints.api).forEach(([_, value]) => {
|
||||
const options = value.options as EndpointOptions;
|
||||
if (options.method === "GET") {
|
||||
paths[value.path] = {
|
||||
get: {
|
||||
tags: ["core", ...(options.metadata?.openapi?.tags || [])],
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
responses: options.metadata?.openapi?.responses || {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
for (const plugin of options.plugins || []) {
|
||||
const pluginEndpoints = getEndpoints(ctx, {
|
||||
...options,
|
||||
plugins: [plugin],
|
||||
});
|
||||
const api = Object.keys(pluginEndpoints.api)
|
||||
.map((key) => {
|
||||
if (
|
||||
baseEndpoints.api[key as keyof typeof baseEndpoints.api] === undefined
|
||||
) {
|
||||
return pluginEndpoints.api[key as keyof typeof pluginEndpoints.api];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((x) => x !== null) as Endpoint[];
|
||||
Object.entries(api).forEach(([key, value]) => {
|
||||
const options = value.options as EndpointOptions;
|
||||
if (options.method === "GET") {
|
||||
paths[value.path] = {
|
||||
get: {
|
||||
tags: options.metadata?.openapi?.tags || [plugin.id],
|
||||
operationId: options.metadata?.openapi?.operationId,
|
||||
security: [
|
||||
{
|
||||
bearerAuth: [],
|
||||
},
|
||||
],
|
||||
parameters: getParameters(options),
|
||||
responses: options.metadata?.openapi?.responses || {
|
||||
"200": {
|
||||
description: "Success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["message"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const res = {
|
||||
openapi: "3.1.1",
|
||||
info: {
|
||||
title: "Better Auth Api",
|
||||
description: "API Reference for your Better Auth Instance",
|
||||
},
|
||||
security: [
|
||||
{
|
||||
apiKeyCookie: [],
|
||||
},
|
||||
],
|
||||
servers: [
|
||||
{
|
||||
url: ctx.baseURL,
|
||||
},
|
||||
],
|
||||
tags: [
|
||||
{
|
||||
name: "Authentication",
|
||||
description:
|
||||
"Some endpoints are public, but some require authentication. We provide all the required endpoints to create an account and authorize yourself.",
|
||||
},
|
||||
],
|
||||
paths,
|
||||
};
|
||||
return res;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "es2022",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"module": "ESNext",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig((env) => {
|
||||
return {
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm", "cjs"],
|
||||
bundle: true,
|
||||
skipNodeModulesBundle: true,
|
||||
external: ["better-call", "better-auth"],
|
||||
};
|
||||
});
|
||||
34
pnpm-lock.yaml
generated
34
pnpm-lock.yaml
generated
@@ -44,9 +44,6 @@ importers:
|
||||
|
||||
demo/nextjs:
|
||||
dependencies:
|
||||
'@better-auth/open-api':
|
||||
specifier: workspace:1.0.0-canary.12
|
||||
version: link:../../packages/open-api
|
||||
'@better-fetch/fetch':
|
||||
specifier: 1.1.12
|
||||
version: 1.1.12
|
||||
@@ -1350,8 +1347,8 @@ importers:
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(encoding@0.1.13)
|
||||
better-call:
|
||||
specifier: 0.3.1
|
||||
version: 0.3.1
|
||||
specifier: 0.3.2
|
||||
version: 0.3.2
|
||||
consola:
|
||||
specifier: ^3.2.3
|
||||
version: 3.2.3
|
||||
@@ -1560,16 +1557,6 @@ importers:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(@types/node@22.8.6)(happy-dom@15.8.0)(lightningcss@1.27.0)(terser@5.36.0)
|
||||
|
||||
packages/open-api:
|
||||
dependencies:
|
||||
better-call:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0
|
||||
devDependencies:
|
||||
better-auth:
|
||||
specifier: workspace:*
|
||||
version: link:../better-auth
|
||||
|
||||
packages:
|
||||
|
||||
'@algolia/cache-browser-local-storage@4.24.0':
|
||||
@@ -8109,11 +8096,8 @@ packages:
|
||||
better-call@0.2.3-beta.2:
|
||||
resolution: {integrity: sha512-ybOtGcR4pOsHI2XE+urR9zcmK+s0YnhJSx8KDj6ul7MUEyYOiMEnq/bylyH62/7qXuYb9q8Oqkp9NF9vWOZ4Mg==}
|
||||
|
||||
better-call@0.3.0:
|
||||
resolution: {integrity: sha512-nBWeQl+O1NCPMkb958VQzVn0AfSWwILGoLCpLbTzz3p0QnsqFYSTe6z8Uzo4p/6Iah8zrRBoLBQvFLUFl80jxA==}
|
||||
|
||||
better-call@0.3.1:
|
||||
resolution: {integrity: sha512-uJoTAVLHCIrRebBxu2rQ/CPpf7r1c7FrfFIL2OS5dVx1z/64nGMZ5sH2GUtYu22XRjbOGEn7KI9eNkLpIo41CQ==}
|
||||
better-call@0.3.2:
|
||||
resolution: {integrity: sha512-ZYUADh4S4JF3QOIgC0QAm4N9Ifb7v3rOEcwkIxr3UXxd+GCrGmEzv7VDe35o1ldX0Zcb9CX3QfT0jdJoMIKA9w==}
|
||||
|
||||
better-opn@3.0.2:
|
||||
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
|
||||
@@ -25761,15 +25745,7 @@ snapshots:
|
||||
set-cookie-parser: 2.7.1
|
||||
typescript: 5.6.3
|
||||
|
||||
better-call@0.3.0:
|
||||
dependencies:
|
||||
'@better-fetch/fetch': 1.1.12
|
||||
rou3: 0.5.1
|
||||
set-cookie-parser: 2.7.1
|
||||
uncrypto: 0.1.3
|
||||
zod: 3.23.8
|
||||
|
||||
better-call@0.3.1:
|
||||
better-call@0.3.2:
|
||||
dependencies:
|
||||
'@better-fetch/fetch': 1.1.12
|
||||
rou3: 0.5.1
|
||||
|
||||
Reference in New Issue
Block a user