better-auth
Version:
The most comprehensive authentication framework for TypeScript.
1 lines • 7.98 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["payload: any","session"],"sources":["../../../src/plugins/one-tap/index.ts"],"sourcesContent":["import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport * as z from \"zod\";\nimport { APIError } from \"../../api\";\nimport { setSessionCookie } from \"../../cookies\";\nimport { parseUserOutput } from \"../../db/schema\";\nimport { toBoolean } from \"../../utils/boolean\";\n\nexport interface OneTapOptions {\n\t/**\n\t * Disable the signup flow\n\t *\n\t * @default false\n\t */\n\tdisableSignup?: boolean | undefined;\n\t/**\n\t * Google Client ID\n\t *\n\t * If a client ID is provided in the social provider configuration,\n\t * it will be used.\n\t */\n\tclientId?: string | undefined;\n}\n\nconst oneTapCallbackBodySchema = z.object({\n\tidToken: z.string().meta({\n\t\tdescription:\n\t\t\t\"Google ID token, which the client obtains from the One Tap API\",\n\t}),\n});\n\nexport const oneTap = (options?: OneTapOptions | undefined) =>\n\t({\n\t\tid: \"one-tap\",\n\t\tendpoints: {\n\t\t\toneTapCallback: createAuthEndpoint(\n\t\t\t\t\"/one-tap/callback\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: oneTapCallbackBodySchema,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\topenapi: {\n\t\t\t\t\t\t\tsummary: \"One tap callback\",\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Use this endpoint to authenticate with Google One Tap\",\n\t\t\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\t\t\tdescription: \"Successful response\",\n\t\t\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tsession: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Session\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t400: {\n\t\t\t\t\t\t\t\t\tdescription: \"Invalid token\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tasync (ctx) => {\n\t\t\t\t\tconst { idToken } = ctx.body;\n\t\t\t\t\tlet payload: any;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst JWKS = createRemoteJWKSet(\n\t\t\t\t\t\t\tnew URL(\"https://www.googleapis.com/oauth2/v3/certs\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst { payload: verifiedPayload } = await jwtVerify(\n\t\t\t\t\t\t\tidToken,\n\t\t\t\t\t\t\tJWKS,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tissuer: [\"https://accounts.google.com\", \"accounts.google.com\"],\n\t\t\t\t\t\t\t\taudience:\n\t\t\t\t\t\t\t\t\toptions?.clientId ||\n\t\t\t\t\t\t\t\t\tctx.context.options.socialProviders?.google?.clientId,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t\tpayload = verifiedPayload;\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\t\t\tmessage: \"invalid id token\",\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst { email, email_verified, name, picture, sub } = payload;\n\t\t\t\t\tif (!email) {\n\t\t\t\t\t\treturn ctx.json({ error: \"Email not available in token\" });\n\t\t\t\t\t}\n\n\t\t\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\t\t\tif (!user) {\n\t\t\t\t\t\tif (options?.disableSignup) {\n\t\t\t\t\t\t\tthrow new APIError(\"BAD_GATEWAY\", {\n\t\t\t\t\t\t\t\tmessage: \"User not found\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst newUser = await ctx.context.internalAdapter.createOAuthUser(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\temail,\n\t\t\t\t\t\t\t\temailVerified:\n\t\t\t\t\t\t\t\t\ttypeof email_verified === \"boolean\"\n\t\t\t\t\t\t\t\t\t\t? email_verified\n\t\t\t\t\t\t\t\t\t\t: toBoolean(email_verified),\n\t\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t\timage: picture,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproviderId: \"google\",\n\t\t\t\t\t\t\t\taccountId: sub,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (!newUser) {\n\t\t\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\t\tmessage: \"Could not create user\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\t\t\tnewUser.user.id,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\t\t\tuser: newUser.user,\n\t\t\t\t\t\t\tsession,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\t\ttoken: session.token,\n\t\t\t\t\t\t\tuser: parseUserOutput(ctx.context.options, newUser.user),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tconst account = await ctx.context.internalAdapter.findAccount(sub);\n\t\t\t\t\tif (!account) {\n\t\t\t\t\t\tconst accountLinking = ctx.context.options.account?.accountLinking;\n\t\t\t\t\t\tconst shouldLinkAccount =\n\t\t\t\t\t\t\taccountLinking?.enabled !== false &&\n\t\t\t\t\t\t\t(accountLinking?.trustedProviders?.includes(\"google\") ||\n\t\t\t\t\t\t\t\temail_verified);\n\t\t\t\t\t\tif (shouldLinkAccount) {\n\t\t\t\t\t\t\tawait ctx.context.internalAdapter.linkAccount({\n\t\t\t\t\t\t\t\tuserId: user.user.id,\n\t\t\t\t\t\t\t\tproviderId: \"google\",\n\t\t\t\t\t\t\t\taccountId: sub,\n\t\t\t\t\t\t\t\tscope: \"openid,profile,email\",\n\t\t\t\t\t\t\t\tidToken,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\t\t\t\tmessage: \"Google sub doesn't match\",\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\t\tuser.user.id,\n\t\t\t\t\t);\n\n\t\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\t\tuser: user.user,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t});\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\ttoken: session.token,\n\t\t\t\t\t\tuser: parseUserOutput(ctx.context.options, user.user),\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\toptions,\n\t}) satisfies BetterAuthPlugin;\n"],"mappings":";;;;;;;;;AAyBA,MAAM,2BAA2B,EAAE,OAAO,EACzC,SAAS,EAAE,QAAQ,CAAC,KAAK,EACxB,aACC,kEACD,CAAC,EACF,CAAC;AAEF,MAAa,UAAU,aACrB;CACA,IAAI;CACJ,WAAW,EACV,gBAAgB,mBACf,qBACA;EACC,QAAQ;EACR,MAAM;EACN,UAAU,EACT,SAAS;GACR,SAAS;GACT,aACC;GACD,WAAW;IACV,KAAK;KACJ,aAAa;KACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;MACP,MAAM;MACN,YAAY;OACX,SAAS,EACR,MAAM,gCACN;OACD,MAAM,EACL,MAAM,6BACN;OACD;MACD,EACD,EACD;KACD;IACD,KAAK,EACJ,aAAa,iBACb;IACD;GACD,EACD;EACD,EACD,OAAO,QAAQ;EACd,MAAM,EAAE,YAAY,IAAI;EACxB,IAAIA;AACJ,MAAI;GAIH,MAAM,EAAE,SAAS,oBAAoB,MAAM,UAC1C,SAJY,mBACZ,IAAI,IAAI,6CAA6C,CACrD,EAIA;IACC,QAAQ,CAAC,+BAA+B,sBAAsB;IAC9D,UACC,SAAS,YACT,IAAI,QAAQ,QAAQ,iBAAiB,QAAQ;IAC9C,CACD;AACD,aAAU;UACH;AACP,SAAM,IAAI,SAAS,eAAe,EACjC,SAAS,oBACT,CAAC;;EAEH,MAAM,EAAE,OAAO,gBAAgB,MAAM,SAAS,QAAQ;AACtD,MAAI,CAAC,MACJ,QAAO,IAAI,KAAK,EAAE,OAAO,gCAAgC,CAAC;EAG3D,MAAM,OAAO,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM;AACrE,MAAI,CAAC,MAAM;AACV,OAAI,SAAS,cACZ,OAAM,IAAI,SAAS,eAAe,EACjC,SAAS,kBACT,CAAC;GAEH,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,gBACjD;IACC;IACA,eACC,OAAO,mBAAmB,YACvB,iBACA,UAAU,eAAe;IAC7B;IACA,OAAO;IACP,EACD;IACC,YAAY;IACZ,WAAW;IACX,CACD;AACD,OAAI,CAAC,QACJ,OAAM,IAAI,SAAS,yBAAyB,EAC3C,SAAS,yBACT,CAAC;GAEH,MAAMC,YAAU,MAAM,IAAI,QAAQ,gBAAgB,cACjD,QAAQ,KAAK,GACb;AACD,SAAM,iBAAiB,KAAK;IAC3B,MAAM,QAAQ;IACd;IACA,CAAC;AACF,UAAO,IAAI,KAAK;IACf,OAAOA,UAAQ;IACf,MAAM,gBAAgB,IAAI,QAAQ,SAAS,QAAQ,KAAK;IACxD,CAAC;;AAGH,MAAI,CADY,MAAM,IAAI,QAAQ,gBAAgB,YAAY,IAAI,EACpD;GACb,MAAM,iBAAiB,IAAI,QAAQ,QAAQ,SAAS;AAKpD,OAHC,gBAAgB,YAAY,UAC3B,gBAAgB,kBAAkB,SAAS,SAAS,IACpD,gBAED,OAAM,IAAI,QAAQ,gBAAgB,YAAY;IAC7C,QAAQ,KAAK,KAAK;IAClB,YAAY;IACZ,WAAW;IACX,OAAO;IACP;IACA,CAAC;OAEF,OAAM,IAAI,SAAS,gBAAgB,EAClC,SAAS,4BACT,CAAC;;EAGJ,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,cACjD,KAAK,KAAK,GACV;AAED,QAAM,iBAAiB,KAAK;GAC3B,MAAM,KAAK;GACX;GACA,CAAC;AACF,SAAO,IAAI,KAAK;GACf,OAAO,QAAQ;GACf,MAAM,gBAAgB,IAAI,QAAQ,SAAS,KAAK,KAAK;GACrD,CAAC;GAEH,EACD;CACD;CACA"}