feat: add better-auth api-key plugin, import endpoint, and API key UI
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { auth } from "@/lib/auth"
|
||||
import { db } from "@/lib/db/index"
|
||||
import { user } from "@/lib/db/schema/auth"
|
||||
import { eq } from "drizzle-orm"
|
||||
import type { Session } from "better-auth"
|
||||
|
||||
type User = typeof auth.$Infer.Session.user
|
||||
|
||||
type ApiKeyGuardResult =
|
||||
| { ok: true; user: User; session: Session | null; keyId: string }
|
||||
| { ok: false; error: string; status: number }
|
||||
|
||||
/**
|
||||
* Attempts to authenticate a request using an API key from the x-api-key header.
|
||||
* Verifies the key via Better Auth and looks up the user from the database.
|
||||
*/
|
||||
export async function authenticateWithApiKey(
|
||||
requestHeaders: Headers,
|
||||
): Promise<ApiKeyGuardResult> {
|
||||
const apiKey = requestHeaders.get("x-api-key")
|
||||
if (!apiKey) {
|
||||
return { ok: false, error: "Missing x-api-key header", status: 401 }
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await auth.api.verifyApiKey({
|
||||
body: {
|
||||
key: apiKey,
|
||||
},
|
||||
})
|
||||
|
||||
if (!result.valid || !result.key) {
|
||||
const errorMessage = String(result.error?.message ?? "Invalid API key")
|
||||
return { ok: false, error: errorMessage, status: 401 }
|
||||
}
|
||||
|
||||
const userId = result.key.referenceId
|
||||
const keyId = result.key.id
|
||||
|
||||
// Look up the user directly from the database
|
||||
const [dbUser] = await db
|
||||
.select()
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1)
|
||||
|
||||
if (!dbUser) {
|
||||
return { ok: false, error: "User not found for API key", status: 401 }
|
||||
}
|
||||
|
||||
// Build a minimal user object matching Better Auth's Session.user type
|
||||
const authedUser: User = {
|
||||
id: dbUser.id,
|
||||
name: dbUser.name,
|
||||
email: dbUser.email,
|
||||
emailVerified: dbUser.emailVerified,
|
||||
image: dbUser.image,
|
||||
createdAt: dbUser.createdAt,
|
||||
updatedAt: dbUser.updatedAt,
|
||||
role: dbUser.role ?? "user",
|
||||
banned: dbUser.banned ?? null,
|
||||
banReason: dbUser.banReason ?? null,
|
||||
banExpires: dbUser.banExpires ?? null,
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
user: authedUser,
|
||||
session: null,
|
||||
keyId,
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[api-key-guard] API key verification failed:", err)
|
||||
return { ok: false, error: "API key verification failed", status: 500 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combined auth guard: first tries session auth (cookie), then falls back
|
||||
* to API key auth (x-api-key header). Returns the authenticated user.
|
||||
*/
|
||||
export async function requireAuthWithApiKeyFallback(
|
||||
requestHeaders: Headers,
|
||||
): Promise<ApiKeyGuardResult> {
|
||||
// Try session auth first
|
||||
const session = await auth.api.getSession({ headers: requestHeaders })
|
||||
if (session) {
|
||||
return {
|
||||
ok: true,
|
||||
user: session.user,
|
||||
session: session.session,
|
||||
keyId: "",
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to API key
|
||||
return authenticateWithApiKey(requestHeaders)
|
||||
}
|
||||
Reference in New Issue
Block a user