diff --git a/demo/nextjs/lib/auth.ts b/demo/nextjs/lib/auth.ts index 2c645b73a6..dc9ef818ca 100644 --- a/demo/nextjs/lib/auth.ts +++ b/demo/nextjs/lib/auth.ts @@ -18,6 +18,7 @@ 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 || ""; @@ -148,6 +149,7 @@ export const auth = betterAuth({ }, }), passkey(), + openAPI(), bearer(), admin(), multiSession(), diff --git a/demo/nextjs/package.json b/demo/nextjs/package.json index 6dfbca50f7..1720e59937 100644 --- a/demo/nextjs/package.json +++ b/demo/nextjs/package.json @@ -11,6 +11,7 @@ "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", diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index 628543ad9c..8c44b4ba0c 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -405,7 +405,7 @@ "@noble/hashes": "^1.5.0", "@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/server": "^10.0.1", - "better-call": "0.2.15-beta.7", + "better-call": "0.3.1", "consola": "^3.2.3", "defu": "^6.1.4", "jose": "^5.9.4", diff --git a/packages/better-auth/script/open-api.json b/packages/better-auth/script/open-api.json new file mode 100644 index 0000000000..66901fefbb --- /dev/null +++ b/packages/better-auth/script/open-api.json @@ -0,0 +1,419 @@ +{ + "/get-session": { + "get": { + "tags": [ + "Session" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [], + "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" + ] + } + } + } + } + } + } + }, + "/verify-email": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "token", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "callbackURL", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "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" + ] + } + } + } + } + } + } + }, + "/reset-password/:token": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "callbackURL", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "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" + ] + } + } + } + } + } + } + }, + "/list-sessions": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [], + "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" + ] + } + } + } + } + } + } + }, + "/list-accounts": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [], + "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" + ] + } + } + } + } + } + } + }, + "/oauth-proxy-callback": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "callbackURL", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "cookies", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "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" + ] + } + } + } + } + } + } + }, + "/ok": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [], + "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" + ] + } + } + } + } + } + } + }, + "/error": { + "get": { + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [], + "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" + ] + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/better-auth/script/open-api.ts b/packages/better-auth/script/open-api.ts index 678039293e..bb837fbf74 100644 --- a/packages/better-auth/script/open-api.ts +++ b/packages/better-auth/script/open-api.ts @@ -1,10 +1,12 @@ import type { EndpointOptions } from "better-call"; import { betterAuth } from "../src"; -import { ZodObject, ZodString } from "zod"; - -export const auth = betterAuth({}); - -type SchemaType = "string" | "number" | "boolean" | "object" | "array"; +import { ZodObject, ZodSchema, ZodString } from "zod"; +import type { OpenAPISchemaType, OpenAPIParameter } from "better-call"; +import fs from "fs/promises"; +import { oAuthProxy } from "../src/plugins"; +export const auth = betterAuth({ + plugins: [oAuthProxy()], +}); const components = { schemas: { @@ -35,27 +37,15 @@ interface Path { get?: { tags?: string[]; operationId?: string; - security?: { - bearerAuth: string[]; - }; - parameters?: { - schema?: { - type: SchemaType; - minLength: number; - description?: string; - example?: string; - }; - required?: boolean; - name: string; - in: string; - }[]; + security?: [{ bearerAuth: string[] }]; + parameters?: OpenAPIParameter[]; responses?: { - [key in 200 | 400 | 401 | 403 | 500]: { + [key in string]: { description?: string; content: { "application/json": { schema: { - type?: SchemaType; + type?: OpenAPISchemaType; properties?: Record; required?: string[]; $ref?: string; @@ -68,15 +58,124 @@ interface Path { } const paths: Record = {}; +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; +} + Object.entries(auth.api).forEach(([key, value]) => { const options = value.options as EndpointOptions; if (options.method === "GET") { - if (options.query instanceof ZodObject) { - Object.entries(options.query.shape).forEach(([key, value]) => { - if (value instanceof ZodString) { - console.log(value); - } - }); - } + paths[value.path] = { + get: { + tags: 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"], + }, + }, + }, + }, + }, + }, + }; } }); + +async function main() { + const pkgJSON = await fs.readFile("./package.json", { + encoding: "utf-8", + }); + + const version = JSON.parse(pkgJSON).version; + + fs.writeFile( + "./script/open-api.json", + JSON.stringify( + { + openapi: "3.1.1", + info: { + title: "Better Auth Api", + version, + }, + paths, + }, + null, + 2, + ), + ); +} + +main(); diff --git a/packages/better-auth/src/api/routes/session.ts b/packages/better-auth/src/api/routes/session.ts index 97e56340ed..c4fde0641b 100644 --- a/packages/better-auth/src/api/routes/session.ts +++ b/packages/better-auth/src/api/routes/session.ts @@ -30,6 +30,11 @@ export const getSession =