UNPKG

better-auth

Version:

The most comprehensive authentication framework for TypeScript.

111 lines (109 loc) 3.86 kB
import { setSessionCookie } from "../../cookies/index.mjs"; import { APIError } from "../../api/index.mjs"; import { toBoolean } from "../../utils/boolean.mjs"; import * as z from "zod"; import { createAuthEndpoint } from "@better-auth/core/api"; import { createRemoteJWKSet, jwtVerify } from "jose"; //#region src/plugins/one-tap/index.ts const oneTapCallbackBodySchema = z.object({ idToken: z.string().meta({ description: "Google ID token, which the client obtains from the One Tap API" }) }); const oneTap = (options) => ({ id: "one-tap", endpoints: { oneTapCallback: createAuthEndpoint("/one-tap/callback", { method: "POST", body: oneTapCallbackBodySchema, metadata: { openapi: { summary: "One tap callback", description: "Use this endpoint to authenticate with Google One Tap", responses: { 200: { description: "Successful response", content: { "application/json": { schema: { type: "object", properties: { session: { $ref: "#/components/schemas/Session" }, user: { $ref: "#/components/schemas/User" } } } } } }, 400: { description: "Invalid token" } } } } }, async (ctx) => { const { idToken } = ctx.body; let payload; try { const { payload: verifiedPayload } = await jwtVerify(idToken, createRemoteJWKSet(new URL("https://www.googleapis.com/oauth2/v3/certs")), { issuer: ["https://accounts.google.com", "accounts.google.com"], audience: options?.clientId || ctx.context.options.socialProviders?.google?.clientId }); payload = verifiedPayload; } catch { throw new APIError("BAD_REQUEST", { message: "invalid id token" }); } const { email, email_verified, name, picture, sub } = payload; if (!email) return ctx.json({ error: "Email not available in token" }); const user = await ctx.context.internalAdapter.findUserByEmail(email); if (!user) { if (options?.disableSignup) throw new APIError("BAD_GATEWAY", { message: "User not found" }); const newUser = await ctx.context.internalAdapter.createOAuthUser({ email, emailVerified: typeof email_verified === "boolean" ? email_verified : toBoolean(email_verified), name, image: picture }, { providerId: "google", accountId: sub }); if (!newUser) throw new APIError("INTERNAL_SERVER_ERROR", { message: "Could not create user" }); const session$1 = await ctx.context.internalAdapter.createSession(newUser.user.id); await setSessionCookie(ctx, { user: newUser.user, session: session$1 }); return ctx.json({ token: session$1.token, user: { id: newUser.user.id, email: newUser.user.email, emailVerified: newUser.user.emailVerified, name: newUser.user.name, image: newUser.user.image, createdAt: newUser.user.createdAt, updatedAt: newUser.user.updatedAt } }); } if (!await ctx.context.internalAdapter.findAccount(sub)) { const accountLinking = ctx.context.options.account?.accountLinking; if (accountLinking?.enabled && (accountLinking.trustedProviders?.includes("google") || email_verified)) await ctx.context.internalAdapter.linkAccount({ userId: user.user.id, providerId: "google", accountId: sub, scope: "openid,profile,email", idToken }); else throw new APIError("UNAUTHORIZED", { message: "Google sub doesn't match" }); } const session = await ctx.context.internalAdapter.createSession(user.user.id); await setSessionCookie(ctx, { user: user.user, session }); return ctx.json({ token: session.token, user: { id: user.user.id, email: user.user.email, emailVerified: user.user.emailVerified, name: user.user.name, image: user.user.image, createdAt: user.user.createdAt, updatedAt: user.user.updatedAt } }); }) }, options }); //#endregion export { oneTap }; //# sourceMappingURL=index.mjs.map